feat: add dislikes table, process them

not displayed anywhere yet tho
This commit is contained in:
əlemi 2024-06-23 17:27:49 +02:00
parent 523cb8b90f
commit 5302e4ad46
Signed by: alemi
GPG key ID: A4895B84D311642C
8 changed files with 188 additions and 3 deletions

2
Cargo.lock generated
View file

@ -140,7 +140,7 @@ checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c"
[[package]] [[package]]
name = "apb" name = "apb"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"chrono", "chrono",

View file

@ -50,6 +50,8 @@ pub enum Relation {
on_delete = "NoAction" on_delete = "NoAction"
)] )]
Instances, Instances,
#[sea_orm(has_many = "super::dislike::Entity")]
Dislikes,
#[sea_orm(has_many = "super::like::Entity")] #[sea_orm(has_many = "super::like::Entity")]
Likes, Likes,
#[sea_orm(has_many = "super::mention::Entity")] #[sea_orm(has_many = "super::mention::Entity")]
@ -98,6 +100,12 @@ impl Related<super::instance::Entity> for Entity {
} }
} }
impl Related<super::dislike::Entity> for Entity {
fn to() -> RelationDef {
Relation::Dislikes.def()
}
}
impl Related<super::like::Entity> for Entity { impl Related<super::like::Entity> for Entity {
fn to() -> RelationDef { fn to() -> RelationDef {
Relation::Likes.def() Relation::Likes.def()

View file

@ -0,0 +1,51 @@
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "dislikes")]
pub struct Model {
#[sea_orm(primary_key)]
pub internal: i64,
pub actor: i64,
pub object: i64,
pub published: ChronoDateTimeUtc,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::actor::Entity",
from = "Column::Actor",
to = "super::actor::Column::Internal",
on_update = "Cascade",
on_delete = "Cascade"
)]
Actors,
#[sea_orm(
belongs_to = "super::object::Entity",
from = "Column::Object",
to = "super::object::Column::Internal",
on_update = "Cascade",
on_delete = "Cascade"
)]
Objects,
}
impl Related<super::actor::Entity> for Entity {
fn to() -> RelationDef {
Relation::Actors.def()
}
}
impl Related<super::object::Entity> for Entity {
fn to() -> RelationDef {
Relation::Objects.def()
}
}
impl ActiveModelBehavior for ActiveModel {}
impl Entity {
pub fn find_by_uid_oid(uid: i64, oid: i64) -> Select<Entity> {
Entity::find().filter(Column::Actor.eq(uid)).filter(Column::Object.eq(oid))
}
}

View file

@ -13,6 +13,7 @@ pub mod job;
pub mod relation; pub mod relation;
pub mod announce; pub mod announce;
pub mod like; pub mod like;
pub mod dislike;
pub mod hashtag; pub mod hashtag;
pub mod mention; pub mod mention;

View file

