diff --git a/crates/api/src/comment.rs b/crates/api/src/comment.rs index ff1010fb1..f92e679f7 100644 --- a/crates/api/src/comment.rs +++ b/crates/api/src/comment.rs @@ -7,12 +7,19 @@ use lemmy_api_common::{ comment::*, get_local_user_view_from_jwt, }; -use lemmy_apub::ApubLikeableType; +use lemmy_apub::{ + activities::voting::{ + undo_vote::UndoVote, + vote::{Vote, VoteType}, + }, + PostOrComment, +}; use lemmy_db_queries::{source::comment::Comment_, Likeable, Saveable}; use lemmy_db_schema::{source::comment::*, LocalUserId}; use lemmy_db_views::{comment_view::CommentView, local_user_view::LocalUserView}; use lemmy_utils::{ApiError, ConnectionId, LemmyError}; use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation}; +use std::convert::TryInto; #[async_trait::async_trait(?Send)] impl Perform for MarkCommentAsRead { @@ -170,6 +177,7 @@ impl Perform for CreateCommentLike { // Only add the like if the score isnt 0 let comment = orig_comment.comment; + let object = PostOrComment::Comment(Box::new(comment)); let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1); if do_add { let like_form2 = like_form.clone(); @@ -178,17 +186,24 @@ impl Perform for CreateCommentLike { return Err(ApiError::err("couldnt_like_comment").into()); } - if like_form.score == 1 { - comment.send_like(&local_user_view.person, context).await?; - } else if like_form.score == -1 { - comment - .send_dislike(&local_user_view.person, context) - .await?; - } + Vote::send( + &object, + &local_user_view.person, + orig_comment.community.id, + like_form.score.try_into()?, + context, + ) + .await?; } else { - comment - .send_undo_like(&local_user_view.person, context) - .await?; + // API doesn't distinguish between Undo/Like and Undo/Dislike + UndoVote::send( + &object, + &local_user_view.person, + orig_comment.community.id, + VoteType::Like, + context, + ) + .await?; } // Have to refetch the comment to get the current state diff --git a/crates/api/src/post.rs b/crates/api/src/post.rs index a3392f550..684f64798 100644 --- a/crates/api/src/post.rs +++ b/crates/api/src/post.rs @@ -10,14 +10,22 @@ use lemmy_api_common::{ post::*, }; use lemmy_apub::{ - activities::{post::create_or_update::CreateOrUpdatePost, CreateOrUpdateType}, - ApubLikeableType, + activities::{ + post::create_or_update::CreateOrUpdatePost, + voting::{ + undo_vote::UndoVote, + vote::{Vote, VoteType}, + }, + CreateOrUpdateType, + }, + PostOrComment, }; use lemmy_db_queries::{source::post::Post_, Crud, Likeable, Saveable}; use lemmy_db_schema::source::{moderator::*, post::*}; use lemmy_db_views::post_view::PostView; use lemmy_utils::{ApiError, ConnectionId, LemmyError}; use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperation}; +use std::convert::TryInto; #[async_trait::async_trait(?Send)] impl Perform for CreatePostLike { @@ -53,6 +61,9 @@ impl Perform for CreatePostLike { }) .await??; + let community_id = post.community_id; + let object = PostOrComment::Post(Box::new(post)); + // 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 { @@ -62,15 +73,24 @@ impl Perform for CreatePostLike { return Err(ApiError::err("couldnt_like_post").into()); } - if like_form.score == 1 { - post.send_like(&local_user_view.person, context).await?; - } else if like_form.score == -1 { - post.send_dislike(&local_user_view.person, context).await?; - } + Vote::send( + &object, + &local_user_view.person, + community_id, + like_form.score.try_into()?, + context, + ) + .await?; } else { - post - .send_undo_like(&local_user_view.person, context) - .await?; + // API doesn't distinguish between Undo/Like and Undo/Dislike + UndoVote::send( + &object, + &local_user_view.person, + community_id, + VoteType::Like, + context, + ) + .await?; } // Mark the post as read diff --git a/crates/api_crud/src/comment/create.rs b/crates/api_crud/src/comment/create.rs index 1b772cd84..5a6fec81c 100644 --- a/crates/api_crud/src/comment/create.rs +++ b/crates/api_crud/src/comment/create.rs @@ -9,10 +9,14 @@ use lemmy_api_common::{ send_local_notifs, }; use lemmy_apub::{ - activities::{comment::create_or_update::CreateOrUpdateComment, CreateOrUpdateType}, + activities::{ + comment::create_or_update::CreateOrUpdateComment, + voting::vote::{Vote, VoteType}, + CreateOrUpdateType, + }, generate_apub_endpoint, - ApubLikeableType, EndpointType, + PostOrComment, }; use lemmy_db_queries::{source::comment::Comment_, Crud, Likeable}; use lemmy_db_schema::source::comment::*; @@ -42,8 +46,9 @@ impl PerformCrud for CreateComment { // Check for a community ban let post_id = data.post_id; let post = get_post(post_id, context.pool()).await?; + let community_id = post.community_id; - check_community_ban(local_user_view.person.id, post.community_id, context.pool()).await?; + check_community_ban(local_user_view.person.id, community_id, context.pool()).await?; // Check if post is locked, no new comments if post.locked { @@ -122,9 +127,15 @@ impl PerformCrud for CreateComment { return Err(ApiError::err("couldnt_like_comment").into()); } - updated_comment - .send_like(&local_user_view.person, context) - .await?; + let object = PostOrComment::Comment(Box::new(updated_comment)); + Vote::send( + &object, + &local_user_view.person, + community_id, + VoteType::Like, + context, + ) + .await?; let person_id = local_user_view.person.id; let mut comment_view = blocking(context.pool(), move |conn| { diff --git a/crates/api_crud/src/post/create.rs b/crates/api_crud/src/post/create.rs index 813c62ce0..bbb720faf 100644 --- a/crates/api_crud/src/post/create.rs +++ b/crates/api_crud/src/post/create.rs @@ -8,10 +8,14 @@ use lemmy_api_common::{ post::*, }; use lemmy_apub::{ - activities::{post::create_or_update::CreateOrUpdatePost, CreateOrUpdateType}, + activities::{ + post::create_or_update::CreateOrUpdatePost, + voting::vote::{Vote, VoteType}, + CreateOrUpdateType, + }, generate_apub_endpoint, - ApubLikeableType, EndpointType, + PostOrComment, }; use lemmy_db_queries::{source::post::Post_, Crud, Likeable}; use lemmy_db_schema::source::post::*; @@ -112,9 +116,15 @@ impl PerformCrud for CreatePost { // Mark the post as read mark_post_as_read(person_id, post_id, context.pool()).await?; - updated_post - .send_like(&local_user_view.person, context) - .await?; + let object = PostOrComment::Post(Box::new(updated_post)); + Vote::send( + &object, + &local_user_view.person, + inserted_post.community_id, + VoteType::Like, + context, + ) + .await?; // Refetch the view let inserted_post_id = inserted_post.id; diff --git a/crates/apub/src/activities/send/comment.rs b/crates/apub/src/activities/send/comment.rs index 619d59b1b..7fc3fb700 100644 --- a/crates/apub/src/activities/send/comment.rs +++ b/crates/apub/src/activities/send/comment.rs @@ -3,15 +3,12 @@ use crate::{ activity_queue::send_to_community, extensions::context::lemmy_context, ActorType, - ApubLikeableType, ApubObjectType, }; use activitystreams::{ activity::{ - kind::{DeleteType, DislikeType, LikeType, RemoveType, UndoType}, + kind::{DeleteType, RemoveType, UndoType}, Delete, - Dislike, - Like, Remove, Undo, }, @@ -170,93 +167,3 @@ impl ApubObjectType for Comment { Ok(()) } } - -#[async_trait::async_trait(?Send)] -impl ApubLikeableType for Comment { - async fn send_like(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { - let post_id = self.post_id; - let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; - - let community_id = post.community_id; - let community = blocking(context.pool(), move |conn| { - Community::read(conn, community_id) - }) - .await??; - - let mut like = Like::new( - creator.actor_id.to_owned().into_inner(), - self.ap_id.to_owned().into_inner(), - ); - like - .set_many_contexts(lemmy_context()) - .set_id(generate_activity_id(LikeType::Like)?) - .set_to(public()) - .set_many_ccs(vec![community.actor_id()]); - - send_to_community(like, creator, &community, None, context).await?; - Ok(()) - } - - async fn send_dislike(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { - let post_id = self.post_id; - let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; - - let community_id = post.community_id; - let community = blocking(context.pool(), move |conn| { - Community::read(conn, community_id) - }) - .await??; - - let mut dislike = Dislike::new( - creator.actor_id.to_owned().into_inner(), - self.ap_id.to_owned().into_inner(), - ); - dislike - .set_many_contexts(lemmy_context()) - .set_id(generate_activity_id(DislikeType::Dislike)?) - .set_to(public()) - .set_many_ccs(vec![community.actor_id()]); - - send_to_community(dislike, creator, &community, None, context).await?; - Ok(()) - } - - async fn send_undo_like( - &self, - creator: &Person, - context: &LemmyContext, - ) -> Result<(), LemmyError> { - let post_id = self.post_id; - let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; - - let community_id = post.community_id; - let community = blocking(context.pool(), move |conn| { - Community::read(conn, community_id) - }) - .await??; - - let mut like = Like::new( - creator.actor_id.to_owned().into_inner(), - self.ap_id.to_owned().into_inner(), - ); - like - .set_many_contexts(lemmy_context()) - .set_id(generate_activity_id(DislikeType::Dislike)?) - .set_to(public()) - .set_many_ccs(vec![community.actor_id()]); - - // Undo that fake activity - let mut undo = Undo::new( - creator.actor_id.to_owned().into_inner(), - like.into_any_base()?, - ); - undo - .set_many_contexts(lemmy_context()) - .set_id(generate_activity_id(UndoType::Undo)?) - .set_to(public()) - .set_many_ccs(vec![community.actor_id()]); - - send_to_community(undo, creator, &community, None, context).await?; - Ok(()) - } -} diff --git a/crates/apub/src/activities/send/post.rs b/crates/apub/src/activities/send/post.rs index 677e7845b..df1a14ef1 100644 --- a/crates/apub/src/activities/send/post.rs +++ b/crates/apub/src/activities/send/post.rs @@ -3,15 +3,12 @@ use crate::{ activity_queue::send_to_community, extensions::context::lemmy_context, ActorType, - ApubLikeableType, ApubObjectType, }; use activitystreams::{ activity::{ - kind::{DeleteType, DislikeType, LikeType, RemoveType, UndoType}, + kind::{DeleteType, RemoveType, UndoType}, Delete, - Dislike, - Like, Remove, Undo, }, @@ -156,84 +153,3 @@ impl ApubObjectType for Post { Ok(()) } } - -#[async_trait::async_trait(?Send)] -impl ApubLikeableType for Post { - async fn send_like(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { - let community_id = self.community_id; - let community = blocking(context.pool(), move |conn| { - Community::read(conn, community_id) - }) - .await??; - - let mut like = Like::new( - creator.actor_id.to_owned().into_inner(), - self.ap_id.to_owned().into_inner(), - ); - like - .set_many_contexts(lemmy_context()) - .set_id(generate_activity_id(LikeType::Like)?) - .set_to(public()) - .set_many_ccs(vec![community.actor_id()]); - - send_to_community(like, creator, &community, None, context).await?; - Ok(()) - } - - async fn send_dislike(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { - let community_id = self.community_id; - let community = blocking(context.pool(), move |conn| { - Community::read(conn, community_id) - }) - .await??; - - let mut dislike = Dislike::new( - creator.actor_id.to_owned().into_inner(), - self.ap_id.to_owned().into_inner(), - ); - dislike - .set_many_contexts(lemmy_context()) - .set_id(generate_activity_id(DislikeType::Dislike)?) - .set_to(public()) - .set_many_ccs(vec![community.actor_id()]); - - send_to_community(dislike, creator, &community, None, context).await?; - Ok(()) - } - - async fn send_undo_like( - &self, - creator: &Person, - context: &LemmyContext, - ) -> Result<(), LemmyError> { - let community_id = self.community_id; - let community = blocking(context.pool(), move |conn| { - Community::read(conn, community_id) - }) - .await??; - - let mut like = Like::new( - creator.actor_id.to_owned().into_inner(), - self.ap_id.to_owned().into_inner(), - ); - like - .set_many_contexts(lemmy_context()) - .set_id(generate_activity_id(LikeType::Like)?) - .set_to(public()) - .set_many_ccs(vec![community.actor_id()]); - - // Undo that fake activity - let mut undo = Undo::new( - creator.actor_id.to_owned().into_inner(), - like.into_any_base()?, - ); - undo - .set_many_contexts(lemmy_context()) - .set_id(generate_activity_id(UndoType::Undo)?) - .set_to(public()) - .set_many_ccs(vec![community.actor_id()]); - - send_to_community(undo, creator, &community, None, context).await?; - Ok(()) - } -} diff --git a/crates/apub/src/activities/voting/mod.rs b/crates/apub/src/activities/voting/mod.rs index 3d4a6d10f..48d9446f8 100644 --- a/crates/apub/src/activities/voting/mod.rs +++ b/crates/apub/src/activities/voting/mod.rs @@ -27,7 +27,7 @@ async fn vote_comment( comment_id, post_id: comment.post_id, person_id: actor.id, - score: vote_type.score(), + score: vote_type.into(), }; let person_id = actor.id; blocking(context.pool(), move |conn| { @@ -55,7 +55,7 @@ async fn vote_post( let like_form = PostLikeForm { post_id: post.id, person_id: actor.id, - score: vote_type.score(), + score: vote_type.into(), }; let person_id = actor.id; blocking(context.pool(), move |conn| { diff --git a/crates/apub/src/activities/voting/undo_vote.rs b/crates/apub/src/activities/voting/undo_vote.rs index 50299645b..8c3137e87 100644 --- a/crates/apub/src/activities/voting/undo_vote.rs +++ b/crates/apub/src/activities/voting/undo_vote.rs @@ -1,17 +1,32 @@ use crate::{ activities::{ + community::announce::AnnouncableActivities, + generate_activity_id, verify_activity, verify_person_in_community, - voting::{undo_vote_comment, undo_vote_post, vote::Vote}, + voting::{ + undo_vote_comment, + undo_vote_post, + vote::{Vote, VoteType}, + }, }, + activity_queue::send_to_community_new, + extensions::context::lemmy_context, fetcher::{ objects::get_or_fetch_and_insert_post_or_comment, person::get_or_fetch_and_upsert_person, }, + ActorType, PostOrComment, }; use activitystreams::activity::kind::UndoType; +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}, + CommunityId, +}; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use std::ops::Deref; @@ -29,6 +44,48 @@ pub struct UndoVote { common: ActivityCommonFields, } +impl UndoVote { + pub async fn send( + object: &PostOrComment, + actor: &Person, + community_id: CommunityId, + kind: VoteType, + context: &LemmyContext, + ) -> Result<(), LemmyError> { + let community = blocking(context.pool(), move |conn| { + Community::read(conn, community_id) + }) + .await??; + let id = generate_activity_id(UndoType::Undo)?; + + let undo_vote = UndoVote { + to: PublicUrl::Public, + object: Vote { + to: PublicUrl::Public, + object: object.ap_id(), + cc: [community.actor_id()], + kind: kind.clone(), + common: ActivityCommonFields { + context: lemmy_context(), + id: generate_activity_id(kind)?, + actor: actor.actor_id(), + unparsed: Default::default(), + }, + }, + cc: [community.actor_id()], + kind: UndoType::Undo, + common: ActivityCommonFields { + context: lemmy_context(), + id: id.clone(), + actor: actor.actor_id(), + unparsed: Default::default(), + }, + }; + let activity = AnnouncableActivities::UndoVote(undo_vote); + send_to_community_new(activity, &id, actor, &community, vec![], context).await + } +} + #[async_trait::async_trait(?Send)] impl ActivityHandler for UndoVote { async fn verify( diff --git a/crates/apub/src/activities/voting/vote.rs b/crates/apub/src/activities/voting/vote.rs index 073c23dc0..0f5a2b5c4 100644 --- a/crates/apub/src/activities/voting/vote.rs +++ b/crates/apub/src/activities/voting/vote.rs @@ -1,46 +1,103 @@ use crate::{ activities::{ + community::announce::AnnouncableActivities, + generate_activity_id, verify_activity, verify_person_in_community, voting::{vote_comment, vote_post}, }, + activity_queue::send_to_community_new, + extensions::context::lemmy_context, fetcher::{ objects::get_or_fetch_and_insert_post_or_comment, person::get_or_fetch_and_upsert_person, }, + ActorType, PostOrComment, }; +use anyhow::anyhow; +use lemmy_api_common::blocking; use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler}; +use lemmy_db_queries::Crud; +use lemmy_db_schema::{ + source::{community::Community, person::Person}, + CommunityId, +}; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use serde::{Deserialize, Serialize}; -use std::ops::Deref; +use std::{convert::TryFrom, ops::Deref}; +use strum_macros::ToString; use url::Url; -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, ToString, Deserialize, Serialize)] pub enum VoteType { Like, Dislike, } -impl VoteType { - pub(crate) fn score(&self) -> i16 { - match self { +impl TryFrom for VoteType { + type Error = LemmyError; + + fn try_from(value: i16) -> Result { + match value { + 1 => Ok(VoteType::Like), + -1 => Ok(VoteType::Dislike), + _ => Err(anyhow!("invalid vote value").into()), + } + } +} + +impl From<&VoteType> for i16 { + fn from(value: &VoteType) -> i16 { + match value { VoteType::Like => 1, VoteType::Dislike => -1, } } } + #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct Vote { - to: PublicUrl, + pub(in crate::activities::voting) to: PublicUrl, pub(in crate::activities::voting) object: Url, - cc: [Url; 1], + pub(in crate::activities::voting) cc: [Url; 1], #[serde(rename = "type")] - kind: VoteType, + pub(in crate::activities::voting) kind: VoteType, #[serde(flatten)] - common: ActivityCommonFields, + pub(in crate::activities::voting) common: ActivityCommonFields, +} + +impl Vote { + pub async fn send( + object: &PostOrComment, + actor: &Person, + community_id: CommunityId, + kind: VoteType, + context: &LemmyContext, + ) -> Result<(), LemmyError> { + let community = blocking(context.pool(), move |conn| { + Community::read(conn, community_id) + }) + .await??; + let id = generate_activity_id(kind.clone())?; + + let vote = Vote { + to: PublicUrl::Public, + object: object.ap_id(), + cc: [community.actor_id()], + kind, + common: ActivityCommonFields { + context: lemmy_context(), + id: id.clone(), + actor: actor.actor_id(), + unparsed: Default::default(), + }, + }; + let activity = AnnouncableActivities::Vote(vote); + send_to_community_new(activity, &id, actor, &community, vec![], context).await + } } #[async_trait::async_trait(?Send)] diff --git a/crates/apub/src/lib.rs b/crates/apub/src/lib.rs index 16773aae5..f0238fd9b 100644 --- a/crates/apub/src/lib.rs +++ b/crates/apub/src/lib.rs @@ -151,21 +151,6 @@ pub trait ApubObjectType { ) -> Result<(), LemmyError>; } -#[async_trait::async_trait(?Send)] -pub trait ApubLikeableType { - async fn send_like(&self, creator: &DbPerson, context: &LemmyContext) -> Result<(), LemmyError>; - async fn send_dislike( - &self, - creator: &DbPerson, - context: &LemmyContext, - ) -> Result<(), LemmyError>; - async fn send_undo_like( - &self, - creator: &DbPerson, - context: &LemmyContext, - ) -> Result<(), LemmyError>; -} - /// Common methods provided by ActivityPub actors (community and person). Not all methods are /// implemented by all actors. pub trait ActorType { @@ -376,6 +361,16 @@ pub enum PostOrComment { Post(Box), } +impl PostOrComment { + pub(crate) fn ap_id(&self) -> Url { + match self { + PostOrComment::Post(p) => p.ap_id.clone(), + PostOrComment::Comment(c) => c.ap_id.clone(), + } + .into() + } +} + /// Tries to find a post or comment in the local database, without any network requests. /// This is used to handle deletions and removals, because in case we dont have the object, we can /// simply ignore the activity.