From: Felix Ableitner Date: Mon, 18 Oct 2021 21:36:44 +0000 (+0200) Subject: Major refactor, adding newtypes for apub crate X-Git-Url: http://these/git/%22/display/44b525e5-4101b003e005e70a-d472d963/static/%7BelementUrl%7D?a=commitdiff_plain;h=1aa0e1997bd8e3c9a4849c485bdddb804f19692e;p=lemmy.git Major refactor, adding newtypes for apub crate - this allows moving FromApub/ToApub traits into apub lib --- diff --git a/Cargo.lock b/Cargo.lock index 06361214..120f6827 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1839,7 +1839,6 @@ dependencies = [ "diesel-derive-newtype", "diesel_migrations", "lazy_static", - "lemmy_apub_lib", "lemmy_utils", "log", "regex", diff --git a/crates/api/src/comment.rs b/crates/api/src/comment.rs index d626c2ea..62b01b33 100644 --- a/crates/api/src/comment.rs +++ b/crates/api/src/comment.rs @@ -191,7 +191,7 @@ impl Perform for CreateCommentLike { // Only add the like if the score isnt 0 let comment = orig_comment.comment; - let object = PostOrComment::Comment(comment); + let object = PostOrComment::Comment(comment.into()); let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1); if do_add { let like_form2 = like_form.clone(); @@ -202,7 +202,7 @@ impl Perform for CreateCommentLike { Vote::send( &object, - &local_user_view.person, + &local_user_view.person.clone().into(), orig_comment.community.id, like_form.score.try_into()?, context, @@ -212,7 +212,7 @@ impl Perform for CreateCommentLike { // API doesn't distinguish between Undo/Like and Undo/Dislike UndoVote::send( &object, - &local_user_view.person, + &local_user_view.person.clone().into(), orig_comment.community.id, VoteType::Like, context, diff --git a/crates/api/src/comment_report.rs b/crates/api/src/comment_report.rs index 86f636a6..a7299b7a 100644 --- a/crates/api/src/comment_report.rs +++ b/crates/api/src/comment_report.rs @@ -79,8 +79,8 @@ impl Perform for CreateCommentReport { Report::send( ObjectId::new(comment_view.comment.ap_id), - &local_user_view.person, - comment_view.community.id, + &local_user_view.person.into(), + ObjectId::new(comment_view.community.actor_id), reason.to_string(), context, ) diff --git a/crates/api/src/community.rs b/crates/api/src/community.rs index 5d7317b2..ac39751c 100644 --- a/crates/api/src/community.rs +++ b/crates/api/src/community.rs @@ -9,14 +9,17 @@ use lemmy_api_common::{ get_local_user_view_from_jwt, is_mod_or_admin, }; -use lemmy_apub::activities::{ - community::{ - add_mod::AddMod, - block_user::BlockUserFromCommunity, - remove_mod::RemoveMod, - undo_block_user::UndoBlockUserFromCommunity, +use lemmy_apub::{ + activities::{ + community::{ + add_mod::AddMod, + block_user::BlockUserFromCommunity, + remove_mod::RemoveMod, + undo_block_user::UndoBlockUserFromCommunity, + }, + following::{follow::FollowCommunity as FollowCommunityApub, undo::UndoFollowCommunity}, }, - following::{follow::FollowCommunity as FollowCommunityApub, undo::UndoFollowCommunity}, + objects::{community::ApubCommunity, person::ApubPerson}, }; use lemmy_db_schema::{ source::{ @@ -68,10 +71,11 @@ impl Perform for FollowCommunity { get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?; let community_id = data.community_id; - let community = blocking(context.pool(), move |conn| { + let community: ApubCommunity = blocking(context.pool(), move |conn| { Community::read(conn, community_id) }) - .await??; + .await?? + .into(); let community_follower_form = CommunityFollowerForm { community_id: data.community_id, person_id: local_user_view.person.id, @@ -97,9 +101,11 @@ impl Perform for FollowCommunity { } else if data.follow { // Dont actually add to the community followers here, because you need // to wait for the accept - FollowCommunityApub::send(&local_user_view.person, &community, context).await?; + FollowCommunityApub::send(&local_user_view.person.clone().into(), &community, context) + .await?; } else { - UndoFollowCommunity::send(&local_user_view.person, &community, context).await?; + UndoFollowCommunity::send(&local_user_view.person.clone().into(), &community, context) + .await?; let unfollow = move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form); blocking(context.pool(), unfollow) .await? @@ -165,7 +171,7 @@ impl Perform for BlockCommunity { Community::read(conn, community_id) }) .await??; - UndoFollowCommunity::send(&local_user_view.person, &community, context).await?; + UndoFollowCommunity::send(&local_user_view.person.into(), &community.into(), context).await?; } else { let unblock = move |conn: &'_ _| CommunityBlock::unblock(conn, &community_block_form); blocking(context.pool(), unblock) @@ -209,14 +215,16 @@ impl Perform for BanFromCommunity { person_id: data.person_id, }; - let community = blocking(context.pool(), move |conn: &'_ _| { + let community: ApubCommunity = blocking(context.pool(), move |conn: &'_ _| { Community::read(conn, community_id) }) - .await??; - let banned_person = blocking(context.pool(), move |conn: &'_ _| { + .await?? + .into(); + let banned_person: ApubPerson = blocking(context.pool(), move |conn: &'_ _| { Person::read(conn, banned_person_id) }) - .await??; + .await?? + .into(); if data.ban { let ban = move |conn: &'_ _| CommunityPersonBan::ban(conn, &community_user_ban_form); @@ -236,8 +244,13 @@ impl Perform for BanFromCommunity { .await? .ok(); - BlockUserFromCommunity::send(&community, &banned_person, &local_user_view.person, context) - .await?; + BlockUserFromCommunity::send( + &community, + &banned_person, + &local_user_view.person.clone().into(), + context, + ) + .await?; } else { let unban = move |conn: &'_ _| CommunityPersonBan::unban(conn, &community_user_ban_form); blocking(context.pool(), unban) @@ -246,7 +259,7 @@ impl Perform for BanFromCommunity { UndoBlockUserFromCommunity::send( &community, &banned_person, - &local_user_view.person, + &local_user_view.person.clone().into(), context, ) .await?; @@ -368,18 +381,32 @@ impl Perform for AddModToCommunity { // Send to federated instances let updated_mod_id = data.person_id; - let updated_mod = blocking(context.pool(), move |conn| { + let updated_mod: ApubPerson = blocking(context.pool(), move |conn| { Person::read(conn, updated_mod_id) }) - .await??; - let community = blocking(context.pool(), move |conn| { + .await?? + .into(); + let community: ApubCommunity = blocking(context.pool(), move |conn| { Community::read(conn, community_id) }) - .await??; + .await?? + .into(); if data.added { - AddMod::send(&community, &updated_mod, &local_user_view.person, context).await?; + AddMod::send( + &community, + &updated_mod, + &local_user_view.person.into(), + context, + ) + .await?; } else { - RemoveMod::send(&community, &updated_mod, &local_user_view.person, context).await?; + RemoveMod::send( + &community, + &updated_mod, + &local_user_view.person.into(), + context, + ) + .await?; } // Note: in case a remote mod is added, this returns the old moderators list, it will only get diff --git a/crates/api/src/post.rs b/crates/api/src/post.rs index 60cdafb9..488c8f59 100644 --- a/crates/api/src/post.rs +++ b/crates/api/src/post.rs @@ -21,6 +21,7 @@ use lemmy_apub::{ CreateOrUpdateType, }, fetcher::post_or_comment::PostOrComment, + objects::post::ApubPost, }; use lemmy_db_schema::{ source::{moderator::*, post::*}, @@ -49,7 +50,9 @@ impl Perform for CreatePostLike { // Check for a community ban let post_id = data.post_id; - let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; + let post: ApubPost = blocking(context.pool(), move |conn| Post::read(conn, post_id)) + .await?? + .into(); check_community_ban(local_user_view.person.id, post.community_id, context.pool()).await?; check_community_deleted_or_removed(post.community_id, context.pool()).await?; @@ -83,7 +86,7 @@ impl Perform for CreatePostLike { Vote::send( &object, - &local_user_view.person, + &local_user_view.person.clone().into(), community_id, like_form.score.try_into()?, context, @@ -93,7 +96,7 @@ impl Perform for CreatePostLike { // API doesn't distinguish between Undo/Like and Undo/Dislike UndoVote::send( &object, - &local_user_view.person, + &local_user_view.person.clone().into(), community_id, VoteType::Like, context, @@ -150,10 +153,11 @@ impl Perform for LockPost { // Update the post let post_id = data.post_id; let locked = data.locked; - let updated_post = blocking(context.pool(), move |conn| { + let updated_post: ApubPost = blocking(context.pool(), move |conn| { Post::update_locked(conn, post_id, locked) }) - .await??; + .await?? + .into(); // Mod tables let form = ModLockPostForm { @@ -166,7 +170,7 @@ impl Perform for LockPost { // apub updates CreateOrUpdatePost::send( &updated_post, - &local_user_view.person, + &local_user_view.person.clone().into(), CreateOrUpdateType::Update, context, ) @@ -218,10 +222,11 @@ impl Perform for StickyPost { // Update the post let post_id = data.post_id; let stickied = data.stickied; - let updated_post = blocking(context.pool(), move |conn| { + let updated_post: ApubPost = blocking(context.pool(), move |conn| { Post::update_stickied(conn, post_id, stickied) }) - .await??; + .await?? + .into(); // Mod tables let form = ModStickyPostForm { @@ -238,7 +243,7 @@ impl Perform for StickyPost { // TODO stickied should pry work like locked for ease of use CreateOrUpdatePost::send( &updated_post, - &local_user_view.person, + &local_user_view.person.clone().into(), CreateOrUpdateType::Update, context, ) diff --git a/crates/api/src/post_report.rs b/crates/api/src/post_report.rs index 35a734d4..3e610bff 100644 --- a/crates/api/src/post_report.rs +++ b/crates/api/src/post_report.rs @@ -88,8 +88,8 @@ impl Perform for CreatePostReport { Report::send( ObjectId::new(post_view.post.ap_id), - &local_user_view.person, - post_view.community.id, + &local_user_view.person.into(), + ObjectId::new(post_view.community.actor_id), reason.to_string(), context, ) diff --git a/crates/api_common/src/lib.rs b/crates/api_common/src/lib.rs index f28c34b0..bf6246a0 100644 --- a/crates/api_common/src/lib.rs +++ b/crates/api_common/src/lib.rs @@ -6,15 +6,11 @@ pub mod site; pub mod websocket; use crate::site::FederatedInstances; -use diesel::PgConnection; use lemmy_db_schema::{ newtypes::{CommunityId, LocalUserId, PersonId, PostId}, source::{ - comment::Comment, community::Community, - person::Person, person_block::PersonBlock, - person_mention::{PersonMention, PersonMentionForm}, post::{Post, PostRead, PostReadForm}, secret::Secret, site::Site, @@ -27,15 +23,7 @@ use lemmy_db_views_actor::{ community_person_ban_view::CommunityPersonBanView, community_view::CommunityView, }; -use lemmy_utils::{ - claims::Claims, - email::send_email, - settings::structs::{FederationConfig, Settings}, - utils::MentionData, - ApiError, - LemmyError, -}; -use log::error; +use lemmy_utils::{claims::Claims, settings::structs::FederationConfig, ApiError, LemmyError}; use url::Url; pub async fn blocking(pool: &DbPool, f: F) -> Result @@ -54,160 +42,6 @@ where res } -pub async fn send_local_notifs( - mentions: Vec, - comment: Comment, - person: Person, - post: Post, - pool: &DbPool, - do_send_email: bool, - settings: &Settings, -) -> Result, LemmyError> { - let settings = settings.to_owned(); - let ids = blocking(pool, move |conn| { - do_send_local_notifs( - conn, - &mentions, - &comment, - &person, - &post, - do_send_email, - &settings, - ) - }) - .await?; - - Ok(ids) -} - -fn do_send_local_notifs( - conn: &PgConnection, - mentions: &[MentionData], - comment: &Comment, - person: &Person, - post: &Post, - do_send_email: bool, - settings: &Settings, -) -> Vec { - let mut recipient_ids = Vec::new(); - - // Send the local mentions - for mention in mentions - .iter() - .filter(|m| m.is_local(&settings.hostname) && m.name.ne(&person.name)) - .collect::>() - { - if let Ok(mention_user_view) = LocalUserView::read_from_name(conn, &mention.name) { - // TODO - // At some point, make it so you can't tag the parent creator either - // This can cause two notifications, one for reply and the other for mention - recipient_ids.push(mention_user_view.local_user.id); - - let user_mention_form = PersonMentionForm { - recipient_id: mention_user_view.person.id, - comment_id: comment.id, - read: None, - }; - - // Allow this to fail softly, since comment edits might re-update or replace it - // Let the uniqueness handle this fail - PersonMention::create(conn, &user_mention_form).ok(); - - // Send an email to those local users that have notifications on - if do_send_email { - send_email_to_user( - &mention_user_view, - "Mentioned by", - "Person Mention", - &comment.content, - settings, - ) - } - } - } - - // Send notifs to the parent commenter / poster - match comment.parent_id { - Some(parent_id) => { - if let Ok(parent_comment) = Comment::read(conn, parent_id) { - // Don't send a notif to yourself - if parent_comment.creator_id != person.id { - // Get the parent commenter local_user - if let Ok(parent_user_view) = LocalUserView::read_person(conn, parent_comment.creator_id) - { - recipient_ids.push(parent_user_view.local_user.id); - - if do_send_email { - send_email_to_user( - &parent_user_view, - "Reply from", - "Comment Reply", - &comment.content, - settings, - ) - } - } - } - } - } - // Its a post - None => { - if post.creator_id != person.id { - if let Ok(parent_user_view) = LocalUserView::read_person(conn, post.creator_id) { - recipient_ids.push(parent_user_view.local_user.id); - - if do_send_email { - send_email_to_user( - &parent_user_view, - "Reply from", - "Post Reply", - &comment.content, - settings, - ) - } - } - } - } - }; - recipient_ids -} - -pub fn send_email_to_user( - local_user_view: &LocalUserView, - subject_text: &str, - body_text: &str, - comment_content: &str, - settings: &Settings, -) { - if local_user_view.person.banned || !local_user_view.local_user.send_notifications_to_email { - return; - } - - if let Some(user_email) = &local_user_view.local_user.email { - let subject = &format!( - "{} - {} {}", - subject_text, settings.hostname, local_user_view.person.name, - ); - let html = &format!( - "

{}


{} - {}

