feat: extended database entities

This commit is contained in:
əlemi 2024-03-21 01:09:33 +01:00
parent efc9c79ab0
commit 30637f93ee
Signed by: alemi
GPG key ID: A4895B84D311642C
5 changed files with 314 additions and 64 deletions

View file

@ -18,7 +18,19 @@ impl MigrationTrait for Migration {
.primary_key() .primary_key()
) )
.col(ColumnDef::new(Users::ActorType).string().not_null()) .col(ColumnDef::new(Users::ActorType).string().not_null())
.col(ColumnDef::new(Users::Domain).string().not_null())
.col(ColumnDef::new(Users::Name).string().not_null()) .col(ColumnDef::new(Users::Name).string().not_null())
.col(ColumnDef::new(Users::Summary).string().null())
.col(ColumnDef::new(Users::Image).string().null())
.col(ColumnDef::new(Users::Icon).string().null())
.col(ColumnDef::new(Users::PreferredUsername).string().null())
.col(ColumnDef::new(Users::Inbox).string().null())
.col(ColumnDef::new(Users::SharedInbox).string().null())
.col(ColumnDef::new(Users::Outbox).string().null())
.col(ColumnDef::new(Users::Following).string().null())
.col(ColumnDef::new(Users::Followers).string().null())
.col(ColumnDef::new(Users::Created).date_time().not_null())
.col(ColumnDef::new(Users::Updated).date_time().not_null())
.to_owned() .to_owned()
) )
.await?; .await?;
@ -38,7 +50,11 @@ impl MigrationTrait for Migration {
.col(ColumnDef::new(Activities::Actor).string().not_null()) .col(ColumnDef::new(Activities::Actor).string().not_null())
.col(ColumnDef::new(Activities::Object).string().null()) .col(ColumnDef::new(Activities::Object).string().null())
.col(ColumnDef::new(Activities::Target).string().null()) .col(ColumnDef::new(Activities::Target).string().null())
.col(ColumnDef::new(Activities::Published).string().null()) .col(ColumnDef::new(Activities::To).json().null())
.col(ColumnDef::new(Activities::Bto).json().null())
.col(ColumnDef::new(Activities::Cc).json().null())
.col(ColumnDef::new(Activities::Bcc).json().null())
.col(ColumnDef::new(Activities::Published).date_time().not_null())
.to_owned() .to_owned()
).await?; ).await?;
@ -58,7 +74,7 @@ impl MigrationTrait for Migration {
.col(ColumnDef::new(Objects::Name).string().null()) .col(ColumnDef::new(Objects::Name).string().null())
.col(ColumnDef::new(Objects::Summary).string().null()) .col(ColumnDef::new(Objects::Summary).string().null())
.col(ColumnDef::new(Objects::Content).string().null()) .col(ColumnDef::new(Objects::Content).string().null())
.col(ColumnDef::new(Objects::Published).string().null()) .col(ColumnDef::new(Objects::Published).string().not_null())
.to_owned() .to_owned()
).await?; ).await?;
@ -86,8 +102,20 @@ impl MigrationTrait for Migration {
enum Users { enum Users {
Table, Table,
Id, Id,
Domain,
ActorType, ActorType,
Name, Name,
Summary,
Image,
Icon,
PreferredUsername,
Inbox,
SharedInbox,
Outbox,
Following,
Followers,
Created,
Updated,
} }
#[derive(DeriveIden)] #[derive(DeriveIden)]
@ -98,7 +126,11 @@ enum Activities {
Actor, Actor,
Object, Object,
Target, Target,
Published Cc,
Bcc,
To,
Bto,
Published,
} }
#[derive(DeriveIden)] #[derive(DeriveIden)]
@ -106,9 +138,9 @@ enum Objects {
Table, Table,
Id, Id,
ObjectType, ObjectType,
AttributedTo,
Name, Name,
Summary, Summary,
AttributedTo,
Content, Content,
Published, Published,
} }

View file

