If viewed actor isnt in db, fetch it from other instance (#2145)

This commit is contained in:
Nutomic 2022-03-23 21:27:51 +00:00 committed by GitHub
parent 0c63dbafb6
commit 8112816e99
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 100 additions and 57 deletions

View file

@ -8,11 +8,16 @@ use lemmy_api_common::{
get_local_user_view_from_jwt, get_local_user_view_from_jwt,
get_local_user_view_from_jwt_opt, get_local_user_view_from_jwt_opt,
is_admin, is_admin,
resolve_actor_identifier,
send_application_approved_email, send_application_approved_email,
site::*, 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::{ use lemmy_db_schema::{
diesel_option_overwrite, diesel_option_overwrite,
from_opt_str_to_opt_enum, 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 search_type: SearchType = from_opt_str_to_opt_enum(&data.type_).unwrap_or(SearchType::All);
let community_id = data.community_id; let community_id = data.community_id;
let community_actor_id = if let Some(name) = &data.community_name { let community_actor_id = if let Some(name) = &data.community_name {
resolve_actor_identifier::<Community>(name, context.pool()) resolve_actor_identifier::<ApubCommunity, Community>(name, context)
.await .await
.ok() .ok()
.map(|c| c.actor_id) .map(|c| c.actor_id)

View file

@ -6,7 +6,6 @@ pub mod site;
pub mod websocket; pub mod websocket;
use crate::site::FederatedInstances; use crate::site::FederatedInstances;
use itertools::Itertools;
use lemmy_db_schema::{ use lemmy_db_schema::{
newtypes::{CommunityId, LocalUserId, PersonId, PostId}, newtypes::{CommunityId, LocalUserId, PersonId, PostId},
source::{ source::{
@ -20,7 +19,7 @@ use lemmy_db_schema::{
secret::Secret, secret::Secret,
site::Site, site::Site,
}, },
traits::{ApubActor, Crud, Readable}, traits::{Crud, Readable},
DbPool, DbPool,
}; };
use lemmy_db_views::{ use lemmy_db_views::{
@ -522,39 +521,6 @@ pub async fn check_private_instance_and_federation_enabled(
Ok(()) 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<Actor>(
identifier: &str,
pool: &DbPool,
) -> Result<Actor, LemmyError>
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> { pub async fn remove_user_data(banned_person_id: PersonId, pool: &DbPool) -> Result<(), LemmyError> {
// Posts // Posts
blocking(pool, move |conn: &'_ _| { blocking(pool, move |conn: &'_ _| {

View file

@ -5,8 +5,8 @@ use lemmy_api_common::{
check_private_instance, check_private_instance,
comment::*, comment::*,
get_local_user_view_from_jwt_opt, get_local_user_view_from_jwt_opt,
resolve_actor_identifier,
}; };
use lemmy_apub::{fetcher::resolve_actor_identifier, objects::community::ApubCommunity};
use lemmy_db_schema::{ use lemmy_db_schema::{
from_opt_str_to_opt_enum, from_opt_str_to_opt_enum,
source::community::Community, source::community::Community,
@ -78,7 +78,7 @@ impl PerformCrud for GetComments {
let community_id = data.community_id; let community_id = data.community_id;
let community_actor_id = if let Some(name) = &data.community_name { let community_actor_id = if let Some(name) = &data.community_name {
resolve_actor_identifier::<Community>(name, context.pool()) resolve_actor_identifier::<ApubCommunity, Community>(name, context)
.await .await
.ok() .ok()
.map(|c| c.actor_id) .map(|c| c.actor_id)

View file

@ -5,8 +5,8 @@ use lemmy_api_common::{
check_private_instance, check_private_instance,
community::*, community::*,
get_local_user_view_from_jwt_opt, get_local_user_view_from_jwt_opt,
resolve_actor_identifier,
}; };
use lemmy_apub::{fetcher::resolve_actor_identifier, objects::community::ApubCommunity};
use lemmy_db_schema::{ use lemmy_db_schema::{
from_opt_str_to_opt_enum, from_opt_str_to_opt_enum,
source::community::Community, source::community::Community,
@ -44,7 +44,7 @@ impl PerformCrud for GetCommunity {
Some(id) => id, Some(id) => id,
None => { None => {
let name = data.name.to_owned().unwrap_or_else(|| "main".to_string()); let name = data.name.to_owned().unwrap_or_else(|| "main".to_string());
resolve_actor_identifier::<Community>(&name, context.pool()) resolve_actor_identifier::<ApubCommunity, Community>(&name, context)
.await .await
.map_err(|e| e.with_message("couldnt_find_community"))? .map_err(|e| e.with_message("couldnt_find_community"))?
.id .id

View file

@ -6,8 +6,8 @@ use lemmy_api_common::{
get_local_user_view_from_jwt_opt, get_local_user_view_from_jwt_opt,
mark_post_as_read, mark_post_as_read,
post::*, post::*,
resolve_actor_identifier,
}; };
use lemmy_apub::{fetcher::resolve_actor_identifier, objects::community::ApubCommunity};
use lemmy_db_schema::{ use lemmy_db_schema::{
from_opt_str_to_opt_enum, from_opt_str_to_opt_enum,
source::community::Community, source::community::Community,
@ -152,7 +152,7 @@ impl PerformCrud for GetPosts {
let limit = data.limit; let limit = data.limit;
let community_id = data.community_id; let community_id = data.community_id;
let community_actor_id = if let Some(name) = &data.community_name { let community_actor_id = if let Some(name) = &data.community_name {
resolve_actor_identifier::<Community>(name, context.pool()) resolve_actor_identifier::<ApubCommunity, Community>(name, context)
.await .await
.ok() .ok()
.map(|c| c.actor_id) .map(|c| c.actor_id)

View file

@ -5,8 +5,8 @@ use lemmy_api_common::{
check_private_instance, check_private_instance,
get_local_user_view_from_jwt_opt, get_local_user_view_from_jwt_opt,
person::*, 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_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::{comment_view::CommentQueryBuilder, post_view::PostQueryBuilder};
use lemmy_db_views_actor::{ use lemmy_db_views_actor::{
@ -51,7 +51,7 @@ impl PerformCrud for GetPersonDetails {
.to_owned() .to_owned()
.unwrap_or_else(|| "admin".to_string()); .unwrap_or_else(|| "admin".to_string());
resolve_actor_identifier::<Person>(&name, context.pool()) resolve_actor_identifier::<ApubPerson, Person>(&name, context)
.await .await
.map_err(|e| e.with_message("couldnt_find_that_username_or_email"))? .map_err(|e| e.with_message("couldnt_find_that_username_or_email"))?
.id .id

View file

@ -34,6 +34,7 @@ pub enum InstanceOrGroup {
impl ApubObject for SiteOrCommunity { impl ApubObject for SiteOrCommunity {
type DataType = LemmyContext; type DataType = LemmyContext;
type ApubType = InstanceOrGroup; type ApubType = InstanceOrGroup;
type DbType = ();
type TombstoneType = (); type TombstoneType = ();
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]

View file

@ -133,6 +133,8 @@ impl ApubObject for ApubCommunityModerators {
// This return value is unused, so just set an empty vec // This return value is unused, so just set an empty vec
Ok(ApubCommunityModerators { 0: vec![] }) Ok(ApubCommunityModerators { 0: vec![] })
} }
type DbType = ();
} }
#[cfg(test)] #[cfg(test)]

View file

@ -118,4 +118,6 @@ impl ApubObject for ApubCommunityOutbox {
// This return value is unused, so just set an empty vec // This return value is unused, so just set an empty vec
Ok(ApubCommunityOutbox { 0: vec![] }) Ok(ApubCommunityOutbox { 0: vec![] })
} }
type DbType = ();
} }

View file

@ -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 post_or_comment;
pub mod search; pub mod search;
pub mod user_or_community; pub mod user_or_community;
pub mod webfinger; 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<Actor, DbActor>(
identifier: &str,
context: &LemmyContext,
) -> Result<DbActor, LemmyError>
where
Actor:
ApubObject<DataType = LemmyContext> + ApubObject<DbType = DbActor> + ActorType + Send + 'static,
for<'de2> <Actor as ApubObject>::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::<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??,
)
}
}

View file

@ -26,6 +26,7 @@ pub enum PageOrNote {
impl ApubObject for PostOrComment { impl ApubObject for PostOrComment {
type DataType = LemmyContext; type DataType = LemmyContext;
type ApubType = PageOrNote; type ApubType = PageOrNote;
type DbType = ();
type TombstoneType = (); type TombstoneType = ();
fn last_refreshed_at(&self) -> Option<NaiveDateTime> { fn last_refreshed_at(&self) -> Option<NaiveDateTime> {

View file

@ -78,6 +78,7 @@ pub enum SearchableApubTypes {
impl ApubObject for SearchableObjects { impl ApubObject for SearchableObjects {
type DataType = LemmyContext; type DataType = LemmyContext;
type ApubType = SearchableApubTypes; type ApubType = SearchableApubTypes;
type DbType = ();
type TombstoneType = (); type TombstoneType = ();
fn last_refreshed_at(&self) -> Option<NaiveDateTime> { fn last_refreshed_at(&self) -> Option<NaiveDateTime> {

View file

@ -26,6 +26,7 @@ pub enum PersonOrGroup {
impl ApubObject for UserOrCommunity { impl ApubObject for UserOrCommunity {
type DataType = LemmyContext; type DataType = LemmyContext;
type ApubType = PersonOrGroup; type ApubType = PersonOrGroup;
type DbType = ();
type TombstoneType = (); type TombstoneType = ();
fn last_refreshed_at(&self) -> Option<NaiveDateTime> { fn last_refreshed_at(&self) -> Option<NaiveDateTime> {

View file

@ -58,6 +58,7 @@ impl From<Comment> for ApubComment {
impl ApubObject for ApubComment { impl ApubObject for ApubComment {
type DataType = LemmyContext; type DataType = LemmyContext;
type ApubType = Note; type ApubType = Note;
type DbType = Comment;
type TombstoneType = Tombstone; type TombstoneType = Tombstone;
fn last_refreshed_at(&self) -> Option<NaiveDateTime> { fn last_refreshed_at(&self) -> Option<NaiveDateTime> {

View file

@ -49,6 +49,7 @@ impl From<Community> for ApubCommunity {
impl ApubObject for ApubCommunity { impl ApubObject for ApubCommunity {
type DataType = LemmyContext; type DataType = LemmyContext;
type ApubType = Group; type ApubType = Group;
type DbType = Community;
type TombstoneType = Tombstone; type TombstoneType = Tombstone;
fn last_refreshed_at(&self) -> Option<NaiveDateTime> { fn last_refreshed_at(&self) -> Option<NaiveDateTime> {
@ -62,7 +63,7 @@ impl ApubObject for ApubCommunity {
) -> Result<Option<Self>, LemmyError> { ) -> Result<Option<Self>, LemmyError> {
Ok( Ok(
blocking(context.pool(), move |conn| { blocking(context.pool(), move |conn| {
Community::read_from_apub_id(conn, object_id) Community::read_from_apub_id(conn, &object_id.into())
}) })
.await?? .await??
.map(Into::into), .map(Into::into),

View file

@ -45,6 +45,7 @@ impl From<Site> for ApubSite {
impl ApubObject for ApubSite { impl ApubObject for ApubSite {
type DataType = LemmyContext; type DataType = LemmyContext;
type ApubType = Instance; type ApubType = Instance;
type DbType = Site;
type TombstoneType = (); type TombstoneType = ();
fn last_refreshed_at(&self) -> Option<NaiveDateTime> { fn last_refreshed_at(&self) -> Option<NaiveDateTime> {

View file

@ -55,6 +55,7 @@ impl From<DbPerson> for ApubPerson {
impl ApubObject for ApubPerson { impl ApubObject for ApubPerson {
type DataType = LemmyContext; type DataType = LemmyContext;
type ApubType = Person; type ApubType = Person;
type DbType = DbPerson;
type TombstoneType = (); type TombstoneType = ();
fn last_refreshed_at(&self) -> Option<NaiveDateTime> { fn last_refreshed_at(&self) -> Option<NaiveDateTime> {
@ -68,7 +69,7 @@ impl ApubObject for ApubPerson {
) -> Result<Option<Self>, LemmyError> { ) -> Result<Option<Self>, LemmyError> {
Ok( Ok(
blocking(context.pool(), move |conn| { blocking(context.pool(), move |conn| {
DbPerson::read_from_apub_id(conn, object_id) DbPerson::read_from_apub_id(conn, &object_id.into())
}) })
.await?? .await??
.map(Into::into), .map(Into::into),

View file

@ -57,6 +57,7 @@ impl From<Post> for ApubPost {
impl ApubObject for ApubPost { impl ApubObject for ApubPost {
type DataType = LemmyContext; type DataType = LemmyContext;
type ApubType = Page; type ApubType = Page;
type DbType = Post;
type TombstoneType = Tombstone; type TombstoneType = Tombstone;
fn last_refreshed_at(&self) -> Option<NaiveDateTime> { fn last_refreshed_at(&self) -> Option<NaiveDateTime> {

View file

@ -46,6 +46,7 @@ impl From<PrivateMessage> for ApubPrivateMessage {
impl ApubObject for ApubPrivateMessage { impl ApubObject for ApubPrivateMessage {
type DataType = LemmyContext; type DataType = LemmyContext;
type ApubType = ChatMessage; type ApubType = ChatMessage;
type DbType = PrivateMessage;
type TombstoneType = (); type TombstoneType = ();
fn last_refreshed_at(&self) -> Option<NaiveDateTime> { fn last_refreshed_at(&self) -> Option<NaiveDateTime> {

View file

@ -24,6 +24,7 @@ pub trait ActivityHandler {
pub trait ApubObject { pub trait ApubObject {
type DataType; type DataType;
type ApubType; type ApubType;
type DbType;
type TombstoneType; type TombstoneType;
/// If this object should be refetched after a certain interval, it should return the last refresh /// If this object should be refetched after a certain interval, it should return the last refresh

View file

@ -24,7 +24,6 @@ use diesel::{
RunQueryDsl, RunQueryDsl,
TextExpressionMethods, TextExpressionMethods,
}; };
use url::Url;
mod safe_type { mod safe_type {
use crate::{schema::community::*, source::community::Community, traits::ToSafe}; use crate::{schema::community::*, source::community::Community, traits::ToSafe};
@ -291,9 +290,8 @@ impl Followable for CommunityFollower {
} }
impl ApubActor for Community { impl ApubActor for Community {
fn read_from_apub_id(conn: &PgConnection, object_id: Url) -> Result<Option<Self>, Error> { fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result<Option<Self>, Error> {
use crate::schema::community::dsl::*; use crate::schema::community::dsl::*;
let object_id: DbUrl = object_id.into();
Ok( Ok(
community community
.filter(actor_id.eq(object_id)) .filter(actor_id.eq(object_id))

View file

@ -15,7 +15,6 @@ use diesel::{
RunQueryDsl, RunQueryDsl,
TextExpressionMethods, TextExpressionMethods,
}; };
use url::Url;
mod safe_type { mod safe_type {
use crate::{schema::person::columns::*, source::person::Person, traits::ToSafe}; use crate::{schema::person::columns::*, source::person::Person, traits::ToSafe};
@ -284,9 +283,8 @@ fn is_banned(banned_: bool, expires: Option<chrono::NaiveDateTime>) -> bool {
} }
impl ApubActor for Person { impl ApubActor for Person {
fn read_from_apub_id(conn: &PgConnection, object_id: Url) -> Result<Option<Self>, Error> { fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result<Option<Self>, Error> {
use crate::schema::person::dsl::*; use crate::schema::person::dsl::*;
let object_id: DbUrl = object_id.into();
Ok( Ok(
person person
.filter(deleted.eq(false)) .filter(deleted.eq(false))

View file

@ -1,6 +1,8 @@
use crate::newtypes::{CommunityId, PersonId}; use crate::{
newtypes::{CommunityId, PersonId},
DbUrl,
};
use diesel::{result::Error, PgConnection}; use diesel::{result::Error, PgConnection};
use url::Url;
pub trait Crud { pub trait Crud {
type Form; type Form;
@ -166,7 +168,7 @@ pub trait ViewToVec {
pub trait ApubActor { pub trait ApubActor {
// TODO: this should be in a trait ApubObject (and implemented for Post, Comment, PrivateMessage as well) // 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<Option<Self>, Error> fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result<Option<Self>, Error>
where where
Self: Sized; Self: Sized;
fn read_from_name(conn: &PgConnection, actor_name: &str) -> Result<Self, Error> fn read_from_name(conn: &PgConnection, actor_name: &str) -> Result<Self, Error>