]> Untitled Git - lemmy.git/commitdiff
Adding federated post and comment likes.
authorDessalines <tyhou13@gmx.com>
Tue, 28 Apr 2020 04:16:02 +0000 (00:16 -0400)
committerDessalines <tyhou13@gmx.com>
Tue, 28 Apr 2020 04:16:02 +0000 (00:16 -0400)
server/src/api/comment.rs
server/src/apub/comment.rs
server/src/apub/community.rs
server/src/apub/mod.rs
server/src/apub/post.rs
server/src/apub/shared_inbox.rs
server/src/apub/user.rs
ui/src/api_tests/api.spec.ts

index fddb42abe236b80bf6c256166b537f3976e17262..a6742e4c027d66fdc3a189caf4e8a9fb27d19895 100644 (file)
@@ -235,6 +235,8 @@ impl Perform for Oper<CreateComment> {
       Err(_e) => return Err(APIError::err("couldnt_like_comment").into()),
     };
 
+    updated_comment.send_like(&user, &conn)?;
+
     let comment_view = CommentView::read(&conn, inserted_comment.id, Some(user_id))?;
 
     let mut res = CommentResponse {
@@ -500,7 +502,8 @@ impl Perform for Oper<CreateCommentLike> {
     }
 
     // Check for a site ban
-    if UserView::read(&conn, user_id)?.banned {
+    let user = User_::read(&conn, user_id)?;
+    if user.banned {
       return Err(APIError::err("site_ban").into());
     }
 
@@ -537,6 +540,14 @@ impl Perform for Oper<CreateCommentLike> {
         Ok(like) => like,
         Err(_e) => return Err(APIError::err("couldnt_like_comment").into()),
       };
+
+      if like_form.score == 1 {
+        comment.send_like(&user, &conn)?;
+      } else if like_form.score == -1 {
+        comment.send_dislike(&user, &conn)?;
+      }
+    } else {
+      // TODO tombstone the like
     }
 
     // Have to refetch the comment to get the current state
index d108b2ee7b9e6c7fe741d57eb0c92561d30f5ea6..db47106e6cc104ade4809412a368fc0bbf73c417 100644 (file)
@@ -94,11 +94,13 @@ impl ApubObjectType for Comment {
     let note = self.to_apub(conn)?;
     let post = Post::read(&conn, self.post_id)?;
     let community = Community::read(conn, post.community_id)?;
+    let id = format!("{}/create/{}", self.ap_id, uuid::Uuid::new_v4());
+
     let mut create = Create::new();
     populate_object_props(
       &mut create.object_props,
       &community.get_followers_url(),
-      &self.ap_id,
+      &id,
     )?;
     create
       .create_props
@@ -128,11 +130,13 @@ impl ApubObjectType for Comment {
     let note = self.to_apub(&conn)?;
     let post = Post::read(&conn, self.post_id)?;
     let community = Community::read(&conn, post.community_id)?;
+    let id = format!("{}/update/{}", self.ap_id, uuid::Uuid::new_v4());
+
     let mut update = Update::new();
     populate_object_props(
       &mut update.object_props,
       &community.get_followers_url(),
-      &self.ap_id,
+      &id,
     )?;
     update
       .update_props
@@ -157,3 +161,71 @@ impl ApubObjectType for Comment {
     Ok(())
   }
 }
+
+impl ApubLikeableType for Comment {
+  fn send_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+    let note = self.to_apub(&conn)?;
+    let post = Post::read(&conn, self.post_id)?;
+    let community = Community::read(&conn, post.community_id)?;
+    let id = format!("{}/like/{}", self.ap_id, uuid::Uuid::new_v4());
+
+    let mut like = Like::new();
+    populate_object_props(&mut like.object_props, &community.get_followers_url(), &id)?;
+    like
+      .like_props
+      .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+      .set_object_base_box(note)?;
+
+    // Insert the sent activity into the activity table
+    let activity_form = activity::ActivityForm {
+      user_id: creator.id,
+      data: serde_json::to_value(&like)?,
+      local: true,
+      updated: None,
+    };
+    activity::Activity::create(&conn, &activity_form)?;
+
+    send_activity(
+      &like,
+      &creator.private_key.as_ref().unwrap(),
+      &creator.actor_id,
+      community.get_follower_inboxes(&conn)?,
+    )?;
+    Ok(())
+  }
+
+  fn send_dislike(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+    let note = self.to_apub(&conn)?;
+    let post = Post::read(&conn, self.post_id)?;
+    let community = Community::read(&conn, post.community_id)?;
+    let id = format!("{}/dislike/{}", self.ap_id, uuid::Uuid::new_v4());
+
+    let mut dislike = Dislike::new();
+    populate_object_props(
+      &mut dislike.object_props,
+      &community.get_followers_url(),
+      &id,
+    )?;
+    dislike
+      .dislike_props
+      .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+      .set_object_base_box(note)?;
+
+    // Insert the sent activity into the activity table
+    let activity_form = activity::ActivityForm {
+      user_id: creator.id,
+      data: serde_json::to_value(&dislike)?,
+      local: true,
+      updated: None,
+    };
+    activity::Activity::create(&conn, &activity_form)?;
+
+    send_activity(
+      &dislike,
+      &creator.private_key.as_ref().unwrap(),
+      &creator.actor_id,
+      community.get_follower_inboxes(&conn)?,
+    )?;
+    Ok(())
+  }
+}
index f5e4f44de7c590ae5c754acb70ee4873382575de..46bd9024c03f3cb2c2985f09f38f8dc81e0a7d46 100644 (file)
@@ -63,11 +63,9 @@ impl ActorType for Community {
       .get_actor_xsd_any_uri()
       .unwrap()
       .to_string();
+    let id = format!("{}/accept/{}", self.actor_id, uuid::Uuid::new_v4());
 
     let mut accept = Accept::new();
-    // TODO using a fake accept id
-    let id = format!("{}/accept/{}", self.actor_id, uuid::Uuid::new_v4());
-    //follow
     accept
       .object_props
       .set_context_xsd_any_uri(context())?
index 4b51ca9f69d1724763b99ee57c6d6e724c601790..14f3e798b08169a90b9d448307a0ac0562c15b47 100644 (file)
@@ -42,16 +42,15 @@ use url::Url;
 use crate::api::comment::CommentResponse;
 use crate::api::post::PostResponse;
 use crate::api::site::SearchResponse;
-use crate::db::activity;
-use crate::db::comment::{Comment, CommentForm};
+use crate::db::comment::{Comment, CommentForm, CommentLike, CommentLikeForm};
 use crate::db::comment_view::CommentView;
 use crate::db::community::{Community, CommunityFollower, CommunityFollowerForm, CommunityForm};
 use crate::db::community_view::{CommunityFollowerView, CommunityView};
-use crate::db::post::{Post, PostForm};
+use crate::db::post::{Post, PostForm, PostLike, PostLikeForm};
 use crate::db::post_view::PostView;
 use crate::db::user::{UserForm, User_};
 use crate::db::user_view::UserView;
-use crate::db::{Crud, Followable, SearchType};
+use crate::db::{activity, Crud, Followable, Likeable, SearchType};
 use crate::routes::nodeinfo::{NodeInfo, NodeInfoWellKnown};
 use crate::routes::{ChatServerParam, DbPoolParam};
 use crate::websocket::{
index 505ab98a017f580aca08056f08be43c6d945748a..af8ee5998b154fc8c8a18097a5da81d9a9a31536 100644 (file)
@@ -98,11 +98,13 @@ impl ApubObjectType for Post {
   fn send_create(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
     let page = self.to_apub(conn)?;
     let community = Community::read(conn, self.community_id)?;
+    let id = format!("{}/create/{}", self.ap_id, uuid::Uuid::new_v4());
+
     let mut create = Create::new();
     populate_object_props(
       &mut create.object_props,
       &community.get_followers_url(),
-      &self.ap_id,
+      &id,
     )?;
     create
       .create_props
@@ -131,11 +133,13 @@ impl ApubObjectType for Post {
   fn send_update(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
     let page = self.to_apub(conn)?;
     let community = Community::read(conn, self.community_id)?;
+    let id = format!("{}/update/{}", self.ap_id, uuid::Uuid::new_v4());
+
     let mut update = Update::new();
     populate_object_props(
       &mut update.object_props,
       &community.get_followers_url(),
-      &self.ap_id,
+      &id,
     )?;
     update
       .update_props
@@ -165,12 +169,10 @@ impl ApubLikeableType for Post {
   fn send_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
     let page = self.to_apub(conn)?;
     let community = Community::read(conn, self.community_id)?;
+    let id = format!("{}/like/{}", self.ap_id, uuid::Uuid::new_v4());
+
     let mut like = Like::new();
-    populate_object_props(
-      &mut like.object_props,
-      &community.get_followers_url(),
-      &self.ap_id,
-    )?;
+    populate_object_props(&mut like.object_props, &community.get_followers_url(), &id)?;
     like
       .like_props
       .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
@@ -197,11 +199,13 @@ impl ApubLikeableType for Post {
   fn send_dislike(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
     let page = self.to_apub(conn)?;
     let community = Community::read(conn, self.community_id)?;
+    let id = format!("{}/dislike/{}", self.ap_id, uuid::Uuid::new_v4());
+
     let mut dislike = Dislike::new();
     populate_object_props(
       &mut dislike.object_props,
       &community.get_followers_url(),
-      &self.ap_id,
+      &id,
     )?;
     dislike
       .dislike_props
index 02244726b35370348adf0c7b5100fbfb1370900d..705a578a0194e58cd3b6fe4e86ac95be9e8ca156 100644 (file)
@@ -5,6 +5,8 @@ use super::*;
 pub enum SharedAcceptedObjects {
   Create(Create),
   Update(Update),
+  Like(Like),
+  Dislike(Dislike),
 }
 
 impl SharedAcceptedObjects {
@@ -12,6 +14,8 @@ impl SharedAcceptedObjects {
     match self {
       SharedAcceptedObjects::Create(c) => c.create_props.get_object_base_box(),
       SharedAcceptedObjects::Update(u) => u.update_props.get_object_base_box(),
+      SharedAcceptedObjects::Like(l) => l.like_props.get_object_base_box(),
+      SharedAcceptedObjects::Dislike(d) => d.dislike_props.get_object_base_box(),
     }
   }
 }
@@ -33,17 +37,29 @@ pub async fn shared_inbox(
   let object = activity.object().cloned().unwrap();
 
   match (activity, object.kind()) {
-    (SharedAcceptedObjects::Create(c), Some("Note")) => {
-      receive_create_comment(&c, &request, &conn, chat_server)
-    }
     (SharedAcceptedObjects::Create(c), Some("Page")) => {
       receive_create_post(&c, &request, &conn, chat_server)
     }
+    (SharedAcceptedObjects::Update(u), Some("Page")) => {
+      receive_update_post(&u, &request, &conn, chat_server)
+    }
+    (SharedAcceptedObjects::Like(l), Some("Page")) => {
+      receive_like_post(&l, &request, &conn, chat_server)
+    }
+    (SharedAcceptedObjects::Dislike(d), Some("Page")) => {
+      receive_dislike_post(&d, &request, &conn, chat_server)
+    }
+    (SharedAcceptedObjects::Create(c), Some("Note")) => {
+      receive_create_comment(&c, &request, &conn, chat_server)
+    }
     (SharedAcceptedObjects::Update(u), Some("Note")) => {
       receive_update_comment(&u, &request, &conn, chat_server)
     }
-    (SharedAcceptedObjects::Update(u), Some("Page")) => {
-      receive_update_post(&u, &request, &conn, chat_server)
+    (SharedAcceptedObjects::Like(l), Some("Note")) => {
+      receive_like_comment(&l, &request, &conn, chat_server)
+    }
+    (SharedAcceptedObjects::Dislike(d), Some("Note")) => {
+      receive_dislike_comment(&d, &request, &conn, chat_server)
     }
     _ => Err(format_err!("Unknown incoming activity type.")),
   }
@@ -202,6 +218,116 @@ fn receive_update_post(
   Ok(HttpResponse::Ok().finish())
 }
 
+fn receive_like_post(
+  like: &Like,
+  request: &HttpRequest,
+  conn: &PgConnection,
+  chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+  let page = like
+    .like_props
+    .get_object_base_box()
+    .to_owned()
+    .unwrap()
+    .to_owned()
+    .to_concrete::<Page>()?;
+
+  let user_uri = like.like_props.get_actor_xsd_any_uri().unwrap().to_string();
+
+  let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
+  verify(request, &user.public_key.unwrap())?;
+
+  // Insert the received activity into the activity table
+  let activity_form = activity::ActivityForm {
+    user_id: user.id,
+    data: serde_json::to_value(&like)?,
+    local: false,
+    updated: None,
+  };
+  activity::Activity::create(&conn, &activity_form)?;
+
+  let post = PostForm::from_apub(&page, conn)?;
+  let post_id = Post::read_from_apub_id(conn, &post.ap_id)?.id;
+
+  let like_form = PostLikeForm {
+    post_id,
+    user_id: user.id,
+    score: 1,
+  };
+  PostLike::remove(&conn, &like_form)?;
+  PostLike::like(&conn, &like_form)?;
+
+  // Refetch the view
+  let post_view = PostView::read(&conn, post_id, None)?;
+
+  let res = PostResponse { post: post_view };
+
+  chat_server.do_send(SendPost {
+    op: UserOperation::CreatePostLike,
+    post: res,
+    my_id: None,
+  });
+
+  Ok(HttpResponse::Ok().finish())
+}
+
+fn receive_dislike_post(
+  dislike: &Dislike,
+  request: &HttpRequest,
+  conn: &PgConnection,
+  chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+  let page = dislike
+    .dislike_props
+    .get_object_base_box()
+    .to_owned()
+    .unwrap()
+    .to_owned()
+    .to_concrete::<Page>()?;
+
+  let user_uri = dislike
+    .dislike_props
+    .get_actor_xsd_any_uri()
+    .unwrap()
+    .to_string();
+
+  let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
+  verify(request, &user.public_key.unwrap())?;
+
+  // Insert the received activity into the activity table
+  let activity_form = activity::ActivityForm {
+    user_id: user.id,
+    data: serde_json::to_value(&dislike)?,
+    local: false,
+    updated: None,
+  };
+  activity::Activity::create(&conn, &activity_form)?;
+
+  let post = PostForm::from_apub(&page, conn)?;
+  let post_id = Post::read_from_apub_id(conn, &post.ap_id)?.id;
+
+  let like_form = PostLikeForm {
+    post_id,
+    user_id: user.id,
+    score: -1,
+  };
+  PostLike::remove(&conn, &like_form)?;
+  PostLike::like(&conn, &like_form)?;
+
+  // Refetch the view
+  let post_view = PostView::read(&conn, post_id, None)?;
+
+  let res = PostResponse { post: post_view };
+
+  chat_server.do_send(SendPost {
+    op: UserOperation::CreatePostLike,
+    post: res,
+    my_id: None,
+  });
+
+  Ok(HttpResponse::Ok().finish())
+}
+
 fn receive_update_comment(
   update: &Update,
   request: &HttpRequest,
@@ -256,3 +382,123 @@ fn receive_update_comment(
 
   Ok(HttpResponse::Ok().finish())
 }
+
+fn receive_like_comment(
+  like: &Like,
+  request: &HttpRequest,
+  conn: &PgConnection,
+  chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+  let note = like
+    .like_props
+    .get_object_base_box()
+    .to_owned()
+    .unwrap()
+    .to_owned()
+    .to_concrete::<Note>()?;
+
+  let user_uri = like.like_props.get_actor_xsd_any_uri().unwrap().to_string();
+
+  let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
+  verify(request, &user.public_key.unwrap())?;
+
+  // Insert the received activity into the activity table
+  let activity_form = activity::ActivityForm {
+    user_id: user.id,
+    data: serde_json::to_value(&like)?,
+    local: false,
+    updated: None,
+  };
+  activity::Activity::create(&conn, &activity_form)?;
+
+  let comment = CommentForm::from_apub(&note, &conn)?;
+  let comment_id = Comment::read_from_apub_id(conn, &comment.ap_id)?.id;
+  let like_form = CommentLikeForm {
+    comment_id,
+    post_id: comment.post_id,
+    user_id: user.id,
+    score: 1,
+  };
+  CommentLike::remove(&conn, &like_form)?;
+  CommentLike::like(&conn, &like_form)?;
+
+  // Refetch the view
+  let comment_view = CommentView::read(&conn, comment_id, None)?;
+
+  // TODO get those recipient actor ids from somewhere
+  let recipient_ids = vec![];
+  let res = CommentResponse {
+    comment: comment_view,
+    recipient_ids,
+  };
+
+  chat_server.do_send(SendComment {
+    op: UserOperation::CreateCommentLike,
+    comment: res,
+    my_id: None,
+  });
+
+  Ok(HttpResponse::Ok().finish())
+}
+
+fn receive_dislike_comment(
+  dislike: &Dislike,
+  request: &HttpRequest,
+  conn: &PgConnection,
+  chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+  let note = dislike
+    .dislike_props
+    .get_object_base_box()
+    .to_owned()
+    .unwrap()
+    .to_owned()
+    .to_concrete::<Note>()?;
+
+  let user_uri = dislike
+    .dislike_props
+    .get_actor_xsd_any_uri()
+    .unwrap()
+    .to_string();
+
+  let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
+  verify(request, &user.public_key.unwrap())?;
+
+  // Insert the received activity into the activity table
+  let activity_form = activity::ActivityForm {
+    user_id: user.id,
+    data: serde_json::to_value(&dislike)?,
+    local: false,
+    updated: None,
+  };
+  activity::Activity::create(&conn, &activity_form)?;
+
+  let comment = CommentForm::from_apub(&note, &conn)?;
+  let comment_id = Comment::read_from_apub_id(conn, &comment.ap_id)?.id;
+  let like_form = CommentLikeForm {
+    comment_id,
+    post_id: comment.post_id,
+    user_id: user.id,
+    score: -1,
+  };
+  CommentLike::remove(&conn, &like_form)?;
+  CommentLike::like(&conn, &like_form)?;
+
+  // Refetch the view
+  let comment_view = CommentView::read(&conn, comment_id, None)?;
+
+  // TODO get those recipient actor ids from somewhere
+  let recipient_ids = vec![];
+  let res = CommentResponse {
+    comment: comment_view,
+    recipient_ids,
+  };
+
+  chat_server.do_send(SendComment {
+    op: UserOperation::CreateCommentLike,
+    comment: res,
+    my_id: None,
+  });
+
+  Ok(HttpResponse::Ok().finish())
+}
index bb9f064cbb963efd661cd7f19243e259b6a33464..d7fd228268bc338dc7aae9af1dbfef114ca3dca8 100644 (file)
@@ -58,7 +58,6 @@ impl ActorType for User_ {
   fn send_follow(&self, follow_actor_id: &str, conn: &PgConnection) -> Result<(), Error> {
     let mut follow = Follow::new();
 
-    // TODO using a fake accept id
     let id = format!("{}/follow/{}", self.actor_id, uuid::Uuid::new_v4());
 
     follow
index 49fd08782a825d680267d45d6eb7077240097531..8a3e94f2940b1d7abd48d0a4823efc2ee1bdd357 100644 (file)
@@ -163,6 +163,7 @@ describe('main', () => {
       expect(createResponse.post.name).toBe(name);
       expect(createResponse.post.community_local).toBe(false);
       expect(createResponse.post.creator_local).toBe(true);
+      expect(createResponse.post.score).toBe(1);
 
       let getPostUrl = `${lemmyBetaApiUrl}/post?id=2`;
       let getPostRes: GetPostResponse = await fetch(getPostUrl, {
@@ -172,6 +173,7 @@ describe('main', () => {
       expect(getPostRes.post.name).toBe(name);
       expect(getPostRes.post.community_local).toBe(true);
       expect(getPostRes.post.creator_local).toBe(false);
+      expect(getPostRes.post.score).toBe(1);
     });
   });
 
@@ -236,6 +238,7 @@ describe('main', () => {
       expect(createResponse.comment.content).toBe(content);
       expect(createResponse.comment.community_local).toBe(false);
       expect(createResponse.comment.creator_local).toBe(true);
+      expect(createResponse.comment.score).toBe(1);
 
       let getPostUrl = `${lemmyBetaApiUrl}/post?id=2`;
       let getPostRes: GetPostResponse = await fetch(getPostUrl, {
@@ -245,6 +248,7 @@ describe('main', () => {
       expect(getPostRes.comments[0].content).toBe(content);
       expect(getPostRes.comments[0].community_local).toBe(true);
       expect(getPostRes.comments[0].creator_local).toBe(false);
+      expect(getPostRes.comments[0].score).toBe(1);
 
       // Now do beta replying to that comment, as a child comment
       let contentBeta = 'A child federated comment from beta';
@@ -270,6 +274,7 @@ describe('main', () => {
       expect(createResponseBeta.comment.community_local).toBe(true);
       expect(createResponseBeta.comment.creator_local).toBe(true);
       expect(createResponseBeta.comment.parent_id).toBe(1);
+      expect(createResponseBeta.comment.score).toBe(1);
 
       // Make sure lemmy alpha sees that new child comment from beta
       let getPostUrlAlpha = `${lemmyAlphaApiUrl}/post?id=2`;
@@ -281,6 +286,7 @@ describe('main', () => {
       expect(getPostResAlpha.comments[0].content).toBe(contentBeta);
       expect(getPostResAlpha.comments[0].community_local).toBe(false);
       expect(getPostResAlpha.comments[0].creator_local).toBe(false);
+      expect(getPostResAlpha.comments[0].score).toBe(1);
     });
   });