@ -1,6 +1,22 @@
use sea_orm::entity::prelude::*; use sea_orm::{entity::prelude::*, FromJsonQueryResult};
use crate::activitystream::{Node, macros::InsertValue, object::{activity::{Activity, ActivityType}, actor::Actor, Object, ObjectType}, Base, BaseType}; use crate::activitystream::{self, link::Link, object::{activity::{Activity, ActivityMut, ActivityType}, actor::Actor, Object, ObjectMut, ObjectType}, Base, BaseMut, BaseType, Node};
#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, FromJsonQueryResult)]
pub struct Audience(pub Vec<String>);
impl<T : Link> From<Node<T>> for Audience {
fn from(value: Node<T>) -> Self {
Audience(
match value {
Node::Empty => vec![],
Node::Link(l) => vec![l.href().to_string()],
Node::Object(o) => if let Some(id) = o.id() { vec![id.to_string()] } else { vec![] },
Node::Array(arr) => arr.into_iter().filter_map(|l| Some(l.id()?.to_string())).collect(),
}
)
}
}
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "activities")] #[sea_orm(table_name = "activities")]
@ -12,14 +28,45 @@ pub struct Model {
pub activity_type: ActivityType, pub activity_type: ActivityType,
pub actor: String, // TODO relates to USER pub actor: String, // TODO relates to USER
pub object: Option<String>, // TODO relates to NOTES maybe????? maybe other tables?????? pub object: Option<String>, // TODO relates to NOTES maybe????? maybe other tables??????
pub target: Option<String>, // TODO relates to USER maybe?? pub target: Option<String>, // TODO relates to USER maybe??
pub cc: Audience,
pub bcc: Audience,
pub to: Audience,
pub bto: Audience,
pub published: ChronoDateTimeUtc, pub published: ChronoDateTimeUtc,
// TODO: origin, result, instrument // TODO: origin, result, instrument
} }
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {} pub enum Relation {
#[sea_orm(
belongs_to = "super::user::Entity",
from = "Column::Actor",
to = "super::user::Column::Id"
)]
User,
#[sea_orm(
belongs_to = "super::object::Entity",
from = "Column::Object",
to = "super::object::Column::Id"
)]
Object,
}
impl Related<super::user::Entity> for Entity {
fn to() -> RelationDef {
Relation::User.def()
}
}
impl Related<super::object::Entity> for Entity {
fn to() -> RelationDef {
Relation::Object.def()
}
}
impl ActiveModelBehavior for ActiveModel {} impl ActiveModelBehavior for ActiveModel {}
@ -33,21 +80,44 @@ impl Base for Model {
} }
fn underlying_json_object(self) -> serde_json::Value { fn underlying_json_object(self) -> serde_json::Value {
let mut map = serde_json::Map::new(); activitystream::object()
map.insert_str("id", Some(&self.id)); .set_id(Some(&self.id))
map.insert_str("type", Some(self.activity_type.as_ref())); .set_activity_type(Some(self.activity_type))
map.insert_str("actor", Some(&self.actor)); .set_actor(Node::link(self.actor))
map.insert_str("object", self.object.as_deref()); .set_object(Node::maybe_link(self.object))
map.insert_str("target", self.target.as_deref()); .set_target(Node::maybe_link(self.target))
map.insert_timestr("published", Some(self.published)); .set_published(Some(self.published))
serde_json::Value::Object(map) .set_to(Node::links(self.to.0.clone()))
.set_bto(Node::empty())
.set_cc(Node::links(self.cc.0.clone()))
.set_bcc(Node::empty())
} }
} }
impl Object for Model { impl Object for Model {
fn object_type(&self) -> Option<ObjectType> {
Some(ObjectType::Activity(self.activity_type))
}
fn published(&self) -> Option<chrono::DateTime<chrono::Utc>> { fn published(&self) -> Option<chrono::DateTime<chrono::Utc>> {
Some(self.published) Some(self.published)
} }
fn to(&self) -> Node<impl Link> {
Node::links(self.to.0.clone())
}
fn bto(&self) -> Node<impl Link> {
Node::links(self.bto.0.clone())
}
fn cc(&self) -> Node<impl Link> {
Node::links(self.cc.0.clone())
}
fn bcc(&self) -> Node<impl Link> {
Node::links(self.bcc.0.clone())
}
} }
impl Activity for Model { impl Activity for Model {
@ -83,6 +153,10 @@ impl Model {
object: activity.object().id().map(|x| x.to_string()), object: activity.object().id().map(|x| x.to_string()),
target: activity.target().id().map(|x| x.to_string()), target: activity.target().id().map(|x| x.to_string()),
published: activity.published().ok_or(super::FieldError("published"))?, published: activity.published().ok_or(super::FieldError("published"))?,
to: activity.to().into(),
bto: activity.bto().into(),
cc: activity.cc().into(),
bcc: activity.bcc().into(),
}) })
} }
} }

View file

