From 22149f7bc77b49d9bdb5c7f58c146b65f79f7aa1 Mon Sep 17 00:00:00 2001 From: alemi Date: Thu, 26 Dec 2024 22:52:37 +0100 Subject: [PATCH] feat: track activity of likes, track like content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit aka emoji reactions 👀👀👀👀👀👀👀👀 --- upub/core/src/model/activity.rs | 16 +++ upub/core/src/model/announce.rs | 15 +++ upub/core/src/model/like.rs | 16 +++ upub/core/src/traits/process.rs | 56 +++++---- upub/migrations/src/lib.rs | 2 + ...24_000002_create_relations_likes_shares.rs | 4 +- .../m20241226_000002_add_like_activities.rs | 115 ++++++++++++++++++ 7 files changed, 202 insertions(+), 22 deletions(-) create mode 100644 upub/migrations/src/m20241226_000002_add_like_activities.rs diff --git a/upub/core/src/model/activity.rs b/upub/core/src/model/activity.rs index ae5a330..13091e0 100644 --- a/upub/core/src/model/activity.rs +++ b/upub/core/src/model/activity.rs @@ -33,6 +33,10 @@ pub enum Relation { Actors, #[sea_orm(has_many = "super::addressing::Entity")] Addressing, + #[sea_orm(has_many = "super::announce::Entity")] + Announces, + #[sea_orm(has_many = "super::like::Entity")] + Likes, #[sea_orm(has_many = "super::notification::Entity")] Notifications, #[sea_orm( @@ -57,6 +61,18 @@ impl Related for Entity { } } +impl Related for Entity { + fn to() -> RelationDef { + Relation::Announces.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Likes.def() + } +} + impl Related for Entity { fn to() -> RelationDef { Relation::Notifications.def() diff --git a/upub/core/src/model/announce.rs b/upub/core/src/model/announce.rs index 9a99944..b5739fa 100644 --- a/upub/core/src/model/announce.rs +++ b/upub/core/src/model/announce.rs @@ -8,10 +8,19 @@ pub struct Model { pub actor: i64, pub object: i64, pub published: ChronoDateTimeUtc, + pub activity: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { + #[sea_orm( + belongs_to = "super::activity::Entity", + from = "Column::Activity", + to = "super::activity::Column::Internal", + on_update = "Cascade", + on_delete = "Cascade" + )] + Activities, #[sea_orm( belongs_to = "super::actor::Entity", from = "Column::Actor", @@ -30,6 +39,12 @@ pub enum Relation { Objects, } +impl Related for Entity { + fn to() -> RelationDef { + Relation::Activities.def() + } +} + impl Related for Entity { fn to() -> RelationDef { Relation::Actors.def() diff --git a/upub/core/src/model/like.rs b/upub/core/src/model/like.rs index e8f1233..922d19a 100644 --- a/upub/core/src/model/like.rs +++ b/upub/core/src/model/like.rs @@ -8,10 +8,20 @@ pub struct Model { pub actor: i64, pub object: i64, pub published: ChronoDateTimeUtc, + pub activity: Option, + pub content: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { + #[sea_orm( + belongs_to = "super::activity::Entity", + from = "Column::Activity", + to = "super::activity::Column::Internal", + on_update = "Cascade", + on_delete = "Cascade" + )] + Activities, #[sea_orm( belongs_to = "super::actor::Entity", from = "Column::Actor", @@ -30,6 +40,12 @@ pub enum Relation { Objects, } +impl Related for Entity { + fn to() -> RelationDef { + Relation::Activities.def() + } +} + impl Related for Entity { fn to() -> RelationDef { Relation::Actors.def() diff --git a/upub/core/src/traits/process.rs b/upub/core/src/traits/process.rs index 06e641d..ed35fd5 100644 --- a/upub/core/src/traits/process.rs +++ b/upub/core/src/traits/process.rs @@ -106,11 +106,26 @@ pub async fn like(ctx: &crate::Context, activity: impl apb::Activity, tx: &Datab return Err(ProcessorError::AlreadyProcessed); } + let published = activity.published().unwrap_or_else(|_|chrono::Utc::now()); + let content = activity.content().unwrap_or_default(); + + // likes without addressing are "silent likes", process them but dont store activity or notify + let aid = if likes_local_object || !activity.addressed().is_empty() { + let mut activity_model = ctx.insert_activity(activity, tx).await?; + if likes_local_object { + activity_model.to.0.push(obj.attributed_to.clone().unwrap_or_default()); + } + ctx.address(Some(&activity_model), None, tx).await?; + Some(activity_model.internal) + } else { None }; + let like = crate::model::like::ActiveModel { internal: NotSet, actor: Set(actor.internal), object: Set(obj.internal), - published: Set(activity.published().unwrap_or_else(|_|chrono::Utc::now())), + published: Set(published), + activity: Set(aid), + content: Set(content), }; crate::model::like::Entity::insert(like).exec(tx).await?; @@ -120,19 +135,12 @@ pub async fn like(ctx: &crate::Context, activity: impl apb::Activity, tx: &Datab .exec(tx) .await?; - // likes without addressing are "silent likes", process them but dont store activity or notify - if likes_local_object || !activity.addressed().is_empty() { - let mut activity_model = ctx.insert_activity(activity, tx).await?; - if likes_local_object { - activity_model.to.0.push(obj.attributed_to.clone().unwrap_or_default()); - } - ctx.address(Some(&activity_model), None, tx).await?; - + if let Some(aid) = aid { // TODO check that object author is in this like addressing!!! otherwise skip notification if let Some(ref attributed_to) = obj.attributed_to { if ctx.is_local(attributed_to) { if let Some(actor_internal) = crate::model::actor::Entity::ap_to_internal(attributed_to, tx).await? { - crate::Query::notify(activity_model.internal, actor_internal) + crate::Query::notify(aid, actor_internal) .exec(tx) .await?; } @@ -534,6 +542,19 @@ pub async fn announce(ctx: &crate::Context, activity: impl apb::Activity, tx: &D let actor = ctx.fetch_user(&activity.actor().id()?, tx).await?; + let published = activity.published().unwrap_or(chrono::Utc::now()); + + // TODO we should probably insert an activity, otherwise this won't appear on timelines!! + // or maybe go update all addressing records for this object, pushing them up + // or maybe create new addressing rows with more recent dates + // or maybe create fake objects that reference the original one + // idk!!!! + let aid = if actor.actor_type == apb::ActorType::Person || ctx.is_local(&actor.id) { + let activity_model = ctx.insert_activity(activity, tx).await?; + ctx.address(Some(&activity_model), None, tx).await?; + Some(activity_model.internal) + } else { None }; + // we only care about announces produced by "Person" actors, because there's intention // anything shared by groups, services or applications is automated: fetch it and be done if actor.actor_type == apb::ActorType::Person { @@ -553,26 +574,19 @@ pub async fn announce(ctx: &crate::Context, activity: impl apb::Activity, tx: &D internal: NotSet, actor: Set(actor.internal), object: Set(object.internal), - published: Set(activity.published().unwrap_or(chrono::Utc::now())), + published: Set(published), + activity: Set(aid), }; crate::model::announce::Entity::insert(share) .exec(tx).await?; } - // TODO we should probably insert an activity, otherwise this won't appear on timelines!! - // or maybe go update all addressing records for this object, pushing them up - // or maybe create new addressing rows with more recent dates - // or maybe create fake objects that reference the original one - // idk!!!! - if actor.actor_type == apb::ActorType::Person || ctx.is_local(&actor.id) { - let activity_model = ctx.insert_activity(activity, tx).await?; - ctx.address(Some(&activity_model), None, tx).await?; - + if let Some(aid) = aid { if let Some(ref attributed_to) = object.attributed_to { if ctx.is_local(attributed_to) { if let Some(actor_internal) = crate::model::actor::Entity::ap_to_internal(attributed_to, tx).await? { - crate::Query::notify(activity_model.internal, actor_internal) + crate::Query::notify(aid, actor_internal) .exec(tx) .await?; } diff --git a/upub/migrations/src/lib.rs b/upub/migrations/src/lib.rs index 254b390..3091cd6 100644 --- a/upub/migrations/src/lib.rs +++ b/upub/migrations/src/lib.rs @@ -21,6 +21,7 @@ mod m20240715_000001_add_quote_uri_to_objects; mod m20240715_000002_add_actors_fields_and_aliases; mod m20240811_000001_add_full_text_index; mod m20241226_000001_add_show_likes_collection; +mod m20241226_000002_add_like_activities; pub struct Migrator; @@ -49,6 +50,7 @@ impl MigratorTrait for Migrator { Box::new(m20240715_000002_add_actors_fields_and_aliases::Migration), Box::new(m20240811_000001_add_full_text_index::Migration), Box::new(m20241226_000001_add_show_likes_collection::Migration), + Box::new(m20241226_000002_add_like_activities::Migration), ] } } diff --git a/upub/migrations/src/m20240524_000002_create_relations_likes_shares.rs b/upub/migrations/src/m20240524_000002_create_relations_likes_shares.rs index 5029156..9ab2497 100644 --- a/upub/migrations/src/m20240524_000002_create_relations_likes_shares.rs +++ b/upub/migrations/src/m20240524_000002_create_relations_likes_shares.rs @@ -21,7 +21,8 @@ pub enum Likes { Internal, Actor, Object, - Activity, // DROPPED + Activity, // DROPPED and ADDED BACK with migration m20241226_000002 + Content, // ADDED with migration m20241226_000002 Published, } @@ -32,6 +33,7 @@ pub enum Announces { Internal, Actor, Object, + Activity, // ADDED with migration m20241226_000002 Published, } diff --git a/upub/migrations/src/m20241226_000002_add_like_activities.rs b/upub/migrations/src/m20241226_000002_add_like_activities.rs new file mode 100644 index 0000000..6e6505b --- /dev/null +++ b/upub/migrations/src/m20241226_000002_add_like_activities.rs @@ -0,0 +1,115 @@ +use sea_orm_migration::prelude::*; + +use crate::{m20240524_000001_create_actor_activity_object_tables::Activities, m20240524_000002_create_relations_likes_shares::{Announces, Likes}}; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .alter_table( + Table::alter() + .table(Likes::Table) + .add_column(ColumnDef::new(Likes::Content).string().not_null().default("")) + .add_column(ColumnDef::new(Likes::Activity).big_integer().null()) + .add_foreign_key( + TableForeignKey::new() + .name("fkey-likes-activity") + .from_tbl(Likes::Table) + .from_col(Likes::Activity) + .to_tbl(Activities::Table) + .to_col(Activities::Internal) + .on_update(ForeignKeyAction::Cascade) + .on_delete(ForeignKeyAction::Cascade) + ) + .to_owned() + ) + .await?; + + manager + .drop_index( + Index::drop() + .name("index-likes-actor-object") + .table(Likes::Table) + .to_owned() + ) + .await?; + + manager + .create_index( + Index::create() + .unique() + .name("index-likes-actor-object-content") + .table(Likes::Table) + .col(Likes::Actor) + .col(Likes::Object) + .col(Likes::Content) + .to_owned() + ).await?; + + manager + .alter_table( + Table::alter() + .table(Announces::Table) + .add_column(ColumnDef::new(Announces::Activity).big_integer().null()) + .add_foreign_key( + TableForeignKey::new() + .name("fkey-announces-activity") + .from_tbl(Announces::Table) + .from_col(Announces::Activity) + .to_tbl(Activities::Table) + .to_col(Activities::Internal) + .on_update(ForeignKeyAction::Cascade) + .on_delete(ForeignKeyAction::Cascade) + ) + .to_owned() + ) + .await?; + + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .alter_table( + Table::alter() + .table(Likes::Table) + .drop_column(Likes::Activity) + .to_owned() + ) + .await?; + + manager + .drop_index( + Index::drop() + .name("index-likes-actor-object-content") + .table(Likes::Table) + .to_owned() + ) + .await?; + + manager + .create_index( + Index::create() + .unique() + .name("index-likes-actor-object") + .table(Likes::Table) + .col(Likes::Actor) + .col(Likes::Object) + .to_owned() + ).await?; + + manager + .alter_table( + Table::alter() + .table(Announces::Table) + .drop_column(Announces::Activity) + .to_owned() + ) + .await?; + + Ok(()) + } +}