@ -50,6 +50,8 @@ pub enum Relation {
Announces, Announces,
#[sea_orm(has_many = "super::attachment::Entity")] #[sea_orm(has_many = "super::attachment::Entity")]
Attachments, Attachments,
#[sea_orm(has_many = "super::dislike::Entity")]
Dislikes,
#[sea_orm(has_many = "super::hashtag::Entity")] #[sea_orm(has_many = "super::hashtag::Entity")]
Hashtags, Hashtags,
#[sea_orm(has_many = "super::like::Entity")] #[sea_orm(has_many = "super::like::Entity")]
@ -96,6 +98,12 @@ impl Related<super::attachment::Entity> for Entity {
} }
} }
impl Related<super::dislike::Entity> for Entity {
fn to() -> RelationDef {
Relation::Dislikes.def()
}
}
impl Related<super::hashtag::Entity> for Entity { impl Related<super::hashtag::Entity> for Entity {
fn to() -> RelationDef { fn to() -> RelationDef {
Relation::Hashtags.def() Relation::Hashtags.def()

View file

@ -39,8 +39,8 @@ impl Processor for crate::Context {
async fn process(&self, activity: impl apb::Activity, tx: &DatabaseTransaction) -> Result<(), ProcessorError> { async fn process(&self, activity: impl apb::Activity, tx: &DatabaseTransaction) -> Result<(), ProcessorError> {
// TODO we could process Links and bare Objects maybe, but probably out of AP spec? // TODO we could process Links and bare Objects maybe, but probably out of AP spec?
match activity.activity_type()? { match activity.activity_type()? {
// TODO emojireacts are NOT likes, but let's process them like ones for now maybe? apb::ActivityType::Like => Ok(like(self, activity, tx).await?),
apb::ActivityType::Like | apb::ActivityType::EmojiReact => Ok(like(self, activity, tx).await?), apb::ActivityType::Dislike => Ok(dislike(self, activity, tx).await?),
apb::ActivityType::Create => Ok(create(self, activity, tx).await?), apb::ActivityType::Create => Ok(create(self, activity, tx).await?),
apb::ActivityType::Follow => Ok(follow(self, activity, tx).await?), apb::ActivityType::Follow => Ok(follow(self, activity, tx).await?),
apb::ActivityType::Announce => Ok(announce(self, activity, tx).await?), apb::ActivityType::Announce => Ok(announce(self, activity, tx).await?),
@ -115,6 +115,36 @@ pub async fn like(ctx: &crate::Context, activity: impl apb::Activity, tx: &Datab
Ok(()) Ok(())
} }
// TODO basically same as like, can we make one function, maybe with const generic???
pub async fn dislike(ctx: &crate::Context, activity: impl apb::Activity, tx: &DatabaseTransaction) -> Result<(), ProcessorError> {
let actor = ctx.fetch_user(activity.actor().id()?, tx).await?;
let obj = ctx.fetch_object(activity.object().id()?, tx).await?;
if crate::model::like::Entity::find_by_uid_oid(actor.internal, obj.internal)
.any(tx)
.await?
{
return Err(ProcessorError::AlreadyProcessed);
}
let dislike = crate::model::dislike::ActiveModel {
internal: NotSet,
actor: Set(actor.internal),
object: Set(obj.internal),
published: Set(activity.published().unwrap_or_else(|_|chrono::Utc::now())),
};
crate::model::dislike::Entity::insert(dislike).exec(tx).await?;
// only dislikes mentioning local users are stored to generate notifications, everything else
// produces side effects but no activity, and thus no notification
if ctx.is_local(&actor.id) || activity.mentioning().iter().any(|x| ctx.is_local(x)) {
ctx.insert_activity(activity, tx).await?;
}
tracing::debug!("{} disliked {}", actor.id, obj.id);
Ok(())
}
pub async fn follow(ctx: &crate::Context, activity: impl apb::Activity, tx: &DatabaseTransaction) -> Result<(), ProcessorError> { pub async fn follow(ctx: &crate::Context, activity: impl apb::Activity, tx: &DatabaseTransaction) -> Result<(), ProcessorError> {
let source_actor = crate::model::actor::Entity::find_by_ap_id(activity.actor().id()?) let source_actor = crate::model::actor::Entity::find_by_ap_id(activity.actor().id()?)
.one(tx) .one(tx)

View file

@ -10,6 +10,7 @@ mod m20240605_000001_add_jobs_table;
mod m20240606_000001_add_audience_to_objects; mod m20240606_000001_add_audience_to_objects;
mod m20240607_000001_activity_ref_is_optional; mod m20240607_000001_activity_ref_is_optional;
mod m20240609_000001_add_instance_field_to_relations; mod m20240609_000001_add_instance_field_to_relations;
mod m20240623_000001_add_dislikes_table;
pub struct Migrator; pub struct Migrator;
@ -27,6 +28,7 @@ impl MigratorTrait for Migrator {
Box::new(m20240606_000001_add_audience_to_objects::Migration), Box::new(m20240606_000001_add_audience_to_objects::Migration),
Box::new(m20240607_000001_activity_ref_is_optional::Migration), Box::new(m20240607_000001_activity_ref_is_optional::Migration),
Box::new(m20240609_000001_add_instance_field_to_relations::Migration), Box::new(m20240609_000001_add_instance_field_to_relations::Migration),
Box::new(m20240623_000001_add_dislikes_table::Migration),
] ]
} }
} }

View file

@ -0,0 +1,85 @@
use sea_orm_migration::prelude::*;
use super::m20240524_000001_create_actor_activity_object_tables::{Actors, Objects};
#[derive(DeriveIden)]
#[allow(clippy::enum_variant_names)]
pub enum Dislikes {
Table,
Internal,
Actor,
Object,
Published,
}
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(Dislikes::Table)
.comment("all dislike events, joining actor to object")
.col(
ColumnDef::new(Dislikes::Internal)
.big_integer()
.not_null()
.primary_key()
.auto_increment()
)
.col(ColumnDef::new(Dislikes::Actor).big_integer().not_null())
.foreign_key(
ForeignKey::create()
.name("fkey-dislikes-actor")
.from(Dislikes::Table, Dislikes::Actor)
.to(Actors::Table, Actors::Internal)
.on_update(ForeignKeyAction::Cascade)
.on_delete(ForeignKeyAction::Cascade)
)
.col(ColumnDef::new(Dislikes::Object).big_integer().not_null())
.foreign_key(
ForeignKey::create()
.name("fkey-dislikes-object")
.from(Dislikes::Table, Dislikes::Object)
.to(Objects::Table, Objects::Internal)
.on_update(ForeignKeyAction::Cascade)
.on_delete(ForeignKeyAction::Cascade)
)
.col(ColumnDef::new(Dislikes::Published).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
.to_owned()
)
.await?;
manager
.create_index(Index::create().name("index-dislikes-actor").table(Dislikes::Table).col(Dislikes::Actor).to_owned())
.await?;
manager
.create_index(Index::create().name("index-dislikes-object").table(Dislikes::Table).col(Dislikes::Object).to_owned())
.await?;
manager
.create_index(
Index::create()
.unique()
.name("index-dislikes-actor-object")
.table(Dislikes::Table)
.col(Dislikes::Actor)
.col(Dislikes::Object)
.to_owned()
).await?;
Ok(())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(Dislikes::Table).to_owned())
.await?;
Ok(())
}
}