From b8d7f00d58c0c76c70436b2a070f19351d5ccbd5 Mon Sep 17 00:00:00 2001
From: Nutomic <me@nutomic.com>
Date: Mon, 2 Aug 2021 20:33:40 +0000
Subject: [PATCH] Rewrite voting (#1685)

* Merge like/dislike activity handlers into vote

* Rewrite vote sending code

* Remove old send_create, send_update functions
---
 crates/api/src/comment.rs                     |  37 +++--
 crates/api/src/post.rs                        |  40 ++++--
 crates/api_crud/src/comment/create.rs         |  23 ++-
 crates/api_crud/src/post/create.rs            |  20 ++-
 .../apub/src/activities/community/announce.rs |  13 +-
 crates/apub/src/activities/mod.rs             |   1 -
 crates/apub/src/activities/send/comment.rs    | 111 +--------------
 crates/apub/src/activities/send/post.rs       | 102 +-------------
 .../src/activities/send/private_message.rs    |  16 ---
 crates/apub/src/activities/voting/dislike.rs  |  54 -------
 crates/apub/src/activities/voting/like.rs     |  54 -------
 crates/apub/src/activities/voting/mod.rs      |  92 +++---------
 .../src/activities/voting/undo_dislike.rs     |  55 --------
 .../apub/src/activities/voting/undo_like.rs   |  55 --------
 .../apub/src/activities/voting/undo_vote.rs   | 122 ++++++++++++++++
 crates/apub/src/activities/voting/vote.rs     | 133 ++++++++++++++++++
 crates/apub/src/http/inbox_enums.rs           |  19 +--
 crates/apub/src/lib.rs                        |  29 ++--
 18 files changed, 382 insertions(+), 594 deletions(-)
 delete mode 100644 crates/apub/src/activities/voting/dislike.rs
 delete mode 100644 crates/apub/src/activities/voting/like.rs
 delete mode 100644 crates/apub/src/activities/voting/undo_dislike.rs
 delete mode 100644 crates/apub/src/activities/voting/undo_like.rs
 create mode 100644 crates/apub/src/activities/voting/undo_vote.rs
 create mode 100644 crates/apub/src/activities/voting/vote.rs

diff --git a/crates/api/src/comment.rs b/crates/api/src/comment.rs
index ff1010fb..f92e679f 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 a3392f55..684f6479 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 1b772cd8..5a6fec81 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 813c62ce..bbb720fa 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/community/announce.rs b/crates/apub/src/activities/community/announce.rs
index b72cd1a5..9cde8a43 100644
--- a/crates/apub/src/activities/community/announce.rs
+++ b/crates/apub/src/activities/community/announce.rs
@@ -19,12 +19,7 @@ use crate::{
     },
     verify_activity,
     verify_community,
-    voting::{
-      dislike::DislikePostOrComment,
-      like::LikePostOrComment,
-      undo_dislike::UndoDislikePostOrComment,
-      undo_like::UndoLikePostOrComment,
-    },
+    voting::{undo_vote::UndoVote, vote::Vote},
   },
   activity_queue::send_activity_new,
   extensions::context::lemmy_context,
