1
0
Fork 0
forked from alemi/upub

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()
)
.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::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()
)
.await?;
@ -38,7 +50,11 @@ impl MigrationTrait for Migration {
.col(ColumnDef::new(Activities::Actor).string().not_null())
.col(ColumnDef::new(Activities::Object).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()
).await?;
@ -58,7 +74,7 @@ impl MigrationTrait for Migration {
.col(ColumnDef::new(Objects::Name).string().null())
.col(ColumnDef::new(Objects::Summary).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()
).await?;
@ -86,8 +102,20 @@ impl MigrationTrait for Migration {
enum Users {
Table,
Id,
Domain,
ActorType,
Name,
Summary,
Image,
Icon,
PreferredUsername,
Inbox,
SharedInbox,
Outbox,
Following,
Followers,
Created,
Updated,
}
#[derive(DeriveIden)]
@ -98,7 +126,11 @@ enum Activities {
Actor,
Object,
Target,
Published
Cc,
Bcc,
To,
Bto,
Published,
}
#[derive(DeriveIden)]
@ -106,9 +138,9 @@ enum Objects {
Table,
Id,
ObjectType,
AttributedTo,
Name,
Summary,
AttributedTo,
Content,
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)]
#[sea_orm(table_name = "activities")]
@ -12,14 +28,45 @@ pub struct Model {
pub activity_type: ActivityType,
pub actor: String, // TODO relates to USER
pub object: Option<String>, // TODO relates to NOTES maybe????? maybe other tables??????
pub target: Option<String>, // TODO relates to USER maybe??
pub cc: Audience,
pub bcc: Audience,
pub to: Audience,
pub bto: Audience,
pub published: ChronoDateTimeUtc,
// TODO: origin, result, instrument
}
#[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 {}
@ -33,21 +80,44 @@ impl Base for Model {
}
fn underlying_json_object(self) -> serde_json::Value {
let mut map = serde_json::Map::new();
map.insert_str("id", Some(&self.id));
map.insert_str("type", Some(self.activity_type.as_ref()));
map.insert_str("actor", Some(&self.actor));
map.insert_str("object", self.object.as_deref());
map.insert_str("target", self.target.as_deref());
map.insert_timestr("published", Some(self.published));
serde_json::Value::Object(map)
activitystream::object()
.set_id(Some(&self.id))
.set_activity_type(Some(self.activity_type))
.set_actor(Node::link(self.actor))
.set_object(Node::maybe_link(self.object))
.set_target(Node::maybe_link(self.target))
.set_published(Some(self.published))
.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 {
fn object_type(&self) -> Option<ObjectType> {
Some(ObjectType::Activity(self.activity_type))
}
fn published(&self) -> Option<chrono::DateTime<chrono::Utc>> {
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 {
@ -83,6 +153,10 @@ impl Model {
object: activity.object().id().map(|x| x.to_string()),
target: activity.target().id().map(|x| x.to_string()),
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 object;
pub mod activity;
@ -7,34 +11,52 @@ pub mod activity;
pub struct FieldError(pub &'static str);
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 {
id: sea_orm::Set(format!("{domain}/users/root")),
name: sea_orm::Set("root".into()),
actor_type: sea_orm::Set(super::activitystream::object::actor::ActorType::Person),
}).exec(db).await?;
let root = user::Model {
id: format!("{domain}/users/root"),
name: "root".into(),
domain: crate::activitypub::domain(&domain),
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() {
let oid = uuid::Uuid::new_v4();
let aid = uuid::Uuid::new_v4();
object::Entity::insert(object::ActiveModel {
id: sea_orm::Set(format!("{domain}/objects/{oid}")),
name: sea_orm::Set(None),
object_type: sea_orm::Set(crate::activitystream::object::ObjectType::Note),
attributed_to: sea_orm::Set(Some(format!("{domain}/users/root"))),
summary: sea_orm::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."))),
published: sea_orm::Set(chrono::Utc::now() - std::time::Duration::from_secs(60*i)),
id: Set(format!("{domain}/objects/{oid}")),
name: Set(None),
object_type: Set(crate::activitystream::object::ObjectType::Note),
attributed_to: Set(Some(format!("{domain}/users/root"))),
summary: Set(None),
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: Set(chrono::Utc::now() - std::time::Duration::from_secs(60*i)),
}).exec(db).await?;
activity::Entity::insert(activity::ActiveModel {
id: sea_orm::Set(format!("{domain}/activities/{aid}")),
activity_type: sea_orm::Set(crate::activitystream::object::activity::ActivityType::Create),
actor: sea_orm::Set(format!("{domain}/users/root")),
object: sea_orm::Set(Some(format!("{domain}/objects/{oid}"))),
target: sea_orm::Set(None),
published: sea_orm::Set(chrono::Utc::now() - std::time::Duration::from_secs(60*i)),
id: Set(format!("{domain}/activities/{aid}")),
activity_type: Set(crate::activitystream::object::activity::ActivityType::Create),
actor: Set(format!("{domain}/users/root")),
object: Set(Some(format!("{domain}/objects/{oid}"))),
target: Set(None),
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?;
}

View file

@ -1,6 +1,7 @@
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)]
#[sea_orm(table_name = "objects")]
@ -17,11 +18,33 @@ pub struct Model {
}
#[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 Base for Model {
impl crate::activitystream::Base for Model {
fn id(&self) -> Option<&str> {
Some(&self.id)
}
@ -31,24 +54,23 @@ impl Base for Model {
}
fn underlying_json_object(self) -> serde_json::Value {
let mut map = serde_json::Map::new();
map.insert_str("id", Some(&self.id));
map.insert_str("type", Some(self.object_type.as_ref()));
map.insert_str("attributedTo", self.attributed_to.as_deref());
map.insert_str("name", self.name.as_deref());
map.insert_str("summary", self.summary.as_deref());
map.insert_str("content", self.content.as_deref());
map.insert_timestr("published", Some(self.published));
serde_json::Value::Object(map)
crate::activitystream::object()
.set_id(Some(&self.id))
.set_object_type(Some(self.object_type))
.set_attributed_to(Node::maybe_link(self.attributed_to))
.set_name(self.name.as_deref())
.set_summary(self.summary.as_deref())
.set_content(self.content.as_deref())
.set_published(Some(self.published))
}
}
impl Object for Model {
impl crate::activitystream::object::Object for Model {
fn object_type(&self) -> Option<ObjectType> {
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())
}
@ -70,7 +92,7 @@ impl Object for 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 {
id: object.id().ok_or(super::FieldError("id"))?.to_string(),
object_type: object.object_type().ok_or(super::FieldError("type"))?,

View file

@ -1,25 +1,63 @@
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)]
#[sea_orm(table_name = "users")]
pub struct Model {
#[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 domain: String,
pub actor_type: ActorType,
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)]
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 Base for Model {
impl crate::activitystream::Base for Model {
fn id(&self) -> Option<&str> {
Some(&self.id)
}
@ -29,32 +67,94 @@ impl Base for Model {
}
fn underlying_json_object(self) -> serde_json::Value {
let mut map = serde_json::Map::new();
map.insert_str("id", Some(&self.id));
map.insert_str("type", Some(self.actor_type.as_ref()));
map.insert_str("name", Some(&self.name));
serde_json::Value::Object(map)
crate::activitystream::object()
.set_id(Some(&self.id))
.set_actor_type(Some(self.actor_type))
.set_name(Some(&self.name))
.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 {
fn name (&self) -> Option<&str> {
impl crate::activitystream::object::Object for Model {
fn name(&self) -> Option<&str> {
Some(&self.name)
}
fn summary(&self) -> Option<&str> {
self.summary.as_deref()
}
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 Actor for Model {
impl crate::activitystream::object::actor::Actor for Model {
fn actor_type(&self) -> Option<ActorType> {
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 {
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 {
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"))?,
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(),
})
}
}