diff --git a/Cargo.lock b/Cargo.lock index 0e87d3cf0..3c46bc3d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1711,6 +1711,7 @@ dependencies = [ "lazy_static", "lemmy_api_common", "lemmy_apub", + "lemmy_apub_lib", "lemmy_db_queries", "lemmy_db_schema", "lemmy_db_views", @@ -1836,6 +1837,8 @@ dependencies = [ "chrono", "diesel", "diesel-derive-newtype", + "lemmy_apub_lib", + "lemmy_utils", "log", "serde", "serde_json", diff --git a/crates/api_crud/Cargo.toml b/crates/api_crud/Cargo.toml index 670342b70..a5c8e6c52 100644 --- a/crates/api_crud/Cargo.toml +++ b/crates/api_crud/Cargo.toml @@ -7,6 +7,7 @@ license = "AGPL-3.0" [dependencies] lemmy_apub = { version = "=0.13.0", path = "../apub" } +lemmy_apub_lib = { version = "=0.13.0", path = "../apub_lib" } lemmy_utils = { version = "=0.13.0", path = "../utils" } lemmy_db_queries = { version = "=0.13.0", path = "../db_queries" } lemmy_db_schema = { version = "=0.13.0", path = "../db_schema" } diff --git a/crates/api_crud/src/community/create.rs b/crates/api_crud/src/community/create.rs index 1cc40d85f..2d1076f3b 100644 --- a/crates/api_crud/src/community/create.rs +++ b/crates/api_crud/src/community/create.rs @@ -7,13 +7,14 @@ use lemmy_api_common::{ is_admin, }; use lemmy_apub::{ + fetcher::object_id::ObjectId, generate_apub_endpoint, generate_followers_url, generate_inbox_url, generate_shared_inbox_url, EndpointType, }; -use lemmy_db_queries::{diesel_option_overwrite_to_url, ApubObject, Crud, Followable, Joinable}; +use lemmy_db_queries::{diesel_option_overwrite_to_url, Crud, Followable, Joinable}; use lemmy_db_schema::source::{ community::{ Community, @@ -67,11 +68,8 @@ impl PerformCrud for CreateCommunity { &data.name, &context.settings().get_protocol_and_hostname(), )?; - let actor_id_cloned = community_actor_id.to_owned(); - let community_dupe = blocking(context.pool(), move |conn| { - Community::read_from_apub_id(conn, &actor_id_cloned) - }) - .await?; + 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("community_already_exists").into()); } diff --git a/crates/api_crud/src/community/read.rs b/crates/api_crud/src/community/read.rs index b29f5f0a0..9008b71da 100644 --- a/crates/api_crud/src/community/read.rs +++ b/crates/api_crud/src/community/read.rs @@ -1,14 +1,8 @@ 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, EndpointType}; -use lemmy_db_queries::{ - from_opt_str_to_opt_enum, - ApubObject, - DeleteableOrRemoveable, - ListingType, - SortType, -}; +use lemmy_apub::{build_actor_id_from_shortname, fetcher::object_id::ObjectId, EndpointType}; +use lemmy_db_queries::{from_opt_str_to_opt_enum, DeleteableOrRemoveable, ListingType, SortType}; use lemmy_db_schema::source::community::*; use lemmy_db_views_actor::{ community_moderator_view::CommunityModeratorView, @@ -38,12 +32,11 @@ impl PerformCrud for GetCommunity { let community_actor_id = build_actor_id_from_shortname(EndpointType::Community, &name, &context.settings())?; - blocking(context.pool(), move |conn| { - Community::read_from_apub_id(conn, &community_actor_id) - }) - .await? - .map_err(|_| ApiError::err("couldnt_find_community"))? - .id + ObjectId::::new(community_actor_id) + .dereference(context, &mut 0) + .await + .map_err(|_| ApiError::err("couldnt_find_community"))? + .id } }; diff --git a/crates/api_crud/src/user/read.rs b/crates/api_crud/src/user/read.rs index b029f2998..ea0090c23 100644 --- a/crates/api_crud/src/user/read.rs +++ b/crates/api_crud/src/user/read.rs @@ -1,8 +1,8 @@ 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, EndpointType}; -use lemmy_db_queries::{from_opt_str_to_opt_enum, ApubObject, SortType}; +use lemmy_apub::{build_actor_id_from_shortname, fetcher::object_id::ObjectId, EndpointType}; +use lemmy_db_queries::{from_opt_str_to_opt_enum, SortType}; use lemmy_db_schema::source::person::*; use lemmy_db_views::{comment_view::CommentQueryBuilder, post_view::PostQueryBuilder}; use lemmy_db_views_actor::{ @@ -45,10 +45,9 @@ impl PerformCrud for GetPersonDetails { let actor_id = build_actor_id_from_shortname(EndpointType::Person, &name, &context.settings())?; - let person = blocking(context.pool(), move |conn| { - Person::read_from_apub_id(conn, &actor_id) - }) - .await?; + let person = ObjectId::::new(actor_id) + .dereference(context, &mut 0) + .await; person .map_err(|_| ApiError::err("couldnt_find_that_username_or_email"))? .id diff --git a/crates/apub/src/activities/community/add_mod.rs b/crates/apub/src/activities/community/add_mod.rs index 93af4efba..f4e75080e 100644 --- a/crates/apub/src/activities/community/add_mod.rs +++ b/crates/apub/src/activities/community/add_mod.rs @@ -88,7 +88,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).await?; + verify_mod_action(&self.actor, self.cc[0].clone(), context, request_counter).await?; verify_add_remove_moderator_target(&self.target, &self.cc[0])?; Ok(()) } diff --git a/crates/apub/src/activities/community/block_user.rs b/crates/apub/src/activities/community/block_user.rs index 976dbc8a3..35db21911 100644 --- a/crates/apub/src/activities/community/block_user.rs +++ b/crates/apub/src/activities/community/block_user.rs @@ -98,7 +98,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).await?; + verify_mod_action(&self.actor, self.cc[0].clone(), context, request_counter).await?; Ok(()) } diff --git a/crates/apub/src/activities/community/remove_mod.rs b/crates/apub/src/activities/community/remove_mod.rs index 3de9ccca1..7e1d344d0 100644 --- a/crates/apub/src/activities/community/remove_mod.rs +++ b/crates/apub/src/activities/community/remove_mod.rs @@ -90,7 +90,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).await?; + verify_mod_action(&self.actor, self.cc[0].clone(), 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 cf70fc32c..d8fda9b11 100644 --- a/crates/apub/src/activities/community/undo_block_user.rs +++ b/crates/apub/src/activities/community/undo_block_user.rs @@ -85,7 +85,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).await?; + verify_mod_action(&self.actor, self.cc[0].clone(), 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 ec8a2bea9..3061fa1f7 100644 --- a/crates/apub/src/activities/community/update.rs +++ b/crates/apub/src/activities/community/update.rs @@ -20,7 +20,7 @@ use activitystreams::{ }; use lemmy_api_common::blocking; use lemmy_apub_lib::{values::PublicUrl, ActivityFields, ActivityHandler, Data}; -use lemmy_db_queries::{ApubObject, Crud}; +use lemmy_db_queries::Crud; use lemmy_db_schema::source::{ community::{Community, CommunityForm}, person::Person, @@ -85,20 +85,17 @@ 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).await?; + verify_mod_action(&self.actor, self.cc[0].clone(), context, request_counter).await?; Ok(()) } async fn receive( self, context: &Data, - _request_counter: &mut i32, + request_counter: &mut i32, ) -> Result<(), LemmyError> { - let cc = self.cc[0].clone().into(); - let community = blocking(context.pool(), move |conn| { - Community::read_from_apub_id(conn, &cc) - }) - .await??; + let cc = self.cc[0].clone(); + let community = cc.dereference(context, request_counter).await?; let updated_community = Group::from_apub_to_form( &self.object, diff --git a/crates/apub/src/activities/deletion/mod.rs b/crates/apub/src/activities/deletion/mod.rs index 5843e5017..2abe7ae7d 100644 --- a/crates/apub/src/activities/deletion/mod.rs +++ b/crates/apub/src/activities/deletion/mod.rs @@ -7,16 +7,11 @@ use crate::{ fetcher::object_id::ObjectId, ActorType, }; +use diesel::PgConnection; use lemmy_api_common::blocking; -use lemmy_apub_lib::{verify_domains_match, ActivityFields}; -use lemmy_db_queries::{ - source::{comment::Comment_, community::Community_, post::Post_}, - ApubObject, -}; -use lemmy_db_schema::{ - source::{comment::Comment, community::Community, person::Person, post::Post}, - DbUrl, -}; +use lemmy_apub_lib::{verify_domains_match, ActivityFields, ApubObject}; +use lemmy_db_queries::source::{comment::Comment_, community::Community_, post::Post_}; +use lemmy_db_schema::source::{comment::Comment, community::Community, person::Person, post::Post}; use lemmy_utils::LemmyError; use lemmy_websocket::{ send::{send_comment_ws_message_simple, send_community_ws_message, send_post_ws_message}, @@ -70,29 +65,32 @@ impl DeletableObjects { ap_id: &Url, context: &LemmyContext, ) -> Result { - let id: DbUrl = ap_id.clone().into(); - - if let Some(c) = DeletableObjects::read_type_from_db::(id.clone(), context).await? { + if let Some(c) = + DeletableObjects::read_type_from_db::(ap_id.clone(), context).await? + { return Ok(DeletableObjects::Community(Box::new(c))); } - if let Some(p) = DeletableObjects::read_type_from_db::(id.clone(), context).await? { + if let Some(p) = DeletableObjects::read_type_from_db::(ap_id.clone(), context).await? { return Ok(DeletableObjects::Post(Box::new(p))); } - if let Some(c) = DeletableObjects::read_type_from_db::(id.clone(), context).await? { + if let Some(c) = DeletableObjects::read_type_from_db::(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: DbUrl, + async fn read_type_from_db( + ap_id: Url, context: &LemmyContext, - ) -> Result, LemmyError> { + ) -> Result, LemmyError> + where + Type: ApubObject + Send + 'static, + { blocking(context.pool(), move |conn| { - Type::read_from_apub_id(conn, &ap_id).ok() + Type::read_from_apub_id(conn, ap_id) }) - .await + .await? } } @@ -114,7 +112,13 @@ pub(in crate::activities) async fn verify_delete_activity( verify_person_in_community(&actor, community_id, context, request_counter).await?; } // community deletion is always a mod (or admin) action - verify_mod_action(&actor, ObjectId::new(c.actor_id()), context).await?; + verify_mod_action( + &actor, + ObjectId::new(c.actor_id()), + context, + request_counter, + ) + .await?; } DeletableObjects::Post(p) => { verify_delete_activity_post_or_comment( @@ -153,7 +157,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).await?; + verify_mod_action(&actor, community_id.clone(), 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)?; diff --git a/crates/apub/src/activities/following/accept.rs b/crates/apub/src/activities/following/accept.rs index ab28b9454..be26e0c14 100644 --- a/crates/apub/src/activities/following/accept.rs +++ b/crates/apub/src/activities/following/accept.rs @@ -18,7 +18,7 @@ use activitystreams::{ }; use lemmy_api_common::blocking; use lemmy_apub_lib::{verify_urls_match, ActivityFields, ActivityHandler, Data}; -use lemmy_db_queries::{ApubObject, Followable}; +use lemmy_db_queries::Followable; use lemmy_db_schema::source::{ community::{Community, CommunityFollower}, person::Person, @@ -44,18 +44,17 @@ pub struct AcceptFollowCommunity { } impl AcceptFollowCommunity { - pub async fn send(follow: FollowCommunity, context: &LemmyContext) -> Result<(), LemmyError> { - let community_id = follow.object.clone(); - let community = blocking(context.pool(), move |conn| { - Community::read_from_apub_id(conn, &community_id.into()) - }) - .await??; - let person_id = follow.actor().clone(); - let person = blocking(context.pool(), move |conn| { - Person::read_from_apub_id(conn, &person_id.into()) - }) - .await??; - + pub async fn send( + follow: FollowCommunity, + context: &LemmyContext, + request_counter: &mut i32, + ) -> Result<(), LemmyError> { + let community = follow.object.dereference_local(context).await?; + let person = follow + .actor + .clone() + .dereference(context, request_counter) + .await?; let accept = AcceptFollowCommunity { actor: ObjectId::new(community.actor_id()), to: ObjectId::new(person.actor_id()), diff --git a/crates/apub/src/activities/following/follow.rs b/crates/apub/src/activities/following/follow.rs index 3171d72f7..e9e1d2b83 100644 --- a/crates/apub/src/activities/following/follow.rs +++ b/crates/apub/src/activities/following/follow.rs @@ -31,7 +31,7 @@ use url::Url; #[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct FollowCommunity { - 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, @@ -117,6 +117,6 @@ impl ActivityHandler for FollowCommunity { }) .await?; - AcceptFollowCommunity::send(self, context).await + AcceptFollowCommunity::send(self, context, request_counter).await } } diff --git a/crates/apub/src/activities/mod.rs b/crates/apub/src/activities/mod.rs index e72f1e1dc..f477a66a4 100644 --- a/crates/apub/src/activities/mod.rs +++ b/crates/apub/src/activities/mod.rs @@ -7,11 +7,7 @@ use crate::{ use anyhow::anyhow; use lemmy_api_common::blocking; use lemmy_apub_lib::{verify_domains_match, ActivityFields}; -use lemmy_db_queries::ApubObject; -use lemmy_db_schema::{ - source::{community::Community, person::Person}, - DbUrl, -}; +use lemmy_db_schema::source::{community::Community, person::Person}; use lemmy_db_views_actor::community_view::CommunityView; use lemmy_utils::{settings::structs::Settings, LemmyError}; use lemmy_websocket::LemmyContext; @@ -104,18 +100,12 @@ pub(crate) async fn verify_mod_action( actor_id: &ObjectId, community_id: ObjectId, context: &LemmyContext, + request_counter: &mut i32, ) -> Result<(), LemmyError> { - let community = blocking(context.pool(), move |conn| { - Community::read_from_apub_id(conn, &community_id.into()) - }) - .await??; + let community = community_id.dereference_local(context).await?; if community.local { - let actor_id: DbUrl = actor_id.clone().into(); - let actor = blocking(context.pool(), move |conn| { - Person::read_from_apub_id(conn, &actor_id) - }) - .await??; + let actor = actor_id.dereference(context, request_counter).await?; // Note: this will also return true for admins in addition to mods, but as we dont know about // remote admins, it doesnt make any difference. diff --git a/crates/apub/src/activities/post/create_or_update.rs b/crates/apub/src/activities/post/create_or_update.rs index a46c9b066..72df0052d 100644 --- a/crates/apub/src/activities/post/create_or_update.rs +++ b/crates/apub/src/activities/post/create_or_update.rs @@ -106,9 +106,9 @@ impl ActivityHandler for CreateOrUpdatePost { } } CreateOrUpdateType::Update => { - let is_mod_action = self.object.is_mod_action(context.pool()).await?; + 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).await?; + verify_mod_action(&self.actor, self.cc[0].clone(), 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())?; diff --git a/crates/apub/src/activities/private_message/delete.rs b/crates/apub/src/activities/private_message/delete.rs index 107a50891..499f0a938 100644 --- a/crates/apub/src/activities/private_message/delete.rs +++ b/crates/apub/src/activities/private_message/delete.rs @@ -13,7 +13,7 @@ use activitystreams::{ }; use lemmy_api_common::blocking; use lemmy_apub_lib::{verify_domains_match, ActivityFields, ActivityHandler, Data}; -use lemmy_db_queries::{source::private_message::PrivateMessage_, ApubObject, Crud}; +use lemmy_db_queries::{source::private_message::PrivateMessage_, Crud}; use lemmy_db_schema::source::{person::Person, private_message::PrivateMessage}; use lemmy_utils::LemmyError; use lemmy_websocket::{send::send_pm_ws_message, LemmyContext, UserOperationCrud}; @@ -25,7 +25,7 @@ use url::Url; pub struct DeletePrivateMessage { actor: ObjectId, to: ObjectId, - pub(in crate::activities::private_message) object: Url, + pub(in crate::activities::private_message) object: ObjectId, #[serde(rename = "type")] kind: DeleteType, id: Url, @@ -44,7 +44,7 @@ impl DeletePrivateMessage { Ok(DeletePrivateMessage { actor: ObjectId::new(actor.actor_id()), to: ObjectId::new(actor.actor_id()), - object: pm.ap_id.clone().into(), + object: ObjectId::new(pm.ap_id.clone()), kind: DeleteType::Delete, id: generate_activity_id( DeleteType::Delete, @@ -80,7 +80,7 @@ impl ActivityHandler for DeletePrivateMessage { ) -> Result<(), LemmyError> { verify_activity(self, &context.settings())?; verify_person(&self.actor, context, request_counter).await?; - verify_domains_match(self.actor.inner(), &self.object)?; + verify_domains_match(self.actor.inner(), self.object.inner())?; Ok(()) } @@ -89,11 +89,7 @@ impl ActivityHandler for DeletePrivateMessage { context: &Data, _request_counter: &mut i32, ) -> Result<(), LemmyError> { - let ap_id = self.object.clone(); - let private_message = blocking(context.pool(), move |conn| { - PrivateMessage::read_from_apub_id(conn, &ap_id.into()) - }) - .await??; + let private_message = self.object.dereference_local(context).await?; let deleted_private_message = blocking(context.pool(), move |conn| { PrivateMessage::update_deleted(conn, private_message.id, true) }) diff --git a/crates/apub/src/activities/private_message/undo_delete.rs b/crates/apub/src/activities/private_message/undo_delete.rs index aff4a59a2..486476298 100644 --- a/crates/apub/src/activities/private_message/undo_delete.rs +++ b/crates/apub/src/activities/private_message/undo_delete.rs @@ -24,7 +24,7 @@ use lemmy_apub_lib::{ ActivityHandler, Data, }; -use lemmy_db_queries::{source::private_message::PrivateMessage_, ApubObject, Crud}; +use lemmy_db_queries::{source::private_message::PrivateMessage_, Crud}; use lemmy_db_schema::source::{person::Person, private_message::PrivateMessage}; use lemmy_utils::LemmyError; use lemmy_websocket::{send::send_pm_ws_message, LemmyContext, UserOperationCrud}; @@ -86,7 +86,7 @@ impl ActivityHandler for UndoDeletePrivateMessage { verify_activity(self, &context.settings())?; verify_person(&self.actor, context, request_counter).await?; verify_urls_match(self.actor(), self.object.actor())?; - verify_domains_match(self.actor(), &self.object.object)?; + verify_domains_match(self.actor(), self.object.object.inner())?; self.object.verify(context, request_counter).await?; Ok(()) } @@ -97,10 +97,7 @@ impl ActivityHandler for UndoDeletePrivateMessage { _request_counter: &mut i32, ) -> Result<(), LemmyError> { let ap_id = self.object.object.clone(); - let private_message = blocking(context.pool(), move |conn| { - PrivateMessage::read_from_apub_id(conn, &ap_id.into()) - }) - .await??; + let private_message = ap_id.dereference_local(context).await?; let deleted_private_message = blocking(context.pool(), move |conn| { PrivateMessage::update_deleted(conn, private_message.id, false) diff --git a/crates/apub/src/fetcher/deletable_apub_object.rs b/crates/apub/src/fetcher/deletable_apub_object.rs index 5df90dd18..503949e10 100644 --- a/crates/apub/src/fetcher/deletable_apub_object.rs +++ b/crates/apub/src/fetcher/deletable_apub_object.rs @@ -6,7 +6,13 @@ use lemmy_db_queries::source::{ person::Person_, post::Post_, }; -use lemmy_db_schema::source::{comment::Comment, community::Community, person::Person, post::Post}; +use lemmy_db_schema::source::{ + comment::Comment, + community::Community, + person::Person, + post::Post, + private_message::PrivateMessage, +}; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; @@ -83,3 +89,11 @@ impl DeletableApubObject for PostOrComment { Ok(()) } } + +#[async_trait::async_trait(?Send)] +impl DeletableApubObject for PrivateMessage { + async fn delete(self, _context: &LemmyContext) -> Result<(), LemmyError> { + // do nothing, because pm can't be fetched over http + unimplemented!() + } +} diff --git a/crates/apub/src/fetcher/object_id.rs b/crates/apub/src/fetcher/object_id.rs index 7b3c535fa..883ee8c16 100644 --- a/crates/apub/src/fetcher/object_id.rs +++ b/crates/apub/src/fetcher/object_id.rs @@ -4,9 +4,10 @@ use crate::{ APUB_JSON_CONTENT_TYPE, }; use anyhow::anyhow; -use diesel::NotFound; +use diesel::{NotFound, PgConnection}; use lemmy_api_common::blocking; -use lemmy_db_queries::{ApubObject, DbPool}; +use lemmy_apub_lib::ApubObject; +use lemmy_db_queries::DbPool; use lemmy_db_schema::DbUrl; use lemmy_utils::{request::retry, settings::structs::Settings, LemmyError}; use lemmy_websocket::LemmyContext; @@ -26,12 +27,12 @@ static REQUEST_LIMIT: i32 = 25; #[derive(Clone, PartialEq, Serialize, Deserialize, Debug)] pub struct ObjectId(Url, #[serde(skip)] PhantomData) where - Kind: FromApub + ApubObject + DeletableApubObject + Send + 'static, + Kind: FromApub + ApubObject + DeletableApubObject + Send + 'static, for<'de2> ::ApubType: serde::Deserialize<'de2>; impl ObjectId where - Kind: FromApub + ApubObject + DeletableApubObject + Send + 'static, + Kind: FromApub + ApubObject + DeletableApubObject + Send + 'static, for<'de> ::ApubType: serde::Deserialize<'de>, { pub fn new(url: T) -> Self @@ -46,12 +47,12 @@ where } /// Fetches an activitypub object, either from local database (if possible), or over http. - pub(crate) async fn dereference( + pub async fn dereference( &self, context: &LemmyContext, request_counter: &mut i32, ) -> Result { - let db_object = self.dereference_locally(context.pool()).await?; + let db_object = self.dereference_from_db(context.pool()).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()?) { @@ -66,30 +67,32 @@ where // TODO: rename to should_refetch_object() if should_refetch_actor(last_refreshed_at) { return self - .dereference_remotely(context, request_counter, Some(object)) + .dereference_from_http(context, request_counter, Some(object)) .await; } } Ok(object) } else { self - .dereference_remotely(context, request_counter, None) + .dereference_from_http(context, request_counter, None) .await } } - /// returning none means the object was not found in local db - async fn dereference_locally(&self, pool: &DbPool) -> Result, LemmyError> { - let id: DbUrl = self.0.clone().into(); - let object = blocking(pool, move |conn| ApubObject::read_from_apub_id(conn, &id)).await?; - match object { - Ok(o) => Ok(Some(o)), - Err(NotFound {}) => Ok(None), - Err(e) => Err(e.into()), - } + /// 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?; + object.ok_or_else(|| anyhow!("object not found in database {}", self).into()) } - async fn dereference_remotely( + /// returning none means the object was not found in local db + async fn dereference_from_db(&self, pool: &DbPool) -> Result, LemmyError> { + let id = self.0.clone(); + blocking(pool, move |conn| ApubObject::read_from_apub_id(conn, id)).await? + } + + async fn dereference_from_http( &self, context: &LemmyContext, request_counter: &mut i32, @@ -128,7 +131,7 @@ where impl Display for ObjectId where - Kind: FromApub + ApubObject + DeletableApubObject + Send + 'static, + Kind: FromApub + ApubObject + DeletableApubObject + Send + 'static, for<'de> ::ApubType: serde::Deserialize<'de>, { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { @@ -138,7 +141,7 @@ where impl From> for Url where - Kind: FromApub + ApubObject + DeletableApubObject + Send + 'static, + Kind: FromApub + ApubObject + DeletableApubObject + Send + 'static, for<'de> ::ApubType: serde::Deserialize<'de>, { fn from(id: ObjectId) -> Self { @@ -148,7 +151,7 @@ where impl From> for DbUrl where - Kind: FromApub + ApubObject + DeletableApubObject + Send + 'static, + Kind: FromApub + ApubObject + DeletableApubObject + 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 531074993..940123aec 100644 --- a/crates/apub/src/fetcher/post_or_comment.rs +++ b/crates/apub/src/fetcher/post_or_comment.rs @@ -1,13 +1,10 @@ use crate::objects::{comment::Note, post::Page, FromApub}; use activitystreams::chrono::NaiveDateTime; -use diesel::{result::Error, PgConnection}; -use lemmy_db_queries::ApubObject; -use lemmy_db_schema::{ - source::{ - comment::{Comment, CommentForm}, - post::{Post, PostForm}, - }, - DbUrl, +use diesel::PgConnection; +use lemmy_apub_lib::ApubObject; +use lemmy_db_schema::source::{ + comment::{Comment, CommentForm}, + post::{Post, PostForm}, }; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; @@ -33,19 +30,23 @@ pub enum PageOrNote { #[async_trait::async_trait(?Send)] impl ApubObject for PostOrComment { + type DataType = PgConnection; + 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: &DbUrl) -> Result + fn read_from_apub_id(conn: &PgConnection, object_id: Url) -> Result, LemmyError> where Self: Sized, { - let post = Post::read_from_apub_id(conn, object_id); + let post = Post::read_from_apub_id(conn, object_id.clone())?; Ok(match post { - Ok(o) => PostOrComment::Post(Box::new(o)), - Err(_) => PostOrComment::Comment(Box::new(Comment::read_from_apub_id(conn, object_id)?)), + Some(o) => Some(PostOrComment::Post(Box::new(o))), + None => { + Comment::read_from_apub_id(conn, object_id)?.map(|c| PostOrComment::Comment(Box::new(c))) + } }) } } diff --git a/crates/apub/src/fetcher/search.rs b/crates/apub/src/fetcher/search.rs index da152f2f9..0faca2da5 100644 --- a/crates/apub/src/fetcher/search.rs +++ b/crates/apub/src/fetcher/search.rs @@ -4,19 +4,18 @@ use crate::{ }; use activitystreams::chrono::NaiveDateTime; use anyhow::anyhow; -use diesel::{result::Error, PgConnection}; +use diesel::PgConnection; use itertools::Itertools; use lemmy_api_common::blocking; -use lemmy_apub_lib::webfinger::{webfinger_resolve_actor, WebfingerType}; +use lemmy_apub_lib::{ + webfinger::{webfinger_resolve_actor, WebfingerType}, + ApubObject, +}; use lemmy_db_queries::{ source::{community::Community_, person::Person_}, - ApubObject, DbPool, }; -use lemmy_db_schema::{ - source::{comment::Comment, community::Community, person::Person, post::Post}, - DbUrl, -}; +use lemmy_db_schema::source::{comment::Comment, community::Community, person::Person, post::Post}; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use serde::Deserialize; @@ -102,6 +101,8 @@ pub enum SearchableApubTypes { } impl ApubObject for SearchableObjects { + type DataType = PgConnection; + fn last_refreshed_at(&self) -> Option { match self { SearchableObjects::Person(p) => p.last_refreshed_at(), @@ -114,23 +115,26 @@ impl ApubObject for SearchableObjects { // TODO: this is inefficient, because if the object is not in local db, it will run 4 db queries // before finally returning an error. it would be nice if we could check all 4 tables in // a single query. - // we could skip this and always return an error, but then it would not be able to mark - // objects as deleted that were deleted by remote server. - fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result { - let c = Community::read_from_apub_id(conn, object_id); - if let Ok(c) = c { - return Ok(SearchableObjects::Community(c)); + // 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())?; + if let Some(c) = c { + return Ok(Some(SearchableObjects::Community(c))); } - let p = Person::read_from_apub_id(conn, object_id); - if let Ok(p) = p { - return Ok(SearchableObjects::Person(p)); + let p = Person::read_from_apub_id(conn, object_id.clone())?; + if let Some(p) = p { + return Ok(Some(SearchableObjects::Person(p))); } - let p = Post::read_from_apub_id(conn, object_id); - if let Ok(p) = p { - return Ok(SearchableObjects::Post(p)); + let p = Post::read_from_apub_id(conn, object_id.clone())?; + if let Some(p) = p { + return Ok(Some(SearchableObjects::Post(p))); } - let c = Comment::read_from_apub_id(conn, object_id); - Ok(SearchableObjects::Comment(c?)) + let c = Comment::read_from_apub_id(conn, object_id)?; + if let Some(c) = c { + return Ok(Some(SearchableObjects::Comment(c))); + } + Ok(None) } } diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index 451ce0554..c92213a62 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -21,7 +21,7 @@ use lemmy_apub_lib::{ values::{MediaTypeHtml, MediaTypeMarkdown}, verify_domains_match, }; -use lemmy_db_queries::{source::post::Post_, ApubObject, Crud, DbPool}; +use lemmy_db_queries::{source::post::Post_, Crud, DbPool}; use lemmy_db_schema::{ self, source::{ @@ -78,12 +78,10 @@ impl Page { /// the current value, it is a mod action and needs to be verified as such. /// /// Both stickied and locked need to be false on a newly created post (verified in [[CreatePost]]. - pub(crate) async fn is_mod_action(&self, pool: &DbPool) -> Result { - let post_id = self.id.clone(); - let old_post = blocking(pool, move |conn| { - Post::read_from_apub_id(conn, &post_id.into()) - }) - .await?; + pub(crate) async fn is_mod_action(&self, context: &LemmyContext) -> Result { + let old_post = ObjectId::::new(self.id.clone()) + .dereference_local(context) + .await; let is_mod_action = if let Ok(old_post) = old_post { self.stickied != Some(old_post.stickied) || self.comments_enabled != Some(!old_post.locked) @@ -101,7 +99,7 @@ impl Page { let community = extract_community(&self.to, context, request_counter).await?; check_slurs(&self.name, &context.settings().slur_regex())?; - verify_domains_match(self.attributed_to.inner(), &self.id)?; + verify_domains_match(self.attributed_to.inner(), &self.id.clone())?; verify_person_in_community( &self.attributed_to, &ObjectId::new(community.actor_id()), @@ -177,7 +175,7 @@ impl FromApub for Post { ) -> 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.pool()).await? { + let ap_id = if page.is_mod_action(context).await? { page.id_unchecked() } else { page.id(expected_domain)? diff --git a/crates/apub_lib/src/lib.rs b/crates/apub_lib/src/lib.rs index 192108e29..361a64b4c 100644 --- a/crates/apub_lib/src/lib.rs +++ b/crates/apub_lib/src/lib.rs @@ -1,6 +1,6 @@ pub mod values; -use activitystreams::error::DomainError; +use activitystreams::{chrono::NaiveDateTime, error::DomainError}; pub use lemmy_apub_lib_derive::*; use lemmy_utils::LemmyError; use std::{ops::Deref, sync::Arc}; @@ -30,6 +30,17 @@ pub trait ActivityHandler { ) -> Result<(), LemmyError>; } +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> + where + Self: Sized; +} + pub fn verify_domains_match(a: &Url, b: &Url) -> Result<(), LemmyError> { if a.domain() != b.domain() { return Err(DomainError.into()); diff --git a/crates/db_queries/src/lib.rs b/crates/db_queries/src/lib.rs index a94fb4659..6ca502397 100644 --- a/crates/db_queries/src/lib.rs +++ b/crates/db_queries/src/lib.rs @@ -12,7 +12,6 @@ extern crate diesel_migrations; #[cfg(test)] extern crate serial_test; -use chrono::NaiveDateTime; use diesel::{result::Error, *}; use lemmy_db_schema::{CommunityId, DbUrl, PersonId}; use lemmy_utils::ApiError; @@ -155,16 +154,6 @@ pub trait DeleteableOrRemoveable { fn blank_out_deleted_or_removed_info(self) -> Self; } -// TODO: move this to apub lib -pub trait ApubObject { - /// 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; - fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result - where - Self: Sized; -} - pub trait MaybeOptional { fn get_optional(self) -> Option; } diff --git a/crates/db_queries/src/source/comment.rs b/crates/db_queries/src/source/comment.rs index ec8466db5..8031131c5 100644 --- a/crates/db_queries/src/source/comment.rs +++ b/crates/db_queries/src/source/comment.rs @@ -1,5 +1,4 @@ -use crate::{ApubObject, Crud, DeleteableOrRemoveable, Likeable, Saveable}; -use chrono::NaiveDateTime; +use crate::{Crud, DeleteableOrRemoveable, Likeable, Saveable}; use diesel::{dsl::*, result::Error, *}; use lemmy_db_schema::{ naive_now, @@ -179,17 +178,6 @@ impl Crud for Comment { } } -impl ApubObject for Comment { - fn last_refreshed_at(&self) -> Option { - None - } - - fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result { - use lemmy_db_schema::schema::comment::dsl::*; - comment.filter(ap_id.eq(object_id)).first::(conn) - } -} - impl Likeable for CommentLike { type Form = CommentLikeForm; type IdType = CommentId; diff --git a/crates/db_queries/src/source/community.rs b/crates/db_queries/src/source/community.rs index dbce19694..26e33380a 100644 --- a/crates/db_queries/src/source/community.rs +++ b/crates/db_queries/src/source/community.rs @@ -1,5 +1,4 @@ -use crate::{ApubObject, Bannable, Crud, DeleteableOrRemoveable, Followable, Joinable}; -use chrono::NaiveDateTime; +use crate::{Bannable, Crud, DeleteableOrRemoveable, Followable, Joinable}; use diesel::{dsl::*, result::Error, *}; use lemmy_db_schema::{ naive_now, @@ -93,19 +92,6 @@ impl Crud for Community { } } -impl ApubObject for Community { - fn last_refreshed_at(&self) -> Option { - Some(self.last_refreshed_at) - } - - fn read_from_apub_id(conn: &PgConnection, for_actor_id: &DbUrl) -> Result { - use lemmy_db_schema::schema::community::dsl::*; - community - .filter(actor_id.eq(for_actor_id)) - .first::(conn) - } -} - pub trait Community_ { fn read_from_name(conn: &PgConnection, community_name: &str) -> Result; fn update_deleted( diff --git a/crates/db_queries/src/source/person.rs b/crates/db_queries/src/source/person.rs index 2b2e7d0e9..3f0c6f867 100644 --- a/crates/db_queries/src/source/person.rs +++ b/crates/db_queries/src/source/person.rs @@ -1,11 +1,9 @@ -use crate::{ApubObject, Crud}; -use chrono::NaiveDateTime; +use crate::Crud; use diesel::{dsl::*, result::Error, *}; use lemmy_db_schema::{ naive_now, schema::person::dsl::*, source::person::{Person, PersonForm}, - DbUrl, PersonId, }; @@ -181,20 +179,6 @@ impl Crud for Person { } } -impl ApubObject for Person { - fn last_refreshed_at(&self) -> Option { - Some(self.last_refreshed_at) - } - - fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result { - use lemmy_db_schema::schema::person::dsl::*; - person - .filter(deleted.eq(false)) - .filter(actor_id.eq(object_id)) - .first::(conn) - } -} - pub trait Person_ { fn ban_person(conn: &PgConnection, person_id: PersonId, ban: bool) -> Result; fn add_admin(conn: &PgConnection, person_id: PersonId, added: bool) -> Result; diff --git a/crates/db_queries/src/source/post.rs b/crates/db_queries/src/source/post.rs index 7f185f429..f9c34be83 100644 --- a/crates/db_queries/src/source/post.rs +++ b/crates/db_queries/src/source/post.rs @@ -1,5 +1,4 @@ -use crate::{ApubObject, Crud, DeleteableOrRemoveable, Likeable, Readable, Saveable}; -use chrono::NaiveDateTime; +use crate::{Crud, DeleteableOrRemoveable, Likeable, Readable, Saveable}; use diesel::{dsl::*, result::Error, *}; use lemmy_db_schema::{ naive_now, @@ -193,17 +192,6 @@ impl Post_ for Post { } } -impl ApubObject for Post { - fn last_refreshed_at(&self) -> Option { - None - } - - fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result { - use lemmy_db_schema::schema::post::dsl::*; - post.filter(ap_id.eq(object_id)).first::(conn) - } -} - impl Likeable for PostLike { type Form = PostLikeForm; type IdType = PostId; diff --git a/crates/db_queries/src/source/private_message.rs b/crates/db_queries/src/source/private_message.rs index 71dca04c7..3aa279b2c 100644 --- a/crates/db_queries/src/source/private_message.rs +++ b/crates/db_queries/src/source/private_message.rs @@ -1,5 +1,4 @@ -use crate::{ApubObject, Crud, DeleteableOrRemoveable}; -use chrono::NaiveDateTime; +use crate::{Crud, DeleteableOrRemoveable}; use diesel::{dsl::*, result::Error, *}; use lemmy_db_schema::{naive_now, source::private_message::*, DbUrl, PersonId, PrivateMessageId}; @@ -30,22 +29,6 @@ impl Crud for PrivateMessage { } } -impl ApubObject for PrivateMessage { - fn last_refreshed_at(&self) -> Option { - None - } - - fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result - where - Self: Sized, - { - use lemmy_db_schema::schema::private_message::dsl::*; - private_message - .filter(ap_id.eq(object_id)) - .first::(conn) - } -} - pub trait PrivateMessage_ { fn update_ap_id( conn: &PgConnection, diff --git a/crates/db_schema/Cargo.toml b/crates/db_schema/Cargo.toml index 869fd99c2..d6ac3d72b 100644 --- a/crates/db_schema/Cargo.toml +++ b/crates/db_schema/Cargo.toml @@ -9,6 +9,8 @@ license = "AGPL-3.0" 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"] } chrono = { version = "0.4.19", features = ["serde"] } serde = { version = "1.0.130", features = ["derive"] } diff --git a/crates/db_schema/src/source/comment.rs b/crates/db_schema/src/source/comment.rs index bd36a48e8..2116a93c2 100644 --- a/crates/db_schema/src/source/comment.rs +++ b/crates/db_schema/src/source/comment.rs @@ -6,7 +6,12 @@ use crate::{ PersonId, PostId, }; +use chrono::NaiveDateTime; +use diesel::{ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl}; +use lemmy_apub_lib::ApubObject; +use lemmy_utils::LemmyError; use serde::Serialize; +use url::Url; // WITH RECURSIVE MyTree AS ( // SELECT * FROM comment WHERE parent_id IS NULL @@ -104,3 +109,17 @@ pub struct CommentSavedForm { pub comment_id: CommentId, pub person_id: PersonId, } + +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()) + } +} diff --git a/crates/db_schema/src/source/community.rs b/crates/db_schema/src/source/community.rs index 1581933f0..16ed7cd3c 100644 --- a/crates/db_schema/src/source/community.rs +++ b/crates/db_schema/src/source/community.rs @@ -4,7 +4,12 @@ use crate::{ DbUrl, PersonId, }; +use chrono::NaiveDateTime; +use diesel::{ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl}; +use lemmy_apub_lib::ApubObject; +use lemmy_utils::LemmyError; use serde::Serialize; +use url::Url; #[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)] #[table_name = "community"] @@ -124,3 +129,22 @@ pub struct CommunityFollowerForm { pub person_id: PersonId, pub pending: bool, } + +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(), + ) + } +} diff --git a/crates/db_schema/src/source/person.rs b/crates/db_schema/src/source/person.rs index 965378616..cdf60c959 100644 --- a/crates/db_schema/src/source/person.rs +++ b/crates/db_schema/src/source/person.rs @@ -3,7 +3,12 @@ use crate::{ DbUrl, PersonId, }; +use chrono::NaiveDateTime; +use diesel::{ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl}; +use lemmy_apub_lib::ApubObject; +use lemmy_utils::LemmyError; use serde::Serialize; +use url::Url; #[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)] #[table_name = "person"] @@ -170,3 +175,23 @@ pub struct PersonForm { pub admin: Option, pub bot_account: Option, } + +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> { + 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(), + ) + } +} diff --git a/crates/db_schema/src/source/post.rs b/crates/db_schema/src/source/post.rs index 536dd9606..a6c8369c3 100644 --- a/crates/db_schema/src/source/post.rs +++ b/crates/db_schema/src/source/post.rs @@ -5,7 +5,12 @@ use crate::{ PersonId, PostId, }; +use chrono::NaiveDateTime; +use diesel::{ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl}; +use lemmy_apub_lib::ApubObject; +use lemmy_utils::LemmyError; use serde::Serialize; +use url::Url; #[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)] #[table_name = "post"] @@ -106,3 +111,17 @@ pub struct PostReadForm { pub post_id: PostId, pub person_id: PersonId, } + +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()) + } +} diff --git a/crates/db_schema/src/source/private_message.rs b/crates/db_schema/src/source/private_message.rs index 6710c2dcf..b4780affd 100644 --- a/crates/db_schema/src/source/private_message.rs +++ b/crates/db_schema/src/source/private_message.rs @@ -1,5 +1,10 @@ use crate::{schema::private_message, DbUrl, PersonId, PrivateMessageId}; +use chrono::NaiveDateTime; +use diesel::{ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl}; +use lemmy_apub_lib::ApubObject; +use lemmy_utils::LemmyError; use serde::Serialize; +use url::Url; #[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug, Serialize)] #[table_name = "private_message"] @@ -29,3 +34,22 @@ pub struct PrivateMessageForm { pub ap_id: Option, pub local: Option, } + +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> { + 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(), + ) + } +}