From: Dessalines Date: Tue, 28 Jul 2020 16:08:28 +0000 (-0400) Subject: Merge branch 'main' into inbox-refactoring-merge X-Git-Url: http://these/git/%7B%60%24%7BwebArchiveUrl%7D/%22%7B%7D/readmes/%22https:/hacktivis.me/%7Brss%7D?a=commitdiff_plain;h=e605d5888833c0aef1e921dda1df8efe979bc403;p=lemmy.git Merge branch 'main' into inbox-refactoring-merge --- e605d5888833c0aef1e921dda1df8efe979bc403 diff --cc server/lemmy_db/src/community.rs index e1f24391,3a78d769..c4930d79 --- a/server/lemmy_db/src/community.rs +++ b/server/lemmy_db/src/community.rs @@@ -99,6 -99,60 +99,57 @@@ impl Community use crate::schema::community::dsl::*; community.filter(local.eq(true)).load::(conn) } + + pub fn update_deleted( + conn: &PgConnection, + community_id: i32, + new_deleted: bool, + ) -> Result { + use crate::schema::community::dsl::*; + diesel::update(community.find(community_id)) + .set(deleted.eq(new_deleted)) + .get_result::(conn) + } + + pub fn update_removed( + conn: &PgConnection, + community_id: i32, + new_removed: bool, + ) -> Result { + use crate::schema::community::dsl::*; + diesel::update(community.find(community_id)) + .set(removed.eq(new_removed)) + .get_result::(conn) + } + + pub fn update_creator( + conn: &PgConnection, + community_id: i32, + new_creator_id: i32, + ) -> Result { + use crate::schema::community::dsl::*; + diesel::update(community.find(community_id)) + .set((creator_id.eq(new_creator_id), updated.eq(naive_now()))) + .get_result::(conn) + } + - fn community_mods_and_admins( - conn: &PgConnection, - community_id: i32, - ) -> Result, Error> { ++ fn community_mods_and_admins(conn: &PgConnection, community_id: i32) -> Result, Error> { + use crate::{community_view::CommunityModeratorView, user_view::UserView}; + let mut mods_and_admins: Vec = Vec::new(); + mods_and_admins.append( + &mut CommunityModeratorView::for_community(conn, community_id) + .map(|v| v.into_iter().map(|m| m.user_id).collect())?, + ); + mods_and_admins + .append(&mut UserView::admins(conn).map(|v| v.into_iter().map(|a| a.id).collect())?); + Ok(mods_and_admins) + } + + pub fn is_mod_or_admin(conn: &PgConnection, user_id: i32, community_id: i32) -> bool { + Self::community_mods_and_admins(conn, community_id) + .unwrap_or_default() + .contains(&user_id) + } } #[derive(Identifiable, Queryable, Associations, PartialEq, Debug)] diff --cc server/src/api/comment.rs index d0dfa7a7,df772f53..7dfce473 --- a/server/src/api/comment.rs +++ b/server/src/api/comment.rs @@@ -175,8 -196,15 +196,8 @@@ impl Perform for Oper // Scan the comment for user mentions, add those rows let mentions = scrape_text_for_mentions(&comment_form.content); - let recipient_ids = send_local_notifs( - mentions, - updated_comment.clone(), - user.clone(), - post, - pool, - true, - ) - .await?; + let recipient_ids = - send_local_notifs(mentions, updated_comment.clone(), &user, post, pool).await?; ++ send_local_notifs(mentions, updated_comment.clone(), &user, post, pool, true).await?; // You like your own comment by default let like_form = CommentLikeForm { @@@ -243,116 -270,124 +263,124 @@@ impl Perform for Oper let orig_comment = blocking(pool, move |conn| CommentView::read(&conn, edit_id, None)).await??; - let mut editors: Vec = vec![orig_comment.creator_id]; - let mut moderators: Vec = vec![]; + // Check for a site ban + let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; + if user.banned { + return Err(APIError::err("site_ban").into()); + } + // Check for a community ban let community_id = orig_comment.community_id; - moderators.append( - &mut blocking(pool, move |conn| { - CommunityModeratorView::for_community(&conn, community_id) - .map(|v| v.into_iter().map(|m| m.user_id).collect()) - }) - .await??, - ); - moderators.append( - &mut blocking(pool, move |conn| { - UserView::admins(conn).map(|v| v.into_iter().map(|a| a.id).collect()) - }) - .await??, - ); - - editors.extend(&moderators); - // You are allowed to mark the comment as read even if you're banned. - if data.read.is_none() { - // Verify its the creator or a mod, or an admin - - if !editors.contains(&user_id) { - return Err(APIError::err("no_comment_edit_allowed").into()); - } - - // Check for a community ban - let community_id = orig_comment.community_id; - let is_banned = - move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); - if blocking(pool, is_banned).await? { - return Err(APIError::err("community_ban").into()); - } + let is_banned = + move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); + if blocking(pool, is_banned).await? { + return Err(APIError::err("community_ban").into()); + } - // Check for a site ban - if user.banned { - return Err(APIError::err("site_ban").into()); - } - } else { - // check that user can mark as read - let parent_id = orig_comment.parent_id; - match parent_id { - Some(pid) => { - let parent_comment = - blocking(pool, move |conn| CommentView::read(&conn, pid, None)).await??; - if user_id != parent_comment.creator_id { - return Err(APIError::err("no_comment_edit_allowed").into()); - } - } - None => { - let parent_post_id = orig_comment.post_id; - let parent_post = blocking(pool, move |conn| Post::read(conn, parent_post_id)).await??; - if user_id != parent_post.creator_id { - return Err(APIError::err("no_comment_edit_allowed").into()); - } - } - } + // Verify that only the creator can edit + if user_id != orig_comment.creator_id { + return Err(APIError::err("no_comment_edit_allowed").into()); } + // Do the update let content_slurs_removed = remove_slurs(&data.content.to_owned()); + let edit_id = data.edit_id; + let updated_comment = match blocking(pool, move |conn| { + Comment::update_content(conn, edit_id, &content_slurs_removed) + }) + .await? + { + Ok(comment) => comment, + Err(_e) => return Err(APIError::err("couldnt_update_comment").into()), + }; + + // Send the apub update + updated_comment + .send_update(&user, &self.client, pool) + .await?; + + // Do the mentions / recipients + let post_id = orig_comment.post_id; + let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??; + + let updated_comment_content = updated_comment.content.to_owned(); + let mentions = scrape_text_for_mentions(&updated_comment_content); + let recipient_ids = - send_local_notifs(mentions, updated_comment, user, post, pool, false).await?; ++ send_local_notifs(mentions, updated_comment, &user, post, pool, false).await?; let edit_id = data.edit_id; - let read_comment = blocking(pool, move |conn| Comment::read(conn, edit_id)).await??; - - let comment_form = { - if data.read.is_none() { - // the ban etc checks should been made and have passed - // the comment can be properly edited - let post_removed = if moderators.contains(&user_id) { - data.removed - } else { - Some(read_comment.removed) - }; - - CommentForm { - content: content_slurs_removed, - parent_id: read_comment.parent_id, - post_id: read_comment.post_id, - creator_id: read_comment.creator_id, - removed: post_removed.to_owned(), - deleted: data.deleted.to_owned(), - read: Some(read_comment.read), - published: None, - updated: Some(naive_now()), - ap_id: read_comment.ap_id, - local: read_comment.local, - } - } else { - // the only field that can be updated it the read field - CommentForm { - content: read_comment.content, - parent_id: read_comment.parent_id, - post_id: read_comment.post_id, - creator_id: read_comment.creator_id, - removed: Some(read_comment.removed).to_owned(), - deleted: Some(read_comment.deleted).to_owned(), - read: data.read.to_owned(), - published: None, - updated: orig_comment.updated, - ap_id: read_comment.ap_id, - local: read_comment.local, - } - } + let comment_view = blocking(pool, move |conn| { + CommentView::read(conn, edit_id, Some(user_id)) + }) + .await??; + + let mut res = CommentResponse { + comment: comment_view, + recipient_ids, + form_id: data.form_id.to_owned(), }; + if let Some(ws) = websocket_info { + ws.chatserver.do_send(SendComment { + op: UserOperation::EditComment, + comment: res.clone(), + my_id: ws.id, + }); + + // strip out the recipient_ids, so that + // users don't get double notifs + res.recipient_ids = Vec::new(); + } + + Ok(res) + } + } + + #[async_trait::async_trait(?Send)] + impl Perform for Oper { + type Response = CommentResponse; + + async fn perform( + &self, + pool: &DbPool, + websocket_info: Option, + ) -> Result { + let data: &DeleteComment = &self.data; + + let claims = match Claims::decode(&data.auth) { + Ok(claims) => claims.claims, + Err(_e) => return Err(APIError::err("not_logged_in").into()), + }; + + let user_id = claims.id; + let edit_id = data.edit_id; - let comment_form2 = comment_form.clone(); + let orig_comment = + blocking(pool, move |conn| CommentView::read(&conn, edit_id, None)).await??; + + // Check for a site ban + let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; + if user.banned { + return Err(APIError::err("site_ban").into()); + } + + // Check for a community ban + let community_id = orig_comment.community_id; + let is_banned = + move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); + if blocking(pool, is_banned).await? { + return Err(APIError::err("community_ban").into()); + } + + // Verify that only the creator can delete + if user_id != orig_comment.creator_id { + return Err(APIError::err("no_comment_edit_allowed").into()); + } + + // Do the delete + let deleted = data.deleted; let updated_comment = match blocking(pool, move |conn| { - Comment::update(conn, edit_id, &comment_form2) + Comment::update_deleted(conn, edit_id, deleted) }) .await? { @@@ -360,61 -395,137 +388,137 @@@ Err(_e) => return Err(APIError::err("couldnt_update_comment").into()), }; - if data.read.is_none() { - if let Some(deleted) = data.deleted.to_owned() { - if deleted { - updated_comment - .send_delete(&user, &self.client, pool) - .await?; - } else { - updated_comment - .send_undo_delete(&user, &self.client, pool) - .await?; - } - } else if let Some(removed) = data.removed.to_owned() { - if moderators.contains(&user_id) { - if removed { - updated_comment - .send_remove(&user, &self.client, pool) - .await?; - } else { - updated_comment - .send_undo_remove(&user, &self.client, pool) - .await?; - } - } - } else { - updated_comment - .send_update(&user, &self.client, pool) - .await?; - } - - // Mod tables - if moderators.contains(&user_id) { - if let Some(removed) = data.removed.to_owned() { - let form = ModRemoveCommentForm { - mod_user_id: user_id, - comment_id: data.edit_id, - removed: Some(removed), - reason: data.reason.to_owned(), - }; - blocking(pool, move |conn| ModRemoveComment::create(conn, &form)).await??; - } - } + // Send the apub message + if deleted { + updated_comment + .send_delete(&user, &self.client, pool) + .await?; + } else { + updated_comment + .send_undo_delete(&user, &self.client, pool) + .await?; } - let post_id = data.post_id; + // Refetch it + let edit_id = data.edit_id; + let comment_view = blocking(pool, move |conn| { + CommentView::read(conn, edit_id, Some(user_id)) + }) + .await??; + + // Build the recipients + let post_id = comment_view.post_id; let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??; + let mentions = vec![]; + let recipient_ids = - send_local_notifs(mentions, updated_comment, user, post, pool, false).await?; ++ send_local_notifs(mentions, updated_comment, &user, post, pool, false).await?; - let mentions = scrape_text_for_mentions(&comment_form.content); - let recipient_ids = send_local_notifs(mentions, updated_comment, &user, post, pool).await?; + let mut res = CommentResponse { + comment: comment_view, + recipient_ids, + form_id: None, + }; + + if let Some(ws) = websocket_info { + ws.chatserver.do_send(SendComment { + op: UserOperation::DeleteComment, + comment: res.clone(), + my_id: ws.id, + }); + + // strip out the recipient_ids, so that + // users don't get double notifs + res.recipient_ids = Vec::new(); + } + + Ok(res) + } + } + + #[async_trait::async_trait(?Send)] + impl Perform for Oper { + type Response = CommentResponse; + + async fn perform( + &self, + pool: &DbPool, + websocket_info: Option, + ) -> Result { + let data: &RemoveComment = &self.data; + + let claims = match Claims::decode(&data.auth) { + Ok(claims) => claims.claims, + Err(_e) => return Err(APIError::err("not_logged_in").into()), + }; + + let user_id = claims.id; + + let edit_id = data.edit_id; + let orig_comment = + blocking(pool, move |conn| CommentView::read(&conn, edit_id, None)).await??; + + // Check for a site ban + let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; + if user.banned { + return Err(APIError::err("site_ban").into()); + } + + // Check for a community ban + let community_id = orig_comment.community_id; + let is_banned = + move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); + if blocking(pool, is_banned).await? { + return Err(APIError::err("community_ban").into()); + } + + // Verify that only a mod or admin can remove + is_mod_or_admin(pool, user_id, community_id).await?; + + // Do the remove + let removed = data.removed; + let updated_comment = match blocking(pool, move |conn| { + Comment::update_removed(conn, edit_id, removed) + }) + .await? + { + Ok(comment) => comment, + Err(_e) => return Err(APIError::err("couldnt_update_comment").into()), + }; + + // Mod tables + let form = ModRemoveCommentForm { + mod_user_id: user_id, + comment_id: data.edit_id, + removed: Some(removed), + reason: data.reason.to_owned(), + }; + blocking(pool, move |conn| ModRemoveComment::create(conn, &form)).await??; + + // Send the apub message + if removed { + updated_comment + .send_remove(&user, &self.client, pool) + .await?; + } else { + updated_comment + .send_undo_remove(&user, &self.client, pool) + .await?; + } + // Refetch it let edit_id = data.edit_id; let comment_view = blocking(pool, move |conn| { CommentView::read(conn, edit_id, Some(user_id)) }) .await??; + // Build the recipients + let post_id = comment_view.post_id; + let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??; + let mentions = vec![]; + let recipient_ids = - send_local_notifs(mentions, updated_comment, user, post, pool, false).await?; ++ send_local_notifs(mentions, updated_comment, &user, post, pool, false).await?; + let mut res = CommentResponse { comment: comment_view, recipient_ids, @@@ -672,13 -870,13 +863,14 @@@ impl Perform for Oper pub async fn send_local_notifs( mentions: Vec, comment: Comment, - user: User_, + user: &User_, post: Post, pool: &DbPool, + do_send_email: bool, ) -> Result, LemmyError> { + let user2 = user.clone(); let ids = blocking(pool, move |conn| { - do_send_local_notifs(conn, &mentions, &comment, &user2, &post) - do_send_local_notifs(conn, &mentions, &comment, &user, &post, do_send_email) ++ do_send_local_notifs(conn, &mentions, &comment, &user2, &post, do_send_email) }) .await?; diff --cc server/src/apub/inbox/activities/announce.rs index 9564555a,00000000..78a005fb mode 100644,000000..100644 --- a/server/src/apub/inbox/activities/announce.rs +++ b/server/src/apub/inbox/activities/announce.rs @@@ -1,56 -1,0 +1,41 @@@ +use crate::{ - apub::{ - inbox::activities::{ ++ apub::inbox::{ ++ activities::{ + create::receive_create, + delete::receive_delete, + dislike::receive_dislike, + like::receive_like, + remove::receive_remove, + undo::receive_undo, + update::receive_update, + }, - inbox::shared_inbox::receive_unhandled_activity, ++ shared_inbox::receive_unhandled_activity, + }, + routes::ChatServerParam, + DbPool, + LemmyError, +}; - use activitystreams_new::{activity::*, prelude::ExtendsExt}; ++use activitystreams_new::{activity::*, base::AnyBase, prelude::ExtendsExt}; +use actix_web::{client::Client, HttpResponse}; - use activitystreams_new::base::AnyBase; + +pub async fn receive_announce( + activity: AnyBase, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let announce = Announce::from_any_base(activity)?.unwrap(); + let kind = announce.object().as_single_kind_str(); + let object = announce.object(); + let object2 = object.clone().one().unwrap(); + match kind { - Some("Create") => { - receive_create(object2, client, pool, chat_server).await - } - Some("Update") => { - receive_update(object2, client, pool, chat_server).await - } - Some("Like") => { - receive_like(object2, client, pool, chat_server).await - } - Some("Dislike") => { - receive_dislike(object2, client, pool, chat_server).await - } - Some("Delete") => { - receive_delete(object2, client, pool, chat_server).await - } - Some("Remove") => { - receive_remove(object2, client, pool, chat_server).await - } - Some("Undo") => { - receive_undo(object2, client, pool, chat_server).await - } ++ Some("Create") => receive_create(object2, client, pool, chat_server).await, ++ Some("Update") => receive_update(object2, client, pool, chat_server).await, ++ Some("Like") => receive_like(object2, client, pool, chat_server).await, ++ Some("Dislike") => receive_dislike(object2, client, pool, chat_server).await, ++ Some("Delete") => receive_delete(object2, client, pool, chat_server).await, ++ Some("Remove") => receive_remove(object2, client, pool, chat_server).await, ++ Some("Undo") => receive_undo(object2, client, pool, chat_server).await, + _ => receive_unhandled_activity(announce), + } +} diff --cc server/src/apub/inbox/activities/create.rs index 413a2977,00000000..da90bea5 mode 100644,000000..100644 --- a/server/src/apub/inbox/activities/create.rs +++ b/server/src/apub/inbox/activities/create.rs @@@ -1,124 -1,0 +1,127 @@@ +use crate::{ + api::{ + comment::{send_local_notifs, CommentResponse}, + post::PostResponse, + }, - apub::inbox::shared_inbox::{get_user_from_activity, receive_unhandled_activity}, + apub::{ ++ inbox::shared_inbox::{ ++ announce_if_community_is_local, ++ get_user_from_activity, ++ receive_unhandled_activity, ++ }, + ActorType, + FromApub, + PageExt, + }, + blocking, + routes::ChatServerParam, + websocket::{ + server::{SendComment, SendPost}, + UserOperation, + }, + DbPool, + LemmyError, +}; - use activitystreams_new::{activity::Create, object::Note, prelude::*}; ++use activitystreams_new::{activity::Create, base::AnyBase, object::Note, prelude::*}; +use actix_web::{client::Client, HttpResponse}; +use lemmy_db::{ + comment::{Comment, CommentForm}, + comment_view::CommentView, + post::{Post, PostForm}, + post_view::PostView, + Crud, +}; +use lemmy_utils::scrape_text_for_mentions; - use activitystreams_new::base::AnyBase; - use crate::apub::inbox::shared_inbox::{announce_if_community_is_local}; + +pub async fn receive_create( + activity: AnyBase, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let create = Create::from_any_base(activity)?.unwrap(); + dbg!(create.object().as_single_kind_str()); + match create.object().as_single_kind_str() { + Some("Page") => receive_create_post(create, client, pool, chat_server).await, + Some("Note") => receive_create_comment(create, client, pool, chat_server).await, + _ => receive_unhandled_activity(create), + } +} + +async fn receive_create_post( + create: Create, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let user = get_user_from_activity(&create, client, pool).await?; + let page = PageExt::from_any_base(create.object().to_owned().one().unwrap())?.unwrap(); + + let post = PostForm::from_apub(&page, client, pool, &user.actor_id()?).await?; + + let inserted_post = blocking(pool, move |conn| Post::create(conn, &post)).await??; + + // Refetch the view + let inserted_post_id = inserted_post.id; + let post_view = blocking(pool, move |conn| { + PostView::read(conn, inserted_post_id, None) + }) + .await??; + + let res = PostResponse { post: post_view }; + + chat_server.do_send(SendPost { + op: UserOperation::CreatePost, + post: res, + my_id: None, + }); + + announce_if_community_is_local(create, &user, client, pool).await?; + Ok(HttpResponse::Ok().finish()) +} + +async fn receive_create_comment( + create: Create, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let user = get_user_from_activity(&create, client, pool).await?; + let note = Note::from_any_base(create.object().to_owned().one().unwrap())?.unwrap(); + + let comment = CommentForm::from_apub(¬e, client, pool, &user.actor_id()?).await?; + + let inserted_comment = blocking(pool, move |conn| Comment::create(conn, &comment)).await??; + + let post_id = inserted_comment.post_id; + let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??; + + // Note: + // Although mentions could be gotten from the post tags (they are included there), or the ccs, + // Its much easier to scrape them from the comment body, since the API has to do that + // anyway. + let mentions = scrape_text_for_mentions(&inserted_comment.content); + let recipient_ids = - send_local_notifs(mentions, inserted_comment.clone(), &user, post, pool).await?; ++ send_local_notifs(mentions, inserted_comment.clone(), &user, post, pool, true).await?; + + // Refetch the view + let comment_view = blocking(pool, move |conn| { + CommentView::read(conn, inserted_comment.id, None) + }) + .await??; + + let res = CommentResponse { + comment: comment_view, + recipient_ids, ++ form_id: None, + }; + + chat_server.do_send(SendComment { + op: UserOperation::CreateComment, + comment: res, + my_id: None, + }); + + announce_if_community_is_local(create, &user, client, pool).await?; + Ok(HttpResponse::Ok().finish()) +} diff --cc server/src/apub/inbox/activities/delete.rs index 9c0d146f,00000000..4b072ebd mode 100644,000000..100644 --- a/server/src/apub/inbox/activities/delete.rs +++ b/server/src/apub/inbox/activities/delete.rs @@@ -1,222 -1,0 +1,224 @@@ +use crate::{ + api::{comment::CommentResponse, community::CommunityResponse, post::PostResponse}, - apub::inbox:: - shared_inbox::{get_user_from_activity, receive_unhandled_activity}, + apub::{ + fetcher::{get_or_fetch_and_insert_remote_comment, get_or_fetch_and_insert_remote_post}, ++ inbox::shared_inbox::{ ++ announce_if_community_is_local, ++ get_user_from_activity, ++ receive_unhandled_activity, ++ }, + ActorType, + FromApub, + GroupExt, + PageExt, + }, + blocking, + routes::ChatServerParam, + websocket::{ + server::{SendComment, SendCommunityRoomMessage, SendPost}, + UserOperation, + }, + DbPool, + LemmyError, +}; - use activitystreams_new::{activity::Delete, object::Note, prelude::*}; ++use activitystreams_new::{activity::Delete, base::AnyBase, object::Note, prelude::*}; +use actix_web::{client::Client, HttpResponse}; +use lemmy_db::{ + comment::{Comment, CommentForm}, + comment_view::CommentView, + community::{Community, CommunityForm}, + community_view::CommunityView, + naive_now, + post::{Post, PostForm}, + post_view::PostView, + Crud, +}; - use activitystreams_new::base::AnyBase; - use crate::apub::inbox::shared_inbox::announce_if_community_is_local; + +pub async fn receive_delete( + activity: AnyBase, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let delete = Delete::from_any_base(activity)?.unwrap(); + match delete.object().as_single_kind_str() { + Some("Page") => receive_delete_post(delete, client, pool, chat_server).await, + Some("Note") => receive_delete_comment(delete, client, pool, chat_server).await, + Some("Group") => receive_delete_community(delete, client, pool, chat_server).await, + _ => receive_unhandled_activity(delete), + } +} + +async fn receive_delete_post( + delete: Delete, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let user = get_user_from_activity(&delete, client, pool).await?; + let page = PageExt::from_any_base(delete.object().to_owned().one().unwrap())?.unwrap(); + + let post_ap_id = PostForm::from_apub(&page, client, pool, &user.actor_id()?) + .await? + .get_ap_id()?; + + let post = get_or_fetch_and_insert_remote_post(&post_ap_id, client, pool).await?; + + let post_form = PostForm { + name: post.name.to_owned(), + url: post.url.to_owned(), + body: post.body.to_owned(), + creator_id: post.creator_id.to_owned(), + community_id: post.community_id, + removed: None, + deleted: Some(true), + nsfw: post.nsfw, + locked: None, + stickied: None, + updated: Some(naive_now()), + embed_title: post.embed_title, + embed_description: post.embed_description, + embed_html: post.embed_html, + thumbnail_url: post.thumbnail_url, + ap_id: post.ap_id, + local: post.local, + published: None, + }; + let post_id = post.id; + blocking(pool, move |conn| Post::update(conn, post_id, &post_form)).await??; + + // Refetch the view + let post_id = post.id; + let post_view = blocking(pool, move |conn| PostView::read(conn, post_id, None)).await??; + + let res = PostResponse { post: post_view }; + + chat_server.do_send(SendPost { + op: UserOperation::EditPost, + post: res, + my_id: None, + }); + + announce_if_community_is_local(delete, &user, client, pool).await?; + Ok(HttpResponse::Ok().finish()) +} + +async fn receive_delete_comment( + delete: Delete, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let user = get_user_from_activity(&delete, client, pool).await?; + let note = Note::from_any_base(delete.object().to_owned().one().unwrap())?.unwrap(); + + let comment_ap_id = CommentForm::from_apub(¬e, client, pool, &user.actor_id()?) + .await? + .get_ap_id()?; + + let comment = get_or_fetch_and_insert_remote_comment(&comment_ap_id, client, pool).await?; + + let comment_form = CommentForm { + content: comment.content.to_owned(), + parent_id: comment.parent_id, + post_id: comment.post_id, + creator_id: comment.creator_id, + removed: None, + deleted: Some(true), + read: None, + published: None, + updated: Some(naive_now()), + ap_id: comment.ap_id, + local: comment.local, + }; + let comment_id = comment.id; + blocking(pool, move |conn| { + Comment::update(conn, comment_id, &comment_form) + }) + .await??; + + // Refetch the view + let comment_id = comment.id; + let comment_view = + blocking(pool, move |conn| CommentView::read(conn, comment_id, None)).await??; + + // TODO get those recipient actor ids from somewhere + let recipient_ids = vec![]; + let res = CommentResponse { + comment: comment_view, + recipient_ids, ++ form_id: None, + }; + + chat_server.do_send(SendComment { + op: UserOperation::EditComment, + comment: res, + my_id: None, + }); + + announce_if_community_is_local(delete, &user, client, pool).await?; + Ok(HttpResponse::Ok().finish()) +} + +async fn receive_delete_community( + delete: Delete, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let group = GroupExt::from_any_base(delete.object().to_owned().one().unwrap())?.unwrap(); + let user = get_user_from_activity(&delete, client, pool).await?; + + let community_actor_id = CommunityForm::from_apub(&group, client, pool, &user.actor_id()?) + .await? + .actor_id; + + let community = blocking(pool, move |conn| { + Community::read_from_actor_id(conn, &community_actor_id) + }) + .await??; + + let community_form = CommunityForm { + name: community.name.to_owned(), + title: community.title.to_owned(), + description: community.description.to_owned(), + category_id: community.category_id, // Note: need to keep this due to foreign key constraint + creator_id: community.creator_id, // Note: need to keep this due to foreign key constraint + removed: None, + published: None, + updated: Some(naive_now()), + deleted: Some(true), + nsfw: community.nsfw, + actor_id: community.actor_id, + local: community.local, + private_key: community.private_key, + public_key: community.public_key, + last_refreshed_at: None, + }; + + let community_id = community.id; + blocking(pool, move |conn| { + Community::update(conn, community_id, &community_form) + }) + .await??; + + let community_id = community.id; + let res = CommunityResponse { + community: blocking(pool, move |conn| { + CommunityView::read(conn, community_id, None) + }) + .await??, + }; + + let community_id = res.community.id; + + chat_server.do_send(SendCommunityRoomMessage { + op: UserOperation::EditCommunity, + response: res, + community_id, + my_id: None, + }); + + announce_if_community_is_local(delete, &user, client, pool).await?; + Ok(HttpResponse::Ok().finish()) +} diff --cc server/src/apub/inbox/activities/dislike.rs index 2b887c55,00000000..94790220 mode 100644,000000..100644 --- a/server/src/apub/inbox/activities/dislike.rs +++ b/server/src/apub/inbox/activities/dislike.rs @@@ -1,132 -1,0 +1,135 @@@ +use crate::{ + api::{comment::CommentResponse, post::PostResponse}, - apub::inbox::shared_inbox::{get_user_from_activity, receive_unhandled_activity}, + apub::{ + fetcher::{get_or_fetch_and_insert_remote_comment, get_or_fetch_and_insert_remote_post}, ++ inbox::shared_inbox::{ ++ announce_if_community_is_local, ++ get_user_from_activity, ++ receive_unhandled_activity, ++ }, + ActorType, + FromApub, + PageExt, + }, + blocking, + routes::ChatServerParam, + websocket::{ + server::{SendComment, SendPost}, + UserOperation, + }, + DbPool, + LemmyError, +}; - use activitystreams_new::{activity::Dislike, object::Note, prelude::*}; ++use activitystreams_new::{activity::Dislike, base::AnyBase, object::Note, prelude::*}; +use actix_web::{client::Client, HttpResponse}; +use lemmy_db::{ + comment::{CommentForm, CommentLike, CommentLikeForm}, + comment_view::CommentView, + post::{PostForm, PostLike, PostLikeForm}, + post_view::PostView, + Likeable, +}; - use activitystreams_new::base::AnyBase; - use crate::apub::inbox::shared_inbox::announce_if_community_is_local; + +pub async fn receive_dislike( + activity: AnyBase, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let dislike = Dislike::from_any_base(activity)?.unwrap(); + match dislike.object().as_single_kind_str() { + Some("Page") => receive_dislike_post(dislike, client, pool, chat_server).await, + Some("Note") => receive_dislike_comment(dislike, client, pool, chat_server).await, + _ => receive_unhandled_activity(dislike), + } +} + +async fn receive_dislike_post( + dislike: Dislike, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let user = get_user_from_activity(&dislike, client, pool).await?; + let page = PageExt::from_any_base(dislike.object().to_owned().one().unwrap())?.unwrap(); + + let post = PostForm::from_apub(&page, client, pool, &user.actor_id()?).await?; + + let post_id = get_or_fetch_and_insert_remote_post(&post.get_ap_id()?, client, pool) + .await? + .id; + + let like_form = PostLikeForm { + post_id, + user_id: user.id, + score: -1, + }; + blocking(pool, move |conn| { + PostLike::remove(conn, &like_form)?; + PostLike::like(conn, &like_form) + }) + .await??; + + // Refetch the view + let post_view = blocking(pool, move |conn| PostView::read(conn, post_id, None)).await??; + + let res = PostResponse { post: post_view }; + + chat_server.do_send(SendPost { + op: UserOperation::CreatePostLike, + post: res, + my_id: None, + }); + + announce_if_community_is_local(dislike, &user, client, pool).await?; + Ok(HttpResponse::Ok().finish()) +} + +async fn receive_dislike_comment( + dislike: Dislike, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let note = Note::from_any_base(dislike.object().to_owned().one().unwrap())?.unwrap(); + let user = get_user_from_activity(&dislike, client, pool).await?; + + let comment = CommentForm::from_apub(¬e, client, pool, &user.actor_id()?).await?; + + let comment_id = get_or_fetch_and_insert_remote_comment(&comment.get_ap_id()?, client, pool) + .await? + .id; + + let like_form = CommentLikeForm { + comment_id, + post_id: comment.post_id, + user_id: user.id, + score: -1, + }; + blocking(pool, move |conn| { + CommentLike::remove(conn, &like_form)?; + CommentLike::like(conn, &like_form) + }) + .await??; + + // Refetch the view + let comment_view = + blocking(pool, move |conn| CommentView::read(conn, comment_id, None)).await??; + + // TODO get those recipient actor ids from somewhere + let recipient_ids = vec![]; + let res = CommentResponse { + comment: comment_view, + recipient_ids, ++ form_id: None, + }; + + chat_server.do_send(SendComment { + op: UserOperation::CreateCommentLike, + comment: res, + my_id: None, + }); + + announce_if_community_is_local(dislike, &user, client, pool).await?; + Ok(HttpResponse::Ok().finish()) +} diff --cc server/src/apub/inbox/activities/like.rs index 51a7d033,00000000..1df20a05 mode 100644,000000..100644 --- a/server/src/apub/inbox/activities/like.rs +++ b/server/src/apub/inbox/activities/like.rs @@@ -1,132 -1,0 +1,135 @@@ +use crate::{ + api::{comment::CommentResponse, post::PostResponse}, - apub::inbox::shared_inbox::{get_user_from_activity, receive_unhandled_activity}, + apub::{ + fetcher::{get_or_fetch_and_insert_remote_comment, get_or_fetch_and_insert_remote_post}, ++ inbox::shared_inbox::{ ++ announce_if_community_is_local, ++ get_user_from_activity, ++ receive_unhandled_activity, ++ }, + ActorType, + FromApub, + PageExt, + }, + blocking, + routes::ChatServerParam, + websocket::{ + server::{SendComment, SendPost}, + UserOperation, + }, + DbPool, + LemmyError, +}; - use activitystreams_new::{activity::Like, object::Note, prelude::*}; ++use activitystreams_new::{activity::Like, base::AnyBase, object::Note, prelude::*}; +use actix_web::{client::Client, HttpResponse}; +use lemmy_db::{ + comment::{CommentForm, CommentLike, CommentLikeForm}, + comment_view::CommentView, + post::{PostForm, PostLike, PostLikeForm}, + post_view::PostView, + Likeable, +}; - use activitystreams_new::base::AnyBase; - use crate::apub::inbox::shared_inbox::announce_if_community_is_local; + +pub async fn receive_like( + activity: AnyBase, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let like = Like::from_any_base(activity)?.unwrap(); + match like.object().as_single_kind_str() { + Some("Page") => receive_like_post(like, client, pool, chat_server).await, + Some("Note") => receive_like_comment(like, client, pool, chat_server).await, + _ => receive_unhandled_activity(like), + } +} + +async fn receive_like_post( + like: Like, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let user = get_user_from_activity(&like, client, pool).await?; + let page = PageExt::from_any_base(like.object().to_owned().one().unwrap())?.unwrap(); + + let post = PostForm::from_apub(&page, client, pool, &user.actor_id()?).await?; + + let post_id = get_or_fetch_and_insert_remote_post(&post.get_ap_id()?, client, pool) + .await? + .id; + + let like_form = PostLikeForm { + post_id, + user_id: user.id, + score: 1, + }; + blocking(pool, move |conn| { + PostLike::remove(conn, &like_form)?; + PostLike::like(conn, &like_form) + }) + .await??; + + // Refetch the view + let post_view = blocking(pool, move |conn| PostView::read(conn, post_id, None)).await??; + + let res = PostResponse { post: post_view }; + + chat_server.do_send(SendPost { + op: UserOperation::CreatePostLike, + post: res, + my_id: None, + }); + + announce_if_community_is_local(like, &user, client, pool).await?; + Ok(HttpResponse::Ok().finish()) +} + +async fn receive_like_comment( + like: Like, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let note = Note::from_any_base(like.object().to_owned().one().unwrap())?.unwrap(); + let user = get_user_from_activity(&like, client, pool).await?; + + let comment = CommentForm::from_apub(¬e, client, pool, &user.actor_id()?).await?; + + let comment_id = get_or_fetch_and_insert_remote_comment(&comment.get_ap_id()?, client, pool) + .await? + .id; + + let like_form = CommentLikeForm { + comment_id, + post_id: comment.post_id, + user_id: user.id, + score: 1, + }; + blocking(pool, move |conn| { + CommentLike::remove(conn, &like_form)?; + CommentLike::like(conn, &like_form) + }) + .await??; + + // Refetch the view + let comment_view = + blocking(pool, move |conn| CommentView::read(conn, comment_id, None)).await??; + + // TODO get those recipient actor ids from somewhere + let recipient_ids = vec![]; + let res = CommentResponse { + comment: comment_view, + recipient_ids, ++ form_id: None, + }; + + chat_server.do_send(SendComment { + op: UserOperation::CreateCommentLike, + comment: res, + my_id: None, + }); + + announce_if_community_is_local(like, &user, client, pool).await?; + Ok(HttpResponse::Ok().finish()) +} diff --cc server/src/apub/inbox/activities/remove.rs index a056b898,00000000..cb2a1292 mode 100644,000000..100644 --- a/server/src/apub/inbox/activities/remove.rs +++ b/server/src/apub/inbox/activities/remove.rs @@@ -1,221 -1,0 +1,224 @@@ +use crate::{ + api::{comment::CommentResponse, community::CommunityResponse, post::PostResponse}, - apub::inbox::shared_inbox::{get_user_from_activity, receive_unhandled_activity}, + apub::{ + fetcher::{get_or_fetch_and_insert_remote_comment, get_or_fetch_and_insert_remote_post}, ++ inbox::shared_inbox::{ ++ announce_if_community_is_local, ++ get_user_from_activity, ++ receive_unhandled_activity, ++ }, + ActorType, + FromApub, + GroupExt, + PageExt, + }, + blocking, + routes::ChatServerParam, + websocket::{ + server::{SendComment, SendCommunityRoomMessage, SendPost}, + UserOperation, + }, + DbPool, + LemmyError, +}; - use activitystreams_new::{activity::Remove, object::Note, prelude::*}; ++use activitystreams_new::{activity::Remove, base::AnyBase, object::Note, prelude::*}; +use actix_web::{client::Client, HttpResponse}; +use lemmy_db::{ + comment::{Comment, CommentForm}, + comment_view::CommentView, + community::{Community, CommunityForm}, + community_view::CommunityView, + naive_now, + post::{Post, PostForm}, + post_view::PostView, + Crud, +}; - use activitystreams_new::base::AnyBase; - use crate::apub::inbox::shared_inbox::announce_if_community_is_local; + +pub async fn receive_remove( + activity: AnyBase, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let remove = Remove::from_any_base(activity)?.unwrap(); + match remove.object().as_single_kind_str() { + Some("Page") => receive_remove_post(remove, client, pool, chat_server).await, + Some("Note") => receive_remove_comment(remove, client, pool, chat_server).await, + Some("Group") => receive_remove_community(remove, client, pool, chat_server).await, + _ => receive_unhandled_activity(remove), + } +} + +async fn receive_remove_post( + remove: Remove, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let mod_ = get_user_from_activity(&remove, client, pool).await?; + let page = PageExt::from_any_base(remove.object().to_owned().one().unwrap())?.unwrap(); + + let post_ap_id = PostForm::from_apub(&page, client, pool, &mod_.actor_id()?) + .await? + .get_ap_id()?; + + let post = get_or_fetch_and_insert_remote_post(&post_ap_id, client, pool).await?; + + let post_form = PostForm { + name: post.name.to_owned(), + url: post.url.to_owned(), + body: post.body.to_owned(), + creator_id: post.creator_id.to_owned(), + community_id: post.community_id, + removed: Some(true), + deleted: None, + nsfw: post.nsfw, + locked: None, + stickied: None, + updated: Some(naive_now()), + embed_title: post.embed_title, + embed_description: post.embed_description, + embed_html: post.embed_html, + thumbnail_url: post.thumbnail_url, + ap_id: post.ap_id, + local: post.local, + published: None, + }; + let post_id = post.id; + blocking(pool, move |conn| Post::update(conn, post_id, &post_form)).await??; + + // Refetch the view + let post_id = post.id; + let post_view = blocking(pool, move |conn| PostView::read(conn, post_id, None)).await??; + + let res = PostResponse { post: post_view }; + + chat_server.do_send(SendPost { + op: UserOperation::EditPost, + post: res, + my_id: None, + }); + + announce_if_community_is_local(remove, &mod_, client, pool).await?; + Ok(HttpResponse::Ok().finish()) +} + +async fn receive_remove_comment( + remove: Remove, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let mod_ = get_user_from_activity(&remove, client, pool).await?; + let note = Note::from_any_base(remove.object().to_owned().one().unwrap())?.unwrap(); + + let comment_ap_id = CommentForm::from_apub(¬e, client, pool, &mod_.actor_id()?) + .await? + .get_ap_id()?; + + let comment = get_or_fetch_and_insert_remote_comment(&comment_ap_id, client, pool).await?; + + let comment_form = CommentForm { + content: comment.content.to_owned(), + parent_id: comment.parent_id, + post_id: comment.post_id, + creator_id: comment.creator_id, + removed: Some(true), + deleted: None, + read: None, + published: None, + updated: Some(naive_now()), + ap_id: comment.ap_id, + local: comment.local, + }; + let comment_id = comment.id; + blocking(pool, move |conn| { + Comment::update(conn, comment_id, &comment_form) + }) + .await??; + + // Refetch the view + let comment_id = comment.id; + let comment_view = + blocking(pool, move |conn| CommentView::read(conn, comment_id, None)).await??; + + // TODO get those recipient actor ids from somewhere + let recipient_ids = vec![]; + let res = CommentResponse { + comment: comment_view, + recipient_ids, ++ form_id: None, + }; + + chat_server.do_send(SendComment { + op: UserOperation::EditComment, + comment: res, + my_id: None, + }); + + announce_if_community_is_local(remove, &mod_, client, pool).await?; + Ok(HttpResponse::Ok().finish()) +} + +async fn receive_remove_community( + remove: Remove, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let mod_ = get_user_from_activity(&remove, client, pool).await?; + let group = GroupExt::from_any_base(remove.object().to_owned().one().unwrap())?.unwrap(); + + let community_actor_id = CommunityForm::from_apub(&group, client, pool, &mod_.actor_id()?) + .await? + .actor_id; + + let community = blocking(pool, move |conn| { + Community::read_from_actor_id(conn, &community_actor_id) + }) + .await??; + + let community_form = CommunityForm { + name: community.name.to_owned(), + title: community.title.to_owned(), + description: community.description.to_owned(), + category_id: community.category_id, // Note: need to keep this due to foreign key constraint + creator_id: community.creator_id, // Note: need to keep this due to foreign key constraint + removed: Some(true), + published: None, + updated: Some(naive_now()), + deleted: None, + nsfw: community.nsfw, + actor_id: community.actor_id, + local: community.local, + private_key: community.private_key, + public_key: community.public_key, + last_refreshed_at: None, + }; + + let community_id = community.id; + blocking(pool, move |conn| { + Community::update(conn, community_id, &community_form) + }) + .await??; + + let community_id = community.id; + let res = CommunityResponse { + community: blocking(pool, move |conn| { + CommunityView::read(conn, community_id, None) + }) + .await??, + }; + + let community_id = res.community.id; + + chat_server.do_send(SendCommunityRoomMessage { + op: UserOperation::EditCommunity, + response: res, + community_id, + my_id: None, + }); + + announce_if_community_is_local(remove, &mod_, client, pool).await?; + Ok(HttpResponse::Ok().finish()) +} diff --cc server/src/apub/inbox/activities/undo.rs index a0847226,00000000..3c5bdb68 mode 100644,000000..100644 --- a/server/src/apub/inbox/activities/undo.rs +++ b/server/src/apub/inbox/activities/undo.rs @@@ -1,550 -1,0 +1,552 @@@ +use crate::{ + api::{comment::CommentResponse, community::CommunityResponse, post::PostResponse}, - apub::inbox::shared_inbox::{get_user_from_activity, receive_unhandled_activity}, + apub::{ + fetcher::{get_or_fetch_and_insert_remote_comment, get_or_fetch_and_insert_remote_post}, ++ inbox::shared_inbox::{ ++ announce_if_community_is_local, ++ get_user_from_activity, ++ receive_unhandled_activity, ++ }, + ActorType, + FromApub, + GroupExt, + PageExt, + }, + blocking, + routes::ChatServerParam, + websocket::{ + server::{SendComment, SendCommunityRoomMessage, SendPost}, + UserOperation, + }, + DbPool, + LemmyError, +}; - use activitystreams_new::{activity::*, object::Note, prelude::*}; ++use activitystreams_new::{activity::*, base::AnyBase, object::Note, prelude::*}; +use actix_web::{client::Client, HttpResponse}; +use lemmy_db::{ + comment::{Comment, CommentForm, CommentLike, CommentLikeForm}, + comment_view::CommentView, + community::{Community, CommunityForm}, + community_view::CommunityView, + naive_now, + post::{Post, PostForm, PostLike, PostLikeForm}, + post_view::PostView, + Crud, + Likeable, +}; - use activitystreams_new::base::AnyBase; - use crate::apub::inbox::shared_inbox::announce_if_community_is_local; + +pub async fn receive_undo( + activity: AnyBase, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let undo = Undo::from_any_base(activity)?.unwrap(); + match undo.object().as_single_kind_str() { + Some("Delete") => receive_undo_delete(undo, client, pool, chat_server).await, + Some("Remove") => receive_undo_remove(undo, client, pool, chat_server).await, + Some("Like") => receive_undo_like(undo, client, pool, chat_server).await, + Some("Dislike") => receive_undo_dislike(undo, client, pool, chat_server).await, + // TODO: handle undo_dislike? + _ => receive_unhandled_activity(undo), + } +} + +async fn receive_undo_delete( + undo: Undo, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let delete = Delete::from_any_base(undo.object().to_owned().one().unwrap())?.unwrap(); + let type_ = delete.object().as_single_kind_str().unwrap(); + match type_ { + "Note" => receive_undo_delete_comment(undo, &delete, client, pool, chat_server).await, + "Page" => receive_undo_delete_post(undo, &delete, client, pool, chat_server).await, + "Group" => receive_undo_delete_community(undo, &delete, client, pool, chat_server).await, + d => Err(format_err!("Undo Delete type {} not supported", d).into()), + } +} + +async fn receive_undo_remove( + undo: Undo, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let remove = Remove::from_any_base(undo.object().to_owned().one().unwrap())?.unwrap(); + + let type_ = remove.object().as_single_kind_str().unwrap(); + match type_ { + "Note" => receive_undo_remove_comment(undo, &remove, client, pool, chat_server).await, + "Page" => receive_undo_remove_post(undo, &remove, client, pool, chat_server).await, + "Group" => receive_undo_remove_community(undo, &remove, client, pool, chat_server).await, + d => Err(format_err!("Undo Delete type {} not supported", d).into()), + } +} + +async fn receive_undo_like( + undo: Undo, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let like = Like::from_any_base(undo.object().to_owned().one().unwrap())?.unwrap(); + + let type_ = like.object().as_single_kind_str().unwrap(); + match type_ { + "Note" => receive_undo_like_comment(undo, &like, client, pool, chat_server).await, + "Page" => receive_undo_like_post(undo, &like, client, pool, chat_server).await, + d => Err(format_err!("Undo Delete type {} not supported", d).into()), + } +} + +async fn receive_undo_dislike( + undo: Undo, - client: &Client, - pool: &DbPool, - chat_server: ChatServerParam, ++ _client: &Client, ++ _pool: &DbPool, ++ _chat_server: ChatServerParam, +) -> Result { + let dislike = Dislike::from_any_base(undo.object().to_owned().one().unwrap())?.unwrap(); + + let type_ = dislike.object().as_single_kind_str().unwrap(); - match type_ { - // TODO: handle dislike - d => Err(format_err!("Undo Delete type {} not supported", d).into()), - } ++ Err(format_err!("Undo Delete type {} not supported", type_).into()) +} + +async fn receive_undo_delete_comment( + undo: Undo, + delete: &Delete, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let user = get_user_from_activity(delete, client, pool).await?; + let note = Note::from_any_base(delete.object().to_owned().one().unwrap())?.unwrap(); + + let comment_ap_id = CommentForm::from_apub(¬e, client, pool, &user.actor_id()?) + .await? + .get_ap_id()?; + + let comment = get_or_fetch_and_insert_remote_comment(&comment_ap_id, client, pool).await?; + + let comment_form = CommentForm { + content: comment.content.to_owned(), + parent_id: comment.parent_id, + post_id: comment.post_id, + creator_id: comment.creator_id, + removed: None, + deleted: Some(false), + read: None, + published: None, + updated: Some(naive_now()), + ap_id: comment.ap_id, + local: comment.local, + }; + let comment_id = comment.id; + blocking(pool, move |conn| { + Comment::update(conn, comment_id, &comment_form) + }) + .await??; + + // Refetch the view + let comment_id = comment.id; + let comment_view = + blocking(pool, move |conn| CommentView::read(conn, comment_id, None)).await??; + + // TODO get those recipient actor ids from somewhere + let recipient_ids = vec![]; + let res = CommentResponse { + comment: comment_view, + recipient_ids, ++ form_id: None, + }; + + chat_server.do_send(SendComment { + op: UserOperation::EditComment, + comment: res, + my_id: None, + }); + + announce_if_community_is_local(undo, &user, client, pool).await?; + Ok(HttpResponse::Ok().finish()) +} + +async fn receive_undo_remove_comment( + undo: Undo, + remove: &Remove, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let mod_ = get_user_from_activity(remove, client, pool).await?; + let note = Note::from_any_base(remove.object().to_owned().one().unwrap())?.unwrap(); + + let comment_ap_id = CommentForm::from_apub(¬e, client, pool, &mod_.actor_id()?) + .await? + .get_ap_id()?; + + let comment = get_or_fetch_and_insert_remote_comment(&comment_ap_id, client, pool).await?; + + let comment_form = CommentForm { + content: comment.content.to_owned(), + parent_id: comment.parent_id, + post_id: comment.post_id, + creator_id: comment.creator_id, + removed: Some(false), + deleted: None, + read: None, + published: None, + updated: Some(naive_now()), + ap_id: comment.ap_id, + local: comment.local, + }; + let comment_id = comment.id; + blocking(pool, move |conn| { + Comment::update(conn, comment_id, &comment_form) + }) + .await??; + + // Refetch the view + let comment_id = comment.id; + let comment_view = + blocking(pool, move |conn| CommentView::read(conn, comment_id, None)).await??; + + // TODO get those recipient actor ids from somewhere + let recipient_ids = vec![]; + let res = CommentResponse { + comment: comment_view, + recipient_ids, ++ form_id: None, + }; + + chat_server.do_send(SendComment { + op: UserOperation::EditComment, + comment: res, + my_id: None, + }); + + announce_if_community_is_local(undo, &mod_, client, pool).await?; + Ok(HttpResponse::Ok().finish()) +} + +async fn receive_undo_delete_post( + undo: Undo, + delete: &Delete, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let user = get_user_from_activity(delete, client, pool).await?; + let page = PageExt::from_any_base(delete.object().to_owned().one().unwrap())?.unwrap(); + + let post_ap_id = PostForm::from_apub(&page, client, pool, &user.actor_id()?) + .await? + .get_ap_id()?; + + let post = get_or_fetch_and_insert_remote_post(&post_ap_id, client, pool).await?; + + let post_form = PostForm { + name: post.name.to_owned(), + url: post.url.to_owned(), + body: post.body.to_owned(), + creator_id: post.creator_id.to_owned(), + community_id: post.community_id, + removed: None, + deleted: Some(false), + nsfw: post.nsfw, + locked: None, + stickied: None, + updated: Some(naive_now()), + embed_title: post.embed_title, + embed_description: post.embed_description, + embed_html: post.embed_html, + thumbnail_url: post.thumbnail_url, + ap_id: post.ap_id, + local: post.local, + published: None, + }; + let post_id = post.id; + blocking(pool, move |conn| Post::update(conn, post_id, &post_form)).await??; + + // Refetch the view + let post_id = post.id; + let post_view = blocking(pool, move |conn| PostView::read(conn, post_id, None)).await??; + + let res = PostResponse { post: post_view }; + + chat_server.do_send(SendPost { + op: UserOperation::EditPost, + post: res, + my_id: None, + }); + + announce_if_community_is_local(undo, &user, client, pool).await?; + Ok(HttpResponse::Ok().finish()) +} + +async fn receive_undo_remove_post( + undo: Undo, + remove: &Remove, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let mod_ = get_user_from_activity(remove, client, pool).await?; + let page = PageExt::from_any_base(remove.object().to_owned().one().unwrap())?.unwrap(); + + let post_ap_id = PostForm::from_apub(&page, client, pool, &mod_.actor_id()?) + .await? + .get_ap_id()?; + + let post = get_or_fetch_and_insert_remote_post(&post_ap_id, client, pool).await?; + + let post_form = PostForm { + name: post.name.to_owned(), + url: post.url.to_owned(), + body: post.body.to_owned(), + creator_id: post.creator_id.to_owned(), + community_id: post.community_id, + removed: Some(false), + deleted: None, + nsfw: post.nsfw, + locked: None, + stickied: None, + updated: Some(naive_now()), + embed_title: post.embed_title, + embed_description: post.embed_description, + embed_html: post.embed_html, + thumbnail_url: post.thumbnail_url, + ap_id: post.ap_id, + local: post.local, + published: None, + }; + let post_id = post.id; + blocking(pool, move |conn| Post::update(conn, post_id, &post_form)).await??; + + // Refetch the view + let post_id = post.id; + let post_view = blocking(pool, move |conn| PostView::read(conn, post_id, None)).await??; + + let res = PostResponse { post: post_view }; + + chat_server.do_send(SendPost { + op: UserOperation::EditPost, + post: res, + my_id: None, + }); + + announce_if_community_is_local(undo, &mod_, client, pool).await?; + Ok(HttpResponse::Ok().finish()) +} + +async fn receive_undo_delete_community( + undo: Undo, + delete: &Delete, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let user = get_user_from_activity(delete, client, pool).await?; + let group = GroupExt::from_any_base(delete.object().to_owned().one().unwrap())?.unwrap(); + + let community_actor_id = CommunityForm::from_apub(&group, client, pool, &user.actor_id()?) + .await? + .actor_id; + + let community = blocking(pool, move |conn| { + Community::read_from_actor_id(conn, &community_actor_id) + }) + .await??; + + let community_form = CommunityForm { + name: community.name.to_owned(), + title: community.title.to_owned(), + description: community.description.to_owned(), + category_id: community.category_id, // Note: need to keep this due to foreign key constraint + creator_id: community.creator_id, // Note: need to keep this due to foreign key constraint + removed: None, + published: None, + updated: Some(naive_now()), + deleted: Some(false), + nsfw: community.nsfw, + actor_id: community.actor_id, + local: community.local, + private_key: community.private_key, + public_key: community.public_key, + last_refreshed_at: None, + }; + + let community_id = community.id; + blocking(pool, move |conn| { + Community::update(conn, community_id, &community_form) + }) + .await??; + + let community_id = community.id; + let res = CommunityResponse { + community: blocking(pool, move |conn| { + CommunityView::read(conn, community_id, None) + }) + .await??, + }; + + let community_id = res.community.id; + + chat_server.do_send(SendCommunityRoomMessage { + op: UserOperation::EditCommunity, + response: res, + community_id, + my_id: None, + }); + + announce_if_community_is_local(undo, &user, client, pool).await?; + Ok(HttpResponse::Ok().finish()) +} + +async fn receive_undo_remove_community( + undo: Undo, + remove: &Remove, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let mod_ = get_user_from_activity(remove, client, pool).await?; + let group = GroupExt::from_any_base(remove.object().to_owned().one().unwrap())?.unwrap(); + + let community_actor_id = CommunityForm::from_apub(&group, client, pool, &mod_.actor_id()?) + .await? + .actor_id; + + let community = blocking(pool, move |conn| { + Community::read_from_actor_id(conn, &community_actor_id) + }) + .await??; + + let community_form = CommunityForm { + name: community.name.to_owned(), + title: community.title.to_owned(), + description: community.description.to_owned(), + category_id: community.category_id, // Note: need to keep this due to foreign key constraint + creator_id: community.creator_id, // Note: need to keep this due to foreign key constraint + removed: Some(false), + published: None, + updated: Some(naive_now()), + deleted: None, + nsfw: community.nsfw, + actor_id: community.actor_id, + local: community.local, + private_key: community.private_key, + public_key: community.public_key, + last_refreshed_at: None, + }; + + let community_id = community.id; + blocking(pool, move |conn| { + Community::update(conn, community_id, &community_form) + }) + .await??; + + let community_id = community.id; + let res = CommunityResponse { + community: blocking(pool, move |conn| { + CommunityView::read(conn, community_id, None) + }) + .await??, + }; + + let community_id = res.community.id; + + chat_server.do_send(SendCommunityRoomMessage { + op: UserOperation::EditCommunity, + response: res, + community_id, + my_id: None, + }); + + announce_if_community_is_local(undo, &mod_, client, pool).await?; + Ok(HttpResponse::Ok().finish()) +} + +async fn receive_undo_like_comment( + undo: Undo, + like: &Like, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let user = get_user_from_activity(like, client, pool).await?; + let note = Note::from_any_base(like.object().to_owned().one().unwrap())?.unwrap(); + + let comment = CommentForm::from_apub(¬e, client, pool, &user.actor_id()?).await?; + + let comment_id = get_or_fetch_and_insert_remote_comment(&comment.get_ap_id()?, client, pool) + .await? + .id; + + let like_form = CommentLikeForm { + comment_id, + post_id: comment.post_id, + user_id: user.id, + score: 0, + }; + blocking(pool, move |conn| CommentLike::remove(conn, &like_form)).await??; + + // Refetch the view + let comment_view = + blocking(pool, move |conn| CommentView::read(conn, comment_id, None)).await??; + + // TODO get those recipient actor ids from somewhere + let recipient_ids = vec![]; + let res = CommentResponse { + comment: comment_view, + recipient_ids, ++ form_id: None, + }; + + chat_server.do_send(SendComment { + op: UserOperation::CreateCommentLike, + comment: res, + my_id: None, + }); + + announce_if_community_is_local(undo, &user, client, pool).await?; + Ok(HttpResponse::Ok().finish()) +} + +async fn receive_undo_like_post( + undo: Undo, + like: &Like, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let user = get_user_from_activity(like, client, pool).await?; + let page = PageExt::from_any_base(like.object().to_owned().one().unwrap())?.unwrap(); + + let post = PostForm::from_apub(&page, client, pool, &user.actor_id()?).await?; + + let post_id = get_or_fetch_and_insert_remote_post(&post.get_ap_id()?, client, pool) + .await? + .id; + + let like_form = PostLikeForm { + post_id, + user_id: user.id, + score: 1, + }; + blocking(pool, move |conn| PostLike::remove(conn, &like_form)).await??; + + // Refetch the view + let post_view = blocking(pool, move |conn| PostView::read(conn, post_id, None)).await??; + + let res = PostResponse { post: post_view }; + + chat_server.do_send(SendPost { + op: UserOperation::CreatePostLike, + post: res, + my_id: None, + }); + + announce_if_community_is_local(undo, &user, client, pool).await?; + Ok(HttpResponse::Ok().finish()) +} diff --cc server/src/apub/inbox/activities/update.rs index 96e0efac,00000000..f46c7ff7 mode 100644,000000..100644 --- a/server/src/apub/inbox/activities/update.rs +++ b/server/src/apub/inbox/activities/update.rs @@@ -1,128 -1,0 +1,128 @@@ +use crate::{ + api::{ + comment::{send_local_notifs, CommentResponse}, + post::PostResponse, + }, - apub::inbox::shared_inbox::{get_user_from_activity, receive_unhandled_activity}, + apub::{ + fetcher::{get_or_fetch_and_insert_remote_comment, get_or_fetch_and_insert_remote_post}, ++ inbox::shared_inbox::{ ++ announce_if_community_is_local, ++ get_user_from_activity, ++ receive_unhandled_activity, ++ }, + ActorType, + FromApub, + PageExt, + }, + blocking, + routes::ChatServerParam, + websocket::{ + server::{SendComment, SendPost}, + UserOperation, + }, + DbPool, + LemmyError, +}; - use activitystreams_new::{ - activity::{Update}, - object::Note, - prelude::*, - }; ++use activitystreams_new::{activity::Update, base::AnyBase, object::Note, prelude::*}; +use actix_web::{client::Client, HttpResponse}; +use lemmy_db::{ + comment::{Comment, CommentForm}, + comment_view::CommentView, + post::{Post, PostForm}, + post_view::PostView, + Crud, +}; +use lemmy_utils::scrape_text_for_mentions; - use activitystreams_new::base::AnyBase; - use crate::apub::inbox::shared_inbox::announce_if_community_is_local; + +pub async fn receive_update( + activity: AnyBase, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let update = Update::from_any_base(activity)?.unwrap(); + match update.object().as_single_kind_str() { + Some("Page") => receive_update_post(update, client, pool, chat_server).await, + Some("Note") => receive_update_comment(update, client, pool, chat_server).await, + _ => receive_unhandled_activity(update), + } +} + +async fn receive_update_post( + update: Update, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let user = get_user_from_activity(&update, client, pool).await?; + let page = PageExt::from_any_base(update.object().to_owned().one().unwrap())?.unwrap(); + + let post = PostForm::from_apub(&page, client, pool, &user.actor_id()?).await?; + + let post_id = get_or_fetch_and_insert_remote_post(&post.get_ap_id()?, client, pool) + .await? + .id; + + blocking(pool, move |conn| Post::update(conn, post_id, &post)).await??; + + // Refetch the view + let post_view = blocking(pool, move |conn| PostView::read(conn, post_id, None)).await??; + + let res = PostResponse { post: post_view }; + + chat_server.do_send(SendPost { + op: UserOperation::EditPost, + post: res, + my_id: None, + }); + + announce_if_community_is_local(update, &user, client, pool).await?; + Ok(HttpResponse::Ok().finish()) +} + +async fn receive_update_comment( + update: Update, + client: &Client, + pool: &DbPool, + chat_server: ChatServerParam, +) -> Result { + let note = Note::from_any_base(update.object().to_owned().one().unwrap())?.unwrap(); + let user = get_user_from_activity(&update, client, pool).await?; + + let comment = CommentForm::from_apub(¬e, client, pool, &user.actor_id()?).await?; + + let comment_id = get_or_fetch_and_insert_remote_comment(&comment.get_ap_id()?, client, pool) + .await? + .id; + + let updated_comment = blocking(pool, move |conn| { + Comment::update(conn, comment_id, &comment) + }) + .await??; + + let post_id = updated_comment.post_id; + let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??; + + let mentions = scrape_text_for_mentions(&updated_comment.content); - let recipient_ids = send_local_notifs(mentions, updated_comment, &user, post, pool).await?; ++ let recipient_ids = ++ send_local_notifs(mentions, updated_comment, &user, post, pool, false).await?; + + // Refetch the view + let comment_view = + blocking(pool, move |conn| CommentView::read(conn, comment_id, None)).await??; + + let res = CommentResponse { + comment: comment_view, + recipient_ids, ++ form_id: None, + }; + + chat_server.do_send(SendComment { + op: UserOperation::EditComment, + comment: res, + my_id: None, + }); + + announce_if_community_is_local(update, &user, client, pool).await?; + Ok(HttpResponse::Ok().finish()) +} diff --cc server/src/apub/inbox/mod.rs index d7b7b743,00000000..b9db61fa mode 100644,000000..100644 --- a/server/src/apub/inbox/mod.rs +++ b/server/src/apub/inbox/mod.rs @@@ -1,4 -1,0 +1,4 @@@ +pub mod activities; +pub mod community_inbox; +pub mod shared_inbox; - pub mod user_inbox; ++pub mod user_inbox; diff --cc server/src/apub/inbox/shared_inbox.rs index 2a51d98a,00000000..f406d83a mode 100644,000000..100644 --- a/server/src/apub/inbox/shared_inbox.rs +++ b/server/src/apub/inbox/shared_inbox.rs @@@ -1,136 -1,0 +1,135 @@@ +use crate::{ + apub::{ ++ community::do_announce, + extensions::signatures::verify, + fetcher::{ + get_or_fetch_and_upsert_remote_actor, ++ get_or_fetch_and_upsert_remote_community, + get_or_fetch_and_upsert_remote_user, + }, + inbox::activities::{ + announce::receive_announce, + create::receive_create, + delete::receive_delete, + dislike::receive_dislike, + like::receive_like, + remove::receive_remove, + undo::receive_undo, + update::receive_update, + }, + insert_activity, + }, + routes::{ChatServerParam, DbPoolParam}, + DbPool, + LemmyError, +}; +use activitystreams_new::{ + activity::{ActorAndObject, ActorAndObjectRef}, - base::{AsBase}, ++ base::{AsBase, Extends}, ++ object::AsObject, + prelude::*, +}; +use actix_web::{client::Client, web, HttpRequest, HttpResponse}; - use lemmy_db::{user::User_}; ++use lemmy_db::user::User_; +use log::debug; - use std::fmt::Debug; - use crate::apub::fetcher::get_or_fetch_and_upsert_remote_community; - use activitystreams_new::object::AsObject; - use crate::apub::community::do_announce; - use activitystreams_new::base::Extends; +use serde::Serialize; ++use std::fmt::Debug; + +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "PascalCase")] +pub enum ValidTypes { + Create, + Update, + Like, + Dislike, + Delete, + Undo, + Remove, + Announce, +} + +// TODO: this isnt entirely correct, cause some of these activities are not ActorAndObject, +// but it might still work due to the anybase conversion +pub type AcceptedActivities = ActorAndObject; + +/// Handler for all incoming activities to user inboxes. +pub async fn shared_inbox( + request: HttpRequest, + input: web::Json, + client: web::Data, + pool: DbPoolParam, + chat_server: ChatServerParam, +) -> Result { + let activity = input.into_inner(); + + let json = serde_json::to_string(&activity)?; + debug!("Shared inbox received activity: {}", json); + + let sender = &activity.actor()?.to_owned().single_xsd_any_uri().unwrap(); + + // TODO: pass this actor in instead of using get_user_from_activity() + let actor = get_or_fetch_and_upsert_remote_actor(sender, &client, &pool).await?; + verify(&request, actor.as_ref())?; + + insert_activity(actor.user_id(), activity.clone(), false, &pool).await?; + + let any_base = activity.clone().into_any_base()?; + let kind = activity.kind().unwrap(); + dbg!(kind); + match kind { - ValidTypes::Announce => { - receive_announce(any_base, &client, &pool, chat_server).await - } ++ ValidTypes::Announce => receive_announce(any_base, &client, &pool, chat_server).await, + ValidTypes::Create => receive_create(any_base, &client, &pool, chat_server).await, + ValidTypes::Update => receive_update(any_base, &client, &pool, chat_server).await, + ValidTypes::Like => receive_like(any_base, &client, &pool, chat_server).await, + ValidTypes::Dislike => receive_dislike(any_base, &client, &pool, chat_server).await, + ValidTypes::Remove => receive_remove(any_base, &client, &pool, chat_server).await, + ValidTypes::Delete => receive_delete(any_base, &client, &pool, chat_server).await, + ValidTypes::Undo => receive_undo(any_base, &client, &pool, chat_server).await, + } +} + - pub(in crate::apub::inbox) fn receive_unhandled_activity(activity: A) -> Result ++pub(in crate::apub::inbox) fn receive_unhandled_activity( ++ activity: A, ++) -> Result +where + A: Debug, +{ + debug!("received unhandled activity type: {:?}", activity); + Ok(HttpResponse::NotImplemented().finish()) +} + +pub(in crate::apub::inbox) async fn get_user_from_activity( + activity: &T, + client: &Client, + pool: &DbPool, +) -> Result - where - T: AsBase + ActorAndObjectRef, ++where ++ T: AsBase + ActorAndObjectRef, +{ + let actor = activity.actor()?; + let user_uri = actor.as_single_xsd_any_uri().unwrap(); + get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await +} + +pub(in crate::apub::inbox) async fn announce_if_community_is_local( + activity: T, + user: &User_, + client: &Client, + pool: &DbPool, +) -> Result<(), LemmyError> - where - T: AsObject, - T: Extends, - Kind: Serialize, - >::Error: From + Send + Sync + 'static, ++where ++ T: AsObject, ++ T: Extends, ++ Kind: Serialize, ++ >::Error: From + Send + Sync + 'static, +{ + let cc = activity.cc().unwrap(); + let cc = cc.as_many().unwrap(); + let community_uri = cc.first().unwrap().as_xsd_any_uri().unwrap(); + let community = get_or_fetch_and_upsert_remote_community(&community_uri, client, pool).await?; + + if community.local { + do_announce(activity.into_any_base()?, &community, &user, client, pool).await?; + } + Ok(()) +} diff --cc server/src/routes/federation.rs index 2d12f99f,cd4c4780..93aaac1c --- a/server/src/routes/federation.rs +++ b/server/src/routes/federation.rs @@@ -1,11 -1,11 +1,9 @@@ use crate::apub::{ comment::get_apub_comment, community::*, - inbox::community_inbox::community_inbox, - community_inbox::community_inbox, ++ inbox::{community_inbox::community_inbox, shared_inbox::shared_inbox, user_inbox::user_inbox}, post::get_apub_post, - inbox::shared_inbox::shared_inbox, - shared_inbox::shared_inbox, user::*, - inbox::user_inbox::user_inbox, - user_inbox::user_inbox, APUB_JSON_CONTENT_TYPE, }; use actix_web::*; diff --cc ui/src/components/create-post.tsx index 4554326d,9d6cbb89..eb86d8f8 --- a/ui/src/components/create-post.tsx +++ b/ui/src/components/create-post.tsx @@@ -100,7 -110,7 +110,7 @@@ export class CreatePost extends Compone return lastLocation.split('/c/')[1]; } } -- return undefined; ++ return; } handlePostCreate(id: number) {