@ -1,3 +1,7 @@
use sea_orm::IntoActiveModel;
use crate::{activitypub::PUBLIC_TARGET, activitystream::object::actor::Actor, model::activity::Audience};
pub mod user; pub mod user;
pub mod object; pub mod object;
pub mod activity; pub mod activity;
@ -7,34 +11,52 @@ pub mod activity;
pub struct FieldError(pub &'static str); pub struct FieldError(pub &'static str);
pub async fn faker(db: &sea_orm::DatabaseConnection, domain: String) -> Result<(), sea_orm::DbErr> { pub async fn faker(db: &sea_orm::DatabaseConnection, domain: String) -> Result<(), sea_orm::DbErr> {
use sea_orm::EntityTrait; use sea_orm::{EntityTrait, Set};
user::Entity::insert(user::ActiveModel { let root = user::Model {
id: sea_orm::Set(format!("{domain}/users/root")), id: format!("{domain}/users/root"),
name: sea_orm::Set("root".into()), name: "root".into(),
actor_type: sea_orm::Set(super::activitystream::object::actor::ActorType::Person), domain: crate::activitypub::domain(&domain),
}).exec(db).await?; preferred_username: Some("Administrator".to_string()),
summary: Some("hello world! i'm manually generated but served dynamically from db!".to_string()),
following: None,
followers: None,
icon: Some("https://cdn.alemi.dev/social/circle-square.png".to_string()),
image: Some("https://cdn.alemi.dev/social/someriver-xs.jpg".to_string()),
inbox: None,
shared_inbox: None,
outbox: None,
actor_type: super::activitystream::object::actor::ActorType::Person,
created: chrono::Utc::now(),
updated: chrono::Utc::now(),
};
user::Entity::insert(root.clone().into_active_model()).exec(db).await?;
for i in (0..100).rev() { for i in (0..100).rev() {
let oid = uuid::Uuid::new_v4(); let oid = uuid::Uuid::new_v4();
let aid = uuid::Uuid::new_v4(); let aid = uuid::Uuid::new_v4();
object::Entity::insert(object::ActiveModel { object::Entity::insert(object::ActiveModel {
id: sea_orm::Set(format!("{domain}/objects/{oid}")), id: Set(format!("{domain}/objects/{oid}")),
name: sea_orm::Set(None), name: Set(None),
object_type: sea_orm::Set(crate::activitystream::object::ObjectType::Note), object_type: Set(crate::activitystream::object::ObjectType::Note),
attributed_to: sea_orm::Set(Some(format!("{domain}/users/root"))), attributed_to: Set(Some(format!("{domain}/users/root"))),
summary: sea_orm::Set(None), summary: Set(None),
content: sea_orm::Set(Some(format!("[{i}] Tic(k). Quasiparticle of intensive multiplicity. Tics (or ticks) are intrinsically several components of autonomously numbering anorganic populations, propagating by contagion between segmentary divisions in the order of nature. Ticks - as nonqualitative differentially-decomposable counting marks - each designate a multitude comprehended as a singular variation in tic(k)-density."))), content: Set(Some(format!("[{i}] Tic(k). Quasiparticle of intensive multiplicity. Tics (or ticks) are intrinsically several components of autonomously numbering anorganic populations, propagating by contagion between segmentary divisions in the order of nature. Ticks - as nonqualitative differentially-decomposable counting marks - each designate a multitude comprehended as a singular variation in tic(k)-density."))),
published: sea_orm::Set(chrono::Utc::now() - std::time::Duration::from_secs(60*i)), published: Set(chrono::Utc::now() - std::time::Duration::from_secs(60*i)),
}).exec(db).await?; }).exec(db).await?;
activity::Entity::insert(activity::ActiveModel { activity::Entity::insert(activity::ActiveModel {
id: sea_orm::Set(format!("{domain}/activities/{aid}")), id: Set(format!("{domain}/activities/{aid}")),
activity_type: sea_orm::Set(crate::activitystream::object::activity::ActivityType::Create), activity_type: Set(crate::activitystream::object::activity::ActivityType::Create),
actor: sea_orm::Set(format!("{domain}/users/root")), actor: Set(format!("{domain}/users/root")),
object: sea_orm::Set(Some(format!("{domain}/objects/{oid}"))), object: Set(Some(format!("{domain}/objects/{oid}"))),
target: sea_orm::Set(None), target: Set(None),
published: sea_orm::Set(chrono::Utc::now() - std::time::Duration::from_secs(60*i)), published: Set(chrono::Utc::now() - std::time::Duration::from_secs(60*i)),
to: Set(Audience(vec![PUBLIC_TARGET.to_string()])),
bto: Set(Audience::default()),
cc: Set(Audience(vec![root.followers().id().unwrap_or("").to_string()])),
bcc: Set(Audience::default()),
}).exec(db).await?; }).exec(db).await?;
} }

View file

@ -1,6 +1,7 @@
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;
use crate::activitystream::prelude::*;
use crate::activitystream::{macros::InsertValue, object::{actor::Actor, Object, ObjectType}, Base, BaseType, Node}; use crate::activitystream::{object::ObjectType, BaseType, Node};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "objects")] #[sea_orm(table_name = "objects")]
@ -17,11 +18,33 @@ pub struct Model {
} }
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {} pub enum Relation {
#[sea_orm(has_many = "super::activity::Entity")]
Activity,
#[sea_orm(
belongs_to = "super::user::Entity",
from = "Column::AttributedTo",
to = "super::user::Column::Id",
)]
User,
}
impl Related<super::activity::Entity> for Entity {
fn to() -> RelationDef {
Relation::Activity.def()
}
}
impl Related<super::user::Entity> for Entity {
fn to() -> RelationDef {
Relation::User.def()
}
}
impl ActiveModelBehavior for ActiveModel {} impl ActiveModelBehavior for ActiveModel {}
impl Base for Model { impl crate::activitystream::Base for Model {
fn id(&self) -> Option<&str> { fn id(&self) -> Option<&str> {
Some(&self.id) Some(&self.id)
} }
@ -31,24 +54,23 @@ impl Base for Model {
} }
fn underlying_json_object(self) -> serde_json::Value { fn underlying_json_object(self) -> serde_json::Value {
let mut map = serde_json::Map::new(); crate::activitystream::object()
map.insert_str("id", Some(&self.id)); .set_id(Some(&self.id))
map.insert_str("type", Some(self.object_type.as_ref())); .set_object_type(Some(self.object_type))
map.insert_str("attributedTo", self.attributed_to.as_deref()); .set_attributed_to(Node::maybe_link(self.attributed_to))
map.insert_str("name", self.name.as_deref()); .set_name(self.name.as_deref())
map.insert_str("summary", self.summary.as_deref()); .set_summary(self.summary.as_deref())
map.insert_str("content", self.content.as_deref()); .set_content(self.content.as_deref())
map.insert_timestr("published", Some(self.published)); .set_published(Some(self.published))
serde_json::Value::Object(map)
} }
} }
impl Object for Model { impl crate::activitystream::object::Object for Model {
fn object_type(&self) -> Option<ObjectType> { fn object_type(&self) -> Option<ObjectType> {
Some(self.object_type) Some(self.object_type)
} }
fn attributed_to(&self) -> Node<impl Actor> { fn attributed_to(&self) -> Node<impl crate::activitystream::object::actor::Actor> {
Node::<serde_json::Value>::from(self.attributed_to.as_deref()) Node::<serde_json::Value>::from(self.attributed_to.as_deref())
} }
@ -70,7 +92,7 @@ impl Object for Model {
} }
impl Model { impl Model {
pub fn new(object: &impl Object) -> Result<Self, super::FieldError> { pub fn new(object: &impl crate::activitystream::object::Object) -> Result<Self, super::FieldError> {
Ok(Model { Ok(Model {
id: object.id().ok_or(super::FieldError("id"))?.to_string(), id: object.id().ok_or(super::FieldError("id"))?.to_string(),
object_type: object.object_type().ok_or(super::FieldError("type"))?, object_type: object.object_type().ok_or(super::FieldError("type"))?,

View file

@ -1,25 +1,63 @@
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;
use crate::activitystream::prelude::*;
use crate::activitystream::{macros::InsertValue, object::actor::{Actor, ActorType}, Base, BaseType, Object, ObjectType}; use crate::{activitypub, activitystream::{object::actor::ActorType, BaseType, Node, ObjectType}};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "users")] #[sea_orm(table_name = "users")]
pub struct Model { pub struct Model {
#[sea_orm(primary_key)] #[sea_orm(primary_key)]
/// must be user@instance.org, even if local! TODO bad design... /// must be full AP ID, since they are unique over the network
pub id: String, pub id: String,
pub domain: String,
pub actor_type: ActorType, pub actor_type: ActorType,
pub name: String, pub name: String,
pub summary: Option<String>,
pub image: Option<String>,
pub icon: Option<String>,
pub preferred_username: Option<String>,
pub inbox: Option<String>,
pub shared_inbox: Option<String>,
pub outbox: Option<String>,
pub following: Option<String>,
pub followers: Option<String>,
pub created: ChronoDateTimeUtc,
pub updated: ChronoDateTimeUtc,
// TODO these are also suggested
// pub liked: Option<String>,
// pub streams: Option<String>,
} }
use crate::activitystream::object::{actor::Actor, collection::Collection, document::Image};
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {} pub enum Relation {
#[sea_orm(has_many = "super::activity::Entity")]
Activity,
#[sea_orm(has_many = "super::object::Entity")]
Object,
}
impl Related<super::activity::Entity> for Entity {
fn to() -> RelationDef {
Relation::Activity.def()
}
}
impl Related<super::object::Entity> for Entity {
fn to() -> RelationDef {
Relation::Object.def()
}
}
impl ActiveModelBehavior for ActiveModel {} impl ActiveModelBehavior for ActiveModel {}
impl Base for Model { impl crate::activitystream::Base for Model {
fn id(&self) -> Option<&str> { fn id(&self) -> Option<&str> {
Some(&self.id) Some(&self.id)
} }
@ -29,32 +67,94 @@ impl Base for Model {
} }
fn underlying_json_object(self) -> serde_json::Value { fn underlying_json_object(self) -> serde_json::Value {
let mut map = serde_json::Map::new(); crate::activitystream::object()
map.insert_str("id", Some(&self.id)); .set_id(Some(&self.id))
map.insert_str("type", Some(self.actor_type.as_ref())); .set_actor_type(Some(self.actor_type))
map.insert_str("name", Some(&self.name)); .set_name(Some(&self.name))
serde_json::Value::Object(map) .set_summary(self.summary.as_deref())
.set_icon(self.icon())
.set_image(self.image())
.set_preferred_username(self.preferred_username.as_deref())
.set_inbox(self.inbox())
.set_outbox(self.outbox())
.set_following(self.following())
.set_followers(self.followers())
.underlying_json_object()
} }
} }
impl Object for Model { impl crate::activitystream::object::Object for Model {
fn name(&self) -> Option<&str> { fn name(&self) -> Option<&str> {
Some(&self.name) Some(&self.name)
} }
fn summary(&self) -> Option<&str> {
self.summary.as_deref()
} }
impl Actor for Model { fn icon(&self) -> Node<impl Image> {
match &self.icon {
Some(x) => Node::link(x.to_string()),
None => Node::Empty,
}
}
fn image(&self) -> Node<impl Image> {
match &self.image {
Some(x) => Node::link(x.to_string()),
None => Node::Empty,
}
}
fn published(&self) -> Option<chrono::DateTime<chrono::Utc>> {
Some(self.created)
}
}
impl crate::activitystream::object::actor::Actor for Model {
fn actor_type(&self) -> Option<ActorType> { fn actor_type(&self) -> Option<ActorType> {
Some(self.actor_type) Some(self.actor_type)
} }
fn preferred_username(&self) -> Option<&str> {
self.preferred_username.as_deref()
}
fn inbox(&self) -> Node<impl Collection> {
Node::link(self.inbox.clone().unwrap_or(format!("https://{}/users/{}/inbox", self.domain, self.name)))
}
fn outbox(&self) -> Node<impl Collection> {
Node::link(self.outbox.clone().unwrap_or(format!("https://{}/users/{}/outbox", self.domain, self.name)))
}
fn following(&self) -> Node<impl Collection> {
Node::link(self.following.clone().unwrap_or(format!("https://{}/users/{}/following", self.domain, self.name)))
}
fn followers(&self) -> Node<impl Collection> {
Node::link(self.following.clone().unwrap_or(format!("https://{}/users/{}/followers", self.domain, self.name)))
}
} }
impl Model { impl Model {
pub fn new(object: &impl Actor) -> Result<Self, super::FieldError> { pub fn new(object: &impl Actor) -> Result<Self, super::FieldError> {
let ap_id = object.id().ok_or(super::FieldError("id"))?.to_string();
let (domain, name) = activitypub::split_id(&ap_id);
Ok(Model { Ok(Model {
id: object.id().ok_or(super::FieldError("id"))?.to_string(), id: ap_id, name, domain,
actor_type: object.actor_type().ok_or(super::FieldError("type"))?, actor_type: object.actor_type().ok_or(super::FieldError("type"))?,
name: object.name().ok_or(super::FieldError("name"))?.to_string(), preferred_username: object.preferred_username().map(|x| x.to_string()),
summary: object.summary().map(|x| x.to_string()),
icon: object.icon().id().map(|x| x.to_string()),
image: object.image().id().map(|x| x.to_string()),
inbox: object.inbox().id().map(|x| x.to_string()),
outbox: object.inbox().id().map(|x| x.to_string()),
shared_inbox: None, // TODO!!! parse endpoints
followers: object.followers().id().map(|x| x.to_string()),
following: object.following().id().map(|x| x.to_string()),
created: object.published().unwrap_or(chrono::Utc::now()),
updated: chrono::Utc::now(),
}) })
} }
} }