]> Untitled Git - lemmy.git/blobdiff - crates/apub/src/fetcher/search.rs
Rewrite fetcher (#1792)
[lemmy.git] / crates / apub / src / fetcher / search.rs
index 70e7c40c1b51085f8f79e7980fb699c5ef152765..6a3cc14f08e7fec21db1fa263e355199b2ccde8f 100644 (file)
@@ -1,52 +1,27 @@
 use crate::{
-  fetcher::{
-    community::get_or_fetch_and_upsert_community,
-    fetch::fetch_remote_object,
-    is_deleted,
-    person::get_or_fetch_and_upsert_person,
-  },
-  find_object_by_id,
+  fetcher::{deletable_apub_object::DeletableApubObject, object_id::ObjectId},
   objects::{comment::Note, community::Group, person::Person as ApubPerson, post::Page, FromApub},
-  Object,
 };
+use activitystreams::chrono::NaiveDateTime;
 use anyhow::anyhow;
+use diesel::{result::Error, PgConnection};
 use itertools::Itertools;
-use lemmy_api_common::{blocking, site::ResolveObjectResponse};
+use lemmy_api_common::blocking;
 use lemmy_apub_lib::webfinger::{webfinger_resolve_actor, WebfingerType};
-use lemmy_db_queries::source::{
-  comment::Comment_,
-  community::Community_,
-  person::Person_,
-  post::Post_,
-  private_message::PrivateMessage_,
+use lemmy_db_queries::{
+  source::{community::Community_, person::Person_},
+  ApubObject,
+  DbPool,
 };
-use lemmy_db_schema::source::{
-  comment::Comment,
-  community::Community,
-  person::Person,
-  post::Post,
-  private_message::PrivateMessage,
+use lemmy_db_schema::{
+  source::{comment::Comment, community::Community, person::Person, post::Post},
+  DbUrl,
 };
-use lemmy_db_views::{
-  comment_view::CommentView,
-  local_user_view::LocalUserView,
-  post_view::PostView,
-};
-use lemmy_db_views_actor::{community_view::CommunityView, person_view::PersonViewSafe};
 use lemmy_utils::LemmyError;
 use lemmy_websocket::LemmyContext;
+use serde::Deserialize;
 use url::Url;
 
-/// The types of ActivityPub objects that can be fetched directly by searching for their ID.
-#[derive(serde::Deserialize, Debug)]
-#[serde(untagged)]
-enum SearchAcceptedObjects {
-  Person(Box<ApubPerson>),
-  Group(Box<Group>),
-  Page(Box<Page>),
-  Comment(Box<Note>),
-}
-
 /// Attempt to parse the query as URL, and fetch an ActivityPub object from it.
 ///
 /// Some working examples for use with the `docker/federation/` setup:
@@ -56,9 +31,8 @@ enum SearchAcceptedObjects {
 /// http://lemmy_delta:8571/comment/2
 pub async fn search_by_apub_id(
   query: &str,
-  local_user_view: Option<LocalUserView>,
   context: &LemmyContext,
-) -> Result<ResolveObjectResponse, LemmyError> {
+) -> Result<SearchableObjects, LemmyError> {
   let query_url = match Url::parse(query) {
     Ok(u) => u,
     Err(_) => {
@@ -75,142 +49,113 @@ pub async fn search_by_apub_id(
       }
       // local actor, read from database and return
       else {
-        let name: String = name.into();
-        return match kind {
-          WebfingerType::Group => {
-            let res = blocking(context.pool(), move |conn| {
-              let community = Community::read_from_name(conn, &name)?;
-              CommunityView::read(conn, community.id, local_user_view.map(|l| l.person.id))
-            })
-            .await??;
-            Ok(ResolveObjectResponse {
-              community: Some(res),
-              ..ResolveObjectResponse::default()
-            })
-          }
-          WebfingerType::Person => {
-            let res = blocking(context.pool(), move |conn| {
-              let person = Person::find_by_name(conn, &name)?;
-              PersonViewSafe::read(conn, person.id)
-            })
-            .await??;
-            Ok(ResolveObjectResponse {
-              person: Some(res),
-              ..ResolveObjectResponse::default()
-            })
-          }
-        };
+        return find_local_actor_by_name(name, kind, context.pool()).await;
       }
     }
   };
 
   let request_counter = &mut 0;
-  // this does a fetch (even for local objects), just to determine its type and fetch it again
-  // below. we need to fix this when rewriting the fetcher.
-  let fetch_response =
-    fetch_remote_object::<SearchAcceptedObjects>(context.client(), &query_url, request_counter)
-      .await;
-  if is_deleted(&fetch_response) {
-    delete_object_locally(&query_url, context).await?;
-    return Err(anyhow!("Object was deleted").into());
-  }
+  ObjectId::new(query_url)
+    .dereference(context, request_counter)
+    .await
+}
 
-  // Necessary because we get a stack overflow using FetchError
-  let fet_res = fetch_response.map_err(|e| LemmyError::from(e.inner))?;
-  build_response(fet_res, query_url, request_counter, context).await
+async fn find_local_actor_by_name(
+  name: &str,
+  kind: WebfingerType,
+  pool: &DbPool,
+) -> Result<SearchableObjects, LemmyError> {
+  let name: String = name.into();
+  Ok(match kind {
+    WebfingerType::Group => SearchableObjects::Community(
+      blocking(pool, move |conn| Community::read_from_name(conn, &name)).await??,
+    ),
+    WebfingerType::Person => SearchableObjects::Person(
+      blocking(pool, move |conn| Person::find_by_name(conn, &name)).await??,
+    ),
+  })
 }
 
-async fn build_response(
-  fetch_response: SearchAcceptedObjects,
-  query_url: Url,
-  recursion_counter: &mut i32,
-  context: &LemmyContext,
-) -> Result<ResolveObjectResponse, LemmyError> {
-  use ResolveObjectResponse as ROR;
-  Ok(match fetch_response {
-    SearchAcceptedObjects::Person(p) => {
-      let person_uri = p.id(&query_url)?;
+/// The types of ActivityPub objects that can be fetched directly by searching for their ID.
+#[derive(Debug)]
+pub enum SearchableObjects {
+  Person(Person),
+  Community(Community),
+  Post(Post),
+  Comment(Comment),
+}
 
-      let person = get_or_fetch_and_upsert_person(person_uri, context, recursion_counter).await?;
-      ROR {
-        person: blocking(context.pool(), move |conn| {
-          PersonViewSafe::read(conn, person.id)
-        })
-        .await?
-        .ok(),
-        ..ROR::default()
-      }
-    }
-    SearchAcceptedObjects::Group(g) => {
-      let community_uri = g.id(&query_url)?;
-      let community =
-        get_or_fetch_and_upsert_community(community_uri, context, recursion_counter).await?;
-      ROR {
-        community: blocking(context.pool(), move |conn| {
-          CommunityView::read(conn, community.id, None)
-        })
-        .await?
-        .ok(),
-        ..ROR::default()
-      }
-    }
-    SearchAcceptedObjects::Page(p) => {
-      let p = Post::from_apub(&p, context, &query_url, recursion_counter).await?;
-      ROR {
-        post: blocking(context.pool(), move |conn| PostView::read(conn, p.id, None))
-          .await?
-          .ok(),
-        ..ROR::default()
-      }
-    }
-    SearchAcceptedObjects::Comment(c) => {
-      let c = Comment::from_apub(&c, context, &query_url, recursion_counter).await?;
-      ROR {
-        comment: blocking(context.pool(), move |conn| {
-          CommentView::read(conn, c.id, None)
-        })
-        .await?
-        .ok(),
-        ..ROR::default()
-      }
-    }
-  })
+#[derive(Deserialize)]
+#[serde(untagged)]
+pub enum SearchableApubTypes {
+  Group(Group),
+  Person(ApubPerson),
+  Page(Page),
+  Note(Note),
 }
 
-async fn delete_object_locally(query_url: &Url, context: &LemmyContext) -> Result<(), LemmyError> {
-  let res = find_object_by_id(context, query_url.to_owned()).await?;
-  match res {
-    Object::Comment(c) => {
-      blocking(context.pool(), move |conn| {
-        Comment::update_deleted(conn, c.id, true)
-      })
-      .await??;
+impl ApubObject for SearchableObjects {
+  fn last_refreshed_at(&self) -> Option<NaiveDateTime> {
+    match self {
+      SearchableObjects::Person(p) => p.last_refreshed_at(),
+      SearchableObjects::Community(c) => c.last_refreshed_at(),
+      SearchableObjects::Post(p) => p.last_refreshed_at(),
+      SearchableObjects::Comment(c) => c.last_refreshed_at(),
     }
-    Object::Post(p) => {
-      blocking(context.pool(), move |conn| {
-        Post::update_deleted(conn, p.id, true)
-      })
-      .await??;
+  }
+
+  // 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<Self, Error> {
+    let c = Community::read_from_apub_id(conn, object_id);
+    if let Ok(c) = c {
+      return Ok(SearchableObjects::Community(c));
     }
-    Object::Person(u) => {
-      // TODO: implement update_deleted() for user, move it to ApubObject trait
-      blocking(context.pool(), move |conn| {
-        Person::delete_account(conn, u.id)
-      })
-      .await??;
+    let p = Person::read_from_apub_id(conn, object_id);
+    if let Ok(p) = p {
+      return Ok(SearchableObjects::Person(p));
     }
-    Object::Community(c) => {
-      blocking(context.pool(), move |conn| {
-        Community::update_deleted(conn, c.id, true)
-      })
-      .await??;
+    let p = Post::read_from_apub_id(conn, object_id);
+    if let Ok(p) = p {
+      return Ok(SearchableObjects::Post(p));
     }
-    Object::PrivateMessage(pm) => {
-      blocking(context.pool(), move |conn| {
-        PrivateMessage::update_deleted(conn, pm.id, true)
-      })
-      .await??;
+    let c = Comment::read_from_apub_id(conn, object_id);
+    Ok(SearchableObjects::Comment(c?))
+  }
+}
+
+#[async_trait::async_trait(?Send)]
+impl FromApub for SearchableObjects {
+  type ApubType = SearchableApubTypes;
+
+  async fn from_apub(
+    apub: &Self::ApubType,
+    context: &LemmyContext,
+    ed: &Url,
+    rc: &mut i32,
+  ) -> Result<Self, LemmyError> {
+    use SearchableApubTypes as SAT;
+    use SearchableObjects as SO;
+    Ok(match apub {
+      SAT::Group(g) => SO::Community(Community::from_apub(g, context, ed, rc).await?),
+      SAT::Person(p) => SO::Person(Person::from_apub(p, context, ed, rc).await?),
+      SAT::Page(p) => SO::Post(Post::from_apub(p, context, ed, rc).await?),
+      SAT::Note(n) => SO::Comment(Comment::from_apub(n, context, ed, rc).await?),
+    })
+  }
+}
+
+#[async_trait::async_trait(?Send)]
+impl DeletableApubObject for SearchableObjects {
+  async fn delete(self, context: &LemmyContext) -> Result<(), LemmyError> {
+    match self {
+      SearchableObjects::Person(p) => p.delete(context).await,
+      SearchableObjects::Community(c) => c.delete(context).await,
+      SearchableObjects::Post(p) => p.delete(context).await,
+      SearchableObjects::Comment(c) => c.delete(context).await,
     }
   }
-  Ok(())
 }