From 9cc1cfc973b6b9a3075369003eead19cf686d868 Mon Sep 17 00:00:00 2001 From: Nutomic <me@nutomic.com> Date: Tue, 8 Dec 2020 17:38:48 +0000 Subject: [PATCH] Apub local object handling (#1297) * Limit visibility of some traits and methods * WIP: alternative way to handle non-local object parsing * finish this * cleanup * Move check for locked post into Comment::from_apub() * Mark user as updated after fetching * Should set last_refreshed_at, not updated * Add ApubObject trait in DB, with method read_from_apub_id() * Create shared, generic implementation for `FromApub`, prefer local data * Check for community ban when parsing post/comment (fixes #1287) * Fix tests (changes in get_object_from_apub() prevented `Update` from working) * Support parsing `like.object` either as URL or object * Send out like.object as URL, instead of full object (fixes #1283) * add todo --- docs/src/contributing_apub_api_outline.md | 4 +- lemmy_api/src/community.rs | 3 +- lemmy_apub/src/activities/receive/comment.rs | 94 +++----------- .../src/activities/receive/comment_undo.rs | 40 ++---- .../src/activities/receive/community.rs | 6 +- lemmy_apub/src/activities/receive/post.rs | 61 ++------- .../src/activities/receive/post_undo.rs | 40 ++---- .../src/activities/receive/private_message.rs | 37 +----- lemmy_apub/src/activities/send/comment.rs | 14 +-- lemmy_apub/src/activities/send/post.rs | 14 +-- .../src/activities/send/private_message.rs | 2 +- lemmy_apub/src/activities/send/user.rs | 5 +- lemmy_apub/src/activity_queue.rs | 8 +- lemmy_apub/src/extensions/signatures.rs | 5 +- lemmy_apub/src/fetcher.rs | 101 +++++---------- lemmy_apub/src/http/comment.rs | 2 +- lemmy_apub/src/http/community.rs | 2 +- lemmy_apub/src/http/post.rs | 2 +- lemmy_apub/src/http/user.rs | 7 +- lemmy_apub/src/inbox/community_inbox.rs | 7 +- lemmy_apub/src/inbox/mod.rs | 6 +- lemmy_apub/src/inbox/receive_for_community.rs | 96 +++++++++++---- lemmy_apub/src/inbox/shared_inbox.rs | 7 +- lemmy_apub/src/inbox/user_inbox.rs | 3 +- lemmy_apub/src/lib.rs | 31 +---- lemmy_apub/src/objects/comment.rs | 44 ++++++- lemmy_apub/src/objects/community.rs | 24 +++- lemmy_apub/src/objects/mod.rs | 116 ++++++++++++++++-- lemmy_apub/src/objects/post.rs | 24 +++- lemmy_apub/src/objects/private_message.rs | 22 +++- lemmy_apub/src/objects/user.rs | 43 ++++++- lemmy_db/src/comment.rs | 33 ++--- lemmy_db/src/community.rs | 35 +++--- lemmy_db/src/lib.rs | 9 ++ lemmy_db/src/post.rs | 81 ++++++------ lemmy_db/src/private_message.rs | 45 +++---- lemmy_db/src/user.rs | 37 +++--- 37 files changed, 579 insertions(+), 531 deletions(-) diff --git a/docs/src/contributing_apub_api_outline.md b/docs/src/contributing_apub_api_outline.md index 92cdf9c0..74280bd5 100644 --- a/docs/src/contributing_apub_api_outline.md +++ b/docs/src/contributing_apub_api_outline.md @@ -438,7 +438,7 @@ Sent to: Community "cc": [ "https://ds9.lemmy.ml/c/main/" ], - "object": ... + "object": "https://enterprise.lemmy.ml/p/123" } ``` @@ -465,7 +465,7 @@ Sent to: Community "cc": [ "https://ds9.lemmy.ml/c/main/" ], - "object": ... + "object": "https://enterprise.lemmy.ml/p/123" } ``` diff --git a/lemmy_api/src/community.rs b/lemmy_api/src/community.rs index 76242020..d7de0e6b 100644 --- a/lemmy_api/src/community.rs +++ b/lemmy_api/src/community.rs @@ -20,6 +20,7 @@ use lemmy_db::{ post::Post, site::*, user_view::*, + ApubObject, Bannable, Crud, Followable, @@ -129,7 +130,7 @@ impl Perform for CreateCommunity { let actor_id = make_apub_endpoint(EndpointType::Community, &data.name).to_string(); let actor_id_cloned = actor_id.to_owned(); let community_dupe = blocking(context.pool(), move |conn| { - Community::read_from_actor_id(conn, &actor_id_cloned) + Community::read_from_apub_id(conn, &actor_id_cloned) }) .await?; if community_dupe.is_ok() { diff --git a/lemmy_apub/src/activities/receive/comment.rs b/lemmy_apub/src/activities/receive/comment.rs index bf8de1eb..ff0fb819 100644 --- a/lemmy_apub/src/activities/receive/comment.rs +++ b/lemmy_apub/src/activities/receive/comment.rs @@ -1,20 +1,13 @@ -use crate::{ - activities::receive::get_actor_as_user, - fetcher::get_or_fetch_and_insert_comment, - ActorType, - FromApub, - NoteExt, -}; +use crate::{activities::receive::get_actor_as_user, objects::FromApub, ActorType, NoteExt}; use activitystreams::{ activity::{ActorAndObjectRefExt, Create, Dislike, Like, Remove, Update}, base::ExtendsExt, }; -use anyhow::{anyhow, Context}; +use anyhow::Context; use lemmy_db::{ - comment::{Comment, CommentForm, CommentLike, CommentLikeForm}, + comment::{Comment, CommentLike, CommentLikeForm}, comment_view::CommentView, post::Post, - Crud, Likeable, }; use lemmy_structs::{blocking, comment::CommentResponse, send_local_notifs}; @@ -30,36 +23,22 @@ pub(crate) async fn receive_create_comment( let note = NoteExt::from_any_base(create.object().to_owned().one().context(location_info!())?)? .context(location_info!())?; - let comment = - CommentForm::from_apub(¬e, context, Some(user.actor_id()?), request_counter).await?; + let comment = Comment::from_apub(¬e, context, user.actor_id()?, request_counter).await?; let post_id = comment.post_id; let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; - if post.locked { - return Err(anyhow!("Post is locked").into()); - } - - let inserted_comment = - blocking(context.pool(), move |conn| Comment::upsert(conn, &comment)).await??; // Note: // Although mentions could be gotten from the post tags (they are included there), or the ccs, // Its much easier to scrape them from the comment body, since the API has to do that // anyway. - let mentions = scrape_text_for_mentions(&inserted_comment.content); - let recipient_ids = send_local_notifs( - mentions, - inserted_comment.clone(), - &user, - post, - context.pool(), - true, - ) - .await?; + let mentions = scrape_text_for_mentions(&comment.content); + let recipient_ids = + send_local_notifs(mentions, comment.clone(), &user, post, context.pool(), true).await?; // Refetch the view let comment_view = blocking(context.pool(), move |conn| { - CommentView::read(conn, inserted_comment.id, None) + CommentView::read(conn, comment.id, None) }) .await??; @@ -87,36 +66,19 @@ pub(crate) async fn receive_update_comment( .context(location_info!())?; let user = get_actor_as_user(&update, context, request_counter).await?; - let comment = - CommentForm::from_apub(¬e, context, Some(user.actor_id()?), request_counter).await?; - - let original_comment_id = - get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context, request_counter) - .await? - .id; - - let updated_comment = blocking(context.pool(), move |conn| { - Comment::update(conn, original_comment_id, &comment) - }) - .await??; + let comment = Comment::from_apub(¬e, context, user.actor_id()?, request_counter).await?; - let post_id = updated_comment.post_id; + let comment_id = comment.id; + let post_id = comment.post_id; let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; - let mentions = scrape_text_for_mentions(&updated_comment.content); - let recipient_ids = send_local_notifs( - mentions, - updated_comment, - &user, - post, - context.pool(), - false, - ) - .await?; + let mentions = scrape_text_for_mentions(&comment.content); + let recipient_ids = + send_local_notifs(mentions, comment, &user, post, context.pool(), false).await?; // Refetch the view let comment_view = blocking(context.pool(), move |conn| { - CommentView::read(conn, original_comment_id, None) + CommentView::read(conn, comment_id, None) }) .await??; @@ -137,19 +99,13 @@ pub(crate) async fn receive_update_comment( pub(crate) async fn receive_like_comment( like: Like, + comment: Comment, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - let note = NoteExt::from_any_base(like.object().to_owned().one().context(location_info!())?)? - .context(location_info!())?; let user = get_actor_as_user(&like, context, request_counter).await?; - let comment = CommentForm::from_apub(¬e, context, None, request_counter).await?; - - let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context, request_counter) - .await? - .id; - + let comment_id = comment.id; let like_form = CommentLikeForm { comment_id, post_id: comment.post_id, @@ -188,25 +144,13 @@ pub(crate) async fn receive_like_comment( pub(crate) async fn receive_dislike_comment( dislike: Dislike, + comment: Comment, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - let note = NoteExt::from_any_base( - dislike - .object() - .to_owned() - .one() - .context(location_info!())?, - )? - .context(location_info!())?; let user = get_actor_as_user(&dislike, context, request_counter).await?; - let comment = CommentForm::from_apub(¬e, context, None, request_counter).await?; - - let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context, request_counter) - .await? - .id; - + let comment_id = comment.id; let like_form = CommentLikeForm { comment_id, post_id: comment.post_id, diff --git a/lemmy_apub/src/activities/receive/comment_undo.rs b/lemmy_apub/src/activities/receive/comment_undo.rs index f44604cc..2ee8c6ea 100644 --- a/lemmy_apub/src/activities/receive/comment_undo.rs +++ b/lemmy_apub/src/activities/receive/comment_undo.rs @@ -1,35 +1,23 @@ -use crate::{ - activities::receive::get_actor_as_user, - fetcher::get_or_fetch_and_insert_comment, - FromApub, - NoteExt, -}; -use activitystreams::{activity::*, prelude::*}; -use anyhow::Context; +use crate::activities::receive::get_actor_as_user; +use activitystreams::activity::{Dislike, Like}; use lemmy_db::{ - comment::{Comment, CommentForm, CommentLike}, + comment::{Comment, CommentLike}, comment_view::CommentView, Likeable, }; use lemmy_structs::{blocking, comment::CommentResponse}; -use lemmy_utils::{location_info, LemmyError}; +use lemmy_utils::LemmyError; use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation}; pub(crate) async fn receive_undo_like_comment( like: &Like, + comment: Comment, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { let user = get_actor_as_user(like, context, request_counter).await?; - let note = NoteExt::from_any_base(like.object().to_owned().one().context(location_info!())?)? - .context(location_info!())?; - - let comment = CommentForm::from_apub(¬e, context, None, request_counter).await?; - - let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context, request_counter) - .await? - .id; + let comment_id = comment.id; let user_id = user.id; blocking(context.pool(), move |conn| { CommentLike::remove(conn, user_id, comment_id) @@ -61,25 +49,13 @@ pub(crate) async fn receive_undo_like_comment( pub(crate) async fn receive_undo_dislike_comment( dislike: &Dislike, + comment: Comment, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { let user = get_actor_as_user(dislike, context, request_counter).await?; - let note = NoteExt::from_any_base( - dislike - .object() - .to_owned() - .one() - .context(location_info!())?, - )? - .context(location_info!())?; - - let comment = CommentForm::from_apub(¬e, context, None, request_counter).await?; - - let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context, request_counter) - .await? - .id; + let comment_id = comment.id; let user_id = user.id; blocking(context.pool(), move |conn| { CommentLike::remove(conn, user_id, comment_id) diff --git a/lemmy_apub/src/activities/receive/community.rs b/lemmy_apub/src/activities/receive/community.rs index ed43b33e..b1866283 100644 --- a/lemmy_apub/src/activities/receive/community.rs +++ b/lemmy_apub/src/activities/receive/community.rs @@ -4,7 +4,7 @@ use activitystreams::{ base::{AnyBase, ExtendsExt}, }; use anyhow::Context; -use lemmy_db::{community::Community, community_view::CommunityView}; +use lemmy_db::{community::Community, community_view::CommunityView, ApubObject}; use lemmy_structs::{blocking, community::CommunityResponse}; use lemmy_utils::{location_info, LemmyError}; use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperation}; @@ -53,7 +53,7 @@ pub(crate) async fn receive_remove_community( .single_xsd_any_uri() .context(location_info!())?; let community = blocking(context.pool(), move |conn| { - Community::read_from_actor_id(conn, community_uri.as_str()) + Community::read_from_apub_id(conn, community_uri.as_str()) }) .await??; @@ -135,7 +135,7 @@ pub(crate) async fn receive_undo_remove_community( .single_xsd_any_uri() .context(location_info!())?; let community = blocking(context.pool(), move |conn| { - Community::read_from_actor_id(conn, community_uri.as_str()) + Community::read_from_apub_id(conn, community_uri.as_str()) }) .await??; diff --git a/lemmy_apub/src/activities/receive/post.rs b/lemmy_apub/src/activities/receive/post.rs index 80044237..0bbf1eaf 100644 --- a/lemmy_apub/src/activities/receive/post.rs +++ b/lemmy_apub/src/activities/receive/post.rs @@ -1,19 +1,12 @@ -use crate::{ - activities::receive::get_actor_as_user, - fetcher::get_or_fetch_and_insert_post, - ActorType, - FromApub, - PageExt, -}; +use crate::{activities::receive::get_actor_as_user, objects::FromApub, ActorType, PageExt}; use activitystreams::{ activity::{Create, Dislike, Like, Remove, Update}, prelude::*, }; use anyhow::Context; use lemmy_db::{ - post::{Post, PostForm, PostLike, PostLikeForm}, + post::{Post, PostLike, PostLikeForm}, post_view::PostView, - Crud, Likeable, }; use lemmy_structs::{blocking, post::PostResponse}; @@ -29,16 +22,12 @@ pub(crate) async fn receive_create_post( let page = PageExt::from_any_base(create.object().to_owned().one().context(location_info!())?)? .context(location_info!())?; - let post = PostForm::from_apub(&page, context, Some(user.actor_id()?), request_counter).await?; - - // Using an upsert, since likes (which fetch the post), sometimes come in before the create - // resulting in double posts. - let inserted_post = blocking(context.pool(), move |conn| Post::upsert(conn, &post)).await??; + let post = Post::from_apub(&page, context, user.actor_id()?, request_counter).await?; // Refetch the view - let inserted_post_id = inserted_post.id; + let post_id = post.id; let post_view = blocking(context.pool(), move |conn| { - PostView::read(conn, inserted_post_id, None) + PostView::read(conn, post_id, None) }) .await??; @@ -62,20 +51,12 @@ pub(crate) async fn receive_update_post( let page = PageExt::from_any_base(update.object().to_owned().one().context(location_info!())?)? .context(location_info!())?; - let post = PostForm::from_apub(&page, context, Some(user.actor_id()?), request_counter).await?; - - let original_post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context, request_counter) - .await? - .id; - - blocking(context.pool(), move |conn| { - Post::update(conn, original_post_id, &post) - }) - .await??; + let post = Post::from_apub(&page, context, user.actor_id()?, request_counter).await?; + let post_id = post.id; // Refetch the view let post_view = blocking(context.pool(), move |conn| { - PostView::read(conn, original_post_id, None) + PostView::read(conn, post_id, None) }) .await??; @@ -92,19 +73,13 @@ pub(crate) async fn receive_update_post( pub(crate) async fn receive_like_post( like: Like, + post: Post, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { let user = get_actor_as_user(&like, context, request_counter).await?; - let page = PageExt::from_any_base(like.object().to_owned().one().context(location_info!())?)? - .context(location_info!())?; - - let post = PostForm::from_apub(&page, context, None, request_counter).await?; - - let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context, request_counter) - .await? - .id; + let post_id = post.id; let like_form = PostLikeForm { post_id, user_id: user.id, @@ -136,25 +111,13 @@ pub(crate) async fn receive_like_post( pub(crate) async fn receive_dislike_post( dislike: Dislike, + post: Post, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { let user = get_actor_as_user(&dislike, context, request_counter).await?; - let page = PageExt::from_any_base( - dislike - .object() - .to_owned() - .one() - .context(location_info!())?, - )? - .context(location_info!())?; - - let post = PostForm::from_apub(&page, context, None, request_counter).await?; - - let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context, request_counter) - .await? - .id; + let post_id = post.id; let like_form = PostLikeForm { post_id, user_id: user.id, diff --git a/lemmy_apub/src/activities/receive/post_undo.rs b/lemmy_apub/src/activities/receive/post_undo.rs index 99d0ed1d..bcbb7fee 100644 --- a/lemmy_apub/src/activities/receive/post_undo.rs +++ b/lemmy_apub/src/activities/receive/post_undo.rs @@ -1,35 +1,23 @@ -use crate::{ - activities::receive::get_actor_as_user, - fetcher::get_or_fetch_and_insert_post, - FromApub, - PageExt, -}; -use activitystreams::{activity::*, prelude::*}; -use anyhow::Context; +use crate::activities::receive::get_actor_as_user; +use activitystreams::activity::{Dislike, Like}; use lemmy_db::{ - post::{Post, PostForm, PostLike}, + post::{Post, PostLike}, post_view::PostView, Likeable, }; use lemmy_structs::{blocking, post::PostResponse}; -use lemmy_utils::{location_info, LemmyError}; +use lemmy_utils::LemmyError; use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperation}; pub(crate) async fn receive_undo_like_post( like: &Like, + post: Post, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { let user = get_actor_as_user(like, context, request_counter).await?; - let page = PageExt::from_any_base(like.object().to_owned().one().context(location_info!())?)? - .context(location_info!())?; - - let post = PostForm::from_apub(&page, context, None, request_counter).await?; - - let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context, request_counter) - .await? - .id; + let post_id = post.id; let user_id = user.id; blocking(context.pool(), move |conn| { PostLike::remove(conn, user_id, post_id) @@ -55,25 +43,13 @@ pub(crate) async fn receive_undo_like_post( pub(crate) async fn receive_undo_dislike_post( dislike: &Dislike, + post: Post, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { let user = get_actor_as_user(dislike, context, request_counter).await?; - let page = PageExt::from_any_base( - dislike - .object() - .to_owned() - .one() - .context(location_info!())?, - )? - .context(location_info!())?; - - let post = PostForm::from_apub(&page, context, None, request_counter).await?; - - let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context, request_counter) - .await? - .id; + let post_id = post.id; let user_id = user.id; blocking(context.pool(), move |conn| { PostLike::remove(conn, user_id, post_id) diff --git a/lemmy_apub/src/activities/receive/private_message.rs b/lemmy_apub/src/activities/receive/private_message.rs index 8f1c95b9..25ccc520 100644 --- a/lemmy_apub/src/activities/receive/private_message.rs +++ b/lemmy_apub/src/activities/receive/private_message.rs @@ -3,7 +3,7 @@ use crate::{ check_is_apub_id_valid, fetcher::get_or_fetch_and_upsert_user, inbox::get_activity_to_and_cc, - FromApub, + objects::FromApub, NoteExt, }; use activitystreams::{ @@ -13,11 +13,7 @@ use activitystreams::{ public, }; use anyhow::{anyhow, Context}; -use lemmy_db::{ - private_message::{PrivateMessage, PrivateMessageForm}, - private_message_view::PrivateMessageView, - Crud, -}; +use lemmy_db::{private_message::PrivateMessage, private_message_view::PrivateMessageView}; use lemmy_structs::{blocking, user::PrivateMessageResponse}; use lemmy_utils::{location_info, LemmyError}; use lemmy_websocket::{messages::SendUserRoomMessage, LemmyContext, UserOperation}; @@ -41,15 +37,10 @@ pub(crate) async fn receive_create_private_message( .context(location_info!())?; let private_message = - PrivateMessageForm::from_apub(¬e, context, Some(expected_domain), request_counter).await?; - - let inserted_private_message = blocking(&context.pool(), move |conn| { - PrivateMessage::create(conn, &private_message) - }) - .await??; + PrivateMessage::from_apub(¬e, context, expected_domain, request_counter).await?; let message = blocking(&context.pool(), move |conn| { - PrivateMessageView::read(conn, inserted_private_message.id) + PrivateMessageView::read(conn, private_message.id) }) .await??; @@ -82,24 +73,8 @@ pub(crate) async fn receive_update_private_message( .to_owned(); let note = NoteExt::from_any_base(object)?.context(location_info!())?; - let private_message_form = - PrivateMessageForm::from_apub(¬e, context, Some(expected_domain), request_counter).await?; - - let private_message_ap_id = private_message_form - .ap_id - .as_ref() - .context(location_info!())? - .clone(); - let private_message = blocking(&context.pool(), move |conn| { - PrivateMessage::read_from_apub_id(conn, &private_message_ap_id) - }) - .await??; - - let private_message_id = private_message.id; - blocking(&context.pool(), move |conn| { - PrivateMessage::update(conn, private_message_id, &private_message_form) - }) - .await??; + let private_message = + PrivateMessage::from_apub(¬e, context, expected_domain, request_counter).await?; let private_message_id = private_message.id; let message = blocking(&context.pool(), move |conn| { diff --git a/lemmy_apub/src/activities/send/comment.rs b/lemmy_apub/src/activities/send/comment.rs index 2aee7b6e..744a1cdd 100644 --- a/lemmy_apub/src/activities/send/comment.rs +++ b/lemmy_apub/src/activities/send/comment.rs @@ -3,10 +3,10 @@ use crate::{ activity_queue::{send_comment_mentions, send_to_community}, extensions::context::lemmy_context, fetcher::get_or_fetch_and_upsert_user, + objects::ToApub, ActorType, ApubLikeableType, ApubObjectType, - ToApub, }; use activitystreams::{ activity::{ @@ -218,8 +218,6 @@ impl ApubObjectType for Comment { #[async_trait::async_trait(?Send)] impl ApubLikeableType for Comment { async fn send_like(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> { - let note = self.to_apub(context.pool()).await?; - let post_id = self.post_id; let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; @@ -229,7 +227,7 @@ impl ApubLikeableType for Comment { }) .await??; - let mut like = Like::new(creator.actor_id.to_owned(), note.into_any_base()?); + let mut like = Like::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?); like .set_many_contexts(lemmy_context()?) .set_id(generate_activity_id(LikeType::Like)?) @@ -241,8 +239,6 @@ impl ApubLikeableType for Comment { } async fn send_dislike(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> { - let note = self.to_apub(context.pool()).await?; - let post_id = self.post_id; let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; @@ -252,7 +248,7 @@ impl ApubLikeableType for Comment { }) .await??; - let mut dislike = Dislike::new(creator.actor_id.to_owned(), note.into_any_base()?); + let mut dislike = Dislike::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?); dislike .set_many_contexts(lemmy_context()?) .set_id(generate_activity_id(DislikeType::Dislike)?) @@ -268,8 +264,6 @@ impl ApubLikeableType for Comment { creator: &User_, context: &LemmyContext, ) -> Result<(), LemmyError> { - let note = self.to_apub(context.pool()).await?; - let post_id = self.post_id; let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; @@ -279,7 +273,7 @@ impl ApubLikeableType for Comment { }) .await??; - let mut like = Like::new(creator.actor_id.to_owned(), note.into_any_base()?); + let mut like = Like::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?); like .set_many_contexts(lemmy_context()?) .set_id(generate_activity_id(DislikeType::Dislike)?) diff --git a/lemmy_apub/src/activities/send/post.rs b/lemmy_apub/src/activities/send/post.rs index da78667f..f6eabb04 100644 --- a/lemmy_apub/src/activities/send/post.rs +++ b/lemmy_apub/src/activities/send/post.rs @@ -2,10 +2,10 @@ use crate::{ activities::send::generate_activity_id, activity_queue::send_to_community, extensions::context::lemmy_context, + objects::ToApub, ActorType, ApubLikeableType, ApubObjectType, - ToApub, }; use activitystreams::{ activity::{ @@ -167,15 +167,13 @@ impl ApubObjectType for Post { #[async_trait::async_trait(?Send)] impl ApubLikeableType for Post { async fn send_like(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> { - let page = self.to_apub(context.pool()).await?; - let community_id = self.community_id; let community = blocking(context.pool(), move |conn| { Community::read(conn, community_id) }) .await??; - let mut like = Like::new(creator.actor_id.to_owned(), page.into_any_base()?); + let mut like = Like::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?); like .set_many_contexts(lemmy_context()?) .set_id(generate_activity_id(LikeType::Like)?) @@ -187,15 +185,13 @@ impl ApubLikeableType for Post { } async fn send_dislike(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> { - let page = self.to_apub(context.pool()).await?; - let community_id = self.community_id; let community = blocking(context.pool(), move |conn| { Community::read(conn, community_id) }) .await??; - let mut dislike = Dislike::new(creator.actor_id.to_owned(), page.into_any_base()?); + let mut dislike = Dislike::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?); dislike .set_many_contexts(lemmy_context()?) .set_id(generate_activity_id(DislikeType::Dislike)?) @@ -211,15 +207,13 @@ impl ApubLikeableType for Post { creator: &User_, context: &LemmyContext, ) -> Result<(), LemmyError> { - let page = self.to_apub(context.pool()).await?; - let community_id = self.community_id; let community = blocking(context.pool(), move |conn| { Community::read(conn, community_id) }) .await??; - let mut like = Like::new(creator.actor_id.to_owned(), page.into_any_base()?); + let mut like = Like::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?); like .set_many_contexts(lemmy_context()?) .set_id(generate_activity_id(LikeType::Like)?) diff --git a/lemmy_apub/src/activities/send/private_message.rs b/lemmy_apub/src/activities/send/private_message.rs index 23528b5c..e8bc979a 100644 --- a/lemmy_apub/src/activities/send/private_message.rs +++ b/lemmy_apub/src/activities/send/private_message.rs @@ -2,9 +2,9 @@ use crate::{ activities::send::generate_activity_id, activity_queue::send_activity_single_dest, extensions::context::lemmy_context, + objects::ToApub, ActorType, ApubObjectType, - ToApub, }; use activitystreams::{ activity::{ diff --git a/lemmy_apub/src/activities/send/user.rs b/lemmy_apub/src/activities/send/user.rs index a94f241d..8c539c4b 100644 --- a/lemmy_apub/src/activities/send/user.rs +++ b/lemmy_apub/src/activities/send/user.rs @@ -16,6 +16,7 @@ use activitystreams::{ use lemmy_db::{ community::{Community, CommunityFollower, CommunityFollowerForm}, user::User_, + ApubObject, DbPool, Followable, }; @@ -46,7 +47,7 @@ impl ActorType for User_ { ) -> Result<(), LemmyError> { let follow_actor_id = follow_actor_id.to_string(); let community = blocking(context.pool(), move |conn| { - Community::read_from_actor_id(conn, &follow_actor_id) + Community::read_from_apub_id(conn, &follow_actor_id) }) .await??; @@ -77,7 +78,7 @@ impl ActorType for User_ { ) -> Result<(), LemmyError> { let follow_actor_id = follow_actor_id.to_string(); let community = blocking(context.pool(), move |conn| { - Community::read_from_actor_id(conn, &follow_actor_id) + Community::read_from_apub_id(conn, &follow_actor_id) }) .await??; diff --git a/lemmy_apub/src/activity_queue.rs b/lemmy_apub/src/activity_queue.rs index 5e4f113b..46780279 100644 --- a/lemmy_apub/src/activity_queue.rs +++ b/lemmy_apub/src/activity_queue.rs @@ -33,7 +33,7 @@ use url::Url; /// * `activity` the apub activity to be sent /// * `creator` the local actor which created the activity /// * `inbox` the inbox url where the activity should be delivered to -pub async fn send_activity_single_dest<T, Kind>( +pub(crate) async fn send_activity_single_dest<T, Kind>( activity: T, creator: &dyn ActorType, inbox: Url, @@ -71,7 +71,7 @@ where /// * `community` the sending community /// * `sender_shared_inbox` in case of an announce, this should be the shared inbox of the inner /// activities creator, as receiving a known activity will cause an error -pub async fn send_to_community_followers<T, Kind>( +pub(crate) async fn send_to_community_followers<T, Kind>( activity: T, community: &Community, context: &LemmyContext, @@ -116,7 +116,7 @@ where /// * `creator` the creator of the activity /// * `community` the destination community /// -pub async fn send_to_community<T, Kind>( +pub(crate) async fn send_to_community<T, Kind>( activity: T, creator: &User_, community: &Community, @@ -160,7 +160,7 @@ where /// * `creator` user who created the comment /// * `mentions` list of inboxes of users which are mentioned in the comment /// * `activity` either a `Create/Note` or `Update/Note` -pub async fn send_comment_mentions<T, Kind>( +pub(crate) async fn send_comment_mentions<T, Kind>( creator: &User_, mentions: Vec<Url>, activity: T, diff --git a/lemmy_apub/src/extensions/signatures.rs b/lemmy_apub/src/extensions/signatures.rs index 6ff61df4..67fe4b1c 100644 --- a/lemmy_apub/src/extensions/signatures.rs +++ b/lemmy_apub/src/extensions/signatures.rs @@ -65,7 +65,10 @@ pub async fn sign_and_send( } /// Verifies the HTTP signature on an incoming inbox request. -pub fn verify_signature(request: &HttpRequest, actor: &dyn ActorType) -> Result<(), LemmyError> { +pub(crate) fn verify_signature( + request: &HttpRequest, + actor: &dyn ActorType, +) -> Result<(), LemmyError> { let public_key = actor.public_key().context(location_info!())?; let verified = CONFIG2 .begin_verify( diff --git a/lemmy_apub/src/fetcher.rs b/lemmy_apub/src/fetcher.rs index ec44bce1..ad977f5b 100644 --- a/lemmy_apub/src/fetcher.rs +++ b/lemmy_apub/src/fetcher.rs @@ -1,7 +1,7 @@ use crate::{ check_is_apub_id_valid, + objects::FromApub, ActorType, - FromApub, GroupExt, NoteExt, PageExt, @@ -13,16 +13,16 @@ use anyhow::{anyhow, Context}; use chrono::NaiveDateTime; use diesel::result::Error::NotFound; use lemmy_db::{ - comment::{Comment, CommentForm}, + comment::Comment, comment_view::CommentView, - community::{Community, CommunityForm, CommunityModerator, CommunityModeratorForm}, + community::{Community, CommunityModerator, CommunityModeratorForm}, community_view::CommunityView, naive_now, - post::{Post, PostForm}, + post::Post, post_view::PostView, - user::{UserForm, User_}, + user::User_, user_view::UserView, - Crud, + ApubObject, Joinable, SearchType, }; @@ -184,22 +184,16 @@ pub async fn search_by_apub_id( response } SearchAcceptedObjects::Page(p) => { - let post_form = PostForm::from_apub(&p, context, Some(query_url), recursion_counter).await?; + let p = Post::from_apub(&p, context, query_url, recursion_counter).await?; - let p = blocking(context.pool(), move |conn| Post::upsert(conn, &post_form)).await??; response.posts = vec![blocking(context.pool(), move |conn| PostView::read(conn, p.id, None)).await??]; response } SearchAcceptedObjects::Comment(c) => { - let comment_form = - CommentForm::from_apub(&c, context, Some(query_url), recursion_counter).await?; + let c = Comment::from_apub(&c, context, query_url, recursion_counter).await?; - let c = blocking(context.pool(), move |conn| { - Comment::upsert(conn, &comment_form) - }) - .await??; response.comments = vec![ blocking(context.pool(), move |conn| { CommentView::read(conn, c.id, None) @@ -243,7 +237,7 @@ pub(crate) async fn get_or_fetch_and_upsert_user( ) -> Result<User_, LemmyError> { let apub_id_owned = apub_id.to_owned(); let user = blocking(context.pool(), move |conn| { - User_::read_from_actor_id(conn, apub_id_owned.as_ref()) + User_::read_from_apub_id(conn, apub_id_owned.as_ref()) }) .await?; @@ -258,15 +252,13 @@ pub(crate) async fn get_or_fetch_and_upsert_user( return Ok(u); } - let mut uf = UserForm::from_apub( - &person?, - context, - Some(apub_id.to_owned()), - recursion_counter, - ) - .await?; - uf.last_refreshed_at = Some(naive_now()); - let user = blocking(context.pool(), move |conn| User_::update(conn, u.id, &uf)).await??; + let user = User_::from_apub(&person?, context, apub_id.to_owned(), recursion_counter).await?; + + let user_id = user.id; + blocking(context.pool(), move |conn| { + User_::mark_as_updated(conn, user_id) + }) + .await??; Ok(user) } @@ -276,14 +268,7 @@ pub(crate) async fn get_or_fetch_and_upsert_user( let person = fetch_remote_object::<PersonExt>(context.client(), apub_id, recursion_counter).await?; - let uf = UserForm::from_apub( - &person, - context, - Some(apub_id.to_owned()), - recursion_counter, - ) - .await?; - let user = blocking(context.pool(), move |conn| User_::upsert(conn, &uf)).await??; + let user = User_::from_apub(&person, context, apub_id.to_owned(), recursion_counter).await?; Ok(user) } @@ -318,7 +303,7 @@ pub(crate) async fn get_or_fetch_and_upsert_community( ) -> Result<Community, LemmyError> { let apub_id_owned = apub_id.to_owned(); let community = blocking(context.pool(), move |conn| { - Community::read_from_actor_id(conn, apub_id_owned.as_str()) + Community::read_from_apub_id(conn, apub_id_owned.as_str()) }) .await?; @@ -354,9 +339,8 @@ async fn fetch_remote_community( } let group = group?; - let cf = - CommunityForm::from_apub(&group, context, Some(apub_id.to_owned()), recursion_counter).await?; - let community = blocking(context.pool(), move |conn| Community::upsert(conn, &cf)).await??; + let community = + Community::from_apub(&group, context, apub_id.to_owned(), recursion_counter).await?; // Also add the community moderators too let attributed_to = group.inner.attributed_to().context(location_info!())?; @@ -406,23 +390,13 @@ async fn fetch_remote_community( } for o in outbox_items { let page = PageExt::from_any_base(o)?.context(location_info!())?; + let page_id = page.id_unchecked().context(location_info!())?; - // The post creator may be from a blocked instance, - // if it errors, then continue - let post = match PostForm::from_apub(&page, context, None, recursion_counter).await { - Ok(post) => post, - Err(_) => continue, - }; - let post_ap_id = post.ap_id.as_ref().context(location_info!())?.clone(); - // Check whether the post already exists in the local db - let existing = blocking(context.pool(), move |conn| { - Post::read_from_apub_id(conn, &post_ap_id) - }) - .await?; - match existing { - Ok(e) => blocking(context.pool(), move |conn| Post::update(conn, e.id, &post)).await??, - Err(_) => blocking(context.pool(), move |conn| Post::upsert(conn, &post)).await??, - }; + // The post creator may be from a blocked instance, if it errors, then skip it + if check_is_apub_id_valid(page_id).is_err() { + continue; + } + Post::from_apub(&page, context, page_id.to_owned(), recursion_counter).await?; // TODO: we need to send a websocket update here } @@ -448,17 +422,9 @@ pub(crate) async fn get_or_fetch_and_insert_post( Ok(p) => Ok(p), Err(NotFound {}) => { debug!("Fetching and creating remote post: {}", post_ap_id); - let post = + let page = fetch_remote_object::<PageExt>(context.client(), post_ap_id, recursion_counter).await?; - let post_form = PostForm::from_apub( - &post, - context, - Some(post_ap_id.to_owned()), - recursion_counter, - ) - .await?; - - let post = blocking(context.pool(), move |conn| Post::upsert(conn, &post_form)).await??; + let post = Post::from_apub(&page, context, post_ap_id.to_owned(), recursion_counter).await?; Ok(post) } @@ -490,25 +456,20 @@ pub(crate) async fn get_or_fetch_and_insert_comment( ); let comment = fetch_remote_object::<NoteExt>(context.client(), comment_ap_id, recursion_counter).await?; - let comment_form = CommentForm::from_apub( + let comment = Comment::from_apub( &comment, context, - Some(comment_ap_id.to_owned()), + comment_ap_id.to_owned(), recursion_counter, ) .await?; - let post_id = comment_form.post_id; + let post_id = comment.post_id; let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; if post.locked { return Err(anyhow!("Post is locked").into()); } - let comment = blocking(context.pool(), move |conn| { - Comment::upsert(conn, &comment_form) - }) - .await??; - Ok(comment) } Err(e) => Err(e.into()), diff --git a/lemmy_apub/src/http/comment.rs b/lemmy_apub/src/http/comment.rs index 936cd981..bb3d13ac 100644 --- a/lemmy_apub/src/http/comment.rs +++ b/lemmy_apub/src/http/comment.rs @@ -1,6 +1,6 @@ use crate::{ http::{create_apub_response, create_apub_tombstone_response}, - ToApub, + objects::ToApub, }; use actix_web::{body::Body, web, web::Path, HttpResponse}; use diesel::result::Error::NotFound; diff --git a/lemmy_apub/src/http/community.rs b/lemmy_apub/src/http/community.rs index 0e2f2802..0a90571f 100644 --- a/lemmy_apub/src/http/community.rs +++ b/lemmy_apub/src/http/community.rs @@ -1,8 +1,8 @@ use crate::{ extensions::context::lemmy_context, http::{create_apub_response, create_apub_tombstone_response}, + objects::ToApub, ActorType, - ToApub, }; use activitystreams::{ base::{AnyBase, BaseExt, ExtendsExt}, diff --git a/lemmy_apub/src/http/post.rs b/lemmy_apub/src/http/post.rs index 7f4bb447..1d25ed95 100644 --- a/lemmy_apub/src/http/post.rs +++ b/lemmy_apub/src/http/post.rs @@ -1,6 +1,6 @@ use crate::{ http::{create_apub_response, create_apub_tombstone_response}, - ToApub, + objects::ToApub, }; use actix_web::{body::Body, web, HttpResponse}; use diesel::result::Error::NotFound; diff --git a/lemmy_apub/src/http/user.rs b/lemmy_apub/src/http/user.rs index 0f2e1a71..1e546d95 100644 --- a/lemmy_apub/src/http/user.rs +++ b/lemmy_apub/src/http/user.rs @@ -1,4 +1,9 @@ -use crate::{extensions::context::lemmy_context, http::create_apub_response, ActorType, ToApub}; +use crate::{ + extensions::context::lemmy_context, + http::create_apub_response, + objects::ToApub, + ActorType, +}; use activitystreams::{ base::BaseExt, collection::{CollectionExt, OrderedCollection}, diff --git a/lemmy_apub/src/inbox/community_inbox.rs b/lemmy_apub/src/inbox/community_inbox.rs index 7c144a00..4bdad2fa 100644 --- a/lemmy_apub/src/inbox/community_inbox.rs +++ b/lemmy_apub/src/inbox/community_inbox.rs @@ -30,6 +30,7 @@ use lemmy_db::{ community::{Community, CommunityFollower, CommunityFollowerForm}, community_view::CommunityUserBanView, user::User_, + ApubObject, DbPool, Followable, }; @@ -118,7 +119,7 @@ pub(crate) async fn community_receive_message( // unconditionally. let actor_id = actor.actor_id_str(); let user = blocking(&context.pool(), move |conn| { - User_::read_from_actor_id(&conn, &actor_id) + User_::read_from_apub_id(&conn, &actor_id) }) .await??; check_community_or_site_ban(&user, &to_community, context.pool()).await?; @@ -242,7 +243,7 @@ async fn handle_undo_follow( verify_activity_domains_valid(&follow, &user_url, false)?; let user = blocking(&context.pool(), move |conn| { - User_::read_from_actor_id(&conn, user_url.as_str()) + User_::read_from_apub_id(&conn, user_url.as_str()) }) .await??; let community_follower_form = CommunityFollowerForm { @@ -260,7 +261,7 @@ async fn handle_undo_follow( Ok(()) } -async fn check_community_or_site_ban( +pub(crate) async fn check_community_or_site_ban( user: &User_, community: &Community, pool: &DbPool, diff --git a/lemmy_apub/src/inbox/mod.rs b/lemmy_apub/src/inbox/mod.rs index ce6c7ede..e04fdd0f 100644 --- a/lemmy_apub/src/inbox/mod.rs +++ b/lemmy_apub/src/inbox/mod.rs @@ -12,7 +12,7 @@ use activitystreams::{ }; use actix_web::HttpRequest; use anyhow::{anyhow, Context}; -use lemmy_db::{activity::Activity, community::Community, user::User_, DbPool}; +use lemmy_db::{activity::Activity, community::Community, user::User_, ApubObject, DbPool}; use lemmy_structs::blocking; use lemmy_utils::{location_info, settings::Settings, LemmyError}; use lemmy_websocket::LemmyContext; @@ -119,7 +119,7 @@ pub(crate) async fn is_addressed_to_local_user( ) -> Result<bool, LemmyError> { for url in to_and_cc { let url = url.to_string(); - let user = blocking(&pool, move |conn| User_::read_from_actor_id(&conn, &url)).await?; + let user = blocking(&pool, move |conn| User_::read_from_apub_id(&conn, &url)).await?; if let Ok(u) = user { if u.local { return Ok(true); @@ -141,7 +141,7 @@ pub(crate) async fn is_addressed_to_community_followers( if url.ends_with("/followers") { let community_url = url.replace("/followers", ""); let community = blocking(&pool, move |conn| { - Community::read_from_actor_id(&conn, &community_url) + Community::read_from_apub_id(&conn, &community_url) }) .await??; if !community.local { diff --git a/lemmy_apub/src/inbox/receive_for_community.rs b/lemmy_apub/src/inbox/receive_for_community.rs index b6dfa1e4..eaad6b1c 100644 --- a/lemmy_apub/src/inbox/receive_for_community.rs +++ b/lemmy_apub/src/inbox/receive_for_community.rs @@ -31,6 +31,7 @@ use crate::{ receive_unhandled_activity, verify_activity_domains_valid, }, + fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post}, inbox::is_addressed_to_public, }; use activitystreams::{ @@ -40,7 +41,7 @@ use activitystreams::{ }; use anyhow::Context; use diesel::result::Error::NotFound; -use lemmy_db::{comment::Comment, post::Post, site::Site, Crud}; +use lemmy_db::{comment::Comment, post::Post, site::Site, ApubObject, Crud}; use lemmy_structs::blocking; use lemmy_utils::{location_info, LemmyError}; use lemmy_websocket::LemmyContext; @@ -96,10 +97,12 @@ pub(in crate::inbox) async fn receive_like_for_community( verify_activity_domains_valid(&like, &expected_domain, false)?; is_addressed_to_public(&like)?; - match like.object().as_single_kind_str() { - Some("Page") => receive_like_post(like, context, request_counter).await, - Some("Note") => receive_like_comment(like, context, request_counter).await, - _ => receive_unhandled_activity(like), + let object_id = get_like_object_id(&like)?; + match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? { + PostOrComment::Post(post) => receive_like_post(like, post, context, request_counter).await, + PostOrComment::Comment(comment) => { + receive_like_comment(like, comment, context, request_counter).await + } } } @@ -122,10 +125,14 @@ pub(in crate::inbox) async fn receive_dislike_for_community( verify_activity_domains_valid(&dislike, &expected_domain, false)?; is_addressed_to_public(&dislike)?; - match dislike.object().as_single_kind_str() { - Some("Page") => receive_dislike_post(dislike, context, request_counter).await, - Some("Note") => receive_dislike_comment(dislike, context, request_counter).await, - _ => receive_unhandled_activity(dislike), + let object_id = get_like_object_id(&dislike)?; + match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? { + PostOrComment::Post(post) => { + receive_dislike_post(dislike, post, context, request_counter).await + } + PostOrComment::Comment(comment) => { + receive_dislike_comment(dislike, comment, context, request_counter).await + } } } @@ -275,14 +282,14 @@ pub(in crate::inbox) async fn receive_undo_like_for_community( verify_activity_domains_valid(&like, &expected_domain, false)?; is_addressed_to_public(&like)?; - let type_ = like - .object() - .as_single_kind_str() - .context(location_info!())?; - match type_ { - "Note" => receive_undo_like_comment(&like, context, request_counter).await, - "Page" => receive_undo_like_post(&like, context, request_counter).await, - _ => receive_unhandled_activity(like), + let object_id = get_like_object_id(&like)?; + match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? { + PostOrComment::Post(post) => { + receive_undo_like_post(&like, post, context, request_counter).await + } + PostOrComment::Comment(comment) => { + receive_undo_like_comment(&like, comment, context, request_counter).await + } } } @@ -298,14 +305,14 @@ pub(in crate::inbox) async fn receive_undo_dislike_for_community( verify_activity_domains_valid(&dislike, &expected_domain, false)?; is_addressed_to_public(&dislike)?; - let type_ = dislike - .object() - .as_single_kind_str() - .context(location_info!())?; - match type_ { - "Note" => receive_undo_dislike_comment(&dislike, context, request_counter).await, - "Page" => receive_undo_dislike_post(&dislike, context, request_counter).await, - _ => receive_unhandled_activity(dislike), + let object_id = get_like_object_id(&dislike)?; + match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? { + PostOrComment::Post(post) => { + receive_undo_dislike_post(&dislike, post, context, request_counter).await + } + PostOrComment::Comment(comment) => { + receive_undo_dislike_comment(&dislike, comment, context, request_counter).await + } } } @@ -341,3 +348,42 @@ async fn find_post_or_comment_by_id( return Err(NotFound.into()); } + +async fn fetch_post_or_comment_by_id( + apub_id: &Url, + context: &LemmyContext, + request_counter: &mut i32, +) -> Result<PostOrComment, LemmyError> { + if let Ok(post) = get_or_fetch_and_insert_post(apub_id, context, request_counter).await { + return Ok(PostOrComment::Post(post)); + } + + if let Ok(comment) = get_or_fetch_and_insert_comment(apub_id, context, request_counter).await { + return Ok(PostOrComment::Comment(comment)); + } + + return Err(NotFound.into()); +} + +fn get_like_object_id<Activity>(like_or_dislike: &Activity) -> Result<Url, LemmyError> +where + Activity: ActorAndObjectRefExt, +{ + // TODO: For backwards compatibility with older Lemmy versions where like.object contains a full + // post/comment. This can be removed after some time, using + // `activity.oject().as_single_xsd_any_uri()` instead. + let object = like_or_dislike.object(); + if let Some(xsd_uri) = object.as_single_xsd_any_uri() { + Ok(xsd_uri.to_owned()) + } else { + Ok( + object + .to_owned() + .one() + .context(location_info!())? + .id() + .context(location_info!())? + .to_owned(), + ) + } +} diff --git a/lemmy_apub/src/inbox/shared_inbox.rs b/lemmy_apub/src/inbox/shared_inbox.rs index 2875696e..826038bf 100644 --- a/lemmy_apub/src/inbox/shared_inbox.rs +++ b/lemmy_apub/src/inbox/shared_inbox.rs @@ -15,7 +15,7 @@ use crate::{ use activitystreams::{activity::ActorAndObject, prelude::*}; use actix_web::{web, HttpRequest, HttpResponse}; use anyhow::Context; -use lemmy_db::{community::Community, DbPool}; +use lemmy_db::{community::Community, ApubObject, DbPool}; use lemmy_structs::blocking; use lemmy_utils::{location_info, LemmyError}; use lemmy_websocket::LemmyContext; @@ -137,10 +137,7 @@ async fn extract_local_community_from_destinations( ) -> Result<Option<Community>, LemmyError> { for url in to_and_cc { let url = url.to_string(); - let community = blocking(&pool, move |conn| { - Community::read_from_actor_id(&conn, &url) - }) - .await?; + let community = blocking(&pool, move |conn| Community::read_from_apub_id(&conn, &url)).await?; if let Ok(c) = community { if c.local { return Ok(Some(c)); diff --git a/lemmy_apub/src/inbox/user_inbox.rs b/lemmy_apub/src/inbox/user_inbox.rs index 2f847a5c..81b9f185 100644 --- a/lemmy_apub/src/inbox/user_inbox.rs +++ b/lemmy_apub/src/inbox/user_inbox.rs @@ -52,6 +52,7 @@ use lemmy_db::{ community::{Community, CommunityFollower}, private_message::PrivateMessage, user::User_, + ApubObject, Followable, }; use lemmy_structs::blocking; @@ -377,7 +378,7 @@ async fn find_community_or_private_message_by_id( ) -> Result<CommunityOrPrivateMessage, LemmyError> { let ap_id = apub_id.to_string(); let community = blocking(context.pool(), move |conn| { - Community::read_from_actor_id(conn, &ap_id) + Community::read_from_apub_id(conn, &ap_id) }) .await?; if let Ok(c) = community { diff --git a/lemmy_apub/src/lib.rs b/lemmy_apub/src/lib.rs index 2e3f7bfc..9b933b6e 100644 --- a/lemmy_apub/src/lib.rs +++ b/lemmy_apub/src/lib.rs @@ -18,7 +18,7 @@ use activitystreams::{ activity::Follow, actor::{ApActor, Group, Person}, base::AnyBase, - object::{ApObject, Note, Page, Tombstone}, + object::{ApObject, Note, Page}, }; use activitystreams_ext::{Ext1, Ext2}; use anyhow::{anyhow, Context}; @@ -109,33 +109,6 @@ fn check_is_apub_id_valid(apub_id: &Url) -> Result<(), LemmyError> { } } -/// Trait for converting an object or actor into the respective ActivityPub type. -#[async_trait::async_trait(?Send)] -pub trait ToApub { - type ApubType; - async fn to_apub(&self, pool: &DbPool) -> Result<Self::ApubType, LemmyError>; - fn to_tombstone(&self) -> Result<Tombstone, LemmyError>; -} - -#[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` If present, ensure that the domains of this and of the apub object ID are - /// identical - async fn from_apub( - apub: &Self::ApubType, - context: &LemmyContext, - expected_domain: Option<Url>, - request_counter: &mut i32, - ) -> Result<Self, LemmyError> - where - Self: Sized; -} - /// Common functions for ActivityPub objects, which are implemented by most (but not all) objects /// and actors in Lemmy. #[async_trait::async_trait(?Send)] @@ -248,7 +221,7 @@ pub trait ActorType { /// Store a sent or received activity in the database, for logging purposes. These records are not /// persistent. -pub async fn insert_activity<T>( +pub(crate) async fn insert_activity<T>( ap_id: &Url, activity: T, local: bool, diff --git a/lemmy_apub/src/objects/comment.rs b/lemmy_apub/src/objects/comment.rs index 277a55d0..56d75a40 100644 --- a/lemmy_apub/src/objects/comment.rs +++ b/lemmy_apub/src/objects/comment.rs @@ -7,19 +7,22 @@ use crate::{ }, objects::{ check_object_domain, + check_object_for_community_or_site_ban, create_tombstone, + get_object_from_apub, get_source_markdown_value, set_content_and_source, + FromApub, + FromApubToForm, + ToApub, }, - FromApub, NoteExt, - ToApub, }; use activitystreams::{ object::{kind::NoteType, ApObject, Note, Tombstone}, prelude::*, }; -use anyhow::Context; +use anyhow::{anyhow, Context}; use lemmy_db::{ comment::{Comment, CommentForm}, community::Community, @@ -87,16 +90,45 @@ impl ToApub for Comment { } #[async_trait::async_trait(?Send)] -impl FromApub for CommentForm { +impl FromApub for Comment { type ApubType = NoteExt; - /// Converts a `Note` to `CommentForm`. + /// Converts a `Note` to `Comment`. /// /// If the parent community, post and comment(s) are not known locally, these are also fetched. async fn from_apub( note: &NoteExt, context: &LemmyContext, - expected_domain: Option<Url>, + expected_domain: Url, + request_counter: &mut i32, + ) -> Result<Comment, LemmyError> { + check_object_for_community_or_site_ban(note, context, request_counter).await?; + + let comment: Comment = + get_object_from_apub(note, context, expected_domain, request_counter).await?; + + let post_id = comment.post_id; + let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; + if post.locked { + // This is not very efficient because a comment gets inserted just to be deleted right + // afterwards, but it seems to be the easiest way to implement it. + blocking(context.pool(), move |conn| { + Comment::delete(conn, comment.id) + }) + .await??; + return Err(anyhow!("Post is locked").into()); + } else { + Ok(comment) + } + } +} + +#[async_trait::async_trait(?Send)] +impl FromApubToForm<NoteExt> for CommentForm { + async fn from_apub( + note: &NoteExt, + context: &LemmyContext, + expected_domain: Url, request_counter: &mut i32, ) -> Result<CommentForm, LemmyError> { let creator_actor_id = ¬e diff --git a/lemmy_apub/src/objects/community.rs b/lemmy_apub/src/objects/community.rs index 2b383ba5..594a5b5e 100644 --- a/lemmy_apub/src/objects/community.rs +++ b/lemmy_apub/src/objects/community.rs @@ -4,13 +4,15 @@ use crate::{ objects::{ check_object_domain, create_tombstone, + get_object_from_apub, get_source_markdown_value, set_content_and_source, + FromApub, + FromApubToForm, + ToApub, }, ActorType, - FromApub, GroupExt, - ToApub, }; use activitystreams::{ actor::{kind::GroupType, ApActor, Endpoints, Group}, @@ -106,14 +108,28 @@ impl ToApub for Community { create_tombstone(self.deleted, &self.actor_id, self.updated, GroupType::Group) } } + #[async_trait::async_trait(?Send)] -impl FromApub for CommunityForm { +impl FromApub for Community { type ApubType = GroupExt; + /// Converts a `Group` to `Community`. + async fn from_apub( + group: &GroupExt, + context: &LemmyContext, + expected_domain: Url, + request_counter: &mut i32, + ) -> Result<Community, LemmyError> { + get_object_from_apub(group, context, expected_domain, request_counter).await + } +} + +#[async_trait::async_trait(?Send)] +impl FromApubToForm<GroupExt> for CommunityForm { async fn from_apub( group: &GroupExt, context: &LemmyContext, - expected_domain: Option<Url>, + expected_domain: Url, request_counter: &mut i32, ) -> Result<Self, LemmyError> { let creator_and_moderator_uris = group.inner.attributed_to().context(location_info!())?; diff --git a/lemmy_apub/src/objects/mod.rs b/lemmy_apub/src/objects/mod.rs index a162c165..898c50f3 100644 --- a/lemmy_apub/src/objects/mod.rs +++ b/lemmy_apub/src/objects/mod.rs @@ -1,4 +1,8 @@ -use crate::check_is_apub_id_valid; +use crate::{ + check_is_apub_id_valid, + fetcher::{get_or_fetch_and_upsert_community, get_or_fetch_and_upsert_user}, + inbox::community_inbox::check_community_or_site_ban, +}; use activitystreams::{ base::{AsBase, BaseExt, ExtendsExt}, markers::Base, @@ -7,7 +11,10 @@ use activitystreams::{ }; use anyhow::{anyhow, Context}; use chrono::NaiveDateTime; -use lemmy_utils::{location_info, utils::convert_datetime, LemmyError}; +use lemmy_db::{ApubObject, Crud, DbPool}; +use lemmy_structs::blocking; +use lemmy_utils::{location_info, settings::Settings, utils::convert_datetime, LemmyError}; +use lemmy_websocket::LemmyContext; use url::Url; pub(crate) mod comment; @@ -16,6 +23,44 @@ pub(crate) mod post; pub(crate) mod private_message; pub(crate) mod user; +/// 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<Self::ApubType, LemmyError>; + fn to_tombstone(&self) -> Result<Tombstone, LemmyError>; +} + +#[async_trait::async_trait(?Send)] +pub(crate) 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 + async fn from_apub( + apub: &Self::ApubType, + context: &LemmyContext, + expected_domain: Url, + request_counter: &mut i32, + ) -> Result<Self, LemmyError> + where + Self: Sized; +} + +#[async_trait::async_trait(?Send)] +pub(in crate::objects) trait FromApubToForm<ApubType> { + async fn from_apub( + apub: &ApubType, + context: &LemmyContext, + expected_domain: Url, + request_counter: &mut i32, + ) -> Result<Self, LemmyError> + where + Self: Sized; +} + /// Updated is actually the deletion time fn create_tombstone<T>( deleted: bool, @@ -43,17 +88,13 @@ where pub(in crate::objects) fn check_object_domain<T, Kind>( apub: &T, - expected_domain: Option<Url>, + expected_domain: Url, ) -> Result<String, LemmyError> where T: Base + AsBase<Kind>, { - let object_id = if let Some(url) = expected_domain { - let domain = url.domain().context(location_info!())?; - apub.id(domain)?.context(location_info!())? - } else { - apub.id_unchecked().context(location_info!())? - }; + let domain = expected_domain.domain().context(location_info!())?; + let object_id = apub.id(domain)?.context(location_info!())?; check_is_apub_id_valid(&object_id)?; Ok(object_id.to_string()) } @@ -127,3 +168,60 @@ pub(in crate::objects) fn check_is_markdown(mime: Option<&Mime>) -> Result<(), L Ok(()) } } + +/// Converts an ActivityPub object (eg `Note`) to a database object (eg `Comment`). If an object +/// with the same ActivityPub ID already exists in the database, it is returned directly. Otherwise +/// the apub object is parsed, inserted and returned. +pub(in crate::objects) async fn get_object_from_apub<From, Kind, To, ToForm>( + from: &From, + context: &LemmyContext, + expected_domain: Url, + request_counter: &mut i32, +) -> Result<To, LemmyError> +where + From: BaseExt<Kind>, + To: ApubObject<ToForm> + Crud<ToForm> + Send + 'static, + ToForm: FromApubToForm<From> + Send + 'static, +{ + let object_id = from.id_unchecked().context(location_info!())?.to_owned(); + let domain = object_id.domain().context(location_info!())?; + + // if its a local object, return it directly from the database + if Settings::get().hostname == domain { + let object = blocking(context.pool(), move |conn| { + To::read_from_apub_id(conn, object_id.as_str()) + }) + .await??; + Ok(object) + } + // otherwise parse and insert, assuring that it comes from the right domain + else { + let to_form = ToForm::from_apub(&from, context, expected_domain, request_counter).await?; + + let to = blocking(context.pool(), move |conn| To::upsert(conn, &to_form)).await??; + Ok(to) + } +} + +pub(in crate::objects) async fn check_object_for_community_or_site_ban<T, Kind>( + object: &T, + context: &LemmyContext, + request_counter: &mut i32, +) -> Result<(), LemmyError> +where + T: ObjectExt<Kind>, +{ + let user_id = object + .attributed_to() + .context(location_info!())? + .as_single_xsd_any_uri() + .context(location_info!())?; + let user = get_or_fetch_and_upsert_user(user_id, context, request_counter).await?; + let community_id = object + .to() + .context(location_info!())? + .as_single_xsd_any_uri() + .context(location_info!())?; + let community = get_or_fetch_and_upsert_community(community_id, context, request_counter).await?; + check_community_or_site_ban(&user, &community, context.pool()).await +} diff --git a/lemmy_apub/src/objects/post.rs b/lemmy_apub/src/objects/post.rs index a058d8b7..d34098c5 100644 --- a/lemmy_apub/src/objects/post.rs +++ b/lemmy_apub/src/objects/post.rs @@ -3,13 +3,16 @@ use crate::{ fetcher::{get_or_fetch_and_upsert_community, get_or_fetch_and_upsert_user}, objects::{ check_object_domain, + check_object_for_community_or_site_ban, create_tombstone, + get_object_from_apub, get_source_markdown_value, set_content_and_source, + FromApub, + FromApubToForm, + ToApub, }, - FromApub, PageExt, - ToApub, }; use activitystreams::{ object::{kind::PageType, ApObject, Image, Page, Tombstone}, @@ -98,7 +101,7 @@ impl ToApub for Post { } #[async_trait::async_trait(?Send)] -impl FromApub for PostForm { +impl FromApub for Post { type ApubType = PageExt; /// Converts a `PageExt` to `PostForm`. @@ -107,7 +110,20 @@ impl FromApub for PostForm { async fn from_apub( page: &PageExt, context: &LemmyContext, - expected_domain: Option<Url>, + expected_domain: Url, + request_counter: &mut i32, + ) -> Result<Post, LemmyError> { + check_object_for_community_or_site_ban(page, context, request_counter).await?; + get_object_from_apub(page, context, expected_domain, request_counter).await + } +} + +#[async_trait::async_trait(?Send)] +impl FromApubToForm<PageExt> for PostForm { + async fn from_apub( + page: &PageExt, + context: &LemmyContext, + expected_domain: Url, request_counter: &mut i32, ) -> Result<PostForm, LemmyError> { let ext = &page.ext_one; diff --git a/lemmy_apub/src/objects/private_message.rs b/lemmy_apub/src/objects/private_message.rs index 4e9af094..ec8b55e7 100644 --- a/lemmy_apub/src/objects/private_message.rs +++ b/lemmy_apub/src/objects/private_message.rs @@ -5,12 +5,14 @@ use crate::{ objects::{ check_object_domain, create_tombstone, + get_object_from_apub, get_source_markdown_value, set_content_and_source, + FromApub, + FromApubToForm, + ToApub, }, - FromApub, NoteExt, - ToApub, }; use activitystreams::{ object::{kind::NoteType, ApObject, Note, Tombstone}, @@ -63,13 +65,25 @@ impl ToApub for PrivateMessage { } #[async_trait::async_trait(?Send)] -impl FromApub for PrivateMessageForm { +impl FromApub for PrivateMessage { type ApubType = NoteExt; async fn from_apub( note: &NoteExt, context: &LemmyContext, - expected_domain: Option<Url>, + expected_domain: Url, + request_counter: &mut i32, + ) -> Result<PrivateMessage, LemmyError> { + get_object_from_apub(note, context, expected_domain, request_counter).await + } +} + +#[async_trait::async_trait(?Send)] +impl FromApubToForm<NoteExt> for PrivateMessageForm { + async fn from_apub( + note: &NoteExt, + context: &LemmyContext, + expected_domain: Url, request_counter: &mut i32, ) -> Result<PrivateMessageForm, LemmyError> { let creator_actor_id = note diff --git a/lemmy_apub/src/objects/user.rs b/lemmy_apub/src/objects/user.rs index ddf33656..18490796 100644 --- a/lemmy_apub/src/objects/user.rs +++ b/lemmy_apub/src/objects/user.rs @@ -1,10 +1,15 @@ use crate::{ extensions::context::lemmy_context, - objects::{check_object_domain, get_source_markdown_value, set_content_and_source}, + objects::{ + check_object_domain, + get_source_markdown_value, + set_content_and_source, + FromApub, + FromApubToForm, + ToApub, + }, ActorType, - FromApub, PersonExt, - ToApub, }; use activitystreams::{ actor::{ApActor, Endpoints, Person}, @@ -16,10 +21,13 @@ use anyhow::Context; use lemmy_db::{ naive_now, user::{UserForm, User_}, + ApubObject, DbPool, }; +use lemmy_structs::blocking; use lemmy_utils::{ location_info, + settings::Settings, utils::{check_slurs, check_slurs_opt, convert_datetime}, LemmyError, }; @@ -81,13 +89,38 @@ impl ToApub for User_ { } #[async_trait::async_trait(?Send)] -impl FromApub for UserForm { +impl FromApub for User_ { type ApubType = PersonExt; + async fn from_apub( + person: &PersonExt, + context: &LemmyContext, + expected_domain: Url, + request_counter: &mut i32, + ) -> Result<User_, LemmyError> { + let user_id = person.id_unchecked().context(location_info!())?.to_owned(); + let domain = user_id.domain().context(location_info!())?; + if domain == Settings::get().hostname { + let user = blocking(context.pool(), move |conn| { + User_::read_from_apub_id(conn, user_id.as_str()) + }) + .await??; + Ok(user) + } else { + let user_form = + UserForm::from_apub(person, context, expected_domain, request_counter).await?; + let user = blocking(context.pool(), move |conn| User_::upsert(conn, &user_form)).await??; + Ok(user) + } + } +} + +#[async_trait::async_trait(?Send)] +impl FromApubToForm<PersonExt> for UserForm { async fn from_apub( person: &PersonExt, _context: &LemmyContext, - expected_domain: Option<Url>, + expected_domain: Url, _request_counter: &mut i32, ) -> Result<Self, LemmyError> { let avatar = match person.icon() { diff --git a/lemmy_db/src/comment.rs b/lemmy_db/src/comment.rs index 9b092825..f54ddd10 100644 --- a/lemmy_db/src/comment.rs +++ b/lemmy_db/src/comment.rs @@ -2,6 +2,7 @@ use super::post::Post; use crate::{ naive_now, schema::{comment, comment_like, comment_saved}, + ApubObject, Crud, Likeable, Saveable, @@ -86,6 +87,23 @@ impl Crud<CommentForm> for Comment { } } +impl ApubObject<CommentForm> for Comment { + fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> { + use crate::schema::comment::dsl::*; + comment.filter(ap_id.eq(object_id)).first::<Self>(conn) + } + + fn upsert(conn: &PgConnection, comment_form: &CommentForm) -> Result<Self, Error> { + use crate::schema::comment::dsl::*; + insert_into(comment) + .values(comment_form) + .on_conflict(ap_id) + .do_update() + .set(comment_form) + .get_result::<Self>(conn) + } +} + impl Comment { pub fn update_ap_id( conn: &PgConnection, @@ -99,11 +117,6 @@ impl Comment { .get_result::<Self>(conn) } - pub fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> { - use crate::schema::comment::dsl::*; - comment.filter(ap_id.eq(object_id)).first::<Self>(conn) - } - pub fn permadelete_for_creator( conn: &PgConnection, for_creator_id: i32, @@ -168,16 +181,6 @@ impl Comment { .set((content.eq(new_content), updated.eq(naive_now()))) .get_result::<Self>(conn) } - - pub fn upsert(conn: &PgConnection, comment_form: &CommentForm) -> Result<Self, Error> { - use crate::schema::comment::dsl::*; - insert_into(comment) - .values(comment_form) - .on_conflict(ap_id) - .do_update() - .set(comment_form) - .get_result::<Self>(conn) - } } #[derive(Identifiable, Queryable, Associations, PartialEq, Debug, Clone)] diff --git a/lemmy_db/src/community.rs b/lemmy_db/src/community.rs index 5f76d514..be40da34 100644 --- a/lemmy_db/src/community.rs +++ b/lemmy_db/src/community.rs @@ -1,6 +1,7 @@ use crate::{ naive_now, schema::{community, community_follower, community_moderator, community_user_ban}, + ApubObject, Bannable, Crud, Followable, @@ -83,19 +84,31 @@ impl Crud<CommunityForm> for Community { } } -impl Community { - pub fn read_from_name(conn: &PgConnection, community_name: &str) -> Result<Self, Error> { +impl ApubObject<CommunityForm> for Community { + fn read_from_apub_id(conn: &PgConnection, for_actor_id: &str) -> Result<Self, Error> { use crate::schema::community::dsl::*; community - .filter(local.eq(true)) - .filter(name.eq(community_name)) + .filter(actor_id.eq(for_actor_id)) .first::<Self>(conn) } - pub fn read_from_actor_id(conn: &PgConnection, for_actor_id: &str) -> Result<Self, Error> { + fn upsert(conn: &PgConnection, community_form: &CommunityForm) -> Result<Community, Error> { + use crate::schema::community::dsl::*; + insert_into(community) + .values(community_form) + .on_conflict(actor_id) + .do_update() + .set(community_form) + .get_result::<Self>(conn) + } +} + +impl Community { + pub fn read_from_name(conn: &PgConnection, community_name: &str) -> Result<Self, Error> { use crate::schema::community::dsl::*; community - .filter(actor_id.eq(for_actor_id)) + .filter(local.eq(true)) + .filter(name.eq(community_name)) .first::<Self>(conn) } @@ -165,16 +178,6 @@ impl Community { .unwrap_or_default() .contains(&user_id) } - - pub fn upsert(conn: &PgConnection, community_form: &CommunityForm) -> Result<Community, Error> { - use crate::schema::community::dsl::*; - insert_into(community) - .values(community_form) - .on_conflict(actor_id) - .do_update() - .set(community_form) - .get_result::<Self>(conn) - } } #[derive(Identifiable, Queryable, Associations, PartialEq, Debug)] diff --git a/lemmy_db/src/lib.rs b/lemmy_db/src/lib.rs index bad646d1..0ca4826a 100644 --- a/lemmy_db/src/lib.rs +++ b/lemmy_db/src/lib.rs @@ -124,6 +124,15 @@ pub trait Reportable<T> { Self: Sized; } +pub trait ApubObject<T> { + fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> + where + Self: Sized; + fn upsert(conn: &PgConnection, user_form: &T) -> Result<Self, Error> + where + Self: Sized; +} + pub trait MaybeOptional<T> { fn get_optional(self) -> Option<T>; } diff --git a/lemmy_db/src/post.rs b/lemmy_db/src/post.rs index 787d5e6c..530f475b 100644 --- a/lemmy_db/src/post.rs +++ b/lemmy_db/src/post.rs @@ -1,6 +1,7 @@ use crate::{ naive_now, schema::{post, post_like, post_read, post_saved}, + ApubObject, Crud, Likeable, Readable, @@ -62,6 +63,47 @@ impl PostForm { } } +impl Crud<PostForm> for Post { + fn read(conn: &PgConnection, post_id: i32) -> Result<Self, Error> { + use crate::schema::post::dsl::*; + post.find(post_id).first::<Self>(conn) + } + + fn delete(conn: &PgConnection, post_id: i32) -> Result<usize, Error> { + use crate::schema::post::dsl::*; + diesel::delete(post.find(post_id)).execute(conn) + } + + fn create(conn: &PgConnection, new_post: &PostForm) -> Result<Self, Error> { + use crate::schema::post::dsl::*; + insert_into(post).values(new_post).get_result::<Self>(conn) + } + + fn update(conn: &PgConnection, post_id: i32, new_post: &PostForm) -> Result<Self, Error> { + use crate::schema::post::dsl::*; + diesel::update(post.find(post_id)) + .set(new_post) + .get_result::<Self>(conn) + } +} + +impl ApubObject<PostForm> for Post { + fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> { + use crate::schema::post::dsl::*; + post.filter(ap_id.eq(object_id)).first::<Self>(conn) + } + + fn upsert(conn: &PgConnection, post_form: &PostForm) -> Result<Post, Error> { + use crate::schema::post::dsl::*; + insert_into(post) + .values(post_form) + .on_conflict(ap_id) + .do_update() + .set(post_form) + .get_result::<Self>(conn) + } +} + impl Post { pub fn read(conn: &PgConnection, post_id: i32) -> Result<Self, Error> { use crate::schema::post::dsl::*; @@ -81,11 +123,6 @@ impl Post { .load::<Self>(conn) } - pub fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> { - use crate::schema::post::dsl::*; - post.filter(ap_id.eq(object_id)).first::<Self>(conn) - } - pub fn update_ap_id(conn: &PgConnection, post_id: i32, apub_id: String) -> Result<Self, Error> { use crate::schema::post::dsl::*; @@ -177,40 +214,6 @@ impl Post { pub fn is_post_creator(user_id: i32, post_creator_id: i32) -> bool { user_id == post_creator_id } - - pub fn upsert(conn: &PgConnection, post_form: &PostForm) -> Result<Post, Error> { - use crate::schema::post::dsl::*; - insert_into(post) - .values(post_form) - .on_conflict(ap_id) - .do_update() - .set(post_form) - .get_result::<Self>(conn) - } -} - -impl Crud<PostForm> for Post { - fn read(conn: &PgConnection, post_id: i32) -> Result<Self, Error> { - use crate::schema::post::dsl::*; - post.find(post_id).first::<Self>(conn) - } - - fn delete(conn: &PgConnection, post_id: i32) -> Result<usize, Error> { - use crate::schema::post::dsl::*; - diesel::delete(post.find(post_id)).execute(conn) - } - - fn create(conn: &PgConnection, new_post: &PostForm) -> Result<Self, Error> { - use crate::schema::post::dsl::*; - insert_into(post).values(new_post).get_result::<Self>(conn) - } - - fn update(conn: &PgConnection, post_id: i32, new_post: &PostForm) -> Result<Self, Error> { - use crate::schema::post::dsl::*; - diesel::update(post.find(post_id)) - .set(new_post) - .get_result::<Self>(conn) - } } #[derive(Identifiable, Queryable, Associations, PartialEq, Debug)] diff --git a/lemmy_db/src/private_message.rs b/lemmy_db/src/private_message.rs index 503a26ab..0e1aef10 100644 --- a/lemmy_db/src/private_message.rs +++ b/lemmy_db/src/private_message.rs @@ -1,4 +1,4 @@ -use crate::{naive_now, schema::private_message, Crud}; +use crate::{naive_now, schema::private_message, ApubObject, Crud}; use diesel::{dsl::*, result::Error, *}; #[derive(Queryable, Identifiable, PartialEq, Debug)] @@ -55,6 +55,28 @@ impl Crud<PrivateMessageForm> for PrivateMessage { } } +impl ApubObject<PrivateMessageForm> for PrivateMessage { + fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> + where + Self: Sized, + { + use crate::schema::private_message::dsl::*; + private_message + .filter(ap_id.eq(object_id)) + .first::<Self>(conn) + } + + fn upsert(conn: &PgConnection, private_message_form: &PrivateMessageForm) -> Result<Self, Error> { + use crate::schema::private_message::dsl::*; + insert_into(private_message) + .values(private_message_form) + .on_conflict(ap_id) + .do_update() + .set(private_message_form) + .get_result::<Self>(conn) + } +} + impl PrivateMessage { pub fn update_ap_id( conn: &PgConnection, @@ -68,13 +90,6 @@ impl PrivateMessage { .get_result::<Self>(conn) } - pub fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> { - use crate::schema::private_message::dsl::*; - private_message - .filter(ap_id.eq(object_id)) - .first::<Self>(conn) - } - pub fn update_content( conn: &PgConnection, private_message_id: i32, @@ -118,20 +133,6 @@ impl PrivateMessage { .set(read.eq(true)) .get_results::<Self>(conn) } - - // TODO use this - pub fn upsert( - conn: &PgConnection, - private_message_form: &PrivateMessageForm, - ) -> Result<Self, Error> { - use crate::schema::private_message::dsl::*; - insert_into(private_message) - .values(private_message_form) - .on_conflict(ap_id) - .do_update() - .set(private_message_form) - .get_result::<Self>(conn) - } } #[cfg(test)] diff --git a/lemmy_db/src/user.rs b/lemmy_db/src/user.rs index 2c4c67ea..d8e833e6 100644 --- a/lemmy_db/src/user.rs +++ b/lemmy_db/src/user.rs @@ -2,6 +2,7 @@ use crate::{ is_email_regex, naive_now, schema::{user_, user_::dsl::*}, + ApubObject, Crud, }; use bcrypt::{hash, DEFAULT_COST}; @@ -89,6 +90,25 @@ impl Crud<UserForm> for User_ { } } +impl ApubObject<UserForm> for User_ { + fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> { + use crate::schema::user_::dsl::*; + user_ + .filter(deleted.eq(false)) + .filter(actor_id.eq(object_id)) + .first::<Self>(conn) + } + + fn upsert(conn: &PgConnection, user_form: &UserForm) -> Result<User_, Error> { + insert_into(user_) + .values(user_form) + .on_conflict(actor_id) + .do_update() + .set(user_form) + .get_result::<Self>(conn) + } +} + impl User_ { pub fn register(conn: &PgConnection, form: &UserForm) -> Result<Self, Error> { let mut edited_user = form.clone(); @@ -135,14 +155,6 @@ impl User_ { .get_result::<Self>(conn) } - pub fn read_from_actor_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> { - use crate::schema::user_::dsl::*; - user_ - .filter(deleted.eq(false)) - .filter(actor_id.eq(object_id)) - .first::<Self>(conn) - } - pub fn find_by_email_or_username( conn: &PgConnection, username_or_email: &str, @@ -179,12 +191,9 @@ impl User_ { ) } - pub fn upsert(conn: &PgConnection, user_form: &UserForm) -> Result<User_, Error> { - insert_into(user_) - .values(user_form) - .on_conflict(actor_id) - .do_update() - .set(user_form) + pub fn mark_as_updated(conn: &PgConnection, user_id: i32) -> Result<User_, Error> { + diesel::update(user_.find(user_id)) + .set((last_refreshed_at.eq(naive_now()),)) .get_result::<Self>(conn) } -- 2.44.1