diff --git a/crates/api/src/post.rs b/crates/api/src/post.rs index 84e0c849e..a3392f550 100644 --- a/crates/api/src/post.rs +++ b/crates/api/src/post.rs @@ -9,7 +9,10 @@ use lemmy_api_common::{ mark_post_as_read, post::*, }; -use lemmy_apub::{activities::post::update::UpdatePost, ApubLikeableType}; +use lemmy_apub::{ + activities::{post::create_or_update::CreateOrUpdatePost, CreateOrUpdateType}, + ApubLikeableType, +}; use lemmy_db_queries::{source::post::Post_, Crud, Likeable, Saveable}; use lemmy_db_schema::source::{moderator::*, post::*}; use lemmy_db_views::post_view::PostView; @@ -140,7 +143,13 @@ impl Perform for LockPost { blocking(context.pool(), move |conn| ModLockPost::create(conn, &form)).await??; // apub updates - UpdatePost::send(&updated_post, &local_user_view.person, context).await?; + CreateOrUpdatePost::send( + &updated_post, + &local_user_view.person, + CreateOrUpdateType::Update, + context, + ) + .await?; // Refetch the post let post_id = data.post_id; @@ -212,7 +221,13 @@ impl Perform for StickyPost { // Apub updates // TODO stickied should pry work like locked for ease of use - UpdatePost::send(&updated_post, &local_user_view.person, context).await?; + CreateOrUpdatePost::send( + &updated_post, + &local_user_view.person, + CreateOrUpdateType::Update, + context, + ) + .await?; // Refetch the post let post_id = data.post_id; diff --git a/crates/api_crud/src/comment/create.rs b/crates/api_crud/src/comment/create.rs index 899555fc8..1b772cd84 100644 --- a/crates/api_crud/src/comment/create.rs +++ b/crates/api_crud/src/comment/create.rs @@ -9,7 +9,7 @@ use lemmy_api_common::{ send_local_notifs, }; use lemmy_apub::{ - activities::comment::create_or_update::{CreateOrUpdateComment, CreateOrUpdateType}, + activities::{comment::create_or_update::CreateOrUpdateComment, CreateOrUpdateType}, generate_apub_endpoint, ApubLikeableType, EndpointType, diff --git a/crates/api_crud/src/comment/update.rs b/crates/api_crud/src/comment/update.rs index f3911faec..d0a0e3971 100644 --- a/crates/api_crud/src/comment/update.rs +++ b/crates/api_crud/src/comment/update.rs @@ -7,8 +7,8 @@ use lemmy_api_common::{ get_local_user_view_from_jwt, send_local_notifs, }; -use lemmy_apub::activities::comment::create_or_update::{ - CreateOrUpdateComment, +use lemmy_apub::activities::{ + comment::create_or_update::CreateOrUpdateComment, CreateOrUpdateType, }; use lemmy_db_queries::{source::comment::Comment_, DeleteableOrRemoveable}; @@ -61,6 +61,7 @@ impl PerformCrud for EditComment { .await? .map_err(|_| ApiError::err("couldnt_update_comment"))?; + // Send the apub update CreateOrUpdateComment::send( &updated_comment, &local_user_view.person, diff --git a/crates/api_crud/src/post/create.rs b/crates/api_crud/src/post/create.rs index b2034a367..813c62ce0 100644 --- a/crates/api_crud/src/post/create.rs +++ b/crates/api_crud/src/post/create.rs @@ -8,7 +8,7 @@ use lemmy_api_common::{ post::*, }; use lemmy_apub::{ - activities::post::create::CreatePost as CreateApubPost, + activities::{post::create_or_update::CreateOrUpdatePost, CreateOrUpdateType}, generate_apub_endpoint, ApubLikeableType, EndpointType, @@ -87,7 +87,13 @@ impl PerformCrud for CreatePost { .await? .map_err(|_| ApiError::err("couldnt_create_post"))?; - CreateApubPost::send(&updated_post, &local_user_view.person, context).await?; + CreateOrUpdatePost::send( + &updated_post, + &local_user_view.person, + CreateOrUpdateType::Create, + context, + ) + .await?; // They like their own post by default let person_id = local_user_view.person.id; diff --git a/crates/api_crud/src/post/update.rs b/crates/api_crud/src/post/update.rs index 6b2780376..dade6f42b 100644 --- a/crates/api_crud/src/post/update.rs +++ b/crates/api_crud/src/post/update.rs @@ -1,7 +1,7 @@ use crate::PerformCrud; use actix_web::web::Data; use lemmy_api_common::{blocking, check_community_ban, get_local_user_view_from_jwt, post::*}; -use lemmy_apub::activities::post::update::UpdatePost; +use lemmy_apub::activities::{post::create_or_update::CreateOrUpdatePost, CreateOrUpdateType}; use lemmy_db_queries::{source::post::Post_, Crud, DeleteableOrRemoveable}; use lemmy_db_schema::{naive_now, source::post::*}; use lemmy_db_views::post_view::PostView; @@ -89,7 +89,13 @@ impl PerformCrud for EditPost { }; // Send apub update - UpdatePost::send(&updated_post, &local_user_view.person, context).await?; + CreateOrUpdatePost::send( + &updated_post, + &local_user_view.person, + CreateOrUpdateType::Update, + context, + ) + .await?; let post_id = data.post_id; let mut post_view = blocking(context.pool(), move |conn| { diff --git a/crates/apub/src/activities/comment/create_or_update.rs b/crates/apub/src/activities/comment/create_or_update.rs index f67040987..4cafafbaa 100644 --- a/crates/apub/src/activities/comment/create_or_update.rs +++ b/crates/apub/src/activities/comment/create_or_update.rs @@ -6,13 +6,14 @@ use crate::{ generate_activity_id, verify_activity, verify_person_in_community, + CreateOrUpdateType, }, activity_queue::send_to_community_new, extensions::context::lemmy_context, objects::{comment::Note, FromApub, ToApub}, ActorType, }; -use activitystreams::{activity::kind::CreateType, link::Mention}; +use activitystreams::link::Mention; use lemmy_api_common::blocking; use lemmy_apub_lib::{ values::PublicUrl, @@ -27,12 +28,6 @@ use lemmy_websocket::{LemmyContext, UserOperationCrud}; use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, Deserialize, Serialize)] -pub enum CreateOrUpdateType { - Create, - Update, -} - #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CreateOrUpdateComment { @@ -62,10 +57,7 @@ impl CreateOrUpdateComment { }) .await??; - let id = match kind { - CreateOrUpdateType::Create => generate_activity_id(CreateType::Create), - CreateOrUpdateType::Update => generate_activity_id(CreateType::Create), - }?; + let id = generate_activity_id(kind.clone())?; let maa = collect_non_local_mentions(comment, &community, context).await?; let create_or_update = CreateOrUpdateComment { diff --git a/crates/apub/src/activities/community/announce.rs b/crates/apub/src/activities/community/announce.rs index 4beee5d29..b72cd1a5c 100644 --- a/crates/apub/src/activities/community/announce.rs +++ b/crates/apub/src/activities/community/announce.rs @@ -12,7 +12,7 @@ use crate::{ undo_delete::UndoDeletePostCommentOrCommunity, }, generate_activity_id, - post::{create::CreatePost, update::UpdatePost}, + post::create_or_update::CreateOrUpdatePost, removal::{ remove::RemovePostCommentCommunityOrMod, undo_remove::UndoRemovePostCommentOrCommunity, @@ -45,8 +45,7 @@ use url::Url; #[serde(untagged)] pub enum AnnouncableActivities { CreateOrUpdateComment(CreateOrUpdateComment), - CreatePost(CreatePost), - UpdatePost(UpdatePost), + CreateOrUpdatePost(Box), LikePostOrComment(LikePostOrComment), DislikePostOrComment(DislikePostOrComment), UndoLikePostOrComment(UndoLikePostOrComment), @@ -86,7 +85,7 @@ impl AnnounceActivity { kind: AnnounceType::Announce, common: ActivityCommonFields { context: lemmy_context(), - id: generate_activity_id(AnnounceType::Announce)?, + id: generate_activity_id(&AnnounceType::Announce)?, actor: community.actor_id(), unparsed: Default::default(), }, diff --git a/crates/apub/src/activities/mod.rs b/crates/apub/src/activities/mod.rs index d8eaa0e77..f4c535ee6 100644 --- a/crates/apub/src/activities/mod.rs +++ b/crates/apub/src/activities/mod.rs @@ -15,6 +15,8 @@ use lemmy_db_schema::{ use lemmy_db_views_actor::community_view::CommunityView; use lemmy_utils::{settings::structs::Settings, LemmyError}; use lemmy_websocket::LemmyContext; +use serde::{Deserialize, Serialize}; +use strum_macros::ToString; use url::{ParseError, Url}; use uuid::Uuid; @@ -28,6 +30,13 @@ pub mod removal; pub mod send; pub mod voting; +#[derive(Clone, Debug, ToString, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub enum CreateOrUpdateType { + Create, + Update, +} + /// Checks that the specified Url actually identifies a Person (by fetching it), and that the person /// doesn't have a site ban. async fn verify_person( diff --git a/crates/apub/src/activities/post/create.rs b/crates/apub/src/activities/post/create_or_update.rs similarity index 51% rename from crates/apub/src/activities/post/create.rs rename to crates/apub/src/activities/post/create_or_update.rs index 3dd8ce554..4af720753 100644 --- a/crates/apub/src/activities/post/create.rs +++ b/crates/apub/src/activities/post/create_or_update.rs @@ -5,7 +5,9 @@ use crate::{ generate_activity_id, post::send_websocket_message, verify_activity, + verify_mod_action, verify_person_in_community, + CreateOrUpdateType, }, activity_queue::send_to_community_new, extensions::context::lemmy_context, @@ -13,7 +15,6 @@ use crate::{ objects::{post::Page, FromApub, ToApub}, ActorType, }; -use activitystreams::activity::kind::CreateType; use anyhow::anyhow; use lemmy_api_common::blocking; use lemmy_apub_lib::{ @@ -31,29 +32,35 @@ use url::Url; #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] -pub struct CreatePost { +pub struct CreateOrUpdatePost { to: PublicUrl, object: Page, cc: [Url; 1], - r#type: CreateType, + #[serde(rename = "type")] + kind: CreateOrUpdateType, #[serde(flatten)] common: ActivityCommonFields, } -impl CreatePost { - pub async fn send(post: &Post, actor: &Person, context: &LemmyContext) -> Result<(), LemmyError> { +impl CreateOrUpdatePost { + pub async fn send( + post: &Post, + actor: &Person, + kind: CreateOrUpdateType, + context: &LemmyContext, + ) -> Result<(), LemmyError> { let community_id = post.community_id; let community = blocking(context.pool(), move |conn| { Community::read(conn, community_id) }) .await??; - let id = generate_activity_id(CreateType::Create)?; - let create = CreatePost { + let id = generate_activity_id(kind.clone())?; + let create_or_update = CreateOrUpdatePost { to: PublicUrl::Public, object: post.to_apub(context.pool()).await?, cc: [community.actor_id()], - r#type: Default::default(), + kind, common: ActivityCommonFields { context: lemmy_context(), id: id.clone(), @@ -62,33 +69,45 @@ impl CreatePost { }, }; - let activity = AnnouncableActivities::CreatePost(create); + let activity = AnnouncableActivities::CreateOrUpdatePost(Box::new(create_or_update)); send_to_community_new(activity, &id, actor, &community, vec![], context).await } } #[async_trait::async_trait(?Send)] -impl ActivityHandler for CreatePost { +impl ActivityHandler for CreateOrUpdatePost { async fn verify( &self, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - let community = extract_community(&self.cc, context, request_counter).await?; - let community_id = &community.actor_id(); - verify_activity(self.common())?; - verify_person_in_community(&self.common.actor, community_id, context, request_counter).await?; - verify_domains_match(&self.common.actor, &self.object.id)?; - verify_urls_match(&self.common.actor, &self.object.attributed_to)?; - // Check that the post isnt locked or stickied, as that isnt possible for newly created posts. - // However, when fetching a remote post we generate a new create activity with the current - // locked/stickied value, so this check may fail. So only check if its a local community, - // because then we will definitely receive all create and update activities separately. - let is_stickied_or_locked = - self.object.stickied == Some(true) || self.object.comments_enabled == Some(false); - if community.local && is_stickied_or_locked { - return Err(anyhow!("New post cannot be stickied or locked").into()); + let community = extract_community(&self.cc, context, request_counter).await?; + let community_id = community.actor_id(); + verify_person_in_community(&self.common.actor, &community_id, context, request_counter).await?; + match self.kind { + CreateOrUpdateType::Create => { + verify_domains_match(&self.common.actor, &self.object.id)?; + verify_urls_match(&self.common.actor, &self.object.attributed_to)?; + // Check that the post isnt locked or stickied, as that isnt possible for newly created posts. + // However, when fetching a remote post we generate a new create activity with the current + // locked/stickied value, so this check may fail. So only check if its a local community, + // because then we will definitely receive all create and update activities separately. + let is_stickied_or_locked = + self.object.stickied == Some(true) || self.object.comments_enabled == Some(false); + if community.local && is_stickied_or_locked { + return Err(anyhow!("New post cannot be stickied or locked").into()); + } + } + CreateOrUpdateType::Update => { + let is_mod_action = self.object.is_mod_action(context.pool()).await?; + if is_mod_action { + verify_mod_action(&self.common.actor, community_id, context).await?; + } else { + verify_domains_match(&self.common.actor, &self.object.id)?; + verify_urls_match(&self.common.actor, &self.object.attributed_to)?; + } + } } self.object.verify(context, request_counter).await?; Ok(()) @@ -110,7 +129,11 @@ impl ActivityHandler for CreatePost { ) .await?; - send_websocket_message(post.id, UserOperationCrud::CreatePost, context).await + let notif_type = match self.kind { + CreateOrUpdateType::Create => UserOperationCrud::CreatePost, + CreateOrUpdateType::Update => UserOperationCrud::EditPost, + }; + send_websocket_message(post.id, notif_type, context).await } fn common(&self) -> &ActivityCommonFields { diff --git a/crates/apub/src/activities/post/mod.rs b/crates/apub/src/activities/post/mod.rs index a46620402..b60348ed0 100644 --- a/crates/apub/src/activities/post/mod.rs +++ b/crates/apub/src/activities/post/mod.rs @@ -4,8 +4,7 @@ use lemmy_db_views::post_view::PostView; use lemmy_utils::LemmyError; use lemmy_websocket::{messages::SendPost, LemmyContext}; -pub mod create; -pub mod update; +pub mod create_or_update; pub(crate) async fn send_websocket_message< OP: ToString + Send + lemmy_websocket::OperationType + 'static, diff --git a/crates/apub/src/activities/post/update.rs b/crates/apub/src/activities/post/update.rs deleted file mode 100644 index f5cd07b9f..000000000 --- a/crates/apub/src/activities/post/update.rs +++ /dev/null @@ -1,106 +0,0 @@ -use crate::{ - activities::{ - community::announce::AnnouncableActivities, - generate_activity_id, - post::send_websocket_message, - verify_activity, - verify_mod_action, - verify_person_in_community, - }, - activity_queue::send_to_community_new, - extensions::context::lemmy_context, - fetcher::community::get_or_fetch_and_upsert_community, - objects::{post::Page, FromApub, ToApub}, - ActorType, -}; -use activitystreams::activity::kind::UpdateType; -use lemmy_api_common::blocking; -use lemmy_apub_lib::{values::PublicUrl, verify_urls_match, ActivityCommonFields, ActivityHandler}; -use lemmy_db_queries::Crud; -use lemmy_db_schema::source::{community::Community, person::Person, post::Post}; -use lemmy_utils::LemmyError; -use lemmy_websocket::{LemmyContext, UserOperationCrud}; -use url::Url; - -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "camelCase")] -pub struct UpdatePost { - to: PublicUrl, - object: Page, - cc: [Url; 1], - r#type: UpdateType, - #[serde(flatten)] - common: ActivityCommonFields, -} - -impl UpdatePost { - pub async fn send(post: &Post, actor: &Person, context: &LemmyContext) -> Result<(), LemmyError> { - let community_id = post.community_id; - let community = blocking(context.pool(), move |conn| { - Community::read(conn, community_id) - }) - .await??; - - let id = generate_activity_id(UpdateType::Update)?; - let update = UpdatePost { - to: PublicUrl::Public, - object: post.to_apub(context.pool()).await?, - cc: [community.actor_id()], - r#type: Default::default(), - common: ActivityCommonFields { - context: lemmy_context(), - id: id.clone(), - actor: actor.actor_id(), - unparsed: Default::default(), - }, - }; - let activity = AnnouncableActivities::UpdatePost(update); - send_to_community_new(activity, &id, actor, &community, vec![], context).await - } -} - -#[async_trait::async_trait(?Send)] -impl ActivityHandler for UpdatePost { - async fn verify( - &self, - context: &LemmyContext, - request_counter: &mut i32, - ) -> Result<(), LemmyError> { - let community_id = get_or_fetch_and_upsert_community(&self.cc[0], context, request_counter) - .await? - .actor_id(); - let is_mod_action = self.object.is_mod_action(context.pool()).await?; - - verify_activity(self.common())?; - verify_person_in_community(&self.common.actor, &community_id, context, request_counter).await?; - if is_mod_action { - verify_mod_action(&self.common.actor, community_id, context).await?; - } else { - verify_urls_match(&self.common.actor, &self.object.attributed_to)?; - } - self.object.verify(context, request_counter).await?; - Ok(()) - } - - async fn receive( - &self, - context: &LemmyContext, - request_counter: &mut i32, - ) -> Result<(), LemmyError> { - let post = Post::from_apub( - &self.object, - context, - self.common.actor.clone(), - request_counter, - // TODO: we already check here if the mod action is valid, can remove that check param - true, - ) - .await?; - - send_websocket_message(post.id, UserOperationCrud::EditPost, context).await - } - - fn common(&self) -> &ActivityCommonFields { - &self.common - } -} diff --git a/crates/apub/src/http/inbox_enums.rs b/crates/apub/src/http/inbox_enums.rs index 20b311505..2950799ae 100644 --- a/crates/apub/src/http/inbox_enums.rs +++ b/crates/apub/src/http/inbox_enums.rs @@ -9,7 +9,7 @@ use crate::activities::{ }, deletion::{delete::DeletePostCommentOrCommunity, undo_delete::UndoDeletePostCommentOrCommunity}, following::{accept::AcceptFollowCommunity, follow::FollowCommunity, undo::UndoFollowCommunity}, - post::{create::CreatePost, update::UpdatePost}, + post::create_or_update::CreateOrUpdatePost, private_message::{ create::CreatePrivateMessage, delete::DeletePrivateMessage, @@ -49,8 +49,7 @@ pub enum GroupInboxActivities { FollowCommunity(FollowCommunity), UndoFollowCommunity(UndoFollowCommunity), CreateOrUpdateComment(CreateOrUpdateComment), - CreatePost(CreatePost), - UpdatePost(UpdatePost), + CreateOrUpdatePost(Box), LikePostOrComment(LikePostOrComment), DislikePostOrComment(DislikePostOrComment), UndoLikePostOrComment(UndoLikePostOrComment), @@ -72,8 +71,7 @@ pub enum SharedInboxActivities { FollowCommunity(FollowCommunity), UndoFollowCommunity(UndoFollowCommunity), CreateOrUpdateComment(CreateOrUpdateComment), - CreatePost(CreatePost), - UpdatePost(UpdatePost), + CreateOrUpdatePost(CreateOrUpdatePost), LikePostOrComment(LikePostOrComment), DislikePostOrComment(DislikePostOrComment), UndoDislikePostOrComment(UndoDislikePostOrComment),