use apb::{BaseMut, Collection, CollectionMut, ObjectMut, ObjectType}; use sea_orm::{entity::prelude::*, QuerySelect, SelectColumns}; use crate::{errors::UpubError, routes::activitypub::jsonld::LD}; use super::Audience; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "objects")] pub struct Model { #[sea_orm(primary_key)] pub internal: i64, #[sea_orm(unique)] pub id: String, pub domain: String, pub object_type: ObjectType, pub attributed_to: Option<String>, pub name: Option<String>, pub summary: Option<String>, pub content: Option<String>, pub sensitive: bool, pub in_reply_to: Option<String>, pub url: Option<String>, pub likes: i32, pub announces: i32, pub replies: i32, pub context: Option<String>, pub to: Audience, pub bto: Audience, pub cc: Audience, pub bcc: Audience, pub published: ChronoDateTimeUtc, pub updated: ChronoDateTimeUtc, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::activity::Entity")] Activities, #[sea_orm( belongs_to = "super::actor::Entity", from = "Column::AttributedTo", to = "super::actor::Column::Id", on_update = "Cascade", on_delete = "NoAction" )] Actors, #[sea_orm(has_many = "super::addressing::Entity")] Addressing, #[sea_orm(has_many = "super::announce::Entity")] Announces, #[sea_orm(has_many = "super::attachment::Entity")] Attachments, #[sea_orm(has_many = "super::hashtag::Entity")] Hashtags, #[sea_orm( belongs_to = "super::instance::Entity", from = "Column::Domain", to = "super::instance::Column::Domain", on_update = "Cascade", on_delete = "NoAction" )] Instances, #[sea_orm(has_many = "super::like::Entity")] Likes, #[sea_orm(has_many = "super::mention::Entity")] Mentions, #[sea_orm( belongs_to = "Entity", from = "Column::InReplyTo", to = "Column::Id", on_update = "Cascade", on_delete = "NoAction" )] Objects, } impl Related<super::activity::Entity> for Entity { fn to() -> RelationDef { Relation::Activities.def() } } impl Related<super::actor::Entity> for Entity { fn to() -> RelationDef { Relation::Actors.def() } } impl Related<super::addressing::Entity> for Entity { fn to() -> RelationDef { Relation::Addressing.def() } } impl Related<super::announce::Entity> for Entity { fn to() -> RelationDef { Relation::Announces.def() } } impl Related<super::attachment::Entity> for Entity { fn to() -> RelationDef { Relation::Attachments.def() } } impl Related<super::hashtag::Entity> for Entity { fn to() -> RelationDef { Relation::Hashtags.def() } } impl Related<super::instance::Entity> for Entity { fn to() -> RelationDef { Relation::Instances.def() } } impl Related<super::like::Entity> for Entity { fn to() -> RelationDef { Relation::Likes.def() } } impl Related<super::mention::Entity> for Entity { fn to() -> RelationDef { Relation::Mentions.def() } } impl Related<Entity> for Entity { fn to() -> RelationDef { Relation::Objects.def() } } impl ActiveModelBehavior for ActiveModel {} impl Entity { pub fn find_by_ap_id(id: &str) -> Select<Entity> { Entity::find().filter(Column::Id.eq(id)) } pub fn delete_by_ap_id(id: &str) -> sea_orm::DeleteMany<Entity> { Entity::delete_many().filter(Column::Id.eq(id)) } pub async fn ap_to_internal(id: &str, db: &DatabaseConnection) -> crate::Result<i64> { Entity::find() .filter(Column::Id.eq(id)) .select_only() .select_column(Column::Internal) .into_tuple::<i64>() .one(db) .await? .ok_or_else(UpubError::not_found) } } impl ActiveModel { pub fn new(object: &impl apb::Object) -> Result<Self, super::FieldError> { let ap_id = object.id().ok_or(super::FieldError("id"))?.to_string(); let domain = crate::server::Context::server(&ap_id); Ok(ActiveModel { internal: sea_orm::ActiveValue::NotSet, domain: sea_orm::ActiveValue::Set(domain), id: sea_orm::ActiveValue::Set(ap_id), object_type: sea_orm::ActiveValue::Set(object.object_type().ok_or(super::FieldError("type"))?), attributed_to: sea_orm::ActiveValue::Set(object.attributed_to().id()), name: sea_orm::ActiveValue::Set(object.name().map(|x| x.to_string())), summary: sea_orm::ActiveValue::Set(object.summary().map(|x| x.to_string())), content: sea_orm::ActiveValue::Set(object.content().map(|x| x.to_string())), context: sea_orm::ActiveValue::Set(object.context().id()), in_reply_to: sea_orm::ActiveValue::Set(object.in_reply_to().id()), published: sea_orm::ActiveValue::Set(object.published().ok_or(super::FieldError("published"))?), updated: sea_orm::ActiveValue::Set(object.updated().unwrap_or_else(chrono::Utc::now)), url: sea_orm::ActiveValue::Set(object.url().id()), replies: sea_orm::ActiveValue::Set(object.replies().get() .map_or(0, |x| x.total_items().unwrap_or(0)) as i32), likes: sea_orm::ActiveValue::Set(object.likes().get() .map_or(0, |x| x.total_items().unwrap_or(0)) as i32), announces: sea_orm::ActiveValue::Set(object.shares().get() .map_or(0, |x| x.total_items().unwrap_or(0)) as i32), to: sea_orm::ActiveValue::Set(object.to().into()), bto: sea_orm::ActiveValue::Set(object.bto().into()), cc: sea_orm::ActiveValue::Set(object.cc().into()), bcc: sea_orm::ActiveValue::Set(object.bcc().into()), sensitive: sea_orm::ActiveValue::Set(object.sensitive().unwrap_or(false)), }) } } impl Model { pub fn ap(self) -> serde_json::Value { serde_json::Value::new_object() .set_id(Some(&self.id)) .set_object_type(Some(self.object_type)) .set_attributed_to(apb::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_context(apb::Node::maybe_link(self.context.clone())) .set_conversation(apb::Node::maybe_link(self.context.clone())) // duplicate context for mastodon .set_in_reply_to(apb::Node::maybe_link(self.in_reply_to.clone())) .set_published(Some(self.published)) .set_updated(Some(self.updated)) .set_to(apb::Node::links(self.to.0.clone())) .set_bto(apb::Node::Empty) .set_cc(apb::Node::links(self.cc.0.clone())) .set_bcc(apb::Node::Empty) .set_url(apb::Node::maybe_link(self.url)) .set_sensitive(Some(self.sensitive)) .set_shares(apb::Node::object( serde_json::Value::new_object() .set_collection_type(Some(apb::CollectionType::OrderedCollection)) .set_total_items(Some(self.announces as u64)) )) .set_likes(apb::Node::object( serde_json::Value::new_object() .set_collection_type(Some(apb::CollectionType::OrderedCollection)) .set_total_items(Some(self.likes as u64)) )) .set_replies(apb::Node::object( serde_json::Value::new_object() .set_collection_type(Some(apb::CollectionType::OrderedCollection)) .set_total_items(Some(self.replies as u64)) )) } } impl apb::target::Addressed for Model { fn addressed(&self) -> Vec<String> { let mut to : Vec<String> = self.to.0.clone(); to.append(&mut self.bto.0.clone()); to.append(&mut self.cc.0.clone()); to.append(&mut self.bcc.0.clone()); to } }