inbox", - body_text, - local_user_view.person.name, - comment_content, - settings.get_protocol_and_hostname() - ); - match send_email( - subject, - user_email, - &local_user_view.person.name, - html, - settings, - ) { - Ok(_o) => _o, - Err(e) => error!("{}", e), - }; - } -} - pub async fn is_mod_or_admin( pool: &DbPool, person_id: PersonId, diff --git a/crates/api_crud/src/comment/create.rs b/crates/api_crud/src/comment/create.rs index ff2442b1..690a5082 100644 --- a/crates/api_crud/src/comment/create.rs +++ b/crates/api_crud/src/comment/create.rs @@ -9,7 +9,6 @@ use lemmy_api_common::{ comment::*, get_local_user_view_from_jwt, get_post, - send_local_notifs, }; use lemmy_apub::{ activities::{ @@ -35,7 +34,11 @@ use lemmy_utils::{ ConnectionId, LemmyError, }; -use lemmy_websocket::{send::send_comment_ws_message, LemmyContext, UserOperationCrud}; +use lemmy_websocket::{ + send::{send_comment_ws_message, send_local_notifs}, + LemmyContext, + UserOperationCrud, +}; #[async_trait::async_trait(?Send)] impl PerformCrud for CreateComment { @@ -117,8 +120,8 @@ impl PerformCrud for CreateComment { .map_err(|e| ApiError::err("couldnt_create_comment", e))?; CreateOrUpdateComment::send( - &updated_comment, - &local_user_view.person, + &updated_comment.clone().into(), + &local_user_view.person.clone().into(), CreateOrUpdateType::Create, context, ) @@ -129,12 +132,11 @@ impl PerformCrud for CreateComment { let mentions = scrape_text_for_mentions(&comment_form.content); let recipient_ids = send_local_notifs( mentions, - updated_comment.clone(), - local_user_view.person.clone(), - post, - context.pool(), + &updated_comment, + &local_user_view.person, + &post, true, - &context.settings(), + context, ) .await?; @@ -151,10 +153,10 @@ impl PerformCrud for CreateComment { .await? .map_err(|e| ApiError::err("couldnt_like_comment", e))?; - let object = PostOrComment::Comment(updated_comment); + let object = PostOrComment::Comment(updated_comment.into()); Vote::send( &object, - &local_user_view.person, + &local_user_view.person.clone().into(), community_id, VoteType::Like, context, diff --git a/crates/api_crud/src/comment/delete.rs b/crates/api_crud/src/comment/delete.rs index 07c920f7..19851f0c 100644 --- a/crates/api_crud/src/comment/delete.rs +++ b/crates/api_crud/src/comment/delete.rs @@ -6,7 +6,6 @@ use lemmy_api_common::{ comment::*, get_local_user_view_from_jwt, is_mod_or_admin, - send_local_notifs, }; use lemmy_apub::activities::deletion::{send_apub_delete, send_apub_remove}; use lemmy_db_schema::{ @@ -20,7 +19,11 @@ use lemmy_db_schema::{ }; use lemmy_db_views::comment_view::CommentView; use lemmy_utils::{ApiError, ConnectionId, LemmyError}; -use lemmy_websocket::{send::send_comment_ws_message, LemmyContext, UserOperationCrud}; +use lemmy_websocket::{ + send::{send_comment_ws_message, send_local_notifs}, + LemmyContext, + UserOperationCrud, +}; #[async_trait::async_trait(?Send)] impl PerformCrud for DeleteComment { @@ -67,8 +70,8 @@ impl PerformCrud for DeleteComment { }) .await??; send_apub_delete( - &local_user_view.person, - &community, + &local_user_view.person.clone().into(), + &community.clone().into(), updated_comment.ap_id.clone().into(), deleted, context, @@ -79,12 +82,11 @@ impl PerformCrud for DeleteComment { let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; let recipient_ids = send_local_notifs( vec![], - updated_comment, - local_user_view.person.clone(), - post, - context.pool(), + &updated_comment, + &local_user_view.person, + &post, false, - &context.settings(), + context, ) .await?; @@ -161,8 +163,8 @@ impl PerformCrud for RemoveComment { }) .await??; send_apub_remove( - &local_user_view.person, - &community, + &local_user_view.person.clone().into(), + &community.into(), updated_comment.ap_id.clone().into(), data.reason.clone().unwrap_or_else(|| "".to_string()), removed, @@ -174,12 +176,11 @@ impl PerformCrud for RemoveComment { let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; let recipient_ids = send_local_notifs( vec![], - updated_comment, - local_user_view.person.clone(), - post, - context.pool(), + &updated_comment, + &local_user_view.person.clone(), + &post, false, - &context.settings(), + context, ) .await?; diff --git a/crates/api_crud/src/comment/update.rs b/crates/api_crud/src/comment/update.rs index 7d45b940..9a164fc3 100644 --- a/crates/api_crud/src/comment/update.rs +++ b/crates/api_crud/src/comment/update.rs @@ -7,7 +7,6 @@ use lemmy_api_common::{ check_post_deleted_or_removed, comment::*, get_local_user_view_from_jwt, - send_local_notifs, }; use lemmy_apub::activities::{ comment::create_or_update::CreateOrUpdateComment, @@ -21,7 +20,11 @@ use lemmy_utils::{ ConnectionId, LemmyError, }; -use lemmy_websocket::{send::send_comment_ws_message, LemmyContext, UserOperationCrud}; +use lemmy_websocket::{ + send::{send_comment_ws_message, send_local_notifs}, + LemmyContext, + UserOperationCrud, +}; #[async_trait::async_trait(?Send)] impl PerformCrud for EditComment { @@ -69,8 +72,8 @@ impl PerformCrud for EditComment { // Send the apub update CreateOrUpdateComment::send( - &updated_comment, - &local_user_view.person, + &updated_comment.clone().into(), + &local_user_view.person.clone().into(), CreateOrUpdateType::Update, context, ) @@ -81,12 +84,11 @@ impl PerformCrud for EditComment { let mentions = scrape_text_for_mentions(&updated_comment_content); let recipient_ids = send_local_notifs( mentions, - updated_comment, - local_user_view.person.clone(), - orig_comment.post, - context.pool(), + &updated_comment, + &local_user_view.person, + &orig_comment.post, false, - &context.settings(), + context, ) .await?; diff --git a/crates/api_crud/src/community/create.rs b/crates/api_crud/src/community/create.rs index cc0e62db..8b7daeb7 100644 --- a/crates/api_crud/src/community/create.rs +++ b/crates/api_crud/src/community/create.rs @@ -12,6 +12,7 @@ use lemmy_apub::{ generate_followers_url, generate_inbox_url, generate_shared_inbox_url, + objects::community::ApubCommunity, EndpointType, }; use lemmy_db_schema::{ @@ -71,7 +72,7 @@ impl PerformCrud for CreateCommunity { &data.name, &context.settings().get_protocol_and_hostname(), )?; - let community_actor_id_wrapped = ObjectId::::new(community_actor_id.clone()); + let community_actor_id_wrapped = ObjectId::::new(community_actor_id.clone()); let community_dupe = community_actor_id_wrapped.dereference_local(context).await; if community_dupe.is_ok() { return Err(ApiError::err_plain("community_already_exists").into()); diff --git a/crates/api_crud/src/community/delete.rs b/crates/api_crud/src/community/delete.rs index 5f9fbb44..bf59e786 100644 --- a/crates/api_crud/src/community/delete.rs +++ b/crates/api_crud/src/community/delete.rs @@ -49,8 +49,8 @@ impl PerformCrud for DeleteCommunity { // Send apub messages send_apub_delete( - &local_user_view.person, - &updated_community, + &local_user_view.person.clone().into(), + &updated_community.clone().into(), updated_community.actor_id.clone().into(), deleted, context, @@ -109,8 +109,8 @@ impl PerformCrud for RemoveCommunity { // Apub messages send_apub_remove( - &local_user_view.person, - &updated_community, + &local_user_view.person.clone().into(), + &updated_community.clone().into(), updated_community.actor_id.clone().into(), data.reason.clone().unwrap_or_else(|| "".to_string()), removed, diff --git a/crates/api_crud/src/community/read.rs b/crates/api_crud/src/community/read.rs index 3b9405af..d18ef99f 100644 --- a/crates/api_crud/src/community/read.rs +++ b/crates/api_crud/src/community/read.rs @@ -1,10 +1,14 @@ use crate::PerformCrud; use actix_web::web::Data; use lemmy_api_common::{blocking, community::*, get_local_user_view_from_jwt_opt}; -use lemmy_apub::{build_actor_id_from_shortname, fetcher::object_id::ObjectId, EndpointType}; +use lemmy_apub::{ + build_actor_id_from_shortname, + fetcher::object_id::ObjectId, + objects::community::ApubCommunity, + EndpointType, +}; use lemmy_db_schema::{ from_opt_str_to_opt_enum, - source::community::Community, traits::DeleteableOrRemoveable, ListingType, SortType, @@ -37,7 +41,7 @@ impl PerformCrud for GetCommunity { let community_actor_id = build_actor_id_from_shortname(EndpointType::Community, &name, &context.settings())?; - ObjectId::::new(community_actor_id) + ObjectId::::new(community_actor_id) .dereference(context, &mut 0) .await .map_err(|e| ApiError::err("couldnt_find_community", e))? diff --git a/crates/api_crud/src/community/update.rs b/crates/api_crud/src/community/update.rs index f269807b..97722a98 100644 --- a/crates/api_crud/src/community/update.rs +++ b/crates/api_crud/src/community/update.rs @@ -71,7 +71,12 @@ impl PerformCrud for EditCommunity { .await? .map_err(|e| ApiError::err("couldnt_update_community", e))?; - UpdateCommunity::send(&updated_community, &local_user_view.person, context).await?; + UpdateCommunity::send( + &updated_community.into(), + &local_user_view.person.into(), + context, + ) + .await?; let op = UserOperationCrud::EditCommunity; send_community_ws_message(data.community_id, op, websocket_id, None, context).await diff --git a/crates/api_crud/src/post/create.rs b/crates/api_crud/src/post/create.rs index e75999e5..3e5fb41e 100644 --- a/crates/api_crud/src/post/create.rs +++ b/crates/api_crud/src/post/create.rs @@ -109,8 +109,8 @@ impl PerformCrud for CreatePost { .map_err(|e| ApiError::err("couldnt_create_post", e))?; CreateOrUpdatePost::send( - &updated_post, - &local_user_view.person, + &updated_post.clone().into(), + &local_user_view.person.clone().into(), CreateOrUpdateType::Create, context, ) @@ -146,10 +146,10 @@ impl PerformCrud for CreatePost { } } - let object = PostOrComment::Post(Box::new(updated_post)); + let object = PostOrComment::Post(Box::new(updated_post.into())); Vote::send( &object, - &local_user_view.person, + &local_user_view.person.clone().into(), inserted_post.community_id, VoteType::Like, context, diff --git a/crates/api_crud/src/post/delete.rs b/crates/api_crud/src/post/delete.rs index 1a9044c3..a701e835 100644 --- a/crates/api_crud/src/post/delete.rs +++ b/crates/api_crud/src/post/delete.rs @@ -63,8 +63,8 @@ impl PerformCrud for DeletePost { }) .await??; send_apub_delete( - &local_user_view.person, - &community, + &local_user_view.person.clone().into(), + &community.into(), updated_post.ap_id.into(), deleted, context, @@ -139,8 +139,8 @@ impl PerformCrud for RemovePost { }) .await??; send_apub_remove( - &local_user_view.person, - &community, + &local_user_view.person.clone().into(), + &community.into(), updated_post.ap_id.into(), data.reason.clone().unwrap_or_else(|| "".to_string()), removed, diff --git a/crates/api_crud/src/post/update.rs b/crates/api_crud/src/post/update.rs index 8c346b88..96e4400a 100644 --- a/crates/api_crud/src/post/update.rs +++ b/crates/api_crud/src/post/update.rs @@ -104,8 +104,8 @@ impl PerformCrud for EditPost { // Send apub update CreateOrUpdatePost::send( - &updated_post, - &local_user_view.person, + &updated_post.into(), + &local_user_view.person.clone().into(), CreateOrUpdateType::Update, context, ) diff --git a/crates/api_crud/src/private_message/create.rs b/crates/api_crud/src/private_message/create.rs index 9e335125..d6560be2 100644 --- a/crates/api_crud/src/private_message/create.rs +++ b/crates/api_crud/src/private_message/create.rs @@ -5,7 +5,6 @@ use lemmy_api_common::{ check_person_block, get_local_user_view_from_jwt, person::{CreatePrivateMessage, PrivateMessageResponse}, - send_email_to_user, }; use lemmy_apub::{ activities::{ @@ -21,7 +20,11 @@ use lemmy_db_schema::{ }; use lemmy_db_views::local_user_view::LocalUserView; use lemmy_utils::{utils::remove_slurs, ApiError, ConnectionId, LemmyError}; -use lemmy_websocket::{send::send_pm_ws_message, LemmyContext, UserOperationCrud}; +use lemmy_websocket::{ + send::{send_email_to_user, send_pm_ws_message}, + LemmyContext, + UserOperationCrud, +}; #[async_trait::async_trait(?Send)] impl PerformCrud for CreatePrivateMessage { @@ -80,8 +83,8 @@ impl PerformCrud for CreatePrivateMessage { .map_err(|e| ApiError::err("couldnt_create_private_message", e))?; CreateOrUpdatePrivateMessage::send( - &updated_private_message, - &local_user_view.person, + &updated_private_message.into(), + &local_user_view.person.into(), CreateOrUpdateType::Create, context, ) diff --git a/crates/api_crud/src/private_message/delete.rs b/crates/api_crud/src/private_message/delete.rs index 0c61a09b..f369f82b 100644 --- a/crates/api_crud/src/private_message/delete.rs +++ b/crates/api_crud/src/private_message/delete.rs @@ -51,14 +51,20 @@ impl PerformCrud for DeletePrivateMessage { // Send the apub update if data.deleted { DeletePrivateMessageApub::send( - &local_user_view.person, - &updated_private_message.blank_out_deleted_or_removed_info(), + &local_user_view.person.into(), + &updated_private_message + .blank_out_deleted_or_removed_info() + .into(), context, ) .await?; } else { - UndoDeletePrivateMessage::send(&local_user_view.person, &updated_private_message, context) - .await?; + UndoDeletePrivateMessage::send( + &local_user_view.person.into(), + &updated_private_message.into(), + context, + ) + .await?; } let op = UserOperationCrud::DeletePrivateMessage; diff --git a/crates/api_crud/src/private_message/update.rs b/crates/api_crud/src/private_message/update.rs index 026a047e..d72e3b13 100644 --- a/crates/api_crud/src/private_message/update.rs +++ b/crates/api_crud/src/private_message/update.rs @@ -47,8 +47,8 @@ impl PerformCrud for EditPrivateMessage { // Send the apub update CreateOrUpdatePrivateMessage::send( - &updated_private_message, - &local_user_view.person, + &updated_private_message.into(), + &local_user_view.person.into(), CreateOrUpdateType::Update, context, ) diff --git a/crates/api_crud/src/user/read.rs b/crates/api_crud/src/user/read.rs index 4eb6771e..a5b69ba4 100644 --- a/crates/api_crud/src/user/read.rs +++ b/crates/api_crud/src/user/read.rs @@ -1,8 +1,13 @@ use crate::PerformCrud; use actix_web::web::Data; use lemmy_api_common::{blocking, get_local_user_view_from_jwt_opt, person::*}; -use lemmy_apub::{build_actor_id_from_shortname, fetcher::object_id::ObjectId, EndpointType}; -use lemmy_db_schema::{from_opt_str_to_opt_enum, source::person::Person, SortType}; +use lemmy_apub::{ + build_actor_id_from_shortname, + fetcher::object_id::ObjectId, + objects::person::ApubPerson, + EndpointType, +}; +use lemmy_db_schema::{from_opt_str_to_opt_enum, SortType}; use lemmy_db_views::{comment_view::CommentQueryBuilder, post_view::PostQueryBuilder}; use lemmy_db_views_actor::{ community_moderator_view::CommunityModeratorView, @@ -44,7 +49,7 @@ impl PerformCrud for GetPersonDetails { let actor_id = build_actor_id_from_shortname(EndpointType::Person, &name, &context.settings())?; - let person = ObjectId::::new(actor_id) + let person = ObjectId::::new(actor_id) .dereference(context, &mut 0) .await; person diff --git a/crates/apub/src/activities/comment/create_or_update.rs b/crates/apub/src/activities/comment/create_or_update.rs index c9b11183..c4591c7c 100644 --- a/crates/apub/src/activities/comment/create_or_update.rs +++ b/crates/apub/src/activities/comment/create_or_update.rs @@ -11,18 +11,22 @@ use crate::{ }, context::lemmy_context, fetcher::object_id::ObjectId, - objects::{comment::Note, FromApub, ToApub}, + objects::{ + comment::{ApubComment, Note}, + community::ApubCommunity, + person::ApubPerson, + }, }; use activitystreams::{base::AnyBase, link::Mention, primitives::OneOrMany, unparsed::Unparsed}; use lemmy_api_common::{blocking, check_post_deleted_or_removed}; use lemmy_apub_lib::{ data::Data, - traits::{ActivityFields, ActivityHandler, ActorType}, + traits::{ActivityFields, ActivityHandler, ActorType, FromApub, ToApub}, values::PublicUrl, verify::verify_domains_match, }; use lemmy_db_schema::{ - source::{comment::Comment, community::Community, person::Person, post::Post}, + source::{community::Community, post::Post}, traits::Crud, }; use lemmy_utils::LemmyError; @@ -33,7 +37,7 @@ use url::Url; #[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct CreateOrUpdateComment { - actor: ObjectId, + actor: ObjectId, to: [PublicUrl; 1], object: Note, cc: Vec, @@ -49,8 +53,8 @@ pub struct CreateOrUpdateComment { impl CreateOrUpdateComment { pub async fn send( - comment: &Comment, - actor: &Person, + comment: &ApubComment, + actor: &ApubPerson, kind: CreateOrUpdateType, context: &LemmyContext, ) -> Result<(), LemmyError> { @@ -58,10 +62,11 @@ impl CreateOrUpdateComment { let post_id = comment.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| { + let community: ApubCommunity = blocking(context.pool(), move |conn| { Community::read(conn, community_id) }) - .await??; + .await?? + .into(); let id = generate_activity_id( kind.clone(), @@ -117,7 +122,7 @@ impl ActivityHandler for CreateOrUpdateComment { request_counter: &mut i32, ) -> Result<(), LemmyError> { let comment = - Comment::from_apub(&self.object, context, self.actor.inner(), request_counter).await?; + ApubComment::from_apub(&self.object, context, self.actor.inner(), request_counter).await?; let recipients = get_notif_recipients(&self.actor, &comment, context, request_counter).await?; let notif_type = match self.kind { CreateOrUpdateType::Create => UserOperationCrud::CreateComment, diff --git a/crates/apub/src/activities/comment/mod.rs b/crates/apub/src/activities/comment/mod.rs index 1ac320cb..9eb2bb44 100644 --- a/crates/apub/src/activities/comment/mod.rs +++ b/crates/apub/src/activities/comment/mod.rs @@ -1,15 +1,18 @@ -use crate::fetcher::object_id::ObjectId; +use crate::{ + fetcher::object_id::ObjectId, + objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson}, +}; use activitystreams::{ base::BaseExt, link::{LinkExt, Mention}, }; use anyhow::anyhow; use itertools::Itertools; -use lemmy_api_common::{blocking, send_local_notifs}; +use lemmy_api_common::blocking; use lemmy_apub_lib::{traits::ActorType, webfinger::WebfingerResponse}; use lemmy_db_schema::{ newtypes::LocalUserId, - source::{comment::Comment, community::Community, person::Person, post::Post}, + source::{comment::Comment, person::Person, post::Post}, traits::Crud, DbPool, }; @@ -18,14 +21,14 @@ use lemmy_utils::{ utils::{scrape_text_for_mentions, MentionData}, LemmyError, }; -use lemmy_websocket::LemmyContext; +use lemmy_websocket::{send::send_local_notifs, LemmyContext}; use log::debug; use url::Url; pub mod create_or_update; async fn get_notif_recipients( - actor: &ObjectId, + actor: &ObjectId, comment: &Comment, context: &LemmyContext, request_counter: &mut i32, @@ -40,16 +43,7 @@ async fn get_notif_recipients( // anyway. // TODO: for compatibility with other projects, it would be much better to read this from cc or tags let mentions = scrape_text_for_mentions(&comment.content); - send_local_notifs( - mentions, - comment.clone(), - actor, - post, - context.pool(), - true, - &context.settings(), - ) - .await + send_local_notifs(mentions, comment, &*actor, &post, true, context).await } pub struct MentionsAndAddresses { @@ -62,12 +56,12 @@ pub struct MentionsAndAddresses { /// and mention tags, so they know where to be sent to. /// Addresses are the persons / addresses that go in the cc field. pub async fn collect_non_local_mentions( - comment: &Comment, - community: &Community, + comment: &ApubComment, + community: &ApubCommunity, context: &LemmyContext, ) -> Result { let parent_creator = get_comment_parent_creator(context.pool(), comment).await?; - let mut addressed_ccs = vec![community.actor_id(), parent_creator.actor_id()]; + let mut addressed_ccs: Vec = vec![community.actor_id(), parent_creator.actor_id()]; // Note: dont include community inbox here, as we send to it separately with `send_to_community()` let mut inboxes = vec![parent_creator.shared_inbox_or_inbox_url()]; @@ -84,9 +78,9 @@ pub async fn collect_non_local_mentions( for mention in &mentions { // TODO should it be fetching it every time? if let Ok(actor_id) = fetch_webfinger_url(mention, context).await { - let actor_id: ObjectId = ObjectId::new(actor_id); + let actor_id: ObjectId = ObjectId::new(actor_id); debug!("mention actor_id: {}", actor_id); - addressed_ccs.push(actor_id.to_owned().to_string().parse()?); + addressed_ccs.push(actor_id.to_string().parse()?); let mention_person = actor_id.dereference(context, &mut 0).await?; inboxes.push(mention_person.shared_inbox_or_inbox_url()); @@ -113,7 +107,7 @@ pub async fn collect_non_local_mentions( async fn get_comment_parent_creator( pool: &DbPool, comment: &Comment, -) -> Result { +) -> Result { let parent_creator_id = if let Some(parent_comment_id) = comment.parent_id { let parent_comment = blocking(pool, move |conn| Comment::read(conn, parent_comment_id)).await??; @@ -123,7 +117,11 @@ async fn get_comment_parent_creator( let parent_post = blocking(pool, move |conn| Post::read(conn, parent_post_id)).await??; parent_post.creator_id }; - Ok(blocking(pool, move |conn| Person::read(conn, parent_creator_id)).await??) + Ok( + blocking(pool, move |conn| Person::read(conn, parent_creator_id)) + .await?? + .into(), + ) } /// Turns a person id like `@name@example.com` into an apub ID, like `https://example.com/user/name`, diff --git a/crates/apub/src/activities/community/add_mod.rs b/crates/apub/src/activities/community/add_mod.rs index 149b92cd..e1cf03e0 100644 --- a/crates/apub/src/activities/community/add_mod.rs +++ b/crates/apub/src/activities/community/add_mod.rs @@ -10,6 +10,7 @@ use crate::{ context::lemmy_context, fetcher::object_id::ObjectId, generate_moderators_url, + objects::{community::ApubCommunity, person::ApubPerson}, }; use activitystreams::{ activity::kind::AddType, @@ -24,10 +25,7 @@ use lemmy_apub_lib::{ values::PublicUrl, }; use lemmy_db_schema::{ - source::{ - community::{Community, CommunityModerator, CommunityModeratorForm}, - person::Person, - }, + source::community::{CommunityModerator, CommunityModeratorForm}, traits::Joinable, }; use lemmy_utils::LemmyError; @@ -38,11 +36,11 @@ use url::Url; #[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct AddMod { - actor: ObjectId, + actor: ObjectId, to: [PublicUrl; 1], - object: ObjectId, + object: ObjectId, target: Url, - cc: [ObjectId; 1], + cc: [ObjectId; 1], #[serde(rename = "type")] kind: AddType, id: Url, @@ -54,9 +52,9 @@ pub struct AddMod { impl AddMod { pub async fn send( - community: &Community, - added_mod: &Person, - actor: &Person, + community: &ApubCommunity, + added_mod: &ApubPerson, + actor: &ApubPerson, context: &LemmyContext, ) -> Result<(), LemmyError> { let id = generate_activity_id( @@ -92,7 +90,7 @@ impl ActivityHandler for AddMod { ) -> Result<(), LemmyError> { verify_activity(self, &context.settings())?; verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?; - verify_mod_action(&self.actor, self.cc[0].clone(), context, request_counter).await?; + verify_mod_action(&self.actor, &self.cc[0], context, request_counter).await?; verify_add_remove_moderator_target(&self.target, &self.cc[0])?; Ok(()) } diff --git a/crates/apub/src/activities/community/announce.rs b/crates/apub/src/activities/community/announce.rs index ec72b842..183dbfd5 100644 --- a/crates/apub/src/activities/community/announce.rs +++ b/crates/apub/src/activities/community/announce.rs @@ -21,6 +21,7 @@ use crate::{ fetcher::object_id::ObjectId, http::is_activity_already_known, insert_activity, + objects::community::ApubCommunity, send_lemmy_activity, CommunityType, }; @@ -35,7 +36,6 @@ use lemmy_apub_lib::{ traits::{ActivityFields, ActivityHandler, ActorType}, values::PublicUrl, }; -use lemmy_db_schema::source::community::Community; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use serde::{Deserialize, Serialize}; @@ -62,7 +62,7 @@ pub enum AnnouncableActivities { #[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct AnnounceActivity { - actor: ObjectId, + actor: ObjectId, to: [PublicUrl; 1], object: AnnouncableActivities, cc: Vec, @@ -78,7 +78,7 @@ pub struct AnnounceActivity { impl AnnounceActivity { pub async fn send( object: AnnouncableActivities, - community: &Community, + community: &ApubCommunity, additional_inboxes: Vec, context: &LemmyContext, ) -> Result<(), LemmyError> { diff --git a/crates/apub/src/activities/community/block_user.rs b/crates/apub/src/activities/community/block_user.rs index 9b4c3e81..a1c76c23 100644 --- a/crates/apub/src/activities/community/block_user.rs +++ b/crates/apub/src/activities/community/block_user.rs @@ -8,6 +8,7 @@ use crate::{ }, context::lemmy_context, fetcher::object_id::ObjectId, + objects::{community::ApubCommunity, person::ApubPerson}, }; use activitystreams::{ activity::kind::BlockType, @@ -22,15 +23,11 @@ use lemmy_apub_lib::{ values::PublicUrl, }; use lemmy_db_schema::{ - source::{ - community::{ - Community, - CommunityFollower, - CommunityFollowerForm, - CommunityPersonBan, - CommunityPersonBanForm, - }, - person::Person, + source::community::{ + CommunityFollower, + CommunityFollowerForm, + CommunityPersonBan, + CommunityPersonBanForm, }, traits::{Bannable, Followable}, }; @@ -42,10 +39,10 @@ use url::Url; #[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct BlockUserFromCommunity { - actor: ObjectId, + actor: ObjectId, to: [PublicUrl; 1], - pub(in crate::activities::community) object: ObjectId, - cc: [ObjectId; 1], + pub(in crate::activities::community) object: ObjectId, + cc: [ObjectId; 1], #[serde(rename = "type")] kind: BlockType, id: Url, @@ -57,9 +54,9 @@ pub struct BlockUserFromCommunity { impl BlockUserFromCommunity { pub(in crate::activities::community) fn new( - community: &Community, - target: &Person, - actor: &Person, + community: &ApubCommunity, + target: &ApubPerson, + actor: &ApubPerson, context: &LemmyContext, ) -> Result { Ok(BlockUserFromCommunity { @@ -78,9 +75,9 @@ impl BlockUserFromCommunity { } pub async fn send( - community: &Community, - target: &Person, - actor: &Person, + community: &ApubCommunity, + target: &ApubPerson, + actor: &ApubPerson, context: &LemmyContext, ) -> Result<(), LemmyError> { let block = BlockUserFromCommunity::new(community, target, actor, context)?; @@ -102,7 +99,7 @@ impl ActivityHandler for BlockUserFromCommunity { ) -> Result<(), LemmyError> { verify_activity(self, &context.settings())?; verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?; - verify_mod_action(&self.actor, self.cc[0].clone(), context, request_counter).await?; + verify_mod_action(&self.actor, &self.cc[0], context, request_counter).await?; Ok(()) } diff --git a/crates/apub/src/activities/community/mod.rs b/crates/apub/src/activities/community/mod.rs index dd6ccb7f..f69ee026 100644 --- a/crates/apub/src/activities/community/mod.rs +++ b/crates/apub/src/activities/community/mod.rs @@ -2,12 +2,12 @@ use crate::{ activities::community::announce::{AnnouncableActivities, AnnounceActivity}, check_is_apub_id_valid, insert_activity, + objects::community::ApubCommunity, send_lemmy_activity, CommunityType, }; use itertools::Itertools; use lemmy_apub_lib::traits::ActorType; -use lemmy_db_schema::source::community::Community; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use url::Url; @@ -20,7 +20,7 @@ pub mod undo_block_user; pub mod update; async fn list_community_follower_inboxes( - community: &Community, + community: &ApubCommunity, additional_inboxes: Vec, context: &LemmyContext, ) -> Result, LemmyError> { @@ -45,7 +45,7 @@ pub(crate) async fn send_to_community( activity: AnnouncableActivities, activity_id: &Url, actor: &T, - community: &Community, + community: &ApubCommunity, additional_inboxes: Vec, context: &LemmyContext, ) -> Result<(), LemmyError> { diff --git a/crates/apub/src/activities/community/remove_mod.rs b/crates/apub/src/activities/community/remove_mod.rs index 4f980d01..b145e717 100644 --- a/crates/apub/src/activities/community/remove_mod.rs +++ b/crates/apub/src/activities/community/remove_mod.rs @@ -11,6 +11,7 @@ use crate::{ context::lemmy_context, fetcher::object_id::ObjectId, generate_moderators_url, + objects::{community::ApubCommunity, person::ApubPerson}, }; use activitystreams::{ activity::kind::RemoveType, @@ -25,10 +26,7 @@ use lemmy_apub_lib::{ values::PublicUrl, }; use lemmy_db_schema::{ - source::{ - community::{Community, CommunityModerator, CommunityModeratorForm}, - person::Person, - }, + source::community::{CommunityModerator, CommunityModeratorForm}, traits::Joinable, }; use lemmy_utils::LemmyError; @@ -39,10 +37,10 @@ use url::Url; #[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct RemoveMod { - actor: ObjectId, + actor: ObjectId, to: [PublicUrl; 1], - pub(in crate::activities) object: ObjectId, - cc: [ObjectId; 1], + pub(in crate::activities) object: ObjectId, + cc: [ObjectId; 1], #[serde(rename = "type")] kind: RemoveType, // if target is set, this is means remove mod from community @@ -56,9 +54,9 @@ pub struct RemoveMod { impl RemoveMod { pub async fn send( - community: &Community, - removed_mod: &Person, - actor: &Person, + community: &ApubCommunity, + removed_mod: &ApubPerson, + actor: &ApubPerson, context: &LemmyContext, ) -> Result<(), LemmyError> { let id = generate_activity_id( @@ -94,7 +92,7 @@ impl ActivityHandler for RemoveMod { verify_activity(self, &context.settings())?; if let Some(target) = &self.target { verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?; - verify_mod_action(&self.actor, self.cc[0].clone(), context, request_counter).await?; + verify_mod_action(&self.actor, &self.cc[0], context, request_counter).await?; verify_add_remove_moderator_target(target, &self.cc[0])?; } else { verify_delete_activity( diff --git a/crates/apub/src/activities/community/undo_block_user.rs b/crates/apub/src/activities/community/undo_block_user.rs index 47cace42..1614de67 100644 --- a/crates/apub/src/activities/community/undo_block_user.rs +++ b/crates/apub/src/activities/community/undo_block_user.rs @@ -12,6 +12,7 @@ use crate::{ }, context::lemmy_context, fetcher::object_id::ObjectId, + objects::{community::ApubCommunity, person::ApubPerson}, }; use activitystreams::{ activity::kind::UndoType, @@ -26,10 +27,7 @@ use lemmy_apub_lib::{ values::PublicUrl, }; use lemmy_db_schema::{ - source::{ - community::{Community, CommunityPersonBan, CommunityPersonBanForm}, - person::Person, - }, + source::community::{CommunityPersonBan, CommunityPersonBanForm}, traits::Bannable, }; use lemmy_utils::LemmyError; @@ -40,10 +38,10 @@ use url::Url; #[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct UndoBlockUserFromCommunity { - actor: ObjectId, + actor: ObjectId, to: [PublicUrl; 1], object: BlockUserFromCommunity, - cc: [ObjectId; 1], + cc: [ObjectId; 1], #[serde(rename = "type")] kind: UndoType, id: Url, @@ -55,9 +53,9 @@ pub struct UndoBlockUserFromCommunity { impl UndoBlockUserFromCommunity { pub async fn send( - community: &Community, - target: &Person, - actor: &Person, + community: &ApubCommunity, + target: &ApubPerson, + actor: &ApubPerson, context: &LemmyContext, ) -> Result<(), LemmyError> { let block = BlockUserFromCommunity::new(community, target, actor, context)?; @@ -93,7 +91,7 @@ impl ActivityHandler for UndoBlockUserFromCommunity { ) -> Result<(), LemmyError> { verify_activity(self, &context.settings())?; verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?; - verify_mod_action(&self.actor, self.cc[0].clone(), context, request_counter).await?; + verify_mod_action(&self.actor, &self.cc[0], context, request_counter).await?; self.object.verify(context, request_counter).await?; Ok(()) } diff --git a/crates/apub/src/activities/community/update.rs b/crates/apub/src/activities/community/update.rs index 02b7126a..1120bd8b 100644 --- a/crates/apub/src/activities/community/update.rs +++ b/crates/apub/src/activities/community/update.rs @@ -8,7 +8,10 @@ use crate::{ }, context::lemmy_context, fetcher::object_id::ObjectId, - objects::{community::Group, ToApub}, + objects::{ + community::{ApubCommunity, Group}, + person::ApubPerson, + }, }; use activitystreams::{ activity::kind::UpdateType, @@ -19,14 +22,11 @@ use activitystreams::{ use lemmy_api_common::blocking; use lemmy_apub_lib::{ data::Data, - traits::{ActivityFields, ActivityHandler, ActorType}, + traits::{ActivityFields, ActivityHandler, ActorType, ToApub}, values::PublicUrl, }; use lemmy_db_schema::{ - source::{ - community::{Community, CommunityForm}, - person::Person, - }, + source::community::{Community, CommunityForm}, traits::Crud, }; use lemmy_utils::LemmyError; @@ -39,11 +39,11 @@ use url::Url; #[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct UpdateCommunity { - actor: ObjectId, + actor: ObjectId, to: [PublicUrl; 1], // TODO: would be nice to use a separate struct here, which only contains the fields updated here object: Group, - cc: [ObjectId; 1], + cc: [ObjectId; 1], #[serde(rename = "type")] kind: UpdateType, id: Url, @@ -55,8 +55,8 @@ pub struct UpdateCommunity { impl UpdateCommunity { pub async fn send( - community: &Community, - actor: &Person, + community: &ApubCommunity, + actor: &ApubPerson, context: &LemmyContext, ) -> Result<(), LemmyError> { let id = generate_activity_id( @@ -89,7 +89,7 @@ impl ActivityHandler for UpdateCommunity { ) -> Result<(), LemmyError> { verify_activity(self, &context.settings())?; verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?; - verify_mod_action(&self.actor, self.cc[0].clone(), context, request_counter).await?; + verify_mod_action(&self.actor, &self.cc[0], context, request_counter).await?; Ok(()) } diff --git a/crates/apub/src/activities/deletion/delete.rs b/crates/apub/src/activities/deletion/delete.rs index 251a3ffa..02df7dc3 100644 --- a/crates/apub/src/activities/deletion/delete.rs +++ b/crates/apub/src/activities/deletion/delete.rs @@ -12,6 +12,7 @@ use crate::{ }, context::lemmy_context, fetcher::object_id::ObjectId, + objects::{community::ApubCommunity, person::ApubPerson}, }; use activitystreams::{ activity::kind::DeleteType, @@ -38,7 +39,6 @@ use lemmy_db_schema::{ ModRemovePost, ModRemovePostForm, }, - person::Person, post::Post, }, traits::Crud, @@ -65,10 +65,10 @@ use url::Url; #[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct Delete { - actor: ObjectId, + actor: ObjectId, to: [PublicUrl; 1], pub(in crate::activities::deletion) object: Url, - pub(in crate::activities::deletion) cc: [ObjectId; 1], + pub(in crate::activities::deletion) cc: [ObjectId; 1], #[serde(rename = "type")] kind: DeleteType, /// If summary is present, this is a mod action (Remove in Lemmy terms). Otherwise, its a user @@ -136,8 +136,8 @@ impl ActivityHandler for Delete { impl Delete { pub(in crate::activities::deletion) fn new( - actor: &Person, - community: &Community, + actor: &ApubPerson, + community: &ApubCommunity, object_id: Url, summary: Option, context: &LemmyContext, @@ -158,8 +158,8 @@ impl Delete { }) } pub(in crate::activities::deletion) async fn send( - actor: &Person, - community: &Community, + actor: &ApubPerson, + community: &ApubCommunity, object_id: Url, summary: Option, context: &LemmyContext, @@ -173,7 +173,7 @@ impl Delete { } pub(in crate::activities) async fn receive_remove_action( - actor: &ObjectId, + actor: &ObjectId, object: &Url, reason: Option, context: &LemmyContext, diff --git a/crates/apub/src/activities/deletion/mod.rs b/crates/apub/src/activities/deletion/mod.rs index 3a38f2a2..4352afe8 100644 --- a/crates/apub/src/activities/deletion/mod.rs +++ b/crates/apub/src/activities/deletion/mod.rs @@ -5,14 +5,14 @@ use crate::{ verify_person_in_community, }, fetcher::object_id::ObjectId, + objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson, post::ApubPost}, }; -use diesel::PgConnection; use lemmy_api_common::blocking; use lemmy_apub_lib::{ traits::{ActivityFields, ActorType, ApubObject}, verify::verify_domains_match, }; -use lemmy_db_schema::source::{comment::Comment, community::Community, person::Person, post::Post}; +use lemmy_db_schema::source::{comment::Comment, community::Community, post::Post}; use lemmy_utils::LemmyError; use lemmy_websocket::{ send::{send_comment_ws_message_simple, send_community_ws_message, send_post_ws_message}, @@ -25,8 +25,8 @@ pub mod delete; pub mod undo_delete; pub async fn send_apub_delete( - actor: &Person, - community: &Community, + actor: &ApubPerson, + community: &ApubCommunity, object_id: Url, deleted: bool, context: &LemmyContext, @@ -41,8 +41,8 @@ pub async fn send_apub_delete( // TODO: remove reason is actually optional in lemmy. we set an empty string in that case, but its // ugly pub async fn send_apub_remove( - actor: &Person, - community: &Community, + actor: &ApubPerson, + community: &ApubCommunity, object_id: Url, reason: String, removed: bool, @@ -56,9 +56,9 @@ pub async fn send_apub_remove( } pub enum DeletableObjects { - Community(Box), - Comment(Box), - Post(Box), + Community(Box), + Comment(Box), + Post(Box), } impl DeletableObjects { @@ -66,39 +66,23 @@ impl DeletableObjects { ap_id: &Url, context: &LemmyContext, ) -> Result { - if let Some(c) = - DeletableObjects::read_type_from_db::(ap_id.clone(), context).await? - { + if let Some(c) = ApubCommunity::read_from_apub_id(ap_id.clone(), context).await? { return Ok(DeletableObjects::Community(Box::new(c))); } - if let Some(p) = DeletableObjects::read_type_from_db::(ap_id.clone(), context).await? { + if let Some(p) = ApubPost::read_from_apub_id(ap_id.clone(), context).await? { return Ok(DeletableObjects::Post(Box::new(p))); } - if let Some(c) = DeletableObjects::read_type_from_db::(ap_id.clone(), context).await? { + if let Some(c) = ApubComment::read_from_apub_id(ap_id.clone(), context).await? { return Ok(DeletableObjects::Comment(Box::new(c))); } Err(diesel::NotFound.into()) } - - // TODO: a method like this should be provided by fetcher module - async fn read_type_from_db( - ap_id: Url, - context: &LemmyContext, - ) -> Result, LemmyError> - where - Type: ApubObject + Send + 'static, - { - blocking(context.pool(), move |conn| { - Type::read_from_apub_id(conn, ap_id) - }) - .await? - } } pub(in crate::activities) async fn verify_delete_activity( object: &Url, activity: &dyn ActivityFields, - community_id: &ObjectId, + community_id: &ObjectId, is_mod_action: bool, context: &LemmyContext, request_counter: &mut i32, @@ -115,7 +99,7 @@ pub(in crate::activities) async fn verify_delete_activity( // community deletion is always a mod (or admin) action verify_mod_action( &actor, - ObjectId::new(c.actor_id()), + &ObjectId::new(c.actor_id()), context, request_counter, ) @@ -124,7 +108,7 @@ pub(in crate::activities) async fn verify_delete_activity( DeletableObjects::Post(p) => { verify_delete_activity_post_or_comment( activity, - &p.ap_id.into(), + &p.ap_id.clone().into(), community_id, is_mod_action, context, @@ -135,7 +119,7 @@ pub(in crate::activities) async fn verify_delete_activity( DeletableObjects::Comment(c) => { verify_delete_activity_post_or_comment( activity, - &c.ap_id.into(), + &c.ap_id.clone().into(), community_id, is_mod_action, context, @@ -150,7 +134,7 @@ pub(in crate::activities) async fn verify_delete_activity( async fn verify_delete_activity_post_or_comment( activity: &dyn ActivityFields, object_id: &Url, - community_id: &ObjectId, + community_id: &ObjectId, is_mod_action: bool, context: &LemmyContext, request_counter: &mut i32, @@ -158,7 +142,7 @@ async fn verify_delete_activity_post_or_comment( let actor = ObjectId::new(activity.actor().clone()); verify_person_in_community(&actor, community_id, context, request_counter).await?; if is_mod_action { - verify_mod_action(&actor, community_id.clone(), context, request_counter).await?; + verify_mod_action(&actor, community_id, context, request_counter).await?; } else { // domain of post ap_id and post.creator ap_id are identical, so we just check the former verify_domains_match(activity.actor(), object_id)?; @@ -177,7 +161,7 @@ struct WebsocketMessages { /// because of the mod log async fn receive_delete_action( object: &Url, - actor: &ObjectId, + actor: &ObjectId, ws_messages: WebsocketMessages, deleted: bool, context: &LemmyContext, diff --git a/crates/apub/src/activities/deletion/undo_delete.rs b/crates/apub/src/activities/deletion/undo_delete.rs index 7a96d430..327bf86c 100644 --- a/crates/apub/src/activities/deletion/undo_delete.rs +++ b/crates/apub/src/activities/deletion/undo_delete.rs @@ -13,6 +13,7 @@ use crate::{ }, context::lemmy_context, fetcher::object_id::ObjectId, + objects::{community::ApubCommunity, person::ApubPerson}, }; use activitystreams::{ activity::kind::UndoType, @@ -27,7 +28,7 @@ use lemmy_apub_lib::{ traits::{ActivityFields, ActivityHandler, ActorType}, values::PublicUrl, }; -use lemmy_db_schema::source::{comment::Comment, community::Community, person::Person, post::Post}; +use lemmy_db_schema::source::{comment::Comment, community::Community, post::Post}; use lemmy_utils::LemmyError; use lemmy_websocket::{ send::{send_comment_ws_message_simple, send_community_ws_message, send_post_ws_message}, @@ -40,10 +41,10 @@ use url::Url; #[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct UndoDelete { - actor: ObjectId, + actor: ObjectId, to: [PublicUrl; 1], object: Delete, - cc: [ObjectId; 1], + cc: [ObjectId; 1], #[serde(rename = "type")] kind: UndoType, id: Url, @@ -102,8 +103,8 @@ impl ActivityHandler for UndoDelete { impl UndoDelete { pub(in crate::activities::deletion) async fn send( - actor: &Person, - community: &Community, + actor: &ApubPerson, + community: &ApubCommunity, object_id: Url, summary: Option, context: &LemmyContext, diff --git a/crates/apub/src/activities/following/accept.rs b/crates/apub/src/activities/following/accept.rs index bfc9ba31..6fa65e7b 100644 --- a/crates/apub/src/activities/following/accept.rs +++ b/crates/apub/src/activities/following/accept.rs @@ -7,6 +7,7 @@ use crate::{ }, context::lemmy_context, fetcher::object_id::ObjectId, + objects::{community::ApubCommunity, person::ApubPerson}, send_lemmy_activity, }; use activitystreams::{ @@ -21,13 +22,7 @@ use lemmy_apub_lib::{ traits::{ActivityFields, ActivityHandler, ActorType}, verify::verify_urls_match, }; -use lemmy_db_schema::{ - source::{ - community::{Community, CommunityFollower}, - person::Person, - }, - traits::Followable, -}; +use lemmy_db_schema::{source::community::CommunityFollower, traits::Followable}; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use serde::{Deserialize, Serialize}; @@ -36,8 +31,8 @@ use url::Url; #[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct AcceptFollowCommunity { - actor: ObjectId, - to: ObjectId, + actor: ObjectId, + to: ObjectId, object: FollowCommunity, #[serde(rename = "type")] kind: AcceptType, @@ -72,7 +67,7 @@ impl AcceptFollowCommunity { context: lemmy_context(), unparsed: Default::default(), }; - let inbox = vec![person.inbox_url.into()]; + let inbox = vec![person.inbox_url()]; send_lemmy_activity(context, &accept, &accept.id, &community, inbox, true).await } } diff --git a/crates/apub/src/activities/following/follow.rs b/crates/apub/src/activities/following/follow.rs index c75a08cd..14424381 100644 --- a/crates/apub/src/activities/following/follow.rs +++ b/crates/apub/src/activities/following/follow.rs @@ -7,6 +7,7 @@ use crate::{ }, context::lemmy_context, fetcher::object_id::ObjectId, + objects::{community::ApubCommunity, person::ApubPerson}, send_lemmy_activity, }; use activitystreams::{ @@ -22,10 +23,7 @@ use lemmy_apub_lib::{ verify::verify_urls_match, }; use lemmy_db_schema::{ - source::{ - community::{Community, CommunityFollower, CommunityFollowerForm}, - person::Person, - }, + source::community::{CommunityFollower, CommunityFollowerForm}, traits::Followable, }; use lemmy_utils::LemmyError; @@ -36,10 +34,10 @@ use url::Url; #[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct FollowCommunity { - pub(in crate::activities::following) actor: ObjectId, + pub(in crate::activities::following) actor: ObjectId, // TODO: is there any reason to put the same community id twice, in to and object? - pub(in crate::activities::following) to: ObjectId, - pub(in crate::activities::following) object: ObjectId, + pub(in crate::activities::following) to: ObjectId, + pub(in crate::activities::following) object: ObjectId, #[serde(rename = "type")] kind: FollowType, id: Url, @@ -51,8 +49,8 @@ pub struct FollowCommunity { impl FollowCommunity { pub(in crate::activities::following) fn new( - actor: &Person, - community: &Community, + actor: &ApubPerson, + community: &ApubCommunity, context: &LemmyContext, ) -> Result { Ok(FollowCommunity { @@ -69,8 +67,8 @@ impl FollowCommunity { }) } pub async fn send( - actor: &Person, - community: &Community, + actor: &ApubPerson, + community: &ApubCommunity, context: &LemmyContext, ) -> Result<(), LemmyError> { let community_follower_form = CommunityFollowerForm { diff --git a/crates/apub/src/activities/following/undo.rs b/crates/apub/src/activities/following/undo.rs index 2699a98c..5c548ae4 100644 --- a/crates/apub/src/activities/following/undo.rs +++ b/crates/apub/src/activities/following/undo.rs @@ -7,6 +7,7 @@ use crate::{ }, context::lemmy_context, fetcher::object_id::ObjectId, + objects::{community::ApubCommunity, person::ApubPerson}, send_lemmy_activity, }; use activitystreams::{ @@ -22,10 +23,7 @@ use lemmy_apub_lib::{ verify::verify_urls_match, }; use lemmy_db_schema::{ - source::{ - community::{Community, CommunityFollower, CommunityFollowerForm}, - person::Person, - }, + source::community::{CommunityFollower, CommunityFollowerForm}, traits::Followable, }; use lemmy_utils::LemmyError; @@ -36,8 +34,8 @@ use url::Url; #[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct UndoFollowCommunity { - actor: ObjectId, - to: ObjectId, + actor: ObjectId, + to: ObjectId, object: FollowCommunity, #[serde(rename = "type")] kind: UndoType, @@ -50,8 +48,8 @@ pub struct UndoFollowCommunity { impl UndoFollowCommunity { pub async fn send( - actor: &Person, - community: &Community, + actor: &ApubPerson, + community: &ApubCommunity, context: &LemmyContext, ) -> Result<(), LemmyError> { let object = FollowCommunity::new(actor, community, context)?; diff --git a/crates/apub/src/activities/mod.rs b/crates/apub/src/activities/mod.rs index 618c14f5..0832a85c 100644 --- a/crates/apub/src/activities/mod.rs +++ b/crates/apub/src/activities/mod.rs @@ -3,15 +3,17 @@ use crate::{ check_is_apub_id_valid, fetcher::object_id::ObjectId, generate_moderators_url, + objects::{community::ApubCommunity, person::ApubPerson}, }; use anyhow::anyhow; use lemmy_api_common::blocking; use lemmy_apub_lib::{traits::ActivityFields, verify::verify_domains_match}; -use lemmy_db_schema::source::{community::Community, person::Person}; +use lemmy_db_schema::source::community::Community; use lemmy_db_views_actor::community_view::CommunityView; use lemmy_utils::{settings::structs::Settings, LemmyError}; use lemmy_websocket::LemmyContext; use serde::{Deserialize, Serialize}; +use std::ops::Deref; use strum_macros::ToString; use url::{ParseError, Url}; use uuid::Uuid; @@ -35,7 +37,7 @@ pub enum CreateOrUpdateType { /// Checks that the specified Url actually identifies a Person (by fetching it), and that the person /// doesn't have a site ban. async fn verify_person( - person_id: &ObjectId, + person_id: &ObjectId, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { @@ -50,7 +52,7 @@ pub(crate) async fn extract_community( cc: &[Url], context: &LemmyContext, request_counter: &mut i32, -) -> Result { +) -> Result { let mut cc_iter = cc.iter(); loop { if let Some(cid) = cc_iter.next() { @@ -67,19 +69,19 @@ pub(crate) async fn extract_community( /// Fetches the person and community to verify their type, then checks if person is banned from site /// or community. pub(crate) async fn verify_person_in_community( - person_id: &ObjectId, - community_id: &ObjectId, + person_id: &ObjectId, + community_id: &ObjectId, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { let community = community_id.dereference(context, request_counter).await?; let person = person_id.dereference(context, request_counter).await?; - check_community_or_site_ban(&person, community.id, context.pool()).await + check_community_or_site_ban(person.deref(), community.id, context.pool()).await } /// Simply check that the url actually refers to a valid group. async fn verify_community( - community_id: &ObjectId, + community_id: &ObjectId, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { @@ -97,8 +99,8 @@ fn verify_activity(activity: &dyn ActivityFields, settings: &Settings) -> Result /// because in case of remote communities, admins can also perform mod actions. As admin status /// is not federated, we cant verify their actions remotely. pub(crate) async fn verify_mod_action( - actor_id: &ObjectId, - community_id: ObjectId, + actor_id: &ObjectId, + community_id: &ObjectId, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { @@ -126,7 +128,7 @@ pub(crate) async fn verify_mod_action( /// /c/community/moderators. Any different values are unsupported. fn verify_add_remove_moderator_target( target: &Url, - community: &ObjectId, + community: &ObjectId, ) -> Result<(), LemmyError> { if target != &generate_moderators_url(&community.clone().into())?.into_inner() { return Err(anyhow!("Unkown target url").into()); diff --git a/crates/apub/src/activities/post/create_or_update.rs b/crates/apub/src/activities/post/create_or_update.rs index 7a1ddc40..cc7ef5c9 100644 --- a/crates/apub/src/activities/post/create_or_update.rs +++ b/crates/apub/src/activities/post/create_or_update.rs @@ -10,21 +10,22 @@ use crate::{ }, context::lemmy_context, fetcher::object_id::ObjectId, - objects::{post::Page, FromApub, ToApub}, + objects::{ + community::ApubCommunity, + person::ApubPerson, + post::{ApubPost, Page}, + }, }; use activitystreams::{base::AnyBase, primitives::OneOrMany, unparsed::Unparsed}; use anyhow::anyhow; use lemmy_api_common::blocking; use lemmy_apub_lib::{ data::Data, - traits::{ActivityFields, ActivityHandler, ActorType}, + traits::{ActivityFields, ActivityHandler, ActorType, FromApub, ToApub}, values::PublicUrl, verify::{verify_domains_match, verify_urls_match}, }; -use lemmy_db_schema::{ - source::{community::Community, person::Person, post::Post}, - traits::Crud, -}; +use lemmy_db_schema::{source::community::Community, traits::Crud}; use lemmy_utils::LemmyError; use lemmy_websocket::{send::send_post_ws_message, LemmyContext, UserOperationCrud}; use serde::{Deserialize, Serialize}; @@ -33,10 +34,10 @@ use url::Url; #[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct CreateOrUpdatePost { - actor: ObjectId, + actor: ObjectId, to: [PublicUrl; 1], object: Page, - cc: [ObjectId; 1], + cc: [ObjectId; 1], #[serde(rename = "type")] kind: CreateOrUpdateType, id: Url, @@ -48,16 +49,17 @@ pub struct CreateOrUpdatePost { impl CreateOrUpdatePost { pub async fn send( - post: &Post, - actor: &Person, + post: &ApubPost, + actor: &ApubPerson, kind: CreateOrUpdateType, context: &LemmyContext, ) -> Result<(), LemmyError> { let community_id = post.community_id; - let community = blocking(context.pool(), move |conn| { + let community: ApubCommunity = blocking(context.pool(), move |conn| { Community::read(conn, community_id) }) - .await??; + .await?? + .into(); let id = generate_activity_id( kind.clone(), @@ -109,7 +111,7 @@ impl ActivityHandler for CreateOrUpdatePost { CreateOrUpdateType::Update => { let is_mod_action = self.object.is_mod_action(context).await?; if is_mod_action { - verify_mod_action(&self.actor, self.cc[0].clone(), context, request_counter).await?; + verify_mod_action(&self.actor, &self.cc[0], context, request_counter).await?; } else { verify_domains_match(self.actor.inner(), self.object.id_unchecked())?; verify_urls_match(self.actor(), self.object.attributed_to.inner())?; @@ -126,7 +128,8 @@ impl ActivityHandler for CreateOrUpdatePost { request_counter: &mut i32, ) -> Result<(), LemmyError> { let actor = self.actor.dereference(context, request_counter).await?; - let post = Post::from_apub(&self.object, context, &actor.actor_id(), request_counter).await?; + let post = + ApubPost::from_apub(&self.object, context, &actor.actor_id(), request_counter).await?; let notif_type = match self.kind { CreateOrUpdateType::Create => UserOperationCrud::CreatePost, diff --git a/crates/apub/src/activities/private_message/create_or_update.rs b/crates/apub/src/activities/private_message/create_or_update.rs index 6b3bc2b6..a1ee5598 100644 --- a/crates/apub/src/activities/private_message/create_or_update.rs +++ b/crates/apub/src/activities/private_message/create_or_update.rs @@ -2,20 +2,20 @@ use crate::{ activities::{generate_activity_id, verify_activity, verify_person, CreateOrUpdateType}, context::lemmy_context, fetcher::object_id::ObjectId, - objects::{private_message::Note, FromApub, ToApub}, + objects::{ + person::ApubPerson, + private_message::{ApubPrivateMessage, Note}, + }, send_lemmy_activity, }; use activitystreams::{base::AnyBase, primitives::OneOrMany, unparsed::Unparsed}; use lemmy_api_common::blocking; use lemmy_apub_lib::{ data::Data, - traits::{ActivityFields, ActivityHandler, ActorType}, + traits::{ActivityFields, ActivityHandler, ActorType, FromApub, ToApub}, verify::verify_domains_match, }; -use lemmy_db_schema::{ - source::{person::Person, private_message::PrivateMessage}, - traits::Crud, -}; +use lemmy_db_schema::{source::person::Person, traits::Crud}; use lemmy_utils::LemmyError; use lemmy_websocket::{send::send_pm_ws_message, LemmyContext, UserOperationCrud}; use serde::{Deserialize, Serialize}; @@ -27,8 +27,8 @@ pub struct CreateOrUpdatePrivateMessage { #[serde(rename = "@context")] pub context: OneOrMany, id: Url, - actor: ObjectId, - to: ObjectId, + actor: ObjectId, + to: ObjectId, object: Note, #[serde(rename = "type")] kind: CreateOrUpdateType, @@ -38,14 +38,16 @@ pub struct CreateOrUpdatePrivateMessage { impl CreateOrUpdatePrivateMessage { pub async fn send( - private_message: &PrivateMessage, - actor: &Person, + private_message: &ApubPrivateMessage, + actor: &ApubPerson, kind: CreateOrUpdateType, context: &LemmyContext, ) -> Result<(), LemmyError> { let recipient_id = private_message.recipient_id; - let recipient = - blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??; + let recipient: ApubPerson = + blocking(context.pool(), move |conn| Person::read(conn, recipient_id)) + .await?? + .into(); let id = generate_activity_id( kind.clone(), @@ -85,7 +87,8 @@ impl ActivityHandler for CreateOrUpdatePrivateMessage { request_counter: &mut i32, ) -> Result<(), LemmyError> { let private_message = - PrivateMessage::from_apub(&self.object, context, self.actor.inner(), request_counter).await?; + ApubPrivateMessage::from_apub(&self.object, context, self.actor.inner(), request_counter) + .await?; let notif_type = match self.kind { CreateOrUpdateType::Create => UserOperationCrud::CreatePrivateMessage, diff --git a/crates/apub/src/activities/private_message/delete.rs b/crates/apub/src/activities/private_message/delete.rs index ded3b3ad..bb374eb4 100644 --- a/crates/apub/src/activities/private_message/delete.rs +++ b/crates/apub/src/activities/private_message/delete.rs @@ -2,6 +2,7 @@ use crate::{ activities::{generate_activity_id, verify_activity, verify_person}, context::lemmy_context, fetcher::object_id::ObjectId, + objects::{person::ApubPerson, private_message::ApubPrivateMessage}, send_lemmy_activity, }; use activitystreams::{ @@ -28,9 +29,9 @@ use url::Url; #[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct DeletePrivateMessage { - actor: ObjectId, - to: ObjectId, - pub(in crate::activities::private_message) object: ObjectId, + actor: ObjectId, + to: ObjectId, + pub(in crate::activities::private_message) object: ObjectId, #[serde(rename = "type")] kind: DeleteType, id: Url, @@ -42,7 +43,7 @@ pub struct DeletePrivateMessage { impl DeletePrivateMessage { pub(in crate::activities::private_message) fn new( - actor: &Person, + actor: &ApubPerson, pm: &PrivateMessage, context: &LemmyContext, ) -> Result { @@ -60,16 +61,18 @@ impl DeletePrivateMessage { }) } pub async fn send( - actor: &Person, - pm: &PrivateMessage, + actor: &ApubPerson, + pm: &ApubPrivateMessage, context: &LemmyContext, ) -> Result<(), LemmyError> { let delete = DeletePrivateMessage::new(actor, pm, context)?; let delete_id = delete.id.clone(); let recipient_id = pm.recipient_id; - let recipient = - blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??; + let recipient: ApubPerson = + blocking(context.pool(), move |conn| Person::read(conn, recipient_id)) + .await?? + .into(); let inbox = vec![recipient.shared_inbox_or_inbox_url()]; send_lemmy_activity(context, &delete, &delete_id, actor, inbox, true).await } diff --git a/crates/apub/src/activities/private_message/undo_delete.rs b/crates/apub/src/activities/private_message/undo_delete.rs index 2c5fd0e7..0263b811 100644 --- a/crates/apub/src/activities/private_message/undo_delete.rs +++ b/crates/apub/src/activities/private_message/undo_delete.rs @@ -7,6 +7,7 @@ use crate::{ }, context::lemmy_context, fetcher::object_id::ObjectId, + objects::{person::ApubPerson, private_message::ApubPrivateMessage}, send_lemmy_activity, }; use activitystreams::{ @@ -33,8 +34,8 @@ use url::Url; #[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct UndoDeletePrivateMessage { - actor: ObjectId, - to: ObjectId, + actor: ObjectId, + to: ObjectId, object: DeletePrivateMessage, #[serde(rename = "type")] kind: UndoType, @@ -47,13 +48,15 @@ pub struct UndoDeletePrivateMessage { impl UndoDeletePrivateMessage { pub async fn send( - actor: &Person, - pm: &PrivateMessage, + actor: &ApubPerson, + pm: &ApubPrivateMessage, context: &LemmyContext, ) -> Result<(), LemmyError> { let recipient_id = pm.recipient_id; - let recipient = - blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??; + let recipient: ApubPerson = + blocking(context.pool(), move |conn| Person::read(conn, recipient_id)) + .await?? + .into(); let object = DeletePrivateMessage::new(actor, pm, context)?; let id = generate_activity_id( diff --git a/crates/apub/src/activities/report.rs b/crates/apub/src/activities/report.rs index aee673cf..d0b1f971 100644 --- a/crates/apub/src/activities/report.rs +++ b/crates/apub/src/activities/report.rs @@ -2,6 +2,7 @@ use crate::{ activities::{generate_activity_id, verify_activity, verify_person_in_community}, context::lemmy_context, fetcher::object_id::ObjectId, + objects::{community::ApubCommunity, person::ApubPerson}, send_lemmy_activity, PostOrComment, }; @@ -17,14 +18,11 @@ use lemmy_apub_lib::{ traits::{ActivityFields, ActivityHandler, ActorType}, }; use lemmy_db_schema::{ - newtypes::CommunityId, source::{ comment_report::{CommentReport, CommentReportForm}, - community::Community, - person::Person, post_report::{PostReport, PostReportForm}, }, - traits::{Crud, Reportable}, + traits::Reportable, }; use lemmy_db_views::{comment_report_view::CommentReportView, post_report_view::PostReportView}; use lemmy_utils::LemmyError; @@ -35,8 +33,8 @@ use url::Url; #[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct Report { - actor: ObjectId, - to: [ObjectId; 1], + actor: ObjectId, + to: [ObjectId; 1], object: ObjectId, summary: String, #[serde(rename = "type")] @@ -51,15 +49,12 @@ pub struct Report { impl Report { pub async fn send( object_id: ObjectId, - actor: &Person, - community_id: CommunityId, + actor: &ApubPerson, + community_id: ObjectId, reason: String, context: &LemmyContext, ) -> Result<(), LemmyError> { - let community = blocking(context.pool(), move |conn| { - Community::read(conn, community_id) - }) - .await??; + let community = community_id.dereference_local(context).await?; let kind = FlagType::Flag; let id = generate_activity_id( kind.clone(), @@ -111,10 +106,10 @@ impl ActivityHandler for Report { let report_form = PostReportForm { creator_id: actor.id, post_id: post.id, - original_post_name: post.name, - original_post_url: post.url, + original_post_name: post.name.clone(), + original_post_url: post.url.clone(), reason: self.summary, - original_post_body: post.body, + original_post_body: post.body.clone(), }; let report = blocking(context.pool(), move |conn| { @@ -138,7 +133,7 @@ impl ActivityHandler for Report { let report_form = CommentReportForm { creator_id: actor.id, comment_id: comment.id, - original_comment_text: comment.content, + original_comment_text: comment.content.clone(), reason: self.summary, }; diff --git a/crates/apub/src/activities/undo_remove.rs b/crates/apub/src/activities/undo_remove.rs index 10305466..5b41e086 100644 --- a/crates/apub/src/activities/undo_remove.rs +++ b/crates/apub/src/activities/undo_remove.rs @@ -5,6 +5,7 @@ use crate::{ verify_activity, }, fetcher::object_id::ObjectId, + objects::{community::ApubCommunity, person::ApubPerson}, }; use activitystreams::{ activity::kind::UndoType, @@ -17,7 +18,6 @@ use lemmy_apub_lib::{ traits::{ActivityFields, ActivityHandler}, values::PublicUrl, }; -use lemmy_db_schema::source::{community::Community, person::Person}; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use serde::{Deserialize, Serialize}; @@ -26,11 +26,11 @@ use url::Url; #[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct UndoRemovePostCommentOrCommunity { - actor: ObjectId, + actor: ObjectId, to: [PublicUrl; 1], // Note, there is no such thing as Undo/Remove/Mod, so we ignore that object: RemoveMod, - cc: [ObjectId; 1], + cc: [ObjectId; 1], #[serde(rename = "type")] kind: UndoType, id: Url, diff --git a/crates/apub/src/activities/voting/mod.rs b/crates/apub/src/activities/voting/mod.rs index 90757eeb..829553d3 100644 --- a/crates/apub/src/activities/voting/mod.rs +++ b/crates/apub/src/activities/voting/mod.rs @@ -1,10 +1,12 @@ -use crate::activities::voting::vote::VoteType; +use crate::{ + activities::voting::vote::VoteType, + objects::{comment::ApubComment, person::ApubPerson, post::ApubPost}, +}; use lemmy_api_common::blocking; use lemmy_db_schema::{ source::{ - comment::{Comment, CommentLike, CommentLikeForm}, - person::Person, - post::{Post, PostLike, PostLikeForm}, + comment::{CommentLike, CommentLikeForm}, + post::{PostLike, PostLikeForm}, }, traits::Likeable, }; @@ -20,8 +22,8 @@ pub mod vote; async fn vote_comment( vote_type: &VoteType, - actor: Person, - comment: &Comment, + actor: ApubPerson, + comment: &ApubComment, context: &LemmyContext, ) -> Result<(), LemmyError> { let comment_id = comment.id; @@ -44,8 +46,8 @@ async fn vote_comment( async fn vote_post( vote_type: &VoteType, - actor: Person, - post: &Post, + actor: ApubPerson, + post: &ApubPost, context: &LemmyContext, ) -> Result<(), LemmyError> { let post_id = post.id; @@ -66,8 +68,8 @@ async fn vote_post( } async fn undo_vote_comment( - actor: Person, - comment: &Comment, + actor: ApubPerson, + comment: &ApubComment, context: &LemmyContext, ) -> Result<(), LemmyError> { let comment_id = comment.id; @@ -82,8 +84,8 @@ async fn undo_vote_comment( } async fn undo_vote_post( - actor: Person, - post: &Post, + actor: ApubPerson, + post: &ApubPost, context: &LemmyContext, ) -> Result<(), LemmyError> { let post_id = post.id; diff --git a/crates/apub/src/activities/voting/undo_vote.rs b/crates/apub/src/activities/voting/undo_vote.rs index 2c87aa89..d72b5245 100644 --- a/crates/apub/src/activities/voting/undo_vote.rs +++ b/crates/apub/src/activities/voting/undo_vote.rs @@ -12,6 +12,7 @@ use crate::{ }, context::lemmy_context, fetcher::object_id::ObjectId, + objects::{community::ApubCommunity, person::ApubPerson}, PostOrComment, }; use activitystreams::{ @@ -27,11 +28,7 @@ use lemmy_apub_lib::{ values::PublicUrl, verify::verify_urls_match, }; -use lemmy_db_schema::{ - newtypes::CommunityId, - source::{community::Community, person::Person}, - traits::Crud, -}; +use lemmy_db_schema::{newtypes::CommunityId, source::community::Community, traits::Crud}; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use serde::{Deserialize, Serialize}; @@ -41,10 +38,10 @@ use url::Url; #[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct UndoVote { - actor: ObjectId, + actor: ObjectId, to: [PublicUrl; 1], object: Vote, - cc: [ObjectId; 1], + cc: [ObjectId; 1], #[serde(rename = "type")] kind: UndoType, id: Url, @@ -57,15 +54,16 @@ pub struct UndoVote { impl UndoVote { pub async fn send( object: &PostOrComment, - actor: &Person, + actor: &ApubPerson, community_id: CommunityId, kind: VoteType, context: &LemmyContext, ) -> Result<(), LemmyError> { - let community = blocking(context.pool(), move |conn| { + let community: ApubCommunity = blocking(context.pool(), move |conn| { Community::read(conn, community_id) }) - .await??; + .await?? + .into(); let object = Vote::new(object, actor, &community, kind.clone(), context)?; let id = generate_activity_id( diff --git a/crates/apub/src/activities/voting/vote.rs b/crates/apub/src/activities/voting/vote.rs index 2463cbc0..5d4c9066 100644 --- a/crates/apub/src/activities/voting/vote.rs +++ b/crates/apub/src/activities/voting/vote.rs @@ -8,6 +8,7 @@ use crate::{ }, context::lemmy_context, fetcher::object_id::ObjectId, + objects::{community::ApubCommunity, person::ApubPerson}, PostOrComment, }; use activitystreams::{base::AnyBase, primitives::OneOrMany, unparsed::Unparsed}; @@ -18,11 +19,7 @@ use lemmy_apub_lib::{ traits::{ActivityFields, ActivityHandler, ActorType}, values::PublicUrl, }; -use lemmy_db_schema::{ - newtypes::CommunityId, - source::{community::Community, person::Person}, - traits::Crud, -}; +use lemmy_db_schema::{newtypes::CommunityId, source::community::Community, traits::Crud}; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use serde::{Deserialize, Serialize}; @@ -60,10 +57,10 @@ impl From<&VoteType> for i16 { #[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct Vote { - actor: ObjectId, + actor: ObjectId, to: [PublicUrl; 1], pub(in crate::activities::voting) object: ObjectId, - cc: [ObjectId; 1], + cc: [ObjectId; 1], #[serde(rename = "type")] pub(in crate::activities::voting) kind: VoteType, id: Url, @@ -76,8 +73,8 @@ pub struct Vote { impl Vote { pub(in crate::activities::voting) fn new( object: &PostOrComment, - actor: &Person, - community: &Community, + actor: &ApubPerson, + community: &ApubCommunity, kind: VoteType, context: &LemmyContext, ) -> Result { @@ -95,7 +92,7 @@ impl Vote { pub async fn send( object: &PostOrComment, - actor: &Person, + actor: &ApubPerson, community_id: CommunityId, kind: VoteType, context: &LemmyContext, @@ -103,7 +100,8 @@ impl Vote { let community = blocking(context.pool(), move |conn| { Community::read(conn, community_id) }) - .await??; + .await?? + .into(); let vote = Vote::new(object, actor, &community, kind, context)?; let vote_id = vote.id.clone(); diff --git a/crates/apub/src/fetcher/community.rs b/crates/apub/src/fetcher/community.rs index 646f8fca..aff571b7 100644 --- a/crates/apub/src/fetcher/community.rs +++ b/crates/apub/src/fetcher/community.rs @@ -1,17 +1,14 @@ use crate::{ activities::community::announce::AnnounceActivity, fetcher::{fetch::fetch_remote_object, object_id::ObjectId}, - objects::community::Group, + objects::{community::Group, person::ApubPerson}, }; use activitystreams::collection::{CollectionExt, OrderedCollection}; use anyhow::Context; use lemmy_api_common::blocking; use lemmy_apub_lib::{data::Data, traits::ActivityHandler}; use lemmy_db_schema::{ - source::{ - community::{Community, CommunityModerator, CommunityModeratorForm}, - person::Person, - }, + source::community::{Community, CommunityModerator, CommunityModeratorForm}, traits::Joinable, }; use lemmy_db_views_actor::community_moderator_view::CommunityModeratorView; @@ -48,7 +45,7 @@ pub(crate) async fn update_community_mods( // Add new mods to database which have been added to moderators collection for mod_id in new_moderators { let mod_id = ObjectId::new(mod_id); - let mod_user: Person = mod_id.dereference(context, request_counter).await?; + let mod_user: ApubPerson = mod_id.dereference(context, request_counter).await?; if !current_moderators .clone() diff --git a/crates/apub/src/fetcher/mod.rs b/crates/apub/src/fetcher/mod.rs index 641d59f0..fd6b982c 100644 --- a/crates/apub/src/fetcher/mod.rs +++ b/crates/apub/src/fetcher/mod.rs @@ -4,13 +4,13 @@ pub mod object_id; pub mod post_or_comment; pub mod search; -use crate::fetcher::object_id::ObjectId; +use crate::{ + fetcher::object_id::ObjectId, + objects::{community::ApubCommunity, person::ApubPerson}, +}; use chrono::NaiveDateTime; use lemmy_apub_lib::traits::ActorType; -use lemmy_db_schema::{ - naive_now, - source::{community::Community, person::Person}, -}; +use lemmy_db_schema::naive_now; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use url::Url; @@ -28,13 +28,13 @@ pub(crate) async fn get_or_fetch_and_upsert_actor( context: &LemmyContext, recursion_counter: &mut i32, ) -> Result, LemmyError> { - let community_id = ObjectId::::new(apub_id.clone()); + let community_id = ObjectId::::new(apub_id.clone()); let community = community_id.dereference(context, recursion_counter).await; let actor: Box = match community { Ok(c) => Box::new(c), Err(_) => { let person_id = ObjectId::new(apub_id); - let person: Person = person_id.dereference(context, recursion_counter).await?; + let person: ApubPerson = person_id.dereference(context, recursion_counter).await?; Box::new(person) } }; diff --git a/crates/apub/src/fetcher/object_id.rs b/crates/apub/src/fetcher/object_id.rs index 771c276a..4e439fb7 100644 --- a/crates/apub/src/fetcher/object_id.rs +++ b/crates/apub/src/fetcher/object_id.rs @@ -1,9 +1,11 @@ -use crate::{fetcher::should_refetch_actor, objects::FromApub}; +use crate::fetcher::should_refetch_actor; use anyhow::anyhow; -use diesel::{NotFound, PgConnection}; -use lemmy_api_common::blocking; -use lemmy_apub_lib::{traits::ApubObject, APUB_JSON_CONTENT_TYPE}; -use lemmy_db_schema::{newtypes::DbUrl, DbPool}; +use diesel::NotFound; +use lemmy_apub_lib::{ + traits::{ApubObject, FromApub}, + APUB_JSON_CONTENT_TYPE, +}; +use lemmy_db_schema::newtypes::DbUrl; use lemmy_utils::{request::retry, settings::structs::Settings, LemmyError}; use lemmy_websocket::LemmyContext; use reqwest::StatusCode; @@ -22,12 +24,12 @@ static REQUEST_LIMIT: i32 = 25; #[derive(Clone, PartialEq, Serialize, Deserialize, Debug)] pub struct ObjectId(Url, #[serde(skip)] PhantomData) where - Kind: FromApub + ApubObject + Send + 'static, + Kind: FromApub + ApubObject + Send + 'static, for<'de2> ::ApubType: serde::Deserialize<'de2>; impl ObjectId where - Kind: FromApub + ApubObject + Send + 'static, + Kind: FromApub + ApubObject + Send + 'static, for<'de> ::ApubType: serde::Deserialize<'de>, { pub fn new(url: T) -> Self @@ -47,7 +49,7 @@ where context: &LemmyContext, request_counter: &mut i32, ) -> Result { - let db_object = self.dereference_from_db(context.pool()).await?; + let db_object = self.dereference_from_db(context).await?; // if its a local object, only fetch it from the database and not over http if self.0.domain() == Some(&Settings::get().get_hostname_without_port()?) { @@ -77,14 +79,14 @@ where /// Fetch an object from the local db. Instead of falling back to http, this throws an error if /// the object is not found in the database. pub async fn dereference_local(&self, context: &LemmyContext) -> Result { - let object = self.dereference_from_db(context.pool()).await?; + let object = self.dereference_from_db(context).await?; object.ok_or_else(|| anyhow!("object not found in database {}", self).into()) } /// returning none means the object was not found in local db - async fn dereference_from_db(&self, pool: &DbPool) -> Result, LemmyError> { + async fn dereference_from_db(&self, context: &LemmyContext) -> Result, LemmyError> { let id = self.0.clone(); - blocking(pool, move |conn| ApubObject::read_from_apub_id(conn, id)).await? + ApubObject::read_from_apub_id(id, context).await } async fn dereference_from_http( @@ -113,7 +115,7 @@ where if res.status() == StatusCode::GONE { if let Some(db_object) = db_object { - blocking(context.pool(), move |conn| db_object.delete(conn)).await??; + db_object.delete(context).await?; } return Err(anyhow!("Fetched remote object {} which was deleted", self).into()); } @@ -126,7 +128,7 @@ where impl Display for ObjectId where - Kind: FromApub + ApubObject + Send + 'static, + Kind: FromApub + ApubObject + Send + 'static, for<'de> ::ApubType: serde::Deserialize<'de>, { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { @@ -136,7 +138,7 @@ where impl From> for Url where - Kind: FromApub + ApubObject + Send + 'static, + Kind: FromApub + ApubObject + Send + 'static, for<'de> ::ApubType: serde::Deserialize<'de>, { fn from(id: ObjectId) -> Self { @@ -146,7 +148,7 @@ where impl From> for DbUrl where - Kind: FromApub + ApubObject + Send + 'static, + Kind: FromApub + ApubObject + Send + 'static, for<'de> ::ApubType: serde::Deserialize<'de>, { fn from(id: ObjectId) -> Self { diff --git a/crates/apub/src/fetcher/post_or_comment.rs b/crates/apub/src/fetcher/post_or_comment.rs index 9d700c96..fd78ad79 100644 --- a/crates/apub/src/fetcher/post_or_comment.rs +++ b/crates/apub/src/fetcher/post_or_comment.rs @@ -1,11 +1,10 @@ -use crate::objects::{comment::Note, post::Page, FromApub}; -use activitystreams::chrono::NaiveDateTime; -use diesel::PgConnection; -use lemmy_apub_lib::traits::ApubObject; -use lemmy_db_schema::source::{ - comment::{Comment, CommentForm}, - post::{Post, PostForm}, +use crate::objects::{ + comment::{ApubComment, Note}, + post::{ApubPost, Page}, }; +use activitystreams::chrono::NaiveDateTime; +use lemmy_apub_lib::traits::{ApubObject, FromApub}; +use lemmy_db_schema::source::{comment::CommentForm, post::PostForm}; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use serde::Deserialize; @@ -13,8 +12,8 @@ use url::Url; #[derive(Clone, Debug)] pub enum PostOrComment { - Post(Box), - Comment(Comment), + Post(Box), + Comment(ApubComment), } pub enum PostOrCommentForm { @@ -31,28 +30,33 @@ pub enum PageOrNote { #[async_trait::async_trait(?Send)] impl ApubObject for PostOrComment { - type DataType = PgConnection; + type DataType = LemmyContext; fn last_refreshed_at(&self) -> Option { None } // TODO: this can probably be implemented using a single sql query - fn read_from_apub_id(conn: &PgConnection, object_id: Url) -> Result, LemmyError> + async fn read_from_apub_id( + object_id: Url, + data: &Self::DataType, + ) -> Result, LemmyError> where Self: Sized, { - let post = Post::read_from_apub_id(conn, object_id.clone())?; + let post = ApubPost::read_from_apub_id(object_id.clone(), data).await?; Ok(match post { Some(o) => Some(PostOrComment::Post(Box::new(o))), - None => Comment::read_from_apub_id(conn, object_id)?.map(PostOrComment::Comment), + None => ApubComment::read_from_apub_id(object_id, data) + .await? + .map(PostOrComment::Comment), }) } - fn delete(self, data: &Self::DataType) -> Result<(), LemmyError> { + async fn delete(self, data: &Self::DataType) -> Result<(), LemmyError> { match self { - PostOrComment::Post(p) => p.delete(data), - PostOrComment::Comment(c) => c.delete(data), + PostOrComment::Post(p) => p.delete(data).await, + PostOrComment::Comment(c) => c.delete(data).await, } } } @@ -60,6 +64,7 @@ impl ApubObject for PostOrComment { #[async_trait::async_trait(?Send)] impl FromApub for PostOrComment { type ApubType = PageOrNote; + type DataType = LemmyContext; async fn from_apub( apub: &PageOrNote, @@ -72,10 +77,10 @@ impl FromApub for PostOrComment { { Ok(match apub { PageOrNote::Page(p) => PostOrComment::Post(Box::new( - Post::from_apub(p, context, expected_domain, request_counter).await?, + ApubPost::from_apub(p, context, expected_domain, request_counter).await?, )), PageOrNote::Note(n) => PostOrComment::Comment( - Comment::from_apub(n, context, expected_domain, request_counter).await?, + ApubComment::from_apub(n, context, expected_domain, request_counter).await?, ), }) } diff --git a/crates/apub/src/fetcher/search.rs b/crates/apub/src/fetcher/search.rs index d7b9a0bf..c67eaa0f 100644 --- a/crates/apub/src/fetcher/search.rs +++ b/crates/apub/src/fetcher/search.rs @@ -1,18 +1,22 @@ use crate::{ fetcher::object_id::ObjectId, - objects::{comment::Note, community::Group, person::Person as ApubPerson, post::Page, FromApub}, + objects::{ + comment::{ApubComment, Note}, + community::{ApubCommunity, Group}, + person::{ApubPerson, Person}, + post::{ApubPost, Page}, + }, }; use activitystreams::chrono::NaiveDateTime; use anyhow::anyhow; -use diesel::PgConnection; use itertools::Itertools; use lemmy_api_common::blocking; use lemmy_apub_lib::{ - traits::ApubObject, + traits::{ApubObject, FromApub}, webfinger::{webfinger_resolve_actor, WebfingerType}, }; use lemmy_db_schema::{ - source::{comment::Comment, community::Community, person::Person, post::Post}, + source::{community::Community, person::Person as DbPerson}, DbPool, }; use lemmy_utils::LemmyError; @@ -73,10 +77,14 @@ async fn find_local_actor_by_name( let name: String = name.into(); Ok(match kind { WebfingerType::Group => SearchableObjects::Community( - blocking(pool, move |conn| Community::read_from_name(conn, &name)).await??, + blocking(pool, move |conn| Community::read_from_name(conn, &name)) + .await?? + .into(), ), WebfingerType::Person => SearchableObjects::Person( - blocking(pool, move |conn| Person::find_by_name(conn, &name)).await??, + blocking(pool, move |conn| DbPerson::find_by_name(conn, &name)) + .await?? + .into(), ), }) } @@ -84,23 +92,24 @@ async fn find_local_actor_by_name( /// The types of ActivityPub objects that can be fetched directly by searching for their ID. #[derive(Debug)] pub enum SearchableObjects { - Person(Person), - Community(Community), - Post(Post), - Comment(Comment), + Person(ApubPerson), + Community(ApubCommunity), + Post(ApubPost), + Comment(ApubComment), } #[derive(Deserialize)] #[serde(untagged)] pub enum SearchableApubTypes { Group(Group), - Person(ApubPerson), + Person(Person), Page(Page), Note(Note), } +#[async_trait::async_trait(?Send)] impl ApubObject for SearchableObjects { - type DataType = PgConnection; + type DataType = LemmyContext; fn last_refreshed_at(&self) -> Option { match self { @@ -116,32 +125,35 @@ impl ApubObject for SearchableObjects { // a single query. // we could skip this and always return an error, but then it would always fetch objects // over http, and not be able to mark objects as deleted that were deleted by remote server. - fn read_from_apub_id(conn: &PgConnection, object_id: Url) -> Result, LemmyError> { - let c = Community::read_from_apub_id(conn, object_id.clone())?; + async fn read_from_apub_id( + object_id: Url, + context: &LemmyContext, + ) -> Result, LemmyError> { + let c = ApubCommunity::read_from_apub_id(object_id.clone(), context).await?; if let Some(c) = c { return Ok(Some(SearchableObjects::Community(c))); } - let p = Person::read_from_apub_id(conn, object_id.clone())?; + let p = ApubPerson::read_from_apub_id(object_id.clone(), context).await?; if let Some(p) = p { return Ok(Some(SearchableObjects::Person(p))); } - let p = Post::read_from_apub_id(conn, object_id.clone())?; + let p = ApubPost::read_from_apub_id(object_id.clone(), context).await?; if let Some(p) = p { return Ok(Some(SearchableObjects::Post(p))); } - let c = Comment::read_from_apub_id(conn, object_id)?; + let c = ApubComment::read_from_apub_id(object_id, context).await?; if let Some(c) = c { return Ok(Some(SearchableObjects::Comment(c))); } Ok(None) } - fn delete(self, conn: &Self::DataType) -> Result<(), LemmyError> { + async fn delete(self, data: &Self::DataType) -> Result<(), LemmyError> { match self { - SearchableObjects::Person(p) => p.delete(conn), - SearchableObjects::Community(c) => c.delete(conn), - SearchableObjects::Post(p) => p.delete(conn), - SearchableObjects::Comment(c) => c.delete(conn), + SearchableObjects::Person(p) => p.delete(data).await, + SearchableObjects::Community(c) => c.delete(data).await, + SearchableObjects::Post(p) => p.delete(data).await, + SearchableObjects::Comment(c) => c.delete(data).await, } } } @@ -149,6 +161,7 @@ impl ApubObject for SearchableObjects { #[async_trait::async_trait(?Send)] impl FromApub for SearchableObjects { type ApubType = SearchableApubTypes; + type DataType = LemmyContext; async fn from_apub( apub: &Self::ApubType, @@ -159,10 +172,10 @@ impl FromApub for SearchableObjects { use SearchableApubTypes as SAT; use SearchableObjects as SO; Ok(match apub { - SAT::Group(g) => SO::Community(Community::from_apub(g, context, ed, rc).await?), - SAT::Person(p) => SO::Person(Person::from_apub(p, context, ed, rc).await?), - SAT::Page(p) => SO::Post(Post::from_apub(p, context, ed, rc).await?), - SAT::Note(n) => SO::Comment(Comment::from_apub(n, context, ed, rc).await?), + SAT::Group(g) => SO::Community(ApubCommunity::from_apub(g, context, ed, rc).await?), + SAT::Person(p) => SO::Person(ApubPerson::from_apub(p, context, ed, rc).await?), + SAT::Page(p) => SO::Post(ApubPost::from_apub(p, context, ed, rc).await?), + SAT::Note(n) => SO::Comment(ApubComment::from_apub(n, context, ed, rc).await?), }) } } diff --git a/crates/apub/src/http/comment.rs b/crates/apub/src/http/comment.rs index 769a9dd8..58543a5b 100644 --- a/crates/apub/src/http/comment.rs +++ b/crates/apub/src/http/comment.rs @@ -1,10 +1,11 @@ use crate::{ http::{create_apub_response, create_apub_tombstone_response}, - objects::ToApub, + objects::comment::ApubComment, }; use actix_web::{body::Body, web, web::Path, HttpResponse}; use diesel::result::Error::NotFound; use lemmy_api_common::blocking; +use lemmy_apub_lib::traits::ToApub; use lemmy_db_schema::{newtypes::CommentId, source::comment::Comment, traits::Crud}; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; @@ -21,7 +22,9 @@ pub(crate) async fn get_apub_comment( context: web::Data, ) -> Result, LemmyError> { let id = CommentId(info.comment_id.parse::()?); - let comment = blocking(context.pool(), move |conn| Comment::read(conn, id)).await??; + let comment: ApubComment = blocking(context.pool(), move |conn| Comment::read(conn, id)) + .await?? + .into(); if !comment.local { return Err(NotFound.into()); } diff --git a/crates/apub/src/http/community.rs b/crates/apub/src/http/community.rs index 2094d15d..7c66ca9d 100644 --- a/crates/apub/src/http/community.rs +++ b/crates/apub/src/http/community.rs @@ -14,7 +14,7 @@ use crate::{ payload_to_string, receive_activity, }, - objects::ToApub, + objects::community::ApubCommunity, }; use activitystreams::{ base::{AnyBase, BaseExt}, @@ -23,7 +23,7 @@ use activitystreams::{ }; use actix_web::{body::Body, web, web::Payload, HttpRequest, HttpResponse}; use lemmy_api_common::blocking; -use lemmy_apub_lib::traits::{ActivityFields, ActivityHandler}; +use lemmy_apub_lib::traits::{ActivityFields, ActivityHandler, ToApub}; use lemmy_db_schema::source::{activity::Activity, community::Community}; use lemmy_db_views_actor::{ community_follower_view::CommunityFollowerView, @@ -44,10 +44,11 @@ pub(crate) async fn get_apub_community_http( info: web::Path, context: web::Data, ) -> Result, LemmyError> { - let community = blocking(context.pool(), move |conn| { + let community: ApubCommunity = blocking(context.pool(), move |conn| { Community::read_from_name(conn, &info.community_name) }) - .await??; + .await?? + .into(); if !community.deleted { let apub = community.to_apub(context.pool()).await?; @@ -173,10 +174,11 @@ pub(crate) async fn get_apub_community_moderators( info: web::Path, context: web::Data, ) -> Result, LemmyError> { - let community = blocking(context.pool(), move |conn| { + let community: ApubCommunity = blocking(context.pool(), move |conn| { Community::read_from_name(conn, &info.community_name) }) - .await??; + .await?? + .into(); // The attributed to, is an ordered vector with the creator actor_ids first, // then the rest of the moderators diff --git a/crates/apub/src/http/person.rs b/crates/apub/src/http/person.rs index 36b6b7d9..16f1bc5c 100644 --- a/crates/apub/src/http/person.rs +++ b/crates/apub/src/http/person.rs @@ -16,7 +16,7 @@ use crate::{ payload_to_string, receive_activity, }, - objects::ToApub, + objects::person::ApubPerson, }; use activitystreams::{ base::BaseExt, @@ -24,7 +24,7 @@ use activitystreams::{ }; use actix_web::{body::Body, web, web::Payload, HttpRequest, HttpResponse}; use lemmy_api_common::blocking; -use lemmy_apub_lib::traits::{ActivityFields, ActivityHandler}; +use lemmy_apub_lib::traits::{ActivityFields, ActivityHandler, ToApub}; use lemmy_db_schema::source::person::Person; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; @@ -44,10 +44,11 @@ pub(crate) async fn get_apub_person_http( ) -> Result, LemmyError> { let user_name = info.into_inner().user_name; // TODO: this needs to be able to read deleted persons, so that it can send tombstones - let person = blocking(context.pool(), move |conn| { + let person: ApubPerson = blocking(context.pool(), move |conn| { Person::find_by_name(conn, &user_name) }) - .await??; + .await?? + .into(); if !person.deleted { let apub = person.to_apub(context.pool()).await?; diff --git a/crates/apub/src/http/post.rs b/crates/apub/src/http/post.rs index c5f22d01..2ce712bc 100644 --- a/crates/apub/src/http/post.rs +++ b/crates/apub/src/http/post.rs @@ -1,10 +1,11 @@ use crate::{ http::{create_apub_response, create_apub_tombstone_response}, - objects::ToApub, + objects::post::ApubPost, }; use actix_web::{body::Body, web, HttpResponse}; use diesel::result::Error::NotFound; use lemmy_api_common::blocking; +use lemmy_apub_lib::traits::ToApub; use lemmy_db_schema::{newtypes::PostId, source::post::Post, traits::Crud}; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; @@ -21,7 +22,9 @@ pub(crate) async fn get_apub_post( context: web::Data, ) -> Result, LemmyError> { let id = PostId(info.post_id.parse::()?); - let post = blocking(context.pool(), move |conn| Post::read(conn, id)).await??; + let post: ApubPost = blocking(context.pool(), move |conn| Post::read(conn, id)) + .await?? + .into(); if !post.local { return Err(NotFound.into()); } diff --git a/crates/apub/src/objects/comment.rs b/crates/apub/src/objects/comment.rs index 1748af79..5745778c 100644 --- a/crates/apub/src/objects/comment.rs +++ b/crates/apub/src/objects/comment.rs @@ -3,11 +3,12 @@ use crate::{ context::lemmy_context, fetcher::object_id::ObjectId, migrations::CommentInReplyToMigration, - objects::{create_tombstone, FromApub, Source, ToApub}, + objects::{create_tombstone, person::ApubPerson, post::ApubPost, Source}, PostOrComment, }; use activitystreams::{ base::AnyBase, + chrono::NaiveDateTime, object::{kind::NoteType, Tombstone}, primitives::OneOrMany, unparsed::Unparsed, @@ -16,7 +17,7 @@ use anyhow::{anyhow, Context}; use chrono::{DateTime, FixedOffset}; use lemmy_api_common::blocking; use lemmy_apub_lib::{ - traits::ActorType, + traits::{ApubObject, FromApub, ToApub}, values::{MediaTypeHtml, MediaTypeMarkdown, PublicUrl}, verify::verify_domains_match, }; @@ -50,7 +51,7 @@ pub struct Note { context: OneOrMany, r#type: NoteType, id: Url, - pub(crate) attributed_to: ObjectId, + pub(crate) attributed_to: ObjectId, /// Indicates that the object is publicly readable. Unlike [`Post.to`], this one doesn't contain /// the community ID, as it would be incompatible with Pleroma (and we can get the community from /// the post in [`in_reply_to`]). @@ -78,7 +79,7 @@ impl Note { &self, context: &LemmyContext, request_counter: &mut i32, - ) -> Result<(Post, Option), LemmyError> { + ) -> Result<(ApubPost, Option), LemmyError> { match &self.in_reply_to { CommentInReplyToMigration::Old(in_reply_to) => { // This post, or the parent comment might not yet exist on this server yet, fetch them. @@ -90,7 +91,7 @@ impl Note { // Nested comments will automatically get fetched recursively let parent_id: Option = match in_reply_to.get(1) { Some(comment_id) => { - let comment_id = ObjectId::::new(comment_id.clone()); + let comment_id = ObjectId::::new(comment_id.clone()); let parent_comment = Box::pin(comment_id.dereference(context, request_counter)).await?; Some(parent_comment.id) @@ -104,16 +105,16 @@ impl Note { let parent = Box::pin(in_reply_to.dereference(context, request_counter).await?); match parent.deref() { PostOrComment::Post(p) => { - // Workaround because I cant figure ut how to get the post out of the box (and we dont + // Workaround because I cant figure out how to get the post out of the box (and we dont // want to stackoverflow in a deep comment hierarchy). let post_id = p.id; let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; - Ok((post, None)) + Ok((post.into(), None)) } PostOrComment::Comment(c) => { let post_id = c.post_id; let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; - Ok((post, Some(c.id))) + Ok((post.into(), Some(c.id))) } } } @@ -138,7 +139,7 @@ impl Note { verify_domains_match(self.attributed_to.inner(), &self.id)?; verify_person_in_community( &self.attributed_to, - &ObjectId::new(community.actor_id()), + &ObjectId::new(community.actor_id), context, request_counter, ) @@ -147,9 +148,57 @@ impl Note { } } +#[derive(Clone, Debug)] +pub struct ApubComment(Comment); + +impl Deref for ApubComment { + type Target = Comment; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From for ApubComment { + fn from(c: Comment) -> Self { + ApubComment { 0: c } + } +} + +#[async_trait::async_trait(?Send)] +impl ApubObject for ApubComment { + type DataType = LemmyContext; + + fn last_refreshed_at(&self) -> Option { + None + } + + async fn read_from_apub_id( + object_id: Url, + context: &LemmyContext, + ) -> Result, LemmyError> { + Ok( + blocking(context.pool(), move |conn| { + Comment::read_from_apub_id(conn, object_id) + }) + .await?? + .map(Into::into), + ) + } + + async fn delete(self, context: &LemmyContext) -> Result<(), LemmyError> { + blocking(context.pool(), move |conn| { + Comment::update_deleted(conn, self.id, true) + }) + .await??; + Ok(()) + } +} + #[async_trait::async_trait(?Send)] -impl ToApub for Comment { +impl ToApub for ApubComment { type ApubType = Note; + type TombstoneType = Tombstone; + type DataType = DbPool; async fn to_apub(&self, pool: &DbPool) -> Result { let creator_id = self.creator_id; @@ -200,8 +249,9 @@ impl ToApub for Comment { } #[async_trait::async_trait(?Send)] -impl FromApub for Comment { +impl FromApub for ApubComment { type ApubType = Note; + type DataType = LemmyContext; /// Converts a `Note` to `Comment`. /// @@ -211,7 +261,7 @@ impl FromApub for Comment { context: &LemmyContext, expected_domain: &Url, request_counter: &mut i32, - ) -> Result { + ) -> Result { let ap_id = Some(note.id(expected_domain)?.clone().into()); let creator = note .attributed_to @@ -238,6 +288,7 @@ impl FromApub for Comment { ap_id, local: Some(false), }; - Ok(blocking(context.pool(), move |conn| Comment::upsert(conn, &form)).await??) + let comment = blocking(context.pool(), move |conn| Comment::upsert(conn, &form)).await??; + Ok(comment.into()) } } diff --git a/crates/apub/src/objects/community.rs b/crates/apub/src/objects/community.rs index 324ae259..56a86cc8 100644 --- a/crates/apub/src/objects/community.rs +++ b/crates/apub/src/objects/community.rs @@ -4,12 +4,13 @@ use crate::{ fetcher::community::{fetch_community_outbox, update_community_mods}, generate_moderators_url, generate_outbox_url, - objects::{create_tombstone, FromApub, ImageObject, Source, ToApub}, + objects::{create_tombstone, ImageObject, Source}, CommunityType, }; use activitystreams::{ actor::{kind::GroupType, Endpoints}, base::AnyBase, + chrono::NaiveDateTime, object::{kind::ImageType, Tombstone}, primitives::OneOrMany, unparsed::Unparsed, @@ -19,7 +20,7 @@ use itertools::Itertools; use lemmy_api_common::blocking; use lemmy_apub_lib::{ signatures::PublicKey, - traits::ActorType, + traits::{ActorType, ApubObject, FromApub, ToApub}, values::{MediaTypeHtml, MediaTypeMarkdown}, verify::verify_domains_match, }; @@ -37,6 +38,7 @@ use lemmy_utils::{ use lemmy_websocket::LemmyContext; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use std::ops::Deref; use url::Url; #[skip_serializing_none] @@ -117,9 +119,83 @@ impl Group { } } +#[derive(Clone, Debug)] +pub struct ApubCommunity(Community); + +impl Deref for ApubCommunity { + type Target = Community; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From for ApubCommunity { + fn from(c: Community) -> Self { + ApubCommunity { 0: c } + } +} + +#[async_trait::async_trait(?Send)] +impl ApubObject for ApubCommunity { + type DataType = LemmyContext; + + fn last_refreshed_at(&self) -> Option { + Some(self.last_refreshed_at) + } + + async fn read_from_apub_id( + object_id: Url, + context: &LemmyContext, + ) -> Result, LemmyError> { + Ok( + blocking(context.pool(), move |conn| { + Community::read_from_apub_id(conn, object_id) + }) + .await?? + .map(Into::into), + ) + } + + async fn delete(self, context: &LemmyContext) -> Result<(), LemmyError> { + blocking(context.pool(), move |conn| { + Community::update_deleted(conn, self.id, true) + }) + .await??; + Ok(()) + } +} + +impl ActorType for ApubCommunity { + fn is_local(&self) -> bool { + self.local + } + fn actor_id(&self) -> Url { + self.actor_id.to_owned().into() + } + fn name(&self) -> String { + self.name.clone() + } + fn public_key(&self) -> Option { + self.public_key.to_owned() + } + fn private_key(&self) -> Option { + self.private_key.to_owned() + } + + fn inbox_url(&self) -> Url { + self.inbox_url.clone().into() + } + + fn shared_inbox_url(&self) -> Option { + self.shared_inbox_url.clone().map(|s| s.into_inner()) + } +} + #[async_trait::async_trait(?Send)] -impl ToApub for Community { +impl ToApub for ApubCommunity { type ApubType = Group; + type TombstoneType = Tombstone; + type DataType = DbPool; async fn to_apub(&self, _pool: &DbPool) -> Result { let source = self.description.clone().map(|bio| Source { @@ -174,8 +250,9 @@ impl ToApub for Community { } #[async_trait::async_trait(?Send)] -impl FromApub for Community { +impl FromApub for ApubCommunity { type ApubType = Group; + type DataType = LemmyContext; /// Converts a `Group` to `Community`, inserts it into the database and updates moderators. async fn from_apub( @@ -183,7 +260,7 @@ impl FromApub for Community { context: &LemmyContext, expected_domain: &Url, request_counter: &mut i32, - ) -> Result { + ) -> Result { let form = Group::from_apub_to_form(group, expected_domain, &context.settings()).await?; let community = blocking(context.pool(), move |conn| Community::upsert(conn, &form)).await??; @@ -192,7 +269,7 @@ impl FromApub for Community { // TODO: doing this unconditionally might cause infinite loop for some reason fetch_community_outbox(context, &group.outbox, request_counter).await?; - Ok(community) + Ok(community.into()) } } diff --git a/crates/apub/src/objects/mod.rs b/crates/apub/src/objects/mod.rs index b558902f..c5f20c29 100644 --- a/crates/apub/src/objects/mod.rs +++ b/crates/apub/src/objects/mod.rs @@ -5,43 +5,14 @@ use activitystreams::{ use anyhow::anyhow; use chrono::NaiveDateTime; use lemmy_apub_lib::values::MediaTypeMarkdown; -use lemmy_db_schema::DbPool; use lemmy_utils::{utils::convert_datetime, LemmyError}; -use lemmy_websocket::LemmyContext; use url::Url; -pub(crate) mod comment; -pub(crate) mod community; -pub(crate) mod person; -pub(crate) mod post; -pub(crate) mod private_message; - -/// Trait for converting an object or actor into the respective ActivityPub type. -#[async_trait::async_trait(?Send)] -pub(crate) trait ToApub { - type ApubType; - async fn to_apub(&self, pool: &DbPool) -> Result; - fn to_tombstone(&self) -> Result; -} - -#[async_trait::async_trait(?Send)] -pub trait FromApub { - type ApubType; - /// Converts an object from ActivityPub type to Lemmy internal type. - /// - /// * `apub` The object to read from - /// * `context` LemmyContext which holds DB pool, HTTP client etc - /// * `expected_domain` Domain where the object was received from. None in case of mod action. - /// * `mod_action_allowed` True if the object can be a mod activity, ignore `expected_domain` in this case - async fn from_apub( - apub: &Self::ApubType, - context: &LemmyContext, - expected_domain: &Url, - request_counter: &mut i32, - ) -> Result - where - Self: Sized; -} +pub mod comment; +pub mod community; +pub mod person; +pub mod post; +pub mod private_message; #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] diff --git a/crates/apub/src/objects/person.rs b/crates/apub/src/objects/person.rs index 45c67f44..53503d00 100644 --- a/crates/apub/src/objects/person.rs +++ b/crates/apub/src/objects/person.rs @@ -2,20 +2,21 @@ use crate::{ check_is_apub_id_valid, context::lemmy_context, generate_outbox_url, - objects::{FromApub, ImageObject, Source, ToApub}, + objects::{ImageObject, Source}, }; use activitystreams::{ actor::Endpoints, base::AnyBase, - chrono::{DateTime, FixedOffset}, + chrono::NaiveDateTime, object::{kind::ImageType, Tombstone}, primitives::OneOrMany, unparsed::Unparsed, }; +use chrono::{DateTime, FixedOffset}; use lemmy_api_common::blocking; use lemmy_apub_lib::{ signatures::PublicKey, - traits::ActorType, + traits::{ActorType, ApubObject, FromApub, ToApub}, values::{MediaTypeHtml, MediaTypeMarkdown}, verify::verify_domains_match, }; @@ -31,6 +32,7 @@ use lemmy_utils::{ use lemmy_websocket::LemmyContext; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use std::ops::Deref; use url::Url; #[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq)] @@ -79,9 +81,85 @@ impl Person { } } +#[derive(Clone, Debug)] +pub struct ApubPerson(DbPerson); + +impl Deref for ApubPerson { + type Target = DbPerson; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From for ApubPerson { + fn from(p: DbPerson) -> Self { + ApubPerson { 0: p } + } +} + #[async_trait::async_trait(?Send)] -impl ToApub for DbPerson { +impl ApubObject for ApubPerson { + type DataType = LemmyContext; + + fn last_refreshed_at(&self) -> Option { + Some(self.last_refreshed_at) + } + + async fn read_from_apub_id( + object_id: Url, + context: &LemmyContext, + ) -> Result, LemmyError> { + Ok( + blocking(context.pool(), move |conn| { + DbPerson::read_from_apub_id(conn, object_id) + }) + .await?? + .map(Into::into), + ) + } + + async fn delete(self, context: &LemmyContext) -> Result<(), LemmyError> { + blocking(context.pool(), move |conn| { + DbPerson::update_deleted(conn, self.id, true) + }) + .await??; + Ok(()) + } +} + +impl ActorType for ApubPerson { + fn is_local(&self) -> bool { + self.local + } + fn actor_id(&self) -> Url { + self.actor_id.to_owned().into_inner() + } + fn name(&self) -> String { + self.name.clone() + } + + fn public_key(&self) -> Option { + self.public_key.to_owned() + } + + fn private_key(&self) -> Option { + self.private_key.to_owned() + } + + fn inbox_url(&self) -> Url { + self.inbox_url.clone().into() + } + + fn shared_inbox_url(&self) -> Option { + self.shared_inbox_url.clone().map(|s| s.into_inner()) + } +} + +#[async_trait::async_trait(?Send)] +impl ToApub for ApubPerson { type ApubType = Person; + type TombstoneType = Tombstone; + type DataType = DbPool; async fn to_apub(&self, _pool: &DbPool) -> Result { let kind = if self.bot_account { @@ -133,15 +211,16 @@ impl ToApub for DbPerson { } #[async_trait::async_trait(?Send)] -impl FromApub for DbPerson { +impl FromApub for ApubPerson { type ApubType = Person; + type DataType = LemmyContext; async fn from_apub( person: &Person, context: &LemmyContext, expected_domain: &Url, _request_counter: &mut i32, - ) -> Result { + ) -> Result { let actor_id = Some(person.id(expected_domain)?.clone().into()); let name = person.preferred_username.clone(); let display_name: Option = person.name.clone(); @@ -184,6 +263,6 @@ impl FromApub for DbPerson { DbPerson::upsert(conn, &person_form) }) .await??; - Ok(person) + Ok(person.into()) } } diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index bb301038..dc6d9651 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -2,7 +2,7 @@ use crate::{ activities::{extract_community, verify_person_in_community}, context::lemmy_context, fetcher::object_id::ObjectId, - objects::{create_tombstone, FromApub, ImageObject, Source, ToApub}, + objects::{create_tombstone, person::ApubPerson, ImageObject, Source}, }; use activitystreams::{ base::AnyBase, @@ -14,10 +14,10 @@ use activitystreams::{ public, unparsed::Unparsed, }; -use chrono::{DateTime, FixedOffset}; +use chrono::{DateTime, FixedOffset, NaiveDateTime}; use lemmy_api_common::blocking; use lemmy_apub_lib::{ - traits::ActorType, + traits::{ActorType, ApubObject, FromApub, ToApub}, values::{MediaTypeHtml, MediaTypeMarkdown}, verify::verify_domains_match, }; @@ -39,6 +39,7 @@ use lemmy_utils::{ use lemmy_websocket::LemmyContext; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use std::ops::Deref; use url::Url; #[skip_serializing_none] @@ -49,7 +50,7 @@ pub struct Page { context: OneOrMany, r#type: PageType, id: Url, - pub(crate) attributed_to: ObjectId, + pub(crate) attributed_to: ObjectId, to: [Url; 2], name: String, content: Option, @@ -80,7 +81,7 @@ impl Page { /// /// Both stickied and locked need to be false on a newly created post (verified in [[CreatePost]]. pub(crate) async fn is_mod_action(&self, context: &LemmyContext) -> Result { - let old_post = ObjectId::::new(self.id.clone()) + let old_post = ObjectId::::new(self.id.clone()) .dereference_local(context) .await; @@ -112,9 +113,57 @@ impl Page { } } +#[derive(Clone, Debug)] +pub struct ApubPost(Post); + +impl Deref for ApubPost { + type Target = Post; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From for ApubPost { + fn from(p: Post) -> Self { + ApubPost { 0: p } + } +} + +#[async_trait::async_trait(?Send)] +impl ApubObject for ApubPost { + type DataType = LemmyContext; + + fn last_refreshed_at(&self) -> Option { + None + } + + async fn read_from_apub_id( + object_id: Url, + context: &LemmyContext, + ) -> Result, LemmyError> { + Ok( + blocking(context.pool(), move |conn| { + Post::read_from_apub_id(conn, object_id) + }) + .await?? + .map(Into::into), + ) + } + + async fn delete(self, context: &LemmyContext) -> Result<(), LemmyError> { + blocking(context.pool(), move |conn| { + Post::update_deleted(conn, self.id, true) + }) + .await??; + Ok(()) + } +} + #[async_trait::async_trait(?Send)] -impl ToApub for Post { +impl ToApub for ApubPost { type ApubType = Page; + type TombstoneType = Tombstone; + type DataType = DbPool; // Turn a Lemmy post into an ActivityPub page that can be sent out over the network. async fn to_apub(&self, pool: &DbPool) -> Result { @@ -165,15 +214,16 @@ impl ToApub for Post { } #[async_trait::async_trait(?Send)] -impl FromApub for Post { +impl FromApub for ApubPost { type ApubType = Page; + type DataType = LemmyContext; async fn from_apub( page: &Page, context: &LemmyContext, expected_domain: &Url, request_counter: &mut i32, - ) -> Result { + ) -> Result { // We can't verify the domain in case of mod action, because the mod may be on a different // instance from the post author. let ap_id = if page.is_mod_action(context).await? { @@ -222,6 +272,7 @@ impl FromApub for Post { ap_id, local: Some(false), }; - Ok(blocking(context.pool(), move |conn| Post::upsert(conn, &form)).await??) + let post = blocking(context.pool(), move |conn| Post::upsert(conn, &form)).await??; + Ok(post.into()) } } diff --git a/crates/apub/src/objects/private_message.rs b/crates/apub/src/objects/private_message.rs index 8cf40c9e..cde1966e 100644 --- a/crates/apub/src/objects/private_message.rs +++ b/crates/apub/src/objects/private_message.rs @@ -1,10 +1,11 @@ use crate::{ context::lemmy_context, fetcher::object_id::ObjectId, - objects::{create_tombstone, FromApub, Source, ToApub}, + objects::{create_tombstone, person::ApubPerson, Source}, }; use activitystreams::{ base::AnyBase, + chrono::NaiveDateTime, object::{kind::NoteType, Tombstone}, primitives::OneOrMany, unparsed::Unparsed, @@ -13,6 +14,7 @@ use anyhow::anyhow; use chrono::{DateTime, FixedOffset}; use lemmy_api_common::blocking; use lemmy_apub_lib::{ + traits::{ApubObject, FromApub, ToApub}, values::{MediaTypeHtml, MediaTypeMarkdown}, verify::verify_domains_match, }; @@ -28,6 +30,7 @@ use lemmy_utils::{utils::convert_datetime, LemmyError}; use lemmy_websocket::LemmyContext; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use std::ops::Deref; use url::Url; #[skip_serializing_none] @@ -38,8 +41,8 @@ pub struct Note { context: OneOrMany, r#type: NoteType, id: Url, - pub(crate) attributed_to: ObjectId, - to: ObjectId, + pub(crate) attributed_to: ObjectId, + to: ObjectId, content: String, media_type: MediaTypeHtml, source: Source, @@ -75,9 +78,54 @@ impl Note { } } +#[derive(Clone, Debug)] +pub struct ApubPrivateMessage(PrivateMessage); + +impl Deref for ApubPrivateMessage { + type Target = PrivateMessage; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From for ApubPrivateMessage { + fn from(pm: PrivateMessage) -> Self { + ApubPrivateMessage { 0: pm } + } +} + #[async_trait::async_trait(?Send)] -impl ToApub for PrivateMessage { +impl ApubObject for ApubPrivateMessage { + type DataType = LemmyContext; + + fn last_refreshed_at(&self) -> Option { + None + } + + async fn read_from_apub_id( + object_id: Url, + context: &LemmyContext, + ) -> Result, LemmyError> { + Ok( + blocking(context.pool(), move |conn| { + PrivateMessage::read_from_apub_id(conn, object_id) + }) + .await?? + .map(Into::into), + ) + } + + async fn delete(self, _context: &LemmyContext) -> Result<(), LemmyError> { + // do nothing, because pm can't be fetched over http + unimplemented!() + } +} + +#[async_trait::async_trait(?Send)] +impl ToApub for ApubPrivateMessage { type ApubType = Note; + type TombstoneType = Tombstone; + type DataType = DbPool; async fn to_apub(&self, pool: &DbPool) -> Result { let creator_id = self.creator_id; @@ -116,15 +164,16 @@ impl ToApub for PrivateMessage { } #[async_trait::async_trait(?Send)] -impl FromApub for PrivateMessage { +impl FromApub for ApubPrivateMessage { type ApubType = Note; + type DataType = LemmyContext; async fn from_apub( note: &Note, context: &LemmyContext, expected_domain: &Url, request_counter: &mut i32, - ) -> Result { + ) -> Result { let ap_id = Some(note.id(expected_domain)?.clone().into()); let creator = note .attributed_to @@ -143,11 +192,10 @@ impl FromApub for PrivateMessage { ap_id, local: Some(false), }; - Ok( - blocking(context.pool(), move |conn| { - PrivateMessage::upsert(conn, &form) - }) - .await??, - ) + let pm = blocking(context.pool(), move |conn| { + PrivateMessage::upsert(conn, &form) + }) + .await??; + Ok(pm.into()) } } diff --git a/crates/apub_lib/src/traits.rs b/crates/apub_lib/src/traits.rs index 16fb3f8e..c2c1d02a 100644 --- a/crates/apub_lib/src/traits.rs +++ b/crates/apub_lib/src/traits.rs @@ -27,17 +27,21 @@ pub trait ActivityHandler { ) -> Result<(), LemmyError>; } +#[async_trait::async_trait(?Send)] pub trait ApubObject { type DataType; /// If this object should be refetched after a certain interval, it should return the last refresh /// time here. This is mainly used to update remote actors. fn last_refreshed_at(&self) -> Option; /// Try to read the object with given ID from local database. Returns Ok(None) if it doesn't exist. - fn read_from_apub_id(data: &Self::DataType, object_id: Url) -> Result, LemmyError> + async fn read_from_apub_id( + object_id: Url, + data: &Self::DataType, + ) -> Result, LemmyError> where Self: Sized; /// Marks the object as deleted in local db. Called when a tombstone is received. - fn delete(self, data: &Self::DataType) -> Result<(), LemmyError>; + async fn delete(self, data: &Self::DataType) -> Result<(), LemmyError>; } /// Common methods provided by ActivityPub actors (community and person). Not all methods are @@ -67,3 +71,35 @@ pub trait ActorType { }) } } + +/// Trait for converting an object or actor into the respective ActivityPub type. +#[async_trait::async_trait(?Send)] +pub trait ToApub { + type ApubType; + type TombstoneType; + type DataType; + + async fn to_apub(&self, data: &Self::DataType) -> Result; + fn to_tombstone(&self) -> Result; +} + +#[async_trait::async_trait(?Send)] +pub trait FromApub { + type ApubType; + type DataType; + + /// Converts an object from ActivityPub type to Lemmy internal type. + /// + /// * `apub` The object to read from + /// * `context` LemmyContext which holds DB pool, HTTP client etc + /// * `expected_domain` Domain where the object was received from. None in case of mod action. + /// * `mod_action_allowed` True if the object can be a mod activity, ignore `expected_domain` in this case + async fn from_apub( + apub: &Self::ApubType, + data: &Self::DataType, + expected_domain: &Url, + request_counter: &mut i32, + ) -> Result + where + Self: Sized; +} diff --git a/crates/db_schema/Cargo.toml b/crates/db_schema/Cargo.toml index a05aed47..dc53d82b 100644 --- a/crates/db_schema/Cargo.toml +++ b/crates/db_schema/Cargo.toml @@ -12,7 +12,6 @@ doctest = false [dependencies] lemmy_utils = { version = "=0.13.0", path = "../utils" } -lemmy_apub_lib = { version = "=0.13.0", path = "../apub_lib" } diesel = { version = "1.4.8", features = ["postgres","chrono","r2d2","serde_json"] } diesel_migrations = "1.4.0" chrono = { version = "0.4.19", features = ["serde"] } diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index aa7e25b2..09389aad 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -11,10 +11,7 @@ use crate::{ }, traits::{Crud, DeleteableOrRemoveable, Likeable, Saveable}, }; -use chrono::NaiveDateTime; use diesel::{dsl::*, result::Error, *}; -use lemmy_apub_lib::traits::ApubObject; -use lemmy_utils::LemmyError; use url::Url; impl Comment { @@ -108,6 +105,17 @@ impl Comment { .set(comment_form) .get_result::(conn) } + pub fn read_from_apub_id(conn: &PgConnection, object_id: Url) -> Result, Error> { + use crate::schema::comment::dsl::*; + let object_id: DbUrl = object_id.into(); + Ok( + comment + .filter(ap_id.eq(object_id)) + .first::(conn) + .ok() + .map(Into::into), + ) + } } impl Crud for Comment { @@ -198,25 +206,6 @@ impl DeleteableOrRemoveable for Comment { } } -impl ApubObject for Comment { - type DataType = PgConnection; - - fn last_refreshed_at(&self) -> Option { - None - } - - fn read_from_apub_id(conn: &PgConnection, object_id: Url) -> Result, LemmyError> { - use crate::schema::comment::dsl::*; - let object_id: DbUrl = object_id.into(); - Ok(comment.filter(ap_id.eq(object_id)).first::(conn).ok()) - } - - fn delete(self, conn: &PgConnection) -> Result<(), LemmyError> { - Comment::update_deleted(conn, self.id, true)?; - Ok(()) - } -} - #[cfg(test)] mod tests { use crate::{ diff --git a/crates/db_schema/src/impls/community.rs b/crates/db_schema/src/impls/community.rs index 8acce790..b2ebb3a0 100644 --- a/crates/db_schema/src/impls/community.rs +++ b/crates/db_schema/src/impls/community.rs @@ -14,10 +14,7 @@ use crate::{ }, traits::{Bannable, Crud, DeleteableOrRemoveable, Followable, Joinable}, }; -use chrono::NaiveDateTime; use diesel::{dsl::*, result::Error, ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl}; -use lemmy_apub_lib::traits::{ActorType, ApubObject}; -use lemmy_utils::LemmyError; use url::Url; mod safe_type { @@ -148,6 +145,17 @@ impl Community { .set(community_form) .get_result::(conn) } + pub fn read_from_apub_id(conn: &PgConnection, object_id: Url) -> Result, Error> { + use crate::schema::community::dsl::*; + let object_id: DbUrl = object_id.into(); + Ok( + community + .filter(actor_id.eq(object_id)) + .first::(conn) + .ok() + .map(Into::into), + ) + } } impl Joinable for CommunityModerator { @@ -297,59 +305,6 @@ impl Followable for CommunityFollower { } } -impl ApubObject for Community { - type DataType = PgConnection; - - fn last_refreshed_at(&self) -> Option { - Some(self.last_refreshed_at) - } - - fn read_from_apub_id(conn: &PgConnection, object_id: Url) -> Result, LemmyError> { - use crate::schema::community::dsl::*; - let object_id: DbUrl = object_id.into(); - Ok( - community - .filter(actor_id.eq(object_id)) - .first::(conn) - .ok(), - ) - } - - fn delete(self, conn: &PgConnection) -> Result<(), LemmyError> { - use crate::schema::community::dsl::*; - diesel::update(community.find(self.id)) - .set((deleted.eq(true), updated.eq(naive_now()))) - .get_result::(conn)?; - Ok(()) - } -} - -impl ActorType for Community { - fn is_local(&self) -> bool { - self.local - } - fn actor_id(&self) -> Url { - self.actor_id.to_owned().into() - } - fn name(&self) -> String { - self.name.clone() - } - fn public_key(&self) -> Option { - self.public_key.to_owned() - } - fn private_key(&self) -> Option { - self.private_key.to_owned() - } - - fn inbox_url(&self) -> Url { - self.inbox_url.clone().into() - } - - fn shared_inbox_url(&self) -> Option { - self.shared_inbox_url.clone().map(|s| s.into_inner()) - } -} - #[cfg(test)] mod tests { use crate::{ diff --git a/crates/db_schema/src/impls/person.rs b/crates/db_schema/src/impls/person.rs index 5b7cb941..a9ba1d2e 100644 --- a/crates/db_schema/src/impls/person.rs +++ b/crates/db_schema/src/impls/person.rs @@ -5,10 +5,7 @@ use crate::{ source::person::{Person, PersonForm}, traits::Crud, }; -use chrono::NaiveDateTime; use diesel::{dsl::*, result::Error, ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl, *}; -use lemmy_apub_lib::traits::{ActorType, ApubObject}; -use lemmy_utils::LemmyError; use url::Url; mod safe_type { @@ -237,61 +234,29 @@ impl Person { .set(person_form) .get_result::(conn) } -} - -impl ApubObject for Person { - type DataType = PgConnection; - fn last_refreshed_at(&self) -> Option { - Some(self.last_refreshed_at) - } - - fn read_from_apub_id(conn: &PgConnection, object_id: Url) -> Result, LemmyError> { + pub fn read_from_apub_id(conn: &PgConnection, object_id: Url) -> Result, Error> { use crate::schema::person::dsl::*; let object_id: DbUrl = object_id.into(); Ok( person .filter(deleted.eq(false)) .filter(actor_id.eq(object_id)) - .first::(conn) - .ok(), + .first::(conn) + .ok() + .map(Into::into), ) } - fn delete(self, conn: &PgConnection) -> Result<(), LemmyError> { + pub fn update_deleted( + conn: &PgConnection, + person_id: PersonId, + new_deleted: bool, + ) -> Result { use crate::schema::person::dsl::*; - diesel::update(person.find(self.id)) - .set((deleted.eq(true), updated.eq(naive_now()))) - .get_result::(conn)?; - Ok(()) - } -} - -impl ActorType for Person { - fn is_local(&self) -> bool { - self.local - } - fn actor_id(&self) -> Url { - self.actor_id.to_owned().into_inner() - } - fn name(&self) -> String { - self.name.clone() - } - - fn public_key(&self) -> Option { - self.public_key.to_owned() - } - - fn private_key(&self) -> Option { - self.private_key.to_owned() - } - - fn inbox_url(&self) -> Url { - self.inbox_url.clone().into() - } - - fn shared_inbox_url(&self) -> Option { - self.shared_inbox_url.clone().map(|s| s.into_inner()) + diesel::update(person.find(person_id)) + .set(deleted.eq(new_deleted)) + .get_result::(conn) } } diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index 2b71c663..c1f0fcbf 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -13,10 +13,7 @@ use crate::{ }, traits::{Crud, DeleteableOrRemoveable, Likeable, Readable, Saveable}, }; -use chrono::NaiveDateTime; use diesel::{dsl::*, result::Error, ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl}; -use lemmy_apub_lib::traits::ApubObject; -use lemmy_utils::LemmyError; use url::Url; impl Crud for Post { @@ -164,6 +161,17 @@ impl Post { .set(post_form) .get_result::(conn) } + pub fn read_from_apub_id(conn: &PgConnection, object_id: Url) -> Result, Error> { + use crate::schema::post::dsl::*; + let object_id: DbUrl = object_id.into(); + Ok( + post + .filter(ap_id.eq(object_id)) + .first::(conn) + .ok() + .map(Into::into), + ) + } } impl Likeable for PostLike { @@ -248,28 +256,6 @@ impl DeleteableOrRemoveable for Post { } } -impl ApubObject for Post { - type DataType = PgConnection; - - fn last_refreshed_at(&self) -> Option { - None - } - - fn read_from_apub_id(conn: &PgConnection, object_id: Url) -> Result, LemmyError> { - use crate::schema::post::dsl::*; - let object_id: DbUrl = object_id.into(); - Ok(post.filter(ap_id.eq(object_id)).first::(conn).ok()) - } - - fn delete(self, conn: &PgConnection) -> Result<(), LemmyError> { - use crate::schema::post::dsl::*; - diesel::update(post.find(self.id)) - .set((deleted.eq(true), updated.eq(naive_now()))) - .get_result::(conn)?; - Ok(()) - } -} - #[cfg(test)] mod tests { use crate::{ diff --git a/crates/db_schema/src/impls/private_message.rs b/crates/db_schema/src/impls/private_message.rs index f252a0c0..735c775e 100644 --- a/crates/db_schema/src/impls/private_message.rs +++ b/crates/db_schema/src/impls/private_message.rs @@ -4,9 +4,7 @@ use crate::{ source::private_message::*, traits::{Crud, DeleteableOrRemoveable}, }; -use chrono::NaiveDateTime; use diesel::{dsl::*, result::Error, *}; -use lemmy_apub_lib::traits::ApubObject; use lemmy_utils::LemmyError; use url::Url; @@ -109,36 +107,27 @@ impl PrivateMessage { .set(private_message_form) .get_result::(conn) } -} - -impl DeleteableOrRemoveable for PrivateMessage { - fn blank_out_deleted_or_removed_info(mut self) -> Self { - self.content = "".into(); - self - } -} - -impl ApubObject for PrivateMessage { - type DataType = PgConnection; - fn last_refreshed_at(&self) -> Option { - None - } - - fn read_from_apub_id(conn: &PgConnection, object_id: Url) -> Result, LemmyError> { + pub fn read_from_apub_id( + conn: &PgConnection, + object_id: Url, + ) -> Result, LemmyError> { use crate::schema::private_message::dsl::*; let object_id: DbUrl = object_id.into(); Ok( private_message .filter(ap_id.eq(object_id)) - .first::(conn) - .ok(), + .first::(conn) + .ok() + .map(Into::into), ) } +} - fn delete(self, _conn: &PgConnection) -> Result<(), LemmyError> { - // do nothing, because pm can't be fetched over http - unimplemented!() +impl DeleteableOrRemoveable for PrivateMessage { + fn blank_out_deleted_or_removed_info(mut self) -> Self { + self.content = "".into(); + self } } diff --git a/crates/websocket/src/send.rs b/crates/websocket/src/send.rs index 5982e24c..95780c32 100644 --- a/crates/websocket/src/send.rs +++ b/crates/websocket/src/send.rs @@ -12,7 +12,13 @@ use lemmy_api_common::{ }; use lemmy_db_schema::{ newtypes::{CommentId, CommunityId, LocalUserId, PersonId, PostId, PrivateMessageId}, - traits::DeleteableOrRemoveable, + source::{ + comment::Comment, + person::Person, + person_mention::{PersonMention, PersonMentionForm}, + post::Post, + }, + traits::{Crud, DeleteableOrRemoveable}, }; use lemmy_db_views::{ comment_view::CommentView, @@ -21,7 +27,14 @@ use lemmy_db_views::{ private_message_view::PrivateMessageView, }; use lemmy_db_views_actor::community_view::CommunityView; -use lemmy_utils::{ConnectionId, LemmyError}; +use lemmy_utils::{ + email::send_email, + settings::structs::Settings, + utils::MentionData, + ConnectionId, + LemmyError, +}; +use log::error; pub async fn send_post_ws_message( post_id: PostId, @@ -167,3 +180,149 @@ pub async fn send_pm_ws_message( Ok(res) } + +pub async fn send_local_notifs( + mentions: Vec, + comment: &Comment, + person: &Person, + post: &Post, + do_send_email: bool, + context: &LemmyContext, +) -> Result, LemmyError> { + let mut recipient_ids = Vec::new(); + + // Send the local mentions + for mention in mentions + .iter() + .filter(|m| m.is_local(&context.settings().hostname) && m.name.ne(&person.name)) + .collect::>() + { + let mention_name = mention.name.clone(); + let user_view = blocking(context.pool(), move |conn| { + LocalUserView::read_from_name(conn, &mention_name) + }) + .await?; + if let Ok(mention_user_view) = user_view { + // TODO + // At some point, make it so you can't tag the parent creator either + // This can cause two notifications, one for reply and the other for mention + recipient_ids.push(mention_user_view.local_user.id); + + let user_mention_form = PersonMentionForm { + recipient_id: mention_user_view.person.id, + comment_id: comment.id, + read: None, + }; + + // Allow this to fail softly, since comment edits might re-update or replace it + // Let the uniqueness handle this fail + blocking(context.pool(), move |conn| { + PersonMention::create(conn, &user_mention_form) + }) + .await? + .ok(); + + // Send an email to those local users that have notifications on + if do_send_email { + send_email_to_user( + &mention_user_view, + "Mentioned by", + "Person Mention", + &comment.content, + &context.settings(), + ) + } + } + } + + // Send notifs to the parent commenter / poster + match comment.parent_id { + Some(parent_id) => { + let parent_comment = + blocking(context.pool(), move |conn| Comment::read(conn, parent_id)).await?; + if let Ok(parent_comment) = parent_comment { + // Don't send a notif to yourself + if parent_comment.creator_id != person.id { + // Get the parent commenter local_user + let user_view = blocking(context.pool(), move |conn| { + LocalUserView::read_person(conn, parent_comment.creator_id) + }) + .await?; + if let Ok(parent_user_view) = user_view { + recipient_ids.push(parent_user_view.local_user.id); + + if do_send_email { + send_email_to_user( + &parent_user_view, + "Reply from", + "Comment Reply", + &comment.content, + &context.settings(), + ) + } + } + } + } + } + // Its a post + None => { + if post.creator_id != person.id { + let creator_id = post.creator_id; + let parent_user = blocking(context.pool(), move |conn| { + LocalUserView::read_person(conn, creator_id) + }) + .await?; + if let Ok(parent_user_view) = parent_user { + recipient_ids.push(parent_user_view.local_user.id); + + if do_send_email { + send_email_to_user( + &parent_user_view, + "Reply from", + "Post Reply", + &comment.content, + &context.settings(), + ) + } + } + } + } + }; + Ok(recipient_ids) +} + +pub fn send_email_to_user( + local_user_view: &LocalUserView, + subject_text: &str, + body_text: &str, + comment_content: &str, + settings: &Settings, +) { + if local_user_view.person.banned || !local_user_view.local_user.send_notifications_to_email { + return; + } + + if let Some(user_email) = &local_user_view.local_user.email { + let subject = &format!( + "{} - {} {}", + subject_text, settings.hostname, local_user_view.person.name, + ); + let html = &format!( + "

{}


{} - {}

inbox", + body_text, + local_user_view.person.name, + comment_content, + settings.get_protocol_and_hostname() + ); + match send_email( + subject, + user_email, + &local_user_view.person.name, + html, + settings, + ) { + Ok(_o) => _o, + Err(e) => error!("{}", e), + }; + } +}