From: Felix Ableitner <me@nutomic.com>
Date: Fri, 5 Mar 2021 13:45:30 +0000 (+0100)
Subject: Use collection for moderators, instead of `attributedTo` (ref #1061)
X-Git-Url: http://these/git/%22%7Bauthor_url%7D/static/gitweb.js?a=commitdiff_plain;h=beb8b9fe695f38cb9b08eb27b208f4f7f0e24e2e;p=lemmy.git

Use collection for moderators, instead of `attributedTo` (ref #1061)
---

diff --git a/crates/apub/src/extensions/context.rs b/crates/apub/src/extensions/context.rs
index b670e60d..2dc5685a 100644
--- a/crates/apub/src/extensions/context.rs
+++ b/crates/apub/src/extensions/context.rs
@@ -11,7 +11,8 @@ pub(crate) fn lemmy_context() -> Result<Vec<AnyBase>, LemmyError> {
     "comments_enabled": {
       "kind": "sc:Boolean",
       "id": "pt:commentsEnabled"
-    }
+    },
+    "moderators": "as:moderators"
   }))?;
   Ok(vec![AnyBase::from(context()), context_ext])
 }
diff --git a/crates/apub/src/extensions/group_extensions.rs b/crates/apub/src/extensions/group_extensions.rs
index face43ca..11ea8382 100644
--- a/crates/apub/src/extensions/group_extensions.rs
+++ b/crates/apub/src/extensions/group_extensions.rs
@@ -1,7 +1,11 @@
-use activitystreams::unparsed::UnparsedMutExt;
+use activitystreams::{
+  collection::{CollectionExt, OrderedCollection},
+  unparsed::UnparsedMutExt,
+};
 use activitystreams_ext::UnparsedExtension;
 use lemmy_utils::LemmyError;
 use serde::{Deserialize, Serialize};
+use url::Url;
 
 /// Activitystreams extension to allow (de)serializing additional Community field
 /// `sensitive` (called 'nsfw' in Lemmy).
@@ -9,12 +13,17 @@ use serde::{Deserialize, Serialize};
 #[serde(rename_all = "camelCase")]
 pub struct GroupExtension {
   pub sensitive: Option<bool>,
+  pub moderators: Option<OrderedCollection>,
 }
 
 impl GroupExtension {
-  pub fn new(sensitive: bool) -> Result<GroupExtension, LemmyError> {
+  pub fn new(sensitive: bool, moderators: Vec<Url>) -> Result<GroupExtension, LemmyError> {
+    let mut mods = OrderedCollection::new();
+    mods.set_total_items(moderators.len() as u64);
+    mods.set_many_items(moderators);
     Ok(GroupExtension {
       sensitive: Some(sensitive),
+      moderators: Some(mods),
     })
   }
 }
@@ -28,11 +37,13 @@ where
   fn try_from_unparsed(unparsed_mut: &mut U) -> Result<Self, Self::Error> {
     Ok(GroupExtension {
       sensitive: unparsed_mut.remove("sensitive")?,
+      moderators: unparsed_mut.remove("moderators")?,
     })
   }
 
   fn try_into_unparsed(self, unparsed_mut: &mut U) -> Result<(), Self::Error> {
     unparsed_mut.insert("sensitive", self.sensitive)?;
+    unparsed_mut.insert("moderators", self.moderators)?;
     Ok(())
   }
 }