@@ -46,10 +41,8 @@ use url::Url;
 pub enum AnnouncableActivities {
   CreateOrUpdateComment(CreateOrUpdateComment),
   CreateOrUpdatePost(Box<CreateOrUpdatePost>),
-  LikePostOrComment(LikePostOrComment),
-  DislikePostOrComment(DislikePostOrComment),
-  UndoLikePostOrComment(UndoLikePostOrComment),
-  UndoDislikePostOrComment(UndoDislikePostOrComment),
+  Vote(Vote),
+  UndoVote(UndoVote),
   DeletePostCommentOrCommunity(DeletePostCommentOrCommunity),
   UndoDeletePostCommentOrCommunity(UndoDeletePostCommentOrCommunity),
   RemovePostCommentCommunityOrMod(RemovePostCommentCommunityOrMod),
diff --git a/crates/apub/src/activities/mod.rs b/crates/apub/src/activities/mod.rs
index f4c535ee..afbad6c8 100644
--- a/crates/apub/src/activities/mod.rs
+++ b/crates/apub/src/activities/mod.rs
@@ -31,7 +31,6 @@ pub mod send;
 pub mod voting;
 
 #[derive(Clone, Debug, ToString, Deserialize, Serialize)]
-#[serde(rename_all = "camelCase")]
 pub enum CreateOrUpdateType {
   Create,
   Update,
diff --git a/crates/apub/src/activities/send/comment.rs b/crates/apub/src/activities/send/comment.rs
index 619d59b1..8b446860 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,
   },
@@ -26,22 +23,6 @@ use lemmy_websocket::LemmyContext;
 
 #[async_trait::async_trait(?Send)]
 impl ApubObjectType for Comment {
-  async fn send_create(
-    &self,
-    _creator: &Person,
-    _context: &LemmyContext,
-  ) -> Result<(), LemmyError> {
-    unimplemented!()
-  }
-
-  async fn send_update(
-    &self,
-    _creator: &Person,
-    _context: &LemmyContext,
-  ) -> Result<(), LemmyError> {
-    unimplemented!()
-  }
-
   async fn send_delete(&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??;
@@ -170,93 +151,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 677e7845..b9713661 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,
   },
@@ -26,22 +23,6 @@ use lemmy_websocket::LemmyContext;
 
 #[async_trait::async_trait(?Send)]
 impl ApubObjectType for Post {
-  async fn send_create(
-    &self,
-    _creator: &Person,
-    _context: &LemmyContext,
-  ) -> Result<(), LemmyError> {
-    unimplemented!()
-  }
-
-  async fn send_update(
-    &self,
-    _creator: &Person,
-    _context: &LemmyContext,
-  ) -> Result<(), LemmyError> {
-    unimplemented!()
-  }
-
   async fn send_delete(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
     let community_id = self.community_id;
     let community = blocking(context.pool(), move |conn| {
@@ -156,84 +137,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/send/private_message.rs b/crates/apub/src/activities/send/private_message.rs
index 7461926c..edcff377 100644
--- a/crates/apub/src/activities/send/private_message.rs
+++ b/crates/apub/src/activities/send/private_message.rs
@@ -22,22 +22,6 @@ use lemmy_websocket::LemmyContext;
 
 #[async_trait::async_trait(?Send)]
 impl ApubObjectType for PrivateMessage {
-  async fn send_create(
-    &self,
-    _creator: &Person,
-    _context: &LemmyContext,
-  ) -> Result<(), LemmyError> {
-    unimplemented!()
-  }
-
-  async fn send_update(
-    &self,
-    _creator: &Person,
-    _context: &LemmyContext,
-  ) -> Result<(), LemmyError> {
-    unimplemented!()
-  }
-
   async fn send_delete(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
     let recipient_id = self.recipient_id;
     let recipient =
diff --git a/crates/apub/src/activities/voting/dislike.rs b/crates/apub/src/activities/voting/dislike.rs
deleted file mode 100644
index 54b67300..00000000
--- a/crates/apub/src/activities/voting/dislike.rs
+++ /dev/null
@@ -1,54 +0,0 @@
-use crate::activities::{
-  verify_activity,
-  verify_person_in_community,
-  voting::receive_like_or_dislike,
-};
-use activitystreams::activity::kind::DislikeType;
-use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler};
-use lemmy_utils::LemmyError;
-use lemmy_websocket::LemmyContext;
-use url::Url;
-
-#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
-#[serde(rename_all = "camelCase")]
-pub struct DislikePostOrComment {
-  to: PublicUrl,
-  pub(in crate::activities) object: Url,
-  cc: [Url; 1],
-  #[serde(rename = "type")]
-  kind: DislikeType,
-  #[serde(flatten)]
-  common: ActivityCommonFields,
-}
-
-#[async_trait::async_trait(?Send)]
-impl ActivityHandler for DislikePostOrComment {
-  async fn verify(
-    &self,
-    context: &LemmyContext,
-    request_counter: &mut i32,
-  ) -> Result<(), LemmyError> {
-    verify_activity(self.common())?;
-    verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?;
-    Ok(())
-  }
-
-  async fn receive(
-    &self,
-    context: &LemmyContext,
-    request_counter: &mut i32,
-  ) -> Result<(), LemmyError> {
-    receive_like_or_dislike(
-      -1,
-      &self.common.actor,
-      &self.object,
-      context,
-      request_counter,
-    )
-    .await
-  }
-
-  fn common(&self) -> &ActivityCommonFields {
-    &self.common
-  }
-}
diff --git a/crates/apub/src/activities/voting/like.rs b/crates/apub/src/activities/voting/like.rs
deleted file mode 100644
index 90f29c42..00000000
--- a/crates/apub/src/activities/voting/like.rs
+++ /dev/null
@@ -1,54 +0,0 @@
-use crate::activities::{
-  verify_activity,
-  verify_person_in_community,
-  voting::receive_like_or_dislike,
-};
-use activitystreams::activity::kind::LikeType;
-use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler};
-use lemmy_utils::LemmyError;
-use lemmy_websocket::LemmyContext;
-use url::Url;
-
-#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
-#[serde(rename_all = "camelCase")]
-pub struct LikePostOrComment {
-  to: PublicUrl,
-  pub(in crate::activities::voting) object: Url,
-  cc: [Url; 1],
-  #[serde(rename = "type")]
-  kind: LikeType,
-  #[serde(flatten)]
-  common: ActivityCommonFields,
-}
-
-#[async_trait::async_trait(?Send)]
-impl ActivityHandler for LikePostOrComment {
-  async fn verify(
-    &self,
-    context: &LemmyContext,
-    request_counter: &mut i32,
-  ) -> Result<(), LemmyError> {
-    verify_activity(self.common())?;
-    verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?;
-    Ok(())
-  }
-
-  async fn receive(
-    &self,
-    context: &LemmyContext,
-    request_counter: &mut i32,
-  ) -> Result<(), LemmyError> {
-    receive_like_or_dislike(
-      1,
-      &self.common.actor,
-      &self.object,
-      context,
-      request_counter,
-    )
-    .await
-  }
-
-  fn common(&self) -> &ActivityCommonFields {
-    &self.common
-  }
-}
diff --git a/crates/apub/src/activities/voting/mod.rs b/crates/apub/src/activities/voting/mod.rs
index 739c4305..48d9446f 100644
--- a/crates/apub/src/activities/voting/mod.rs
+++ b/crates/apub/src/activities/voting/mod.rs
@@ -1,62 +1,33 @@
-use crate::{
-  activities::{
-    comment::send_websocket_message as send_comment_message,
-    post::send_websocket_message as send_post_message,
-  },
-  fetcher::{
-    objects::get_or_fetch_and_insert_post_or_comment,
-    person::get_or_fetch_and_upsert_person,
-  },
-  PostOrComment,
+use crate::activities::{
+  comment::send_websocket_message as send_comment_message,
+  post::send_websocket_message as send_post_message,
+  voting::vote::VoteType,
 };
 use lemmy_api_common::blocking;
 use lemmy_db_queries::Likeable;
 use lemmy_db_schema::source::{
   comment::{Comment, CommentLike, CommentLikeForm},
+  person::Person,
   post::{Post, PostLike, PostLikeForm},
 };
 use lemmy_utils::LemmyError;
 use lemmy_websocket::{LemmyContext, UserOperation};
-use std::ops::Deref;
-use url::Url;
 
-pub mod dislike;
-pub mod like;
-pub mod undo_dislike;
-pub mod undo_like;
+pub mod undo_vote;
+pub mod vote;
 
-pub(in crate::activities::voting) async fn receive_like_or_dislike(
-  score: i16,
-  actor: &Url,
-  object: &Url,
-  context: &LemmyContext,
-  request_counter: &mut i32,
-) -> Result<(), LemmyError> {
-  match get_or_fetch_and_insert_post_or_comment(object, context, request_counter).await? {
-    PostOrComment::Post(p) => {
-      like_or_dislike_post(score, actor, p.deref(), context, request_counter).await
-    }
-    PostOrComment::Comment(c) => {
-      like_or_dislike_comment(score, actor, c.deref(), context, request_counter).await
-    }
-  }
-}
-
-async fn like_or_dislike_comment(
-  score: i16,
-  actor: &Url,
+async fn vote_comment(
+  vote_type: &VoteType,
+  actor: Person,
   comment: &Comment,
   context: &LemmyContext,
-  request_counter: &mut i32,
 ) -> Result<(), LemmyError> {
-  let actor = get_or_fetch_and_upsert_person(actor, context, request_counter).await?;
-
   let comment_id = comment.id;
   let like_form = CommentLikeForm {
     comment_id,
     post_id: comment.post_id,
     person_id: actor.id,
-    score,
+    score: vote_type.into(),
   };
   let person_id = actor.id;
   blocking(context.pool(), move |conn| {
@@ -74,20 +45,17 @@ async fn like_or_dislike_comment(
   .await
 }
 
-async fn like_or_dislike_post(
-  score: i16,
-  actor: &Url,
+async fn vote_post(
+  vote_type: &VoteType,
+  actor: Person,
   post: &Post,
   context: &LemmyContext,
-  request_counter: &mut i32,
 ) -> Result<(), LemmyError> {
-  let actor = get_or_fetch_and_upsert_person(actor, context, request_counter).await?;
-
   let post_id = post.id;
   let like_form = PostLikeForm {
     post_id: post.id,
     person_id: actor.id,
-    score,
+    score: vote_type.into(),
   };
   let person_id = actor.id;
   blocking(context.pool(), move |conn| {
@@ -99,30 +67,11 @@ async fn like_or_dislike_post(
   send_post_message(post.id, UserOperation::CreatePostLike, context).await
 }
 
-pub(in crate::activities::voting) async fn receive_undo_like_or_dislike(
-  actor: &Url,
-  object: &Url,
-  context: &LemmyContext,
-  request_counter: &mut i32,
-) -> Result<(), LemmyError> {
-  match get_or_fetch_and_insert_post_or_comment(object, context, request_counter).await? {
-    PostOrComment::Post(p) => {
-      undo_like_or_dislike_post(actor, p.deref(), context, request_counter).await
-    }
-    PostOrComment::Comment(c) => {
-      undo_like_or_dislike_comment(actor, c.deref(), context, request_counter).await
-    }
-  }
-}
-
-async fn undo_like_or_dislike_comment(
-  actor: &Url,
+async fn undo_vote_comment(
+  actor: Person,
   comment: &Comment,
   context: &LemmyContext,
-  request_counter: &mut i32,
 ) -> Result<(), LemmyError> {
-  let actor = get_or_fetch_and_upsert_person(actor, context, request_counter).await?;
-
   let comment_id = comment.id;
   let person_id = actor.id;
   blocking(context.pool(), move |conn| {
@@ -139,14 +88,11 @@ async fn undo_like_or_dislike_comment(
   .await
 }
 
-async fn undo_like_or_dislike_post(
-  actor: &Url,
+async fn undo_vote_post(
+  actor: Person,
   post: &Post,
   context: &LemmyContext,
-  request_counter: &mut i32,
 ) -> Result<(), LemmyError> {
-  let actor = get_or_fetch_and_upsert_person(actor, context, request_counter).await?;
-
   let post_id = post.id;
   let person_id = actor.id;
   blocking(context.pool(), move |conn| {
diff --git a/crates/apub/src/activities/voting/undo_dislike.rs b/crates/apub/src/activities/voting/undo_dislike.rs
deleted file mode 100644
index 13e3e1f7..00000000
--- a/crates/apub/src/activities/voting/undo_dislike.rs
+++ /dev/null
@@ -1,55 +0,0 @@
-use crate::activities::{
-  verify_activity,
-  verify_person_in_community,
-  voting::{dislike::DislikePostOrComment, receive_undo_like_or_dislike},
-};
-use activitystreams::activity::kind::UndoType;
-use lemmy_apub_lib::{values::PublicUrl, verify_urls_match, ActivityCommonFields, ActivityHandler};
-use lemmy_utils::LemmyError;
-use lemmy_websocket::LemmyContext;
-use url::Url;
-
-#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
-#[serde(rename_all = "camelCase")]
-pub struct UndoDislikePostOrComment {
-  to: PublicUrl,
-  object: DislikePostOrComment,
-  cc: [Url; 1],
-  #[serde(rename = "type")]
-  kind: UndoType,
-  #[serde(flatten)]
-  common: ActivityCommonFields,
-}
-
-#[async_trait::async_trait(?Send)]
-impl ActivityHandler for UndoDislikePostOrComment {
-  async fn verify(
-    &self,
-    context: &LemmyContext,
-    request_counter: &mut i32,
-  ) -> Result<(), LemmyError> {
-    verify_activity(self.common())?;
-    verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?;
-    verify_urls_match(&self.common.actor, &self.object.common().actor)?;
-    self.object.verify(context, request_counter).await?;
-    Ok(())
-  }
-
-  async fn receive(
-    &self,
-    context: &LemmyContext,
-    request_counter: &mut i32,
-  ) -> Result<(), LemmyError> {
-    receive_undo_like_or_dislike(
-      &self.common.actor,
-      &self.object.object,
-      context,
-      request_counter,
-    )
-    .await
-  }
-
-  fn common(&self) -> &ActivityCommonFields {
-    &self.common
-  }
-}
diff --git a/crates/apub/src/activities/voting/undo_like.rs b/crates/apub/src/activities/voting/undo_like.rs
deleted file mode 100644
index ab15da70..00000000
--- a/crates/apub/src/activities/voting/undo_like.rs
+++ /dev/null
@@ -1,55 +0,0 @@
-use crate::activities::{
-  verify_activity,
-  verify_person_in_community,
-  voting::{like::LikePostOrComment, receive_undo_like_or_dislike},
-};
-use activitystreams::activity::kind::UndoType;
-use lemmy_apub_lib::{values::PublicUrl, verify_urls_match, ActivityCommonFields, ActivityHandler};
-use lemmy_utils::LemmyError;
-use lemmy_websocket::LemmyContext;
-use url::Url;
-
-#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
-#[serde(rename_all = "camelCase")]
-pub struct UndoLikePostOrComment {
-  to: PublicUrl,
-  object: LikePostOrComment,
-  cc: [Url; 1],
-  #[serde(rename = "type")]
-  kind: UndoType,
-  #[serde(flatten)]
-  common: ActivityCommonFields,
-}
-
-#[async_trait::async_trait(?Send)]
-impl ActivityHandler for UndoLikePostOrComment {
-  async fn verify(
-    &self,
-    context: &LemmyContext,
-    request_counter: &mut i32,
-  ) -> Result<(), LemmyError> {
-    verify_activity(self.common())?;
-    verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?;
-    verify_urls_match(&self.common.actor, &self.object.common().actor)?;
-    self.object.verify(context, request_counter).await?;
-    Ok(())
-  }
-
-  async fn receive(
-    &self,
-    context: &LemmyContext,
-    request_counter: &mut i32,
-  ) -> Result<(), LemmyError> {
-    receive_undo_like_or_dislike(
-      &self.common.actor,
-      &self.object.object,
-      context,
-      request_counter,
-    )
-    .await
-  }
-
-  fn common(&self) -> &ActivityCommonFields {
-    &self.common
-  }
-}
diff --git a/crates/apub/src/activities/voting/undo_vote.rs b/crates/apub/src/activities/voting/undo_vote.rs
new file mode 100644
index 00000000..8c3137e8
--- /dev/null
+++ b/crates/apub/src/activities/voting/undo_vote.rs
@@ -0,0 +1,122 @@
+use crate::{
+  activities::{
+    community::announce::AnnouncableActivities,
+    generate_activity_id,
+    verify_activity,
+    verify_person_in_community,
+    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;
+use url::Url;
+
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct UndoVote {
+  to: PublicUrl,
+  object: Vote,
+  cc: [Url; 1],
+  #[serde(rename = "type")]
+  kind: UndoType,
+  #[serde(flatten)]
+  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(
+    &self,
+    context: &LemmyContext,
+    request_counter: &mut i32,
+  ) -> Result<(), LemmyError> {
+    verify_activity(self.common())?;
+    verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?;
+    verify_urls_match(&self.common.actor, &self.object.common().actor)?;
+    self.object.verify(context, request_counter).await?;
+    Ok(())
+  }
+
+  async fn receive(
+    &self,
+    context: &LemmyContext,
+    request_counter: &mut i32,
+  ) -> Result<(), LemmyError> {
+    let actor =
+      get_or_fetch_and_upsert_person(&self.common.actor, context, request_counter).await?;
+    let object =
+      get_or_fetch_and_insert_post_or_comment(&self.object.object, context, request_counter)
+        .await?;
+    match object {
+      PostOrComment::Post(p) => undo_vote_post(actor, p.deref(), context).await,
+      PostOrComment::Comment(c) => undo_vote_comment(actor, c.deref(), context).await,
+    }
+  }
+
+  fn common(&self) -> &ActivityCommonFields {
+    &self.common
+  }
+}
diff --git a/crates/apub/src/activities/voting/vote.rs b/crates/apub/src/activities/voting/vote.rs
new file mode 100644
index 00000000..0f5a2b5c
--- /dev/null
+++ b/crates/apub/src/activities/voting/vote.rs
@@ -0,0 +1,133 @@
+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::{convert::TryFrom, ops::Deref};
+use strum_macros::ToString;
+use url::Url;
+
+#[derive(Clone, Debug, ToString, Deserialize, Serialize)]
+pub enum VoteType {
+  Like,
+  Dislike,
+}
+
+impl TryFrom<i16> for VoteType {
+  type Error = LemmyError;
+
+  fn try_from(value: i16) -> Result<Self, Self::Error> {
+    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 {
+  pub(in crate::activities::voting) to: PublicUrl,
+  pub(in crate::activities::voting) object: Url,
+  pub(in crate::activities::voting) cc: [Url; 1],
+  #[serde(rename = "type")]
+  pub(in crate::activities::voting) kind: VoteType,
+  #[serde(flatten)]
+  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)]
+impl ActivityHandler for Vote {
+  async fn verify(
+    &self,
+    context: &LemmyContext,
+    request_counter: &mut i32,
+  ) -> Result<(), LemmyError> {
+    verify_activity(self.common())?;
+    verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?;
+    Ok(())
+  }
+
+  async fn receive(
+    &self,
+    context: &LemmyContext,
+    request_counter: &mut i32,
+  ) -> Result<(), LemmyError> {
+    let actor =
+      get_or_fetch_and_upsert_person(&self.common.actor, context, request_counter).await?;
+    let object =
+      get_or_fetch_and_insert_post_or_comment(&self.object, context, request_counter).await?;
+    match object {
+      PostOrComment::Post(p) => vote_post(&self.kind, actor, p.deref(), context).await,
+      PostOrComment::Comment(c) => vote_comment(&self.kind, actor, c.deref(), 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 f8ee0cb2..03404877 100644
--- a/crates/apub/src/http/inbox_enums.rs
+++ b/crates/apub/src/http/inbox_enums.rs
@@ -19,12 +19,7 @@ use crate::activities::{
     remove::RemovePostCommentCommunityOrMod,
     undo_remove::UndoRemovePostCommentOrCommunity,
   },
-  voting::{
-    dislike::DislikePostOrComment,
-    like::LikePostOrComment,
-    undo_dislike::UndoDislikePostOrComment,
-    undo_like::UndoLikePostOrComment,
-  },
+  voting::{undo_vote::UndoVote, vote::Vote},
 };
 use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler};
 use lemmy_utils::LemmyError;
@@ -48,10 +43,8 @@ pub enum GroupInboxActivities {
   UndoFollowCommunity(UndoFollowCommunity),
   CreateOrUpdateComment(CreateOrUpdateComment),
   CreateOrUpdatePost(Box<CreateOrUpdatePost>),
-  LikePostOrComment(LikePostOrComment),
-  DislikePostOrComment(DislikePostOrComment),
-  UndoLikePostOrComment(UndoLikePostOrComment),
-  UndoDislikePostOrComment(UndoDislikePostOrComment),
+  Vote(Vote),
+  UndoVote(UndoVote),
   DeletePostCommentOrCommunity(DeletePostCommentOrCommunity),
   UndoDeletePostCommentOrCommunity(UndoDeletePostCommentOrCommunity),
   RemovePostCommentOrCommunity(RemovePostCommentCommunityOrMod),
@@ -70,10 +63,8 @@ pub enum SharedInboxActivities {
   UndoFollowCommunity(UndoFollowCommunity),
   CreateOrUpdateComment(CreateOrUpdateComment),
   CreateOrUpdatePost(Box<CreateOrUpdatePost>),
-  LikePostOrComment(LikePostOrComment),
-  DislikePostOrComment(DislikePostOrComment),
-  UndoDislikePostOrComment(UndoDislikePostOrComment),
-  UndoLikePostOrComment(UndoLikePostOrComment),
+  Vote(Vote),
+  UndoVote(UndoVote),
   DeletePostCommentOrCommunity(DeletePostCommentOrCommunity),
   UndoDeletePostCommentOrCommunity(UndoDeletePostCommentOrCommunity),
   RemovePostCommentOrCommunity(RemovePostCommentCommunityOrMod),
diff --git a/crates/apub/src/lib.rs b/crates/apub/src/lib.rs
index 16773aae..19123057 100644
--- a/crates/apub/src/lib.rs
+++ b/crates/apub/src/lib.rs
@@ -132,10 +132,6 @@ pub fn check_is_apub_id_valid(apub_id: &Url, use_strict_allowlist: bool) -> Resu
 /// and actors in Lemmy.
 #[async_trait::async_trait(?Send)]
 pub trait ApubObjectType {
-  async fn send_create(&self, creator: &DbPerson, context: &LemmyContext)
-    -> Result<(), LemmyError>;
-  async fn send_update(&self, creator: &DbPerson, context: &LemmyContext)
-    -> Result<(), LemmyError>;
   async fn send_delete(&self, creator: &DbPerson, context: &LemmyContext)
     -> Result<(), LemmyError>;
   async fn send_undo_delete(
@@ -151,21 +147,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 +357,16 @@ pub enum PostOrComment {
   Post(Box<Post>),
 }
 
+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.
-- 
2.44.1