diff --git a/src/model/like.rs b/src/model/like.rs index 004dd4e..c92f2b8 100644 --- a/src/model/like.rs +++ b/src/model/like.rs @@ -58,3 +58,9 @@ impl Related for Entity { } impl ActiveModelBehavior for ActiveModel {} + +impl Entity { + pub fn find_by_uid_oid(uid: i64, oid: i64) -> Select { + Entity::find().filter(Column::Actor.eq(uid)).filter(Column::Object.eq(oid)) + } +} diff --git a/src/server/builders.rs b/src/server/builders.rs index 039883b..6ca040c 100644 --- a/src/server/builders.rs +++ b/src/server/builders.rs @@ -63,3 +63,22 @@ pub fn collection(id: &str, total_items: Option) -> crate::Result crate::Result; +} + +#[axum::async_trait] +impl AnyQuery for sea_orm::Select { + async fn any(self, db: &sea_orm::DatabaseConnection) -> crate::Result { + Ok(self.one(db).await?.is_some()) + } +} + +#[axum::async_trait] +impl AnyQuery for sea_orm::Selector { + async fn any(self, db: &sea_orm::DatabaseConnection) -> crate::Result { + Ok(self.one(db).await?.is_some()) + } +} diff --git a/src/server/context.rs b/src/server/context.rs index e310010..f3202b4 100644 --- a/src/server/context.rs +++ b/src/server/context.rs @@ -6,7 +6,7 @@ use sea_orm::{ActiveValue::NotSet, ColumnTrait, DatabaseConnection, EntityTrait, use crate::{config::Config, errors::UpubError, model, server::fetcher::Fetcher}; use uriproxy::UriClass; -use super::dispatcher::Dispatcher; +use super::{builders::AnyQuery, dispatcher::Dispatcher}; #[derive(Clone)] @@ -181,43 +181,34 @@ impl Context { } pub async fn is_local_internal_object(&self, internal: i64) -> crate::Result { - Ok( - model::object::Entity::find() - .filter(model::object::Column::Internal.eq(internal)) - .select_only() - .select_column(model::object::Column::Internal) - .into_tuple::() - .one(self.db()) - .await? - .is_some() - ) + model::object::Entity::find() + .filter(model::object::Column::Internal.eq(internal)) + .select_only() + .select_column(model::object::Column::Internal) + .into_tuple::() + .any(self.db()) + .await } pub async fn is_local_internal_activity(&self, internal: i64) -> crate::Result { - Ok( - model::activity::Entity::find() - .filter(model::activity::Column::Internal.eq(internal)) - .select_only() - .select_column(model::activity::Column::Internal) - .into_tuple::() - .one(self.db()) - .await? - .is_some() - ) + model::activity::Entity::find() + .filter(model::activity::Column::Internal.eq(internal)) + .select_only() + .select_column(model::activity::Column::Internal) + .into_tuple::() + .any(self.db()) + .await } #[allow(unused)] pub async fn is_local_internal_actor(&self, internal: i64) -> crate::Result { - Ok( - model::actor::Entity::find() - .filter(model::actor::Column::Internal.eq(internal)) - .select_only() - .select_column(model::actor::Column::Internal) - .into_tuple::() - .one(self.db()) - .await? - .is_some() - ) + model::actor::Entity::find() + .filter(model::actor::Column::Internal.eq(internal)) + .select_only() + .select_column(model::actor::Column::Internal) + .into_tuple::() + .any(self.db()) + .await } pub async fn expand_addressing(&self, targets: Vec) -> crate::Result> { diff --git a/src/server/inbox.rs b/src/server/inbox.rs index 998980d..3b69f27 100644 --- a/src/server/inbox.rs +++ b/src/server/inbox.rs @@ -2,7 +2,7 @@ use apb::{target::Addressed, Activity, Base, Object}; use reqwest::StatusCode; use sea_orm::{sea_query::Expr, ActiveValue::{Set, NotSet}, ColumnTrait, Condition, EntityTrait, QueryFilter, QuerySelect, SelectColumns}; -use crate::{errors::{LoggableError, UpubError}, model, server::normalizer::Normalizer}; +use crate::{errors::{LoggableError, UpubError}, model, server::{builders::AnyQuery, normalizer::Normalizer}}; use super::{fetcher::Fetcher, Context}; @@ -31,43 +31,42 @@ impl apb::server::Inbox for Context { let internal_uid = model::actor::Entity::ap_to_internal(&uid, self.db()).await?; let object_uri = activity.object().id().ok_or(UpubError::bad_request())?; let obj = self.fetch_object(&object_uri).await?; + if model::like::Entity::find_by_uid_oid(internal_uid, obj.internal) + .any(self.db()) + .await? + { + return Err(UpubError::not_modified()); + } + + let activity_model = self.insert_activity(activity, Some(server)).await?; let like = model::like::ActiveModel { internal: NotSet, actor: Set(internal_uid), object: Set(obj.internal), - published: Set(activity.published().unwrap_or(chrono::Utc::now())), + activity: Set(activity_model.internal), + published: Set(activity_model.published), }; - match model::like::Entity::insert(like).exec(self.db()).await { - Err(sea_orm::DbErr::RecordNotInserted) => Err(UpubError::not_modified()), - Err(sea_orm::DbErr::Exec(_)) => Err(UpubError::not_modified()), // bad fix for sqlite - Err(e) => { - tracing::error!("unexpected error procesing like from {uid} to {}: {e}", obj.id); - Err(UpubError::internal_server_error()) - } - Ok(_) => { - let activity_model = self.insert_activity(activity, Some(server)).await?; - let mut expanded_addressing = self.expand_addressing(activity_model.addressed()).await?; - if expanded_addressing.is_empty() { // WHY MASTODON!!!!!!! - expanded_addressing.push( - model::object::Entity::find_by_id(obj.internal) - .select_only() - .select_column(model::object::Column::AttributedTo) - .into_tuple::() - .one(self.db()) - .await? - .ok_or_else(UpubError::not_found)? - ); - } - self.address_to(Some(activity_model.internal), None, &expanded_addressing).await?; - model::object::Entity::update_many() - .col_expr(model::object::Column::Likes, Expr::col(model::object::Column::Likes).add(1)) - .filter(model::object::Column::Internal.eq(obj.internal)) - .exec(self.db()) - .await?; - tracing::info!("{} liked {}", uid, obj.id); - Ok(()) - }, + model::like::Entity::insert(like).exec(self.db()).await?; + model::object::Entity::update_many() + .col_expr(model::object::Column::Likes, Expr::col(model::object::Column::Likes).add(1)) + .filter(model::object::Column::Internal.eq(obj.internal)) + .exec(self.db()) + .await?; + let mut expanded_addressing = self.expand_addressing(activity_model.addressed()).await?; + if expanded_addressing.is_empty() { // WHY MASTODON!!!!!!! + expanded_addressing.push( + model::object::Entity::find_by_id(obj.internal) + .select_only() + .select_column(model::object::Column::AttributedTo) + .into_tuple::() + .one(self.db()) + .await? + .ok_or_else(UpubError::not_found)? + ); } + self.address_to(Some(activity_model.internal), None, &expanded_addressing).await?; + tracing::info!("{} liked {}", uid, obj.id); + Ok(()) } async fn follow(&self, _: String, activity: serde_json::Value) -> crate::Result<()> { diff --git a/src/server/outbox.rs b/src/server/outbox.rs index 08054e5..6ac32bb 100644 --- a/src/server/outbox.rs +++ b/src/server/outbox.rs @@ -4,7 +4,7 @@ use sea_orm::{sea_query::Expr, ActiveValue::{Set, NotSet, Unchanged}, ColumnTrai use crate::{errors::UpubError, model}; -use super::{fetcher::Fetcher, normalizer::Normalizer, Context}; +use super::{builders::AnyQuery, fetcher::Fetcher, normalizer::Normalizer, Context}; #[axum::async_trait] @@ -114,25 +114,33 @@ impl apb::server::Outbox for Context { let activity_targets = activity.addressed(); let oid = activity.object().id().ok_or_else(UpubError::bad_request)?; let obj_model = self.fetch_object(&oid).await?; - let activity_model = model::activity::ActiveModel::new( - &activity - .set_id(Some(&aid)) - .set_actor(Node::link(uid.clone())) - .set_published(Some(chrono::Utc::now())) - )?; let internal_uid = model::actor::Entity::ap_to_internal(&uid, self.db()).await?; + if model::like::Entity::find_by_uid_oid(internal_uid, obj_model.internal) + .any(self.db()) + .await? + { + return Err(UpubError::not_modified()); + } + + let activity_model = self.insert_activity( + activity + .set_id(Some(&aid)) + .set_actor(Node::link(uid.clone())) + .set_published(Some(chrono::Utc::now())), + Some(self.domain().to_string()), + ).await?; + let like_model = model::like::ActiveModel { internal: NotSet, actor: Set(internal_uid), object: Set(obj_model.internal), + activity: Set(activity_model.internal), published: Set(chrono::Utc::now()), }; model::like::Entity::insert(like_model).exec(self.db()).await?; - model::activity::Entity::insert(activity_model) - .exec(self.db()).await?; model::object::Entity::update_many() .col_expr(model::object::Column::Likes, Expr::col(model::object::Column::Likes).add(1)) .filter(model::object::Column::Internal.eq(obj_model.internal))