From d82194cfe9f98984a64353f6511bb73b4f08baa4 Mon Sep 17 00:00:00 2001 From: Nutomic <me@nutomic.com> Date: Tue, 1 Aug 2023 15:53:36 +0200 Subject: [PATCH] Rewrite some federation actions to remove Perform/SendActivity (ref #3670) (#3758) --- crates/api/src/comment/like.rs | 111 ++++++------ crates/api/src/post/like.rs | 97 ++++++----- crates/api/src/post/mod.rs | 12 +- crates/api_common/src/send_activity.rs | 10 +- crates/api_crud/src/comment/create.rs | 3 +- crates/api_crud/src/comment/delete.rs | 110 ++++++------ crates/api_crud/src/comment/read.rs | 3 +- crates/api_crud/src/comment/remove.rs | 125 +++++++------- crates/api_crud/src/comment/update.rs | 129 +++++++------- crates/api_crud/src/post/update.rs | 159 +++++++++--------- .../activities/create_or_update/comment.rs | 21 --- .../src/activities/create_or_update/post.rs | 25 +-- crates/apub/src/activities/deletion/mod.rs | 47 +----- crates/apub/src/activities/mod.rs | 23 ++- crates/apub/src/activities/voting/mod.rs | 89 ++-------- src/api_routes_http.rs | 29 ++-- 16 files changed, 466 insertions(+), 527 deletions(-) diff --git a/crates/api/src/comment/like.rs b/crates/api/src/comment/like.rs index e84c55cb..13122b4b 100644 --- a/crates/api/src/comment/like.rs +++ b/crates/api/src/comment/like.rs @@ -1,9 +1,10 @@ -use crate::Perform; -use actix_web::web::Data; +use activitypub_federation::config::Data; +use actix_web::web::Json; use lemmy_api_common::{ build_response::build_comment_response, comment::{CommentResponse, CreateCommentLike}, context::LemmyContext, + send_activity::{ActivityChannel, SendActivityData}, utils::{check_community_ban, check_downvotes_enabled, local_user_view_from_jwt}, }; use lemmy_db_schema::{ @@ -17,70 +18,80 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::{CommentView, LocalUserView}; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use std::ops::Deref; -#[async_trait::async_trait(?Send)] -impl Perform for CreateCommentLike { - type Response = CommentResponse; +#[tracing::instrument(skip(context))] +pub async fn like_comment( + data: Json<CreateCommentLike>, + context: Data<LemmyContext>, +) -> Result<Json<CommentResponse>, LemmyError> { + let local_site = LocalSite::read(&mut context.pool()).await?; + let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?; - #[tracing::instrument(skip(context))] - async fn perform(&self, context: &Data<LemmyContext>) -> Result<CommentResponse, LemmyError> { - let data: &CreateCommentLike = self; - let local_site = LocalSite::read(&mut context.pool()).await?; - let local_user_view = local_user_view_from_jwt(&data.auth, context).await?; + let mut recipient_ids = Vec::<LocalUserId>::new(); - let mut recipient_ids = Vec::<LocalUserId>::new(); + // Don't do a downvote if site has downvotes disabled + check_downvotes_enabled(data.score, &local_site)?; - // Don't do a downvote if site has downvotes disabled - check_downvotes_enabled(data.score, &local_site)?; + let comment_id = data.comment_id; + let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?; - let comment_id = data.comment_id; - let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?; + check_community_ban( + local_user_view.person.id, + orig_comment.community.id, + &mut context.pool(), + ) + .await?; - check_community_ban( - local_user_view.person.id, - orig_comment.community.id, - &mut context.pool(), - ) - .await?; - - // Add parent poster or commenter to recipients - let comment_reply = CommentReply::read_by_comment(&mut context.pool(), comment_id).await; - if let Ok(reply) = comment_reply { - let recipient_id = reply.recipient_id; - if let Ok(local_recipient) = - LocalUserView::read_person(&mut context.pool(), recipient_id).await - { - recipient_ids.push(local_recipient.local_user.id); - } + // Add parent poster or commenter to recipients + let comment_reply = CommentReply::read_by_comment(&mut context.pool(), comment_id).await; + if let Ok(reply) = comment_reply { + let recipient_id = reply.recipient_id; + if let Ok(local_recipient) = LocalUserView::read_person(&mut context.pool(), recipient_id).await + { + recipient_ids.push(local_recipient.local_user.id); } + } - let like_form = CommentLikeForm { - comment_id: data.comment_id, - post_id: orig_comment.post.id, - person_id: local_user_view.person.id, - score: data.score, - }; + let like_form = CommentLikeForm { + comment_id: data.comment_id, + post_id: orig_comment.post.id, + person_id: local_user_view.person.id, + score: data.score, + }; - // Remove any likes first - let person_id = local_user_view.person.id; + // Remove any likes first + let person_id = local_user_view.person.id; - CommentLike::remove(&mut context.pool(), person_id, comment_id).await?; + CommentLike::remove(&mut context.pool(), person_id, comment_id).await?; - // Only add the like if the score isnt 0 - let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1); - if do_add { - CommentLike::like(&mut context.pool(), &like_form) - .await - .with_lemmy_type(LemmyErrorType::CouldntLikeComment)?; - } + // Only add the like if the score isnt 0 + let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1); + if do_add { + CommentLike::like(&mut context.pool(), &like_form) + .await + .with_lemmy_type(LemmyErrorType::CouldntLikeComment)?; + } + + ActivityChannel::submit_activity( + SendActivityData::LikePostOrComment( + orig_comment.comment.ap_id, + local_user_view.person.clone(), + orig_comment.community, + data.score, + ), + &context, + ) + .await?; + Ok(Json( build_comment_response( - context, + context.deref(), comment_id, Some(local_user_view), None, recipient_ids, ) - .await - } + .await?, + )) } diff --git a/crates/api/src/post/like.rs b/crates/api/src/post/like.rs index 025129d3..1ff119f0 100644 --- a/crates/api/src/post/like.rs +++ b/crates/api/src/post/like.rs @@ -1,9 +1,10 @@ -use crate::Perform; -use actix_web::web::Data; +use activitypub_federation::config::Data; +use actix_web::web::Json; use lemmy_api_common::{ build_response::build_post_response, context::LemmyContext, post::{CreatePostLike, PostResponse}, + send_activity::{ActivityChannel, SendActivityData}, utils::{ check_community_ban, check_community_deleted_or_removed, @@ -14,66 +15,78 @@ use lemmy_api_common::{ }; use lemmy_db_schema::{ source::{ + community::Community, local_site::LocalSite, post::{Post, PostLike, PostLikeForm}, }, traits::{Crud, Likeable}, }; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use std::ops::Deref; -#[async_trait::async_trait(?Send)] -impl Perform for CreatePostLike { - type Response = PostResponse; +#[tracing::instrument(skip(context))] +pub async fn like_post( + data: Json<CreatePostLike>, + context: Data<LemmyContext>, +) -> Result<Json<PostResponse>, LemmyError> { + let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?; + let local_site = LocalSite::read(&mut context.pool()).await?; - #[tracing::instrument(skip(context))] - async fn perform(&self, context: &Data<LemmyContext>) -> Result<PostResponse, LemmyError> { - let data: &CreatePostLike = self; - let local_user_view = local_user_view_from_jwt(&data.auth, context).await?; - let local_site = LocalSite::read(&mut context.pool()).await?; + // Don't do a downvote if site has downvotes disabled + check_downvotes_enabled(data.score, &local_site)?; - // Don't do a downvote if site has downvotes disabled - check_downvotes_enabled(data.score, &local_site)?; + // Check for a community ban + let post_id = data.post_id; + let post = Post::read(&mut context.pool(), post_id).await?; - // Check for a community ban - let post_id = data.post_id; - let post = Post::read(&mut context.pool(), post_id).await?; + check_community_ban( + local_user_view.person.id, + post.community_id, + &mut context.pool(), + ) + .await?; + check_community_deleted_or_removed(post.community_id, &mut context.pool()).await?; - check_community_ban( - local_user_view.person.id, - post.community_id, - &mut context.pool(), - ) - .await?; - check_community_deleted_or_removed(post.community_id, &mut context.pool()).await?; + let like_form = PostLikeForm { + post_id: data.post_id, + person_id: local_user_view.person.id, + score: data.score, + }; - let like_form = PostLikeForm { - post_id: data.post_id, - person_id: local_user_view.person.id, - score: data.score, - }; + // Remove any likes first + let person_id = local_user_view.person.id; - // Remove any likes first - let person_id = local_user_view.person.id; + PostLike::remove(&mut context.pool(), person_id, post_id).await?; - PostLike::remove(&mut context.pool(), person_id, post_id).await?; + // Only add the like if the score isnt 0 + let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1); + if do_add { + PostLike::like(&mut context.pool(), &like_form) + .await + .with_lemmy_type(LemmyErrorType::CouldntLikePost)?; + } - // Only add the like if the score isnt 0 - let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1); - if do_add { - PostLike::like(&mut context.pool(), &like_form) - .await - .with_lemmy_type(LemmyErrorType::CouldntLikePost)?; - } + // Mark the post as read + mark_post_as_read(person_id, post_id, &mut context.pool()).await?; - // Mark the post as read - mark_post_as_read(person_id, post_id, &mut context.pool()).await?; + ActivityChannel::submit_activity( + SendActivityData::LikePostOrComment( + post.ap_id, + local_user_view.person.clone(), + Community::read(&mut context.pool(), post.community_id).await?, + data.score, + ), + &context, + ) + .await?; + Ok(Json( build_post_response( - context, + context.deref(), post.community_id, local_user_view.person.id, post_id, ) - .await - } + .await?, + )) } diff --git a/crates/api/src/post/mod.rs b/crates/api/src/post/mod.rs index 4d825135..a3b84134 100644 --- a/crates/api/src/post/mod.rs +++ b/crates/api/src/post/mod.rs @@ -1,6 +1,6 @@ -mod feature; -mod get_link_metadata; -mod like; -mod lock; -mod mark_read; -mod save; +pub mod feature; +pub mod get_link_metadata; +pub mod like; +pub mod lock; +pub mod mark_read; +pub mod save; diff --git a/crates/api_common/src/send_activity.rs b/crates/api_common/src/send_activity.rs index 994aea2a..7954db28 100644 --- a/crates/api_common/src/send_activity.rs +++ b/crates/api_common/src/send_activity.rs @@ -1,7 +1,10 @@ use crate::context::LemmyContext; use activitypub_federation::config::Data; use futures::future::BoxFuture; -use lemmy_db_schema::source::{comment::Comment, post::Post}; +use lemmy_db_schema::{ + newtypes::DbUrl, + source::{comment::Comment, community::Community, person::Person, post::Post}, +}; use lemmy_utils::{error::LemmyResult, SYNCHRONOUS_FEDERATION}; use once_cell::sync::{Lazy, OnceCell}; use tokio::{ @@ -22,7 +25,12 @@ pub static MATCH_OUTGOING_ACTIVITIES: OnceCell<MatchOutgoingActivitiesBoxed> = O #[derive(Debug)] pub enum SendActivityData { CreatePost(Post), + UpdatePost(Post), CreateComment(Comment), + DeleteComment(Comment, Person, Community), + RemoveComment(Comment, Person, Community, Option<String>), + UpdateComment(Comment), + LikePostOrComment(DbUrl, Person, Community, i16), } // TODO: instead of static, move this into LemmyContext. make sure that stopping the process with diff --git a/crates/api_crud/src/comment/create.rs b/crates/api_crud/src/comment/create.rs index f334efe5..8b8afc8f 100644 --- a/crates/api_crud/src/comment/create.rs +++ b/crates/api_crud/src/comment/create.rs @@ -36,7 +36,6 @@ use lemmy_utils::{ validation::is_valid_body_field, }, }; -use std::ops::Deref; const MAX_COMMENT_DEPTH_LIMIT: usize = 100; @@ -196,7 +195,7 @@ pub async fn create_comment( Ok(Json( build_comment_response( - context.deref(), + &context, inserted_comment.id, Some(local_user_view), data.form_id.clone(), diff --git a/crates/api_crud/src/comment/delete.rs b/crates/api_crud/src/comment/delete.rs index eba7d1de..10063c66 100644 --- a/crates/api_crud/src/comment/delete.rs +++ b/crates/api_crud/src/comment/delete.rs @@ -1,9 +1,10 @@ -use crate::PerformCrud; -use actix_web::web::Data; +use activitypub_federation::config::Data; +use actix_web::web::Json; use lemmy_api_common::{ build_response::{build_comment_response, send_local_notifs}, comment::{CommentResponse, DeleteComment}, context::LemmyContext, + send_activity::{ActivityChannel, SendActivityData}, utils::{check_community_ban, local_user_view_from_jwt}, }; use lemmy_db_schema::{ @@ -15,66 +16,75 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::CommentView; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; -use std::ops::Deref; -#[async_trait::async_trait(?Send)] -impl PerformCrud for DeleteComment { - type Response = CommentResponse; +#[tracing::instrument(skip(context))] +pub async fn delete_comment( + data: Json<DeleteComment>, + context: Data<LemmyContext>, +) -> Result<Json<CommentResponse>, LemmyError> { + let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?; - #[tracing::instrument(skip(context))] - async fn perform(&self, context: &Data<LemmyContext>) -> Result<CommentResponse, LemmyError> { - let data: &DeleteComment = self; - let local_user_view = local_user_view_from_jwt(&data.auth, context).await?; + let comment_id = data.comment_id; + let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?; - let comment_id = data.comment_id; - let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?; + // Dont delete it if its already been deleted. + if orig_comment.comment.deleted == data.deleted { + return Err(LemmyErrorType::CouldntUpdateComment)?; + } - // Dont delete it if its already been deleted. - if orig_comment.comment.deleted == data.deleted { - return Err(LemmyErrorType::CouldntUpdateComment)?; - } + check_community_ban( + local_user_view.person.id, + orig_comment.community.id, + &mut context.pool(), + ) + .await?; - check_community_ban( - local_user_view.person.id, - orig_comment.community.id, - &mut context.pool(), - ) - .await?; + // Verify that only the creator can delete + if local_user_view.person.id != orig_comment.creator.id { + return Err(LemmyErrorType::NoCommentEditAllowed)?; + } - // Verify that only the creator can delete - if local_user_view.person.id != orig_comment.creator.id { - return Err(LemmyErrorType::NoCommentEditAllowed)?; - } + // Do the delete + let deleted = data.deleted; + let updated_comment = Comment::update( + &mut context.pool(), + comment_id, + &CommentUpdateForm::builder().deleted(Some(deleted)).build(), + ) + .await + .with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?; - // Do the delete - let deleted = data.deleted; - let updated_comment = Comment::update( - &mut context.pool(), - comment_id, - &CommentUpdateForm::builder().deleted(Some(deleted)).build(), - ) - .await - .with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?; + let post_id = updated_comment.post_id; + let post = Post::read(&mut context.pool(), post_id).await?; + let recipient_ids = send_local_notifs( + vec![], + &updated_comment, + &local_user_view.person, + &post, + false, + &context, + ) + .await?; + let updated_comment_id = updated_comment.id; - let post_id = updated_comment.post_id; - let post = Post::read(&mut context.pool(), post_id).await?; - let recipient_ids = send_local_notifs( - vec![], - &updated_comment, - &local_user_view.person, - &post, - false, - context, - ) - .await?; + ActivityChannel::submit_activity( + SendActivityData::DeleteComment( + updated_comment, + local_user_view.person.clone(), + orig_comment.community, + ), + &context, + ) + .await?; + Ok(Json( build_comment_response( - context.deref(), - updated_comment.id, + &context, + updated_comment_id, Some(local_user_view), None, recipient_ids, ) - .await - } + .await?, + )) } diff --git a/crates/api_crud/src/comment/read.rs b/crates/api_crud/src/comment/read.rs index 1a794dc5..4ade60df 100644 --- a/crates/api_crud/src/comment/read.rs +++ b/crates/api_crud/src/comment/read.rs @@ -7,7 +7,6 @@ use lemmy_api_common::{ }; use lemmy_db_schema::source::local_site::LocalSite; use lemmy_utils::error::LemmyError; -use std::ops::Deref; #[tracing::instrument(skip(context))] pub async fn get_comment( @@ -20,6 +19,6 @@ pub async fn get_comment( check_private_instance(&local_user_view, &local_site)?; Ok(Json( - build_comment_response(context.deref(), data.id, local_user_view, None, vec![]).await?, + build_comment_response(&context, data.id, local_user_view, None, vec![]).await?, )) } diff --git a/crates/api_crud/src/comment/remove.rs b/crates/api_crud/src/comment/remove.rs index cfc3ccff..b2071298 100644 --- a/crates/api_crud/src/comment/remove.rs +++ b/crates/api_crud/src/comment/remove.rs @@ -1,9 +1,10 @@ -use crate::PerformCrud; -use actix_web::web::Data; +use activitypub_federation::config::Data; +use actix_web::web::Json; use lemmy_api_common::{ build_response::{build_comment_response, send_local_notifs}, comment::{CommentResponse, RemoveComment}, context::LemmyContext, + send_activity::{ActivityChannel, SendActivityData}, utils::{check_community_ban, is_mod_or_admin, local_user_view_from_jwt}, }; use lemmy_db_schema::{ @@ -16,73 +17,83 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::CommentView; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; -use std::ops::Deref; -#[async_trait::async_trait(?Send)] -impl PerformCrud for RemoveComment { - type Response = CommentResponse; +#[tracing::instrument(skip(context))] +pub async fn remove_comment( + data: Json<RemoveComment>, + context: Data<LemmyContext>, +) -> Result<Json<CommentResponse>, LemmyError> { + let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?; - #[tracing::instrument(skip(context))] - async fn perform(&self, context: &Data<LemmyContext>) -> Result<CommentResponse, LemmyError> { - let data: &RemoveComment = self; - let local_user_view = local_user_view_from_jwt(&data.auth, context).await?; + let comment_id = data.comment_id; + let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?; - let comment_id = data.comment_id; - let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?; + check_community_ban( + local_user_view.person.id, + orig_comment.community.id, + &mut context.pool(), + ) + .await?; - check_community_ban( - local_user_view.person.id, - orig_comment.community.id, - &mut context.pool(), - ) - .await?; + // Verify that only a mod or admin can remove + is_mod_or_admin( + &mut context.pool(), + local_user_view.person.id, + orig_comment.community.id, + ) + .await?; - // Verify that only a mod or admin can remove - is_mod_or_admin( - &mut context.pool(), - local_user_view.person.id, - orig_comment.community.id, - ) - .await?; + // Do the remove + let removed = data.removed; + let updated_comment = Comment::update( + &mut context.pool(), + comment_id, + &CommentUpdateForm::builder().removed(Some(removed)).build(), + ) + .await + .with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?; - // Do the remove - let removed = data.removed; - let updated_comment = Comment::update( - &mut context.pool(), - comment_id, - &CommentUpdateForm::builder().removed(Some(removed)).build(), - ) - .await - .with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?; + // Mod tables + let form = ModRemoveCommentForm { + mod_person_id: local_user_view.person.id, + comment_id: data.comment_id, + removed: Some(removed), + reason: data.reason.clone(), + }; + ModRemoveComment::create(&mut context.pool(), &form).await?; - // Mod tables - let form = ModRemoveCommentForm { - mod_person_id: local_user_view.person.id, - comment_id: data.comment_id, - removed: Some(removed), - reason: data.reason.clone(), - }; - ModRemoveComment::create(&mut context.pool(), &form).await?; + let post_id = updated_comment.post_id; + let post = Post::read(&mut context.pool(), post_id).await?; + let recipient_ids = send_local_notifs( + vec![], + &updated_comment, + &local_user_view.person.clone(), + &post, + false, + &context, + ) + .await?; + let updated_comment_id = updated_comment.id; - let post_id = updated_comment.post_id; - let post = Post::read(&mut context.pool(), post_id).await?; - let recipient_ids = send_local_notifs( - vec![], - &updated_comment, - &local_user_view.person.clone(), - &post, - false, - context, - ) - .await?; + ActivityChannel::submit_activity( + SendActivityData::RemoveComment( + updated_comment, + local_user_view.person.clone(), + orig_comment.community, + data.reason.clone(), + ), + &context, + ) + .await?; + Ok(Json( build_comment_response( - context.deref(), - updated_comment.id, + &context, + updated_comment_id, Some(local_user_view), None, recipient_ids, ) - .await - } + .await?, + )) } diff --git a/crates/api_crud/src/comment/update.rs b/crates/api_crud/src/comment/update.rs index 5d4d75a3..c7b82ba9 100644 --- a/crates/api_crud/src/comment/update.rs +++ b/crates/api_crud/src/comment/update.rs @@ -1,9 +1,10 @@ -use crate::PerformCrud; -use actix_web::web::Data; +use activitypub_federation::config::Data; +use actix_web::web::Json; use lemmy_api_common::{ build_response::{build_comment_response, send_local_notifs}, comment::{CommentResponse, EditComment}, context::LemmyContext, + send_activity::{ActivityChannel, SendActivityData}, utils::{ check_community_ban, local_site_to_slur_regex, @@ -29,79 +30,83 @@ use lemmy_utils::{ validation::is_valid_body_field, }, }; -use std::ops::Deref; -#[async_trait::async_trait(?Send)] -impl PerformCrud for EditComment { - type Response = CommentResponse; +#[tracing::instrument(skip(context))] +pub async fn update_comment( + data: Json<EditComment>, + context: Data<LemmyContext>, +) -> Result<Json<CommentResponse>, LemmyError> { + let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?; + let local_site = LocalSite::read(&mut context.pool()).await?; - #[tracing::instrument(skip(context))] - async fn perform(&self, context: &Data<LemmyContext>) -> Result<CommentResponse, LemmyError> { - let data: &EditComment = self; - let local_user_view = local_user_view_from_jwt(&data.auth, context).await?; - let local_site = LocalSite::read(&mut context.pool()).await?; + let comment_id = data.comment_id; + let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?; - let comment_id = data.comment_id; - let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?; + check_community_ban( + local_user_view.person.id, + orig_comment.community.id, + &mut context.pool(), + ) + .await?; - check_community_ban( - local_user_view.person.id, - orig_comment.community.id, - &mut context.pool(), - ) - .await?; + // Verify that only the creator can edit + if local_user_view.person.id != orig_comment.creator.id { + return Err(LemmyErrorType::NoCommentEditAllowed)?; + } - // Verify that only the creator can edit - if local_user_view.person.id != orig_comment.creator.id { - return Err(LemmyErrorType::NoCommentEditAllowed)?; - } + let language_id = data.language_id; + CommunityLanguage::is_allowed_community_language( + &mut context.pool(), + language_id, + orig_comment.community.id, + ) + .await?; - let language_id = self.language_id; - CommunityLanguage::is_allowed_community_language( - &mut context.pool(), - language_id, - orig_comment.community.id, - ) - .await?; + // Update the Content + let content = data + .content + .as_ref() + .map(|c| remove_slurs(c, &local_site_to_slur_regex(&local_site))); + is_valid_body_field(&content, false)?; + let content = sanitize_html_opt(&content); - // Update the Content - let content = data - .content - .as_ref() - .map(|c| remove_slurs(c, &local_site_to_slur_regex(&local_site))); - is_valid_body_field(&content, false)?; - let content = sanitize_html_opt(&content); + let comment_id = data.comment_id; + let form = CommentUpdateForm::builder() + .content(content) + .language_id(data.language_id) + .updated(Some(Some(naive_now()))) + .build(); + let updated_comment = Comment::update(&mut context.pool(), comment_id, &form) + .await + .with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?; - let comment_id = data.comment_id; - let form = CommentUpdateForm::builder() - .content(content) - .language_id(data.language_id) - .updated(Some(Some(naive_now()))) - .build(); - let updated_comment = Comment::update(&mut context.pool(), comment_id, &form) - .await - .with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?; + // Do the mentions / recipients + let updated_comment_content = updated_comment.content.clone(); + let mentions = scrape_text_for_mentions(&updated_comment_content); + let recipient_ids = send_local_notifs( + mentions, + &updated_comment, + &local_user_view.person, + &orig_comment.post, + false, + &context, + ) + .await?; - // Do the mentions / recipients - let updated_comment_content = updated_comment.content.clone(); - let mentions = scrape_text_for_mentions(&updated_comment_content); - let recipient_ids = send_local_notifs( - mentions, - &updated_comment, - &local_user_view.person, - &orig_comment.post, - false, - context, - ) - .await?; + ActivityChannel::submit_activity( + SendActivityData::UpdateComment(updated_comment.clone()), + &context, + ) + .await?; + Ok(Json( build_comment_response( - context.deref(), + &context, updated_comment.id, Some(local_user_view), - self.form_id.clone(), + data.form_id.clone(), recipient_ids, ) - .await - } + .await?, + )) } diff --git a/crates/api_crud/src/post/update.rs b/crates/api_crud/src/post/update.rs index f3be5f6a..3341392b 100644 --- a/crates/api_crud/src/post/update.rs +++ b/crates/api_crud/src/post/update.rs @@ -1,10 +1,11 @@ -use crate::PerformCrud; -use actix_web::web::Data; +use activitypub_federation::config::Data; +use actix_web::web::Json; use lemmy_api_common::{ build_response::build_post_response, context::LemmyContext, post::{EditPost, PostResponse}, request::fetch_site_data, + send_activity::{ActivityChannel, SendActivityData}, utils::{ check_community_ban, local_site_to_slur_regex, @@ -28,95 +29,97 @@ use lemmy_utils::{ validation::{check_url_scheme, clean_url_params, is_valid_body_field, is_valid_post_title}, }, }; +use std::ops::Deref; -#[async_trait::async_trait(?Send)] -impl PerformCrud for EditPost { - type Response = PostResponse; +#[tracing::instrument(skip(context))] +pub async fn update_post( + data: Json<EditPost>, + context: Data<LemmyContext>, +) -> Result<Json<PostResponse>, LemmyError> { + let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?; + let local_site = LocalSite::read(&mut context.pool()).await?; - #[tracing::instrument(skip(context))] - async fn perform(&self, context: &Data<LemmyContext>) -> Result<PostResponse, LemmyError> { - let data: &EditPost = self; - let local_user_view = local_user_view_from_jwt(&data.auth, context).await?; - let local_site = LocalSite::read(&mut context.pool()).await?; + let data_url = data.url.as_ref(); - let data_url = data.url.as_ref(); + // TODO No good way to handle a clear. + // Issue link: https://github.com/LemmyNet/lemmy/issues/2287 + let url = Some(data_url.map(clean_url_params).map(Into::into)); - // TODO No good way to handle a clear. - // Issue link: https://github.com/LemmyNet/lemmy/issues/2287 - let url = Some(data_url.map(clean_url_params).map(Into::into)); + let slur_regex = local_site_to_slur_regex(&local_site); + check_slurs_opt(&data.name, &slur_regex)?; + check_slurs_opt(&data.body, &slur_regex)?; - let slur_regex = local_site_to_slur_regex(&local_site); - check_slurs_opt(&data.name, &slur_regex)?; - check_slurs_opt(&data.body, &slur_regex)?; + if let Some(name) = &data.name { + is_valid_post_title(name)?; + } - if let Some(name) = &data.name { - is_valid_post_title(name)?; - } + is_valid_body_field(&data.body, true)?; + check_url_scheme(&data.url)?; - is_valid_body_field(&data.body, true)?; - check_url_scheme(&data.url)?; + let post_id = data.post_id; + let orig_post = Post::read(&mut context.pool(), post_id).await?; - let post_id = data.post_id; - let orig_post = Post::read(&mut context.pool(), post_id).await?; + check_community_ban( + local_user_view.person.id, + orig_post.community_id, + &mut context.pool(), + ) + .await?; - check_community_ban( - local_user_view.person.id, - orig_post.community_id, - &mut context.pool(), - ) - .await?; - - // Verify that only the creator can edit - if !Post::is_post_creator(local_user_view.person.id, orig_post.creator_id) { - return Err(LemmyErrorType::NoPostEditAllowed)?; - } - - // Fetch post links and Pictrs cached image - let data_url = data.url.as_ref(); - let (metadata_res, thumbnail_url) = - fetch_site_data(context.client(), context.settings(), data_url, true).await; - let (embed_title, embed_description, embed_video_url) = metadata_res - .map(|u| (Some(u.title), Some(u.description), Some(u.embed_video_url))) - .unwrap_or_default(); - - let name = sanitize_html_opt(&data.name); - let body = sanitize_html_opt(&data.body); - let body = diesel_option_overwrite(body); - let embed_title = embed_title.map(|e| sanitize_html_opt(&e)); - let embed_description = embed_description.map(|e| sanitize_html_opt(&e)); - - let language_id = self.language_id; - CommunityLanguage::is_allowed_community_language( - &mut context.pool(), - language_id, - orig_post.community_id, - ) - .await?; - - let post_form = PostUpdateForm::builder() - .name(name) - .url(url) - .body(body) - .nsfw(data.nsfw) - .embed_title(embed_title) - .embed_description(embed_description) - .embed_video_url(embed_video_url) - .language_id(data.language_id) - .thumbnail_url(Some(thumbnail_url)) - .updated(Some(Some(naive_now()))) - .build(); - - let post_id = data.post_id; - Post::update(&mut context.pool(), post_id, &post_form) - .await - .with_lemmy_type(LemmyErrorType::CouldntUpdatePost)?; + // Verify that only the creator can edit + if !Post::is_post_creator(local_user_view.person.id, orig_post.creator_id) { + return Err(LemmyErrorType::NoPostEditAllowed)?; + } + + // Fetch post links and Pictrs cached image + let data_url = data.url.as_ref(); + let (metadata_res, thumbnail_url) = + fetch_site_data(context.client(), context.settings(), data_url, true).await; + let (embed_title, embed_description, embed_video_url) = metadata_res + .map(|u| (Some(u.title), Some(u.description), Some(u.embed_video_url))) + .unwrap_or_default(); + + let name = sanitize_html_opt(&data.name); + let body = sanitize_html_opt(&data.body); + let body = diesel_option_overwrite(body); + let embed_title = embed_title.map(|e| sanitize_html_opt(&e)); + let embed_description = embed_description.map(|e| sanitize_html_opt(&e)); + + let language_id = data.language_id; + CommunityLanguage::is_allowed_community_language( + &mut context.pool(), + language_id, + orig_post.community_id, + ) + .await?; + + let post_form = PostUpdateForm::builder() + .name(name) + .url(url) + .body(body) + .nsfw(data.nsfw) + .embed_title(embed_title) + .embed_description(embed_description) + .embed_video_url(embed_video_url) + .language_id(data.language_id) + .thumbnail_url(Some(thumbnail_url)) + .updated(Some(Some(naive_now()))) + .build(); + + let post_id = data.post_id; + let updated_post = Post::update(&mut context.pool(), post_id, &post_form) + .await + .with_lemmy_type(LemmyErrorType::CouldntUpdatePost)?; + ActivityChannel::submit_activity(SendActivityData::UpdatePost(updated_post), &context).await?; + + Ok(Json( build_post_response( - context, + context.deref(), orig_post.community_id, local_user_view.person.id, post_id, ) - .await - } + .await?, + )) } diff --git a/crates/apub/src/activities/create_or_update/comment.rs b/crates/apub/src/activities/create_or_update/comment.rs index fa235a7f..d54ca309 100644 --- a/crates/apub/src/activities/create_or_update/comment.rs +++ b/crates/apub/src/activities/create_or_update/comment.rs @@ -14,7 +14,6 @@ use crate::{ activities::{create_or_update::note::CreateOrUpdateNote, CreateOrUpdateType}, InCommunity, }, - SendActivity, }; use activitypub_federation::{ config::Data, @@ -25,7 +24,6 @@ use activitypub_federation::{ }; use lemmy_api_common::{ build_response::send_local_notifs, - comment::{CommentResponse, EditComment}, context::LemmyContext, utils::{check_post_deleted_or_removed, is_mod_or_admin}, }; @@ -43,25 +41,6 @@ use lemmy_db_schema::{ use lemmy_utils::{error::LemmyError, utils::mention::scrape_text_for_mentions}; use url::Url; -#[async_trait::async_trait] -impl SendActivity for EditComment { - type Response = CommentResponse; - - async fn send_activity( - _request: &Self, - response: &Self::Response, - context: &Data<LemmyContext>, - ) -> Result<(), LemmyError> { - CreateOrUpdateNote::send( - response.comment_view.comment.clone(), - response.comment_view.creator.id, - CreateOrUpdateType::Update, - context.reset_request_count(), - ) - .await - } -} - impl CreateOrUpdateNote { #[tracing::instrument(skip(comment, person_id, kind, context))] pub(crate) async fn send( diff --git a/crates/apub/src/activities/create_or_update/post.rs b/crates/apub/src/activities/create_or_update/post.rs index 4767114f..a9ac7936 100644 --- a/crates/apub/src/activities/create_or_update/post.rs +++ b/crates/apub/src/activities/create_or_update/post.rs @@ -14,7 +14,6 @@ use crate::{ activities::{create_or_update::page::CreateOrUpdatePage, CreateOrUpdateType}, InCommunity, }, - SendActivity, }; use activitypub_federation::{ config::Data, @@ -22,10 +21,7 @@ use activitypub_federation::{ protocol::verification::{verify_domains_match, verify_urls_match}, traits::{ActivityHandler, Actor, Object}, }; -use lemmy_api_common::{ - context::LemmyContext, - post::{EditPost, PostResponse}, -}; +use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::{ aggregates::structs::PostAggregates, newtypes::PersonId, @@ -39,25 +35,6 @@ use lemmy_db_schema::{ use lemmy_utils::error::{LemmyError, LemmyErrorType}; use url::Url; -#[async_trait::async_trait] -impl SendActivity for EditPost { - type Response = PostResponse; - - async fn send_activity( - _request: &Self, - response: &Self::Response, - context: &Data<LemmyContext>, - ) -> Result<(), LemmyError> { - CreateOrUpdatePage::send( - response.post_view.post.clone(), - response.post_view.creator.id, - CreateOrUpdateType::Update, - context.reset_request_count(), - ) - .await - } -} - impl CreateOrUpdatePage { pub(crate) async fn new( post: ApubPost, diff --git a/crates/apub/src/activities/deletion/mod.rs b/crates/apub/src/activities/deletion/mod.rs index 3b8c8b53..c571ac22 100644 --- a/crates/apub/src/activities/deletion/mod.rs +++ b/crates/apub/src/activities/deletion/mod.rs @@ -29,7 +29,6 @@ use activitypub_federation::{ traits::{Actor, Object}, }; use lemmy_api_common::{ - comment::{CommentResponse, DeleteComment, RemoveComment}, community::{CommunityResponse, DeleteCommunity, RemoveCommunity}, context::LemmyContext, post::{DeletePost, PostResponse, RemovePost}, @@ -102,50 +101,6 @@ impl SendActivity for RemovePost { } } -#[async_trait::async_trait] -impl SendActivity for DeleteComment { - type Response = CommentResponse; - - async fn send_activity( - request: &Self, - response: &Self::Response, - context: &Data<LemmyContext>, - ) -> Result<(), LemmyError> { - let community_id = response.comment_view.community.id; - let community = Community::read(&mut context.pool(), community_id).await?; - let person = Person::read(&mut context.pool(), response.comment_view.creator.id).await?; - let deletable = DeletableObjects::Comment(response.comment_view.comment.clone().into()); - send_apub_delete_in_community(person, community, deletable, None, request.deleted, context) - .await - } -} - -#[async_trait::async_trait] -impl SendActivity for RemoveComment { - type Response = CommentResponse; - - async fn send_activity( - request: &Self, - response: &Self::Response, - context: &Data<LemmyContext>, - ) -> Result<(), LemmyError> { - let local_user_view = local_user_view_from_jwt(&request.auth, context).await?; - let comment = Comment::read(&mut context.pool(), request.comment_id).await?; - let community = - Community::read(&mut context.pool(), response.comment_view.community.id).await?; - let deletable = DeletableObjects::Comment(comment.into()); - send_apub_delete_in_community( - local_user_view.person, - community, - deletable, - request.reason.clone().or_else(|| Some(String::new())), - request.removed, - context, - ) - .await - } -} - #[async_trait::async_trait] impl SendActivity for DeletePrivateMessage { type Response = PrivateMessageResponse; @@ -217,7 +172,7 @@ impl SendActivity for RemoveCommunity { /// Parameter `reason` being set indicates that this is a removal by a mod. If its unset, this /// action was done by a normal user. #[tracing::instrument(skip_all)] -async fn send_apub_delete_in_community( +pub(crate) async fn send_apub_delete_in_community( actor: Person, community: Community, object: DeletableObjects, diff --git a/crates/apub/src/activities/mod.rs b/crates/apub/src/activities/mod.rs index c7d19e37..c577f8bc 100644 --- a/crates/apub/src/activities/mod.rs +++ b/crates/apub/src/activities/mod.rs @@ -1,4 +1,8 @@ use crate::{ + activities::{ + deletion::{send_apub_delete_in_community, DeletableObjects}, + voting::send_like_activity, + }, objects::{community::ApubCommunity, person::ApubPerson}, protocol::activities::{ create_or_update::{note::CreateOrUpdateNote, page::CreateOrUpdatePage}, @@ -222,15 +226,30 @@ pub async fn match_outgoing_activities( ) -> LemmyResult<()> { let context = context.reset_request_count(); let fed_task = async { + use SendActivityData::*; match data { - SendActivityData::CreatePost(post) => { + CreatePost(post) | UpdatePost(post) => { let creator_id = post.creator_id; CreateOrUpdatePage::send(post, creator_id, CreateOrUpdateType::Create, context).await } - SendActivityData::CreateComment(comment) => { + CreateComment(comment) | UpdateComment(comment) => { let creator_id = comment.creator_id; CreateOrUpdateNote::send(comment, creator_id, CreateOrUpdateType::Create, context).await } + DeleteComment(comment, actor, community) => { + let is_deleted = comment.deleted; + let deletable = DeletableObjects::Comment(comment.into()); + send_apub_delete_in_community(actor, community, deletable, None, is_deleted, &context).await + } + RemoveComment(comment, actor, community, reason) => { + let is_removed = comment.removed; + let deletable = DeletableObjects::Comment(comment.into()); + send_apub_delete_in_community(actor, community, deletable, reason, is_removed, &context) + .await + } + LikePostOrComment(object_id, person, community, score) => { + send_like_activity(object_id, person, community, score, context).await + } } }; if *SYNCHRONOUS_FEDERATION { diff --git a/crates/apub/src/activities/voting/mod.rs b/crates/apub/src/activities/voting/mod.rs index 24250c50..742a4407 100644 --- a/crates/apub/src/activities/voting/mod.rs +++ b/crates/apub/src/activities/voting/mod.rs @@ -2,106 +2,51 @@ use crate::{ activities::community::send_activity_in_community, activity_lists::AnnouncableActivities, fetcher::post_or_comment::PostOrComment, - objects::{comment::ApubComment, person::ApubPerson, post::ApubPost}, + objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson, post::ApubPost}, protocol::activities::voting::{ undo_vote::UndoVote, vote::{Vote, VoteType}, }, - SendActivity, }; use activitypub_federation::{config::Data, fetch::object_id::ObjectId}; -use lemmy_api_common::{ - comment::{CommentResponse, CreateCommentLike}, - context::LemmyContext, - post::{CreatePostLike, PostResponse}, - sensitive::Sensitive, - utils::local_user_view_from_jwt, -}; +use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::{ - newtypes::CommunityId, + newtypes::DbUrl, source::{ comment::{CommentLike, CommentLikeForm}, community::Community, person::Person, post::{PostLike, PostLikeForm}, }, - traits::{Crud, Likeable}, + traits::Likeable, }; use lemmy_utils::error::LemmyError; pub mod undo_vote; pub mod vote; -#[async_trait::async_trait] -impl SendActivity for CreatePostLike { - type Response = PostResponse; - - async fn send_activity( - request: &Self, - response: &Self::Response, - context: &Data<LemmyContext>, - ) -> Result<(), LemmyError> { - let object_id = ObjectId::from(response.post_view.post.ap_id.clone()); - let community_id = response.post_view.community.id; - send_activity( - object_id, - community_id, - request.score, - &request.auth, - context, - ) - .await - } -} - -#[async_trait::async_trait] -impl SendActivity for CreateCommentLike { - type Response = CommentResponse; - - async fn send_activity( - request: &Self, - response: &Self::Response, - context: &Data<LemmyContext>, - ) -> Result<(), LemmyError> { - let object_id = ObjectId::from(response.comment_view.comment.ap_id.clone()); - let community_id = response.comment_view.community.id; - send_activity( - object_id, - community_id, - request.score, - &request.auth, - context, - ) - .await - } -} - -async fn send_activity( - object_id: ObjectId<PostOrComment>, - community_id: CommunityId, +pub(crate) async fn send_like_activity( + object_id: DbUrl, + actor: Person, + community: Community, score: i16, - jwt: &Sensitive<String>, - context: &Data<LemmyContext>, + context: Data<LemmyContext>, ) -> Result<(), LemmyError> { - let community = Community::read(&mut context.pool(), community_id) - .await? - .into(); - let local_user_view = local_user_view_from_jwt(jwt, context).await?; - let actor = Person::read(&mut context.pool(), local_user_view.person.id) - .await? - .into(); + let object_id: ObjectId<PostOrComment> = object_id.try_into()?; + let actor: ApubPerson = actor.into(); + let community: ApubCommunity = community.into(); // score of 1 means upvote, -1 downvote, 0 undo a previous vote if score != 0 { - let vote = Vote::new(object_id, &actor, &community, score.try_into()?, context)?; + let vote = Vote::new(object_id, &actor, &community, score.try_into()?, &context)?; let activity = AnnouncableActivities::Vote(vote); - send_activity_in_community(activity, &actor, &community, vec![], false, context).await + send_activity_in_community(activity, &actor, &community, vec![], false, &context).await } else { // Lemmy API doesnt distinguish between Undo/Like and Undo/Dislike, so we hardcode it here. - let vote = Vote::new(object_id, &actor, &community, VoteType::Like, context)?; - let undo_vote = UndoVote::new(vote, &actor, &community, context)?; + let vote = Vote::new(object_id, &actor, &community, VoteType::Like, &context)?; + let undo_vote = UndoVote::new(vote, &actor, &community, &context)?; let activity = AnnouncableActivities::UndoVote(undo_vote); - send_activity_in_community(activity, &actor, &community, vec![], false, context).await + send_activity_in_community(activity, &actor, &community, vec![], false, &context).await } } diff --git a/src/api_routes_http.rs b/src/api_routes_http.rs index e372e340..6d538e63 100644 --- a/src/api_routes_http.rs +++ b/src/api_routes_http.rs @@ -1,12 +1,13 @@ use actix_web::{guard, web, Error, HttpResponse, Result}; use lemmy_api::{ - comment::{distinguish::distinguish_comment, save::save_comment}, + comment::{distinguish::distinguish_comment, like::like_comment, save::save_comment}, comment_report::{list::list_comment_reports, resolve::resolve_comment_report}, local_user::notifications::mark_reply_read::mark_reply_as_read, + post::like::like_post, Perform, }; use lemmy_api_common::{ - comment::{CreateCommentLike, CreateCommentReport, DeleteComment, EditComment, RemoveComment}, + comment::CreateCommentReport, community::{ AddModToCommunity, BanFromCommunity, @@ -43,10 +44,8 @@ use lemmy_api_common::{ VerifyEmail, }, post::{ - CreatePostLike, CreatePostReport, DeletePost, - EditPost, FeaturePost, GetSiteMetadata, ListPostReports, @@ -79,9 +78,15 @@ use lemmy_api_common::{ }, }; use lemmy_api_crud::{ - comment::{create::create_comment, read::get_comment}, + comment::{ + create::create_comment, + delete::delete_comment, + read::get_comment, + remove::remove_comment, + update::update_comment, + }, community::list::list_communities, - post::{create::create_post, read::get_post}, + post::{create::create_post, read::get_post, update::update_post}, private_message::read::get_private_message, site::{create::create_site, read::get_site, update::update_site}, PerformCrud, @@ -173,7 +178,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) { web::scope("/post") .wrap(rate_limit.message()) .route("", web::get().to(get_post)) - .route("", web::put().to(route_post_crud::<EditPost>)) + .route("", web::put().to(update_post)) .route("/delete", web::post().to(route_post_crud::<DeletePost>)) .route("/remove", web::post().to(route_post_crud::<RemovePost>)) .route( @@ -183,7 +188,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) { .route("/lock", web::post().to(route_post::<LockPost>)) .route("/feature", web::post().to(route_post::<FeaturePost>)) .route("/list", web::get().to(list_posts)) - .route("/like", web::post().to(route_post::<CreatePostLike>)) + .route("/like", web::post().to(like_post)) .route("/save", web::put().to(route_post::<SavePost>)) .route("/report", web::post().to(route_post::<CreatePostReport>)) .route( @@ -208,12 +213,12 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) { web::scope("/comment") .wrap(rate_limit.message()) .route("", web::get().to(get_comment)) - .route("", web::put().to(route_post_crud::<EditComment>)) - .route("/delete", web::post().to(route_post_crud::<DeleteComment>)) - .route("/remove", web::post().to(route_post_crud::<RemoveComment>)) + .route("", web::put().to(update_comment)) + .route("/delete", web::post().to(delete_comment)) + .route("/remove", web::post().to(remove_comment)) .route("/mark_as_read", web::post().to(mark_reply_as_read)) .route("/distinguish", web::post().to(distinguish_comment)) - .route("/like", web::post().to(route_post::<CreateCommentLike>)) + .route("/like", web::post().to(like_comment)) .route("/save", web::put().to(save_comment)) .route("/list", web::get().to(list_comments)) .route("/report", web::post().to(route_post::<CreateCommentReport>)) -- 2.44.1