diff --git a/crates/apub/src/fetcher/community.rs b/crates/apub/src/fetcher/community.rs
index fb545ed6..b2440bf2 100644
--- a/crates/apub/src/fetcher/community.rs
+++ b/crates/apub/src/fetcher/community.rs
@@ -1,10 +1,5 @@
 use crate::{
-  fetcher::{
-    fetch::fetch_remote_object,
-    get_or_fetch_and_upsert_user,
-    is_deleted,
-    should_refetch_actor,
-  },
+  fetcher::{fetch::fetch_remote_object, is_deleted, should_refetch_actor},
   inbox::user_inbox::receive_announce,
   objects::FromApub,
   GroupExt,
@@ -12,13 +7,12 @@ use crate::{
 use activitystreams::{
   actor::ApActorExt,
   collection::{CollectionExt, OrderedCollection},
-  object::ObjectExt,
 };
 use anyhow::Context;
 use diesel::result::Error::NotFound;
 use lemmy_api_structs::blocking;
-use lemmy_db_queries::{source::community::Community_, ApubObject, Joinable};
-use lemmy_db_schema::source::community::{Community, CommunityModerator, CommunityModeratorForm};
+use lemmy_db_queries::{source::community::Community_, ApubObject};
+use lemmy_db_schema::source::community::Community;
 use lemmy_utils::{location_info, LemmyError};
 use lemmy_websocket::LemmyContext;
 use log::debug;
@@ -80,40 +74,6 @@ async fn fetch_remote_community(
   let community =
     Community::from_apub(&group, context, apub_id.to_owned(), recursion_counter).await?;
 
-  // Also add the community moderators too
-  let attributed_to = group.inner.attributed_to().context(location_info!())?;
-  let creator_and_moderator_uris: Vec<&Url> = attributed_to
-    .as_many()
-    .context(location_info!())?
-    .iter()
-    .map(|a| a.as_xsd_any_uri().context(""))
-    .collect::<Result<Vec<&Url>, anyhow::Error>>()?;
-
-  let mut creator_and_moderators = Vec::new();
-
-  for uri in creator_and_moderator_uris {
-    let c_or_m = get_or_fetch_and_upsert_user(uri, context, recursion_counter).await?;
-
-    creator_and_moderators.push(c_or_m);
-  }
-
-  // TODO: need to make this work to update mods of existing communities
-  if old_community.is_none() {
-    let community_id = community.id;
-    blocking(context.pool(), move |conn| {
-      for mod_ in creator_and_moderators {
-        let community_moderator_form = CommunityModeratorForm {
-          community_id,
-          user_id: mod_.id,
-        };
-
-        CommunityModerator::join(conn, &community_moderator_form)?;
-      }
-      Ok(()) as Result<(), LemmyError>
-    })
-    .await??;
-  }
-
   // only fetch outbox for new communities, otherwise this can create an infinite loop
   if old_community.is_none() {
     let outbox = group.inner.outbox()?.context(location_info!())?;
diff --git a/crates/apub/src/objects/community.rs b/crates/apub/src/objects/community.rs
index 200497b7..9431f71c 100644
--- a/crates/apub/src/objects/community.rs
+++ b/crates/apub/src/objects/community.rs
@@ -23,10 +23,11 @@ use activitystreams::{
 use activitystreams_ext::Ext2;
 use anyhow::Context;
 use lemmy_api_structs::blocking;
-use lemmy_db_queries::DbPool;
+use lemmy_db_queries::{DbPool, Joinable};
 use lemmy_db_schema::{
   naive_now,
-  source::community::{Community, CommunityForm},
+  source::community::{Community, CommunityForm, CommunityModerator, CommunityModeratorForm},
+  DbUrl,
 };
 use lemmy_db_views_actor::community_moderator_view::CommunityModeratorView;
 use lemmy_utils::{
@@ -51,18 +52,13 @@ impl ToApub for Community {
       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))
-      .set_many_attributed_tos(moderators);
+      .set_published(convert_datetime(self.published));
 
     if let Some(u) = self.updated.to_owned() {
       group.set_updated(convert_datetime(u));
@@ -93,9 +89,14 @@ impl ToApub for Community {
         ..Default::default()
       });
 
+    let moderators: Vec<Url> = moderators
+      .into_iter()
+      .map(|m| m.moderator.actor_id.into_inner())
+      .collect();
+
     Ok(Ext2::new(
       ap_actor,
-      GroupExtension::new(self.nsfw)?,
+      GroupExtension::new(self.nsfw, moderators)?,
       self.get_public_key_ext()?,
     ))
   }
@@ -114,14 +115,57 @@ impl ToApub for Community {
 impl FromApub for Community {
   type ApubType = GroupExt;
 
-  /// Converts a `Group` to `Community`.
+  /// Converts a `Group` to `Community`, inserts it into the database and updates moderators.
   async fn from_apub(
     group: &GroupExt,
     context: &LemmyContext,
     expected_domain: Url,
     request_counter: &mut i32,
   ) -> Result<Community, LemmyError> {
-    get_object_from_apub(group, context, expected_domain, request_counter).await
+    let community: Community =
+      get_object_from_apub(group, context, expected_domain, request_counter).await?;
+
+    let new_moderators = get_community_moderators(group)?;
+    let community_id = community.id;
+    let current_moderators = blocking(context.pool(), move |conn| {
+      CommunityModeratorView::for_community(&conn, community_id)
+    })
+    .await??;
+    // Remove old mods from database which arent in the moderators collection anymore
+    for mod_user in &current_moderators {
+      if !new_moderators.contains(&&mod_user.moderator.actor_id.clone().into()) {
+        let community_moderator_form = CommunityModeratorForm {
+          community_id: mod_user.community.id,
+          user_id: mod_user.moderator.id,
+        };
+        blocking(context.pool(), move |conn| {
+          CommunityModerator::leave(conn, &community_moderator_form)
+        })
+        .await??;
+      }
+    }
+
+    // Add new mods to database which have been added to moderators collection
+    for mod_uri in new_moderators {
+      let mod_user = get_or_fetch_and_upsert_user(&mod_uri, context, request_counter).await?;
+      let current_mod_uris: Vec<DbUrl> = current_moderators
+        .clone()
+        .iter()
+        .map(|c| c.moderator.actor_id.clone())
+        .collect();
+      if !current_mod_uris.contains(&mod_user.actor_id) {
+        let community_moderator_form = CommunityModeratorForm {
+          community_id: community.id,
+          user_id: mod_user.id,
+        };
+        blocking(context.pool(), move |conn| {
+          CommunityModerator::join(conn, &community_moderator_form)
+        })
+        .await??;
+      }
+    }
+
+    Ok(community)
   }
 }
 
@@ -133,15 +177,8 @@ impl FromApubToForm<GroupExt> for CommunityForm {
     expected_domain: Url,
     request_counter: &mut i32,
   ) -> Result<Self, LemmyError> {
-    let creator_and_moderator_uris = group.inner.attributed_to().context(location_info!())?;
-    let creator_uri = creator_and_moderator_uris
-      .as_many()
-      .context(location_info!())?
-      .iter()
-      .next()
-      .context(location_info!())?
-      .as_xsd_any_uri()
-      .context(location_info!())?;
+    let moderator_uris = get_community_moderators(group)?;
+    let creator_uri = moderator_uris.first().context(location_info!())?;
 
     let creator = get_or_fetch_and_upsert_user(creator_uri, context, request_counter).await?;
     let name = group
@@ -226,3 +263,20 @@ impl FromApubToForm<GroupExt> for CommunityForm {
     })
   }
 }
+
+fn get_community_moderators(group: &GroupExt) -> Result<Vec<&Url>, LemmyError> {
+  if let Some(moderators) = &group.ext_one.moderators {
+    Ok(
+      moderators
+        .items()
+        .map(|i| i.as_many())
+        .flatten()
+        .context(location_info!())?
+        .iter()
+        .filter_map(|i| i.as_xsd_any_uri())
+        .collect(),
+    )
+  } else {
+    Ok(vec![])
+  }
+}
diff --git a/docker/federation/docker-compose.yml b/docker/federation/docker-compose.yml
index c40de902..a2ee7f26 100644
--- a/docker/federation/docker-compose.yml
+++ b/docker/federation/docker-compose.yml
@@ -29,7 +29,7 @@ services:
       - ./volumes/pictrs_alpha:/mnt
 
   lemmy-alpha-ui:
-    image: dessalines/lemmy-ui:0.9.9
+    image: lemmy-ui:test
     environment:
       - LEMMY_INTERNAL_HOST=lemmy-alpha:8541
       - LEMMY_EXTERNAL_HOST=localhost:8541
@@ -58,7 +58,7 @@ services:
       - ./volumes/postgres_alpha:/var/lib/postgresql/data
 
   lemmy-beta-ui:
-    image: dessalines/lemmy-ui:0.9.9
+    image: lemmy-ui:test
     environment:
       - LEMMY_INTERNAL_HOST=lemmy-beta:8551
       - LEMMY_EXTERNAL_HOST=localhost:8551
diff --git a/docker/federation/lemmy_alpha.hjson b/docker/federation/lemmy_alpha.hjson
index e806397a..4819fb26 100644
--- a/docker/federation/lemmy_alpha.hjson
+++ b/docker/federation/lemmy_alpha.hjson
@@ -1,4 +1,5 @@
 {
+  hostname: lemmy-alpha:8541
   port: 8541
   tls_enabled: false
   jwt_secret: changeme