From 8112816e99c910e1da2fbfb7d6b46e31f0248ce2 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Wed, 23 Mar 2022 21:27:51 +0000 Subject: [PATCH] If viewed actor isnt in db, fetch it from other instance (#2145) --- crates/api/src/site.rs | 11 +++- crates/api_common/src/lib.rs | 36 +---------- crates/api_crud/src/comment/read.rs | 4 +- crates/api_crud/src/community/read.rs | 4 +- crates/api_crud/src/post/read.rs | 4 +- crates/api_crud/src/user/read.rs | 4 +- crates/apub/src/activities/block/mod.rs | 1 + .../src/collections/community_moderators.rs | 2 + .../apub/src/collections/community_outbox.rs | 2 + crates/apub/src/fetcher/mod.rs | 59 +++++++++++++++++++ crates/apub/src/fetcher/post_or_comment.rs | 1 + crates/apub/src/fetcher/search.rs | 1 + crates/apub/src/fetcher/user_or_community.rs | 1 + crates/apub/src/objects/comment.rs | 1 + crates/apub/src/objects/community.rs | 3 +- crates/apub/src/objects/instance.rs | 1 + crates/apub/src/objects/person.rs | 3 +- crates/apub/src/objects/post.rs | 1 + crates/apub/src/objects/private_message.rs | 1 + crates/apub_lib/src/traits.rs | 1 + crates/db_schema/src/impls/community.rs | 4 +- crates/db_schema/src/impls/person.rs | 4 +- crates/db_schema/src/traits.rs | 8 ++- 23 files changed, 100 insertions(+), 57 deletions(-) diff --git a/crates/api/src/site.rs b/crates/api/src/site.rs index e97856990..677576e13 100644 --- a/crates/api/src/site.rs +++ b/crates/api/src/site.rs @@ -8,11 +8,16 @@ use lemmy_api_common::{ get_local_user_view_from_jwt, get_local_user_view_from_jwt_opt, is_admin, - resolve_actor_identifier, send_application_approved_email, site::*, }; -use lemmy_apub::fetcher::search::{search_by_apub_id, SearchableObjects}; +use lemmy_apub::{ + fetcher::{ + resolve_actor_identifier, + search::{search_by_apub_id, SearchableObjects}, + }, + objects::community::ApubCommunity, +}; use lemmy_db_schema::{ diesel_option_overwrite, from_opt_str_to_opt_enum, @@ -197,7 +202,7 @@ impl Perform for Search { let search_type: SearchType = from_opt_str_to_opt_enum(&data.type_).unwrap_or(SearchType::All); let community_id = data.community_id; let community_actor_id = if let Some(name) = &data.community_name { - resolve_actor_identifier::(name, context.pool()) + resolve_actor_identifier::(name, context) .await .ok() .map(|c| c.actor_id) diff --git a/crates/api_common/src/lib.rs b/crates/api_common/src/lib.rs index a95365e9e..68ad36744 100644 --- a/crates/api_common/src/lib.rs +++ b/crates/api_common/src/lib.rs @@ -6,7 +6,6 @@ pub mod site; pub mod websocket; use crate::site::FederatedInstances; -use itertools::Itertools; use lemmy_db_schema::{ newtypes::{CommunityId, LocalUserId, PersonId, PostId}, source::{ @@ -20,7 +19,7 @@ use lemmy_db_schema::{ secret::Secret, site::Site, }, - traits::{ApubActor, Crud, Readable}, + traits::{Crud, Readable}, DbPool, }; use lemmy_db_views::{ @@ -522,39 +521,6 @@ pub async fn check_private_instance_and_federation_enabled( Ok(()) } -/// Resolve actor identifier (eg `!news@example.com`) from local database to avoid network requests. -/// This only works for local actors, and remote actors which were previously fetched (so it doesnt -/// trigger any new fetch). -#[tracing::instrument(skip_all)] -pub async fn resolve_actor_identifier( - identifier: &str, - pool: &DbPool, -) -> Result -where - Actor: ApubActor + Send + 'static, -{ - // remote actor - if identifier.contains('@') { - let (name, domain) = identifier - .splitn(2, '@') - .collect_tuple() - .expect("invalid query"); - let name = name.to_string(); - let domain = format!("{}://{}", Settings::get().get_protocol_string(), domain); - Ok( - blocking(pool, move |conn| { - Actor::read_from_name_and_domain(conn, &name, &domain) - }) - .await??, - ) - } - // local actor - else { - let identifier = identifier.to_string(); - Ok(blocking(pool, move |conn| Actor::read_from_name(conn, &identifier)).await??) - } -} - pub async fn remove_user_data(banned_person_id: PersonId, pool: &DbPool) -> Result<(), LemmyError> { // Posts blocking(pool, move |conn: &'_ _| { diff --git a/crates/api_crud/src/comment/read.rs b/crates/api_crud/src/comment/read.rs index 77ef1f962..d04a0b45a 100644 --- a/crates/api_crud/src/comment/read.rs +++ b/crates/api_crud/src/comment/read.rs @@ -5,8 +5,8 @@ use lemmy_api_common::{ check_private_instance, comment::*, get_local_user_view_from_jwt_opt, - resolve_actor_identifier, }; +use lemmy_apub::{fetcher::resolve_actor_identifier, objects::community::ApubCommunity}; use lemmy_db_schema::{ from_opt_str_to_opt_enum, source::community::Community, @@ -78,7 +78,7 @@ impl PerformCrud for GetComments { let community_id = data.community_id; let community_actor_id = if let Some(name) = &data.community_name { - resolve_actor_identifier::(name, context.pool()) + resolve_actor_identifier::(name, context) .await .ok() .map(|c| c.actor_id) diff --git a/crates/api_crud/src/community/read.rs b/crates/api_crud/src/community/read.rs index ca30292f3..de567c1fb 100644 --- a/crates/api_crud/src/community/read.rs +++ b/crates/api_crud/src/community/read.rs @@ -5,8 +5,8 @@ use lemmy_api_common::{ check_private_instance, community::*, get_local_user_view_from_jwt_opt, - resolve_actor_identifier, }; +use lemmy_apub::{fetcher::resolve_actor_identifier, objects::community::ApubCommunity}; use lemmy_db_schema::{ from_opt_str_to_opt_enum, source::community::Community, @@ -44,7 +44,7 @@ impl PerformCrud for GetCommunity { Some(id) => id, None => { let name = data.name.to_owned().unwrap_or_else(|| "main".to_string()); - resolve_actor_identifier::(&name, context.pool()) + resolve_actor_identifier::(&name, context) .await .map_err(|e| e.with_message("couldnt_find_community"))? .id diff --git a/crates/api_crud/src/post/read.rs b/crates/api_crud/src/post/read.rs index 6b54c5b23..724442245 100644 --- a/crates/api_crud/src/post/read.rs +++ b/crates/api_crud/src/post/read.rs @@ -6,8 +6,8 @@ use lemmy_api_common::{ get_local_user_view_from_jwt_opt, mark_post_as_read, post::*, - resolve_actor_identifier, }; +use lemmy_apub::{fetcher::resolve_actor_identifier, objects::community::ApubCommunity}; use lemmy_db_schema::{ from_opt_str_to_opt_enum, source::community::Community, @@ -152,7 +152,7 @@ impl PerformCrud for GetPosts { let limit = data.limit; let community_id = data.community_id; let community_actor_id = if let Some(name) = &data.community_name { - resolve_actor_identifier::(name, context.pool()) + resolve_actor_identifier::(name, context) .await .ok() .map(|c| c.actor_id) diff --git a/crates/api_crud/src/user/read.rs b/crates/api_crud/src/user/read.rs index e98d2ff0c..55cc219be 100644 --- a/crates/api_crud/src/user/read.rs +++ b/crates/api_crud/src/user/read.rs @@ -5,8 +5,8 @@ use lemmy_api_common::{ check_private_instance, get_local_user_view_from_jwt_opt, person::*, - resolve_actor_identifier, }; +use lemmy_apub::{fetcher::resolve_actor_identifier, objects::person::ApubPerson}; use lemmy_db_schema::{from_opt_str_to_opt_enum, source::person::Person, SortType}; use lemmy_db_views::{comment_view::CommentQueryBuilder, post_view::PostQueryBuilder}; use lemmy_db_views_actor::{ @@ -51,7 +51,7 @@ impl PerformCrud for GetPersonDetails { .to_owned() .unwrap_or_else(|| "admin".to_string()); - resolve_actor_identifier::(&name, context.pool()) + resolve_actor_identifier::(&name, context) .await .map_err(|e| e.with_message("couldnt_find_that_username_or_email"))? .id diff --git a/crates/apub/src/activities/block/mod.rs b/crates/apub/src/activities/block/mod.rs index 7460acec8..f41e352f6 100644 --- a/crates/apub/src/activities/block/mod.rs +++ b/crates/apub/src/activities/block/mod.rs @@ -34,6 +34,7 @@ pub enum InstanceOrGroup { impl ApubObject for SiteOrCommunity { type DataType = LemmyContext; type ApubType = InstanceOrGroup; + type DbType = (); type TombstoneType = (); #[tracing::instrument(skip_all)] diff --git a/crates/apub/src/collections/community_moderators.rs b/crates/apub/src/collections/community_moderators.rs index a9a691b8c..528254826 100644 --- a/crates/apub/src/collections/community_moderators.rs +++ b/crates/apub/src/collections/community_moderators.rs @@ -133,6 +133,8 @@ impl ApubObject for ApubCommunityModerators { // This return value is unused, so just set an empty vec Ok(ApubCommunityModerators { 0: vec![] }) } + + type DbType = (); } #[cfg(test)] diff --git a/crates/apub/src/collections/community_outbox.rs b/crates/apub/src/collections/community_outbox.rs index eaa9acd9f..3b4c8534d 100644 --- a/crates/apub/src/collections/community_outbox.rs +++ b/crates/apub/src/collections/community_outbox.rs @@ -118,4 +118,6 @@ impl ApubObject for ApubCommunityOutbox { // This return value is unused, so just set an empty vec Ok(ApubCommunityOutbox { 0: vec![] }) } + + type DbType = (); } diff --git a/crates/apub/src/fetcher/mod.rs b/crates/apub/src/fetcher/mod.rs index ff92e99fd..16173568e 100644 --- a/crates/apub/src/fetcher/mod.rs +++ b/crates/apub/src/fetcher/mod.rs @@ -1,4 +1,63 @@ +use crate::fetcher::webfinger::webfinger_resolve_actor; +use itertools::Itertools; +use lemmy_api_common::blocking; +use lemmy_apub_lib::traits::{ActorType, ApubObject}; +use lemmy_db_schema::traits::ApubActor; +use lemmy_utils::{settings::structs::Settings, LemmyError}; +use lemmy_websocket::LemmyContext; + pub mod post_or_comment; pub mod search; pub mod user_or_community; pub mod webfinger; + +/// Resolve actor identifier (eg `!news@example.com`) from local database to avoid network requests. +/// This only works for local actors, and remote actors which were previously fetched (so it doesnt +/// trigger any new fetch). +#[tracing::instrument(skip_all)] +pub async fn resolve_actor_identifier( + identifier: &str, + context: &LemmyContext, +) -> Result +where + Actor: + ApubObject + ApubObject + ActorType + Send + 'static, + for<'de2> ::ApubType: serde::Deserialize<'de2>, + DbActor: ApubActor + Send + 'static, +{ + // remote actor + if identifier.contains('@') { + let (name, domain) = identifier + .splitn(2, '@') + .collect_tuple() + .expect("invalid query"); + let name = name.to_string(); + let domain = format!("{}://{}", Settings::get().get_protocol_string(), domain); + let actor = blocking(context.pool(), move |conn| { + DbActor::read_from_name_and_domain(conn, &name, &domain) + }) + .await?; + if actor.is_ok() { + Ok(actor?) + } else { + // Fetch the actor from its home instance using webfinger + let id = webfinger_resolve_actor::(identifier, context, &mut 0).await?; + let actor: DbActor = blocking(context.pool(), move |conn| { + DbActor::read_from_apub_id(conn, &id) + }) + .await?? + .expect("actor exists as we fetched just before"); + Ok(actor) + } + } + // local actor + else { + let identifier = identifier.to_string(); + Ok( + blocking(context.pool(), move |conn| { + DbActor::read_from_name(conn, &identifier) + }) + .await??, + ) + } +} diff --git a/crates/apub/src/fetcher/post_or_comment.rs b/crates/apub/src/fetcher/post_or_comment.rs index 2702385cb..f03a113ec 100644 --- a/crates/apub/src/fetcher/post_or_comment.rs +++ b/crates/apub/src/fetcher/post_or_comment.rs @@ -26,6 +26,7 @@ pub enum PageOrNote { impl ApubObject for PostOrComment { type DataType = LemmyContext; type ApubType = PageOrNote; + type DbType = (); type TombstoneType = (); fn last_refreshed_at(&self) -> Option { diff --git a/crates/apub/src/fetcher/search.rs b/crates/apub/src/fetcher/search.rs index 6271cec12..e4ae45e68 100644 --- a/crates/apub/src/fetcher/search.rs +++ b/crates/apub/src/fetcher/search.rs @@ -78,6 +78,7 @@ pub enum SearchableApubTypes { impl ApubObject for SearchableObjects { type DataType = LemmyContext; type ApubType = SearchableApubTypes; + type DbType = (); type TombstoneType = (); fn last_refreshed_at(&self) -> Option { diff --git a/crates/apub/src/fetcher/user_or_community.rs b/crates/apub/src/fetcher/user_or_community.rs index 9e38e3dca..ee86adcec 100644 --- a/crates/apub/src/fetcher/user_or_community.rs +++ b/crates/apub/src/fetcher/user_or_community.rs @@ -26,6 +26,7 @@ pub enum PersonOrGroup { impl ApubObject for UserOrCommunity { type DataType = LemmyContext; type ApubType = PersonOrGroup; + type DbType = (); type TombstoneType = (); fn last_refreshed_at(&self) -> Option { diff --git a/crates/apub/src/objects/comment.rs b/crates/apub/src/objects/comment.rs index b719ba45b..2b706aac5 100644 --- a/crates/apub/src/objects/comment.rs +++ b/crates/apub/src/objects/comment.rs @@ -58,6 +58,7 @@ impl From for ApubComment { impl ApubObject for ApubComment { type DataType = LemmyContext; type ApubType = Note; + type DbType = Comment; type TombstoneType = Tombstone; fn last_refreshed_at(&self) -> Option { diff --git a/crates/apub/src/objects/community.rs b/crates/apub/src/objects/community.rs index 82b6d797c..87646e91f 100644 --- a/crates/apub/src/objects/community.rs +++ b/crates/apub/src/objects/community.rs @@ -49,6 +49,7 @@ impl From for ApubCommunity { impl ApubObject for ApubCommunity { type DataType = LemmyContext; type ApubType = Group; + type DbType = Community; type TombstoneType = Tombstone; fn last_refreshed_at(&self) -> Option { @@ -62,7 +63,7 @@ impl ApubObject for ApubCommunity { ) -> Result, LemmyError> { Ok( blocking(context.pool(), move |conn| { - Community::read_from_apub_id(conn, object_id) + Community::read_from_apub_id(conn, &object_id.into()) }) .await?? .map(Into::into), diff --git a/crates/apub/src/objects/instance.rs b/crates/apub/src/objects/instance.rs index f12696c49..9b5ef117d 100644 --- a/crates/apub/src/objects/instance.rs +++ b/crates/apub/src/objects/instance.rs @@ -45,6 +45,7 @@ impl From for ApubSite { impl ApubObject for ApubSite { type DataType = LemmyContext; type ApubType = Instance; + type DbType = Site; type TombstoneType = (); fn last_refreshed_at(&self) -> Option { diff --git a/crates/apub/src/objects/person.rs b/crates/apub/src/objects/person.rs index 14e79e944..26edc39b9 100644 --- a/crates/apub/src/objects/person.rs +++ b/crates/apub/src/objects/person.rs @@ -55,6 +55,7 @@ impl From for ApubPerson { impl ApubObject for ApubPerson { type DataType = LemmyContext; type ApubType = Person; + type DbType = DbPerson; type TombstoneType = (); fn last_refreshed_at(&self) -> Option { @@ -68,7 +69,7 @@ impl ApubObject for ApubPerson { ) -> Result, LemmyError> { Ok( blocking(context.pool(), move |conn| { - DbPerson::read_from_apub_id(conn, object_id) + DbPerson::read_from_apub_id(conn, &object_id.into()) }) .await?? .map(Into::into), diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index a002e72c7..c7d48a686 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -57,6 +57,7 @@ impl From for ApubPost { impl ApubObject for ApubPost { type DataType = LemmyContext; type ApubType = Page; + type DbType = Post; type TombstoneType = Tombstone; fn last_refreshed_at(&self) -> Option { diff --git a/crates/apub/src/objects/private_message.rs b/crates/apub/src/objects/private_message.rs index 4896e1991..3768577cc 100644 --- a/crates/apub/src/objects/private_message.rs +++ b/crates/apub/src/objects/private_message.rs @@ -46,6 +46,7 @@ impl From for ApubPrivateMessage { impl ApubObject for ApubPrivateMessage { type DataType = LemmyContext; type ApubType = ChatMessage; + type DbType = PrivateMessage; type TombstoneType = (); fn last_refreshed_at(&self) -> Option { diff --git a/crates/apub_lib/src/traits.rs b/crates/apub_lib/src/traits.rs index bc57dfa40..43fec524a 100644 --- a/crates/apub_lib/src/traits.rs +++ b/crates/apub_lib/src/traits.rs @@ -24,6 +24,7 @@ pub trait ActivityHandler { pub trait ApubObject { type DataType; type ApubType; + type DbType; type TombstoneType; /// If this object should be refetched after a certain interval, it should return the last refresh diff --git a/crates/db_schema/src/impls/community.rs b/crates/db_schema/src/impls/community.rs index 43b7db3e2..a7b509963 100644 --- a/crates/db_schema/src/impls/community.rs +++ b/crates/db_schema/src/impls/community.rs @@ -24,7 +24,6 @@ use diesel::{ RunQueryDsl, TextExpressionMethods, }; -use url::Url; mod safe_type { use crate::{schema::community::*, source::community::Community, traits::ToSafe}; @@ -291,9 +290,8 @@ impl Followable for CommunityFollower { } impl ApubActor for Community { - fn read_from_apub_id(conn: &PgConnection, object_id: Url) -> Result, Error> { + fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result, Error> { use crate::schema::community::dsl::*; - let object_id: DbUrl = object_id.into(); Ok( community .filter(actor_id.eq(object_id)) diff --git a/crates/db_schema/src/impls/person.rs b/crates/db_schema/src/impls/person.rs index 4c052c8b6..165d9308e 100644 --- a/crates/db_schema/src/impls/person.rs +++ b/crates/db_schema/src/impls/person.rs @@ -15,7 +15,6 @@ use diesel::{ RunQueryDsl, TextExpressionMethods, }; -use url::Url; mod safe_type { use crate::{schema::person::columns::*, source::person::Person, traits::ToSafe}; @@ -284,9 +283,8 @@ fn is_banned(banned_: bool, expires: Option) -> bool { } impl ApubActor for Person { - fn read_from_apub_id(conn: &PgConnection, object_id: Url) -> Result, Error> { + fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result, Error> { use crate::schema::person::dsl::*; - let object_id: DbUrl = object_id.into(); Ok( person .filter(deleted.eq(false)) diff --git a/crates/db_schema/src/traits.rs b/crates/db_schema/src/traits.rs index ad984d7bf..98f973273 100644 --- a/crates/db_schema/src/traits.rs +++ b/crates/db_schema/src/traits.rs @@ -1,6 +1,8 @@ -use crate::newtypes::{CommunityId, PersonId}; +use crate::{ + newtypes::{CommunityId, PersonId}, + DbUrl, +}; use diesel::{result::Error, PgConnection}; -use url::Url; pub trait Crud { type Form; @@ -166,7 +168,7 @@ pub trait ViewToVec { pub trait ApubActor { // TODO: this should be in a trait ApubObject (and implemented for Post, Comment, PrivateMessage as well) - fn read_from_apub_id(conn: &PgConnection, object_id: Url) -> Result, Error> + fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result, Error> where Self: Sized; fn read_from_name(conn: &PgConnection, actor_name: &str) -> Result