]> Untitled Git - lemmy.git/blobdiff - crates/apub/src/objects/community.rs
Rewrite activitypub following, person, community, pm (#1692)
[lemmy.git] / crates / apub / src / objects / community.rs
index f9948588d5e9b6b322680ee1d3f8ee35fcf2f31d..5269d76630c3add1701d1509bf7f2bcf05b09d62 100644 (file)
 use crate::{
-  extensions::{context::lemmy_context, group_extension::GroupExtension},
+  extensions::{context::lemmy_context, signatures::PublicKey},
   fetcher::community::fetch_community_mods,
   generate_moderators_url,
-  objects::{
-    check_object_domain,
-    create_tombstone,
-    get_object_from_apub,
-    get_source_markdown_value,
-    set_content_and_source,
-    FromApub,
-    FromApubToForm,
-    ToApub,
-  },
+  objects::{create_tombstone, FromApub, ImageObject, Source, ToApub},
   ActorType,
-  GroupExt,
 };
 use activitystreams::{
-  actor::{kind::GroupType, ApActor, Endpoints, Group},
-  base::BaseExt,
-  object::{ApObject, Image, Tombstone},
-  prelude::*,
+  actor::{kind::GroupType, Endpoints},
+  base::AnyBase,
+  object::{kind::ImageType, Tombstone},
+  primitives::OneOrMany,
+  unparsed::Unparsed,
 };
-use activitystreams_ext::Ext2;
-use anyhow::Context;
+use chrono::{DateTime, FixedOffset};
 use lemmy_api_common::blocking;
-use lemmy_db_queries::DbPool;
+use lemmy_apub_lib::{
+  values::{MediaTypeHtml, MediaTypeMarkdown},
+  verify_domains_match,
+};
+use lemmy_db_queries::{ApubObject, DbPool};
 use lemmy_db_schema::{
   naive_now,
   source::community::{Community, CommunityForm},
 };
-use lemmy_db_views_actor::community_moderator_view::CommunityModeratorView;
 use lemmy_utils::{
-  location_info,
-  utils::{check_slurs, check_slurs_opt, convert_datetime},
+  utils::{check_slurs, check_slurs_opt, convert_datetime, markdown_to_html},
   LemmyError,
 };
 use lemmy_websocket::LemmyContext;
+use serde::{Deserialize, Serialize};
+use serde_with::skip_serializing_none;
 use url::Url;
 
-#[async_trait::async_trait(?Send)]
-impl ToApub for Community {
-  type ApubType = GroupExt;
-
-  async fn to_apub(&self, pool: &DbPool) -> Result<GroupExt, LemmyError> {
-    let id = self.id;
-    let moderators = blocking(pool, move |conn| {
-      CommunityModeratorView::for_community(conn, id)
-    })
-    .await??;
-    let moderators: Vec<Url> = moderators
-      .into_iter()
-      .map(|m| m.moderator.actor_id.into_inner())
-      .collect();
-
-    let mut group = ApObject::new(Group::new());
-    group
-      .set_many_contexts(lemmy_context())
-      .set_id(self.actor_id.to_owned().into())
-      .set_name(self.title.to_owned())
-      .set_published(convert_datetime(self.published))
-      // NOTE: included attritubed_to field for compatibility with lemmy v0.9.9
-      .set_many_attributed_tos(moderators);
+#[skip_serializing_none]
+#[derive(Clone, Debug, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Group {
+  #[serde(rename = "@context")]
+  context: OneOrMany<AnyBase>,
+  #[serde(rename = "type")]
+  kind: GroupType,
+  id: Url,
+  /// username, set at account creation and can never be changed
+  preferred_username: String,
+  /// title (can be changed at any time)
+  name: String,
+  content: Option<String>,
+  media_type: Option<MediaTypeHtml>,
+  source: Option<Source>,
+  icon: Option<ImageObject>,
+  /// banner
+  image: Option<ImageObject>,
+  // lemmy extension
+  sensitive: Option<bool>,
+  // lemmy extension
+  pub(crate) moderators: Option<Url>,
+  inbox: Url,
+  pub(crate) outbox: Url,
+  followers: Url,
+  endpoints: Endpoints<Url>,
+  public_key: PublicKey,
+  published: DateTime<FixedOffset>,
+  updated: Option<DateTime<FixedOffset>>,
+  #[serde(flatten)]
+  unparsed: Unparsed,
+}
 
-    if let Some(u) = self.updated.to_owned() {
-      group.set_updated(convert_datetime(u));
-    }
-    if let Some(d) = self.description.to_owned() {
-      set_content_and_source(&mut group, &d)?;
-    }
+impl Group {
+  pub(crate) fn id(&self, expected_domain: &Url) -> Result<&Url, LemmyError> {
+    verify_domains_match(&self.id, expected_domain)?;
+    Ok(&self.id)
+  }
+  pub(crate) async fn from_apub_to_form(
+    group: &Group,
+    expected_domain: &Url,
+  ) -> Result<CommunityForm, LemmyError> {
+    let actor_id = Some(group.id(expected_domain)?.clone().into());
+    let name = group.preferred_username.clone();
+    let title = group.name.clone();
+    let description = group.source.clone().map(|s| s.content);
+    let shared_inbox = group.endpoints.shared_inbox.clone().map(|s| s.into());
 
-    if let Some(icon_url) = &self.icon {
-      let mut image = Image::new();
-      image.set_url::<Url>(icon_url.to_owned().into());
-      group.set_icon(image.into_any_base()?);
-    }
+    check_slurs(&name)?;
+    check_slurs(&title)?;
+    check_slurs_opt(&description)?;
 
-    if let Some(banner_url) = &self.banner {
-      let mut image = Image::new();
-      image.set_url::<Url>(banner_url.to_owned().into());
-      group.set_image(image.into_any_base()?);
-    }
+    Ok(CommunityForm {
+      name,
+      title,
+      description,
+      removed: None,
+      published: Some(group.published.naive_local()),
+      updated: group.updated.map(|u| u.naive_local()),
+      deleted: None,
+      nsfw: Some(group.sensitive.unwrap_or(false)),
+      actor_id,
+      local: Some(false),
+      private_key: None,
+      public_key: Some(group.public_key.public_key_pem.clone()),
+      last_refreshed_at: Some(naive_now()),
+      icon: Some(group.icon.clone().map(|i| i.url.into())),
+      banner: Some(group.image.clone().map(|i| i.url.into())),
+      followers_url: Some(group.followers.clone().into()),
+      inbox_url: Some(group.inbox.clone().into()),
+      shared_inbox_url: Some(shared_inbox),
+    })
+  }
+}
 
-    let mut ap_actor = ApActor::new(self.inbox_url.clone().into(), group);
-    ap_actor
-      .set_preferred_username(self.name.to_owned())
-      .set_outbox(self.get_outbox_url()?)
-      .set_followers(self.followers_url.clone().into())
-      .set_endpoints(Endpoints {
-        shared_inbox: Some(self.get_shared_inbox_or_inbox_url()),
+#[async_trait::async_trait(?Send)]
+impl ToApub for Community {
+  type ApubType = Group;
+
+  async fn to_apub(&self, _pool: &DbPool) -> Result<Group, LemmyError> {
+    let source = self.description.clone().map(|bio| Source {
+      content: bio,
+      media_type: MediaTypeMarkdown::Markdown,
+    });
+    let icon = self.icon.clone().map(|url| ImageObject {
+      kind: ImageType::Image,
+      url: url.into(),
+    });
+    let image = self.banner.clone().map(|url| ImageObject {
+      kind: ImageType::Image,
+      url: url.into(),
+    });
+
+    let group = Group {
+      context: lemmy_context(),
+      kind: GroupType::Group,
+      id: self.actor_id(),
+      preferred_username: self.name.clone(),
+      name: self.title.clone(),
+      content: self.description.as_ref().map(|b| markdown_to_html(b)),
+      media_type: self.description.as_ref().map(|_| MediaTypeHtml::Html),
+      source,
+      icon,
+      image,
+      sensitive: Some(self.nsfw),
+      moderators: Some(generate_moderators_url(&self.actor_id)?.into()),
+      inbox: self.inbox_url.clone().into(),
+      outbox: self.get_outbox_url()?,
+      followers: self.followers_url.clone().into(),
+      endpoints: Endpoints {
+        shared_inbox: self.shared_inbox_url.clone().map(|s| s.into()),
         ..Default::default()
-      });
-
-    Ok(Ext2::new(
-      ap_actor,
-      GroupExtension::new(self.nsfw, generate_moderators_url(&self.actor_id)?.into())?,
-      self.get_public_key_ext()?,
-    ))
+      },
+      public_key: self.get_public_key()?,
+      published: convert_datetime(self.published),
+      updated: self.updated.map(convert_datetime),
+      unparsed: Default::default(),
+    };
+    Ok(group)
   }
 
   fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
@@ -110,116 +166,19 @@ impl ToApub for Community {
 
 #[async_trait::async_trait(?Send)]
 impl FromApub for Community {
-  type ApubType = GroupExt;
+  type ApubType = Group;
 
   /// Converts a `Group` to `Community`, inserts it into the database and updates moderators.
   async fn from_apub(
-    group: &GroupExt,
+    group: &Group,
     context: &LemmyContext,
-    expected_domain: Url,
+    expected_domain: &Url,
     request_counter: &mut i32,
-    mod_action_allowed: bool,
   ) -> Result<Community, LemmyError> {
-    get_object_from_apub(
-      group,
-      context,
-      expected_domain,
-      request_counter,
-      mod_action_allowed,
-    )
-    .await
-  }
-}
-
-#[async_trait::async_trait(?Send)]
-impl FromApubToForm<GroupExt> for CommunityForm {
-  async fn from_apub(
-    group: &GroupExt,
-    context: &LemmyContext,
-    expected_domain: Url,
-    request_counter: &mut i32,
-    _mod_action_allowed: bool,
-  ) -> Result<Self, LemmyError> {
     fetch_community_mods(context, group, request_counter).await?;
+    let form = Group::from_apub_to_form(group, expected_domain).await?;
 
-    let name = group
-      .inner
-      .preferred_username()
-      .context(location_info!())?
-      .to_string();
-    let title = group
-      .inner
-      .name()
-      .context(location_info!())?
-      .as_one()
-      .context(location_info!())?
-      .as_xsd_string()
-      .context(location_info!())?
-      .to_string();
-
-    let description = get_source_markdown_value(group)?;
-
-    check_slurs(&name)?;
-    check_slurs(&title)?;
-    check_slurs_opt(&description)?;
-
-    let icon = match group.icon() {
-      Some(any_image) => Some(
-        Image::from_any_base(any_image.as_one().context(location_info!())?.clone())
-          .context(location_info!())?
-          .context(location_info!())?
-          .url()
-          .context(location_info!())?
-          .as_single_xsd_any_uri()
-          .map(|u| u.to_owned().into()),
-      ),
-      None => None,
-    };
-    let banner = match group.image() {
-      Some(any_image) => Some(
-        Image::from_any_base(any_image.as_one().context(location_info!())?.clone())
-          .context(location_info!())?
-          .context(location_info!())?
-          .url()
-          .context(location_info!())?
-          .as_single_xsd_any_uri()
-          .map(|u| u.to_owned().into()),
-      ),
-      None => None,
-    };
-    let shared_inbox = group
-      .inner
-      .endpoints()?
-      .map(|e| e.shared_inbox)
-      .flatten()
-      .map(|s| s.to_owned().into());
-
-    Ok(CommunityForm {
-      name,
-      title,
-      description,
-      removed: None,
-      published: group.inner.published().map(|u| u.to_owned().naive_local()),
-      updated: group.inner.updated().map(|u| u.to_owned().naive_local()),
-      deleted: None,
-      nsfw: Some(group.ext_one.sensitive.unwrap_or(false)),
-      actor_id: Some(check_object_domain(group, expected_domain, true)?),
-      local: Some(false),
-      private_key: None,
-      public_key: Some(group.ext_two.to_owned().public_key.public_key_pem),
-      last_refreshed_at: Some(naive_now()),
-      icon,
-      banner,
-      followers_url: Some(
-        group
-          .inner
-          .followers()?
-          .context(location_info!())?
-          .to_owned()
-          .into(),
-      ),
-      inbox_url: Some(group.inner.inbox()?.to_owned().into()),
-      shared_inbox_url: Some(shared_inbox),
-    })
+    let community = blocking(context.pool(), move |conn| Community::upsert(conn, &form)).await??;
+    Ok(community)
   }
 }