]> Untitled Git - lemmy.git/blob - crates/apub/src/collections/community_moderators.rs
Cache & Optimize Woodpecker CI (#3450)
[lemmy.git] / crates / apub / src / collections / community_moderators.rs
1 use crate::{
2   objects::{community::ApubCommunity, person::ApubPerson},
3   protocol::collections::group_moderators::GroupModerators,
4 };
5 use activitypub_federation::{
6   config::Data,
7   fetch::object_id::ObjectId,
8   kinds::collection::OrderedCollectionType,
9   protocol::verification::verify_domains_match,
10   traits::Collection,
11 };
12 use lemmy_api_common::{context::LemmyContext, utils::generate_moderators_url};
13 use lemmy_db_schema::{
14   source::community::{CommunityModerator, CommunityModeratorForm},
15   traits::Joinable,
16 };
17 use lemmy_db_views_actor::structs::CommunityModeratorView;
18 use lemmy_utils::error::LemmyError;
19 use url::Url;
20
21 #[derive(Clone, Debug)]
22 pub(crate) struct ApubCommunityModerators(pub(crate) Vec<CommunityModeratorView>);
23
24 #[async_trait::async_trait]
25 impl Collection for ApubCommunityModerators {
26   type Owner = ApubCommunity;
27   type DataType = LemmyContext;
28   type Kind = GroupModerators;
29   type Error = LemmyError;
30
31   #[tracing::instrument(skip_all)]
32   async fn read_local(
33     owner: &Self::Owner,
34     data: &Data<Self::DataType>,
35   ) -> Result<Self::Kind, LemmyError> {
36     let moderators = CommunityModeratorView::for_community(&mut data.pool(), owner.id).await?;
37     let ordered_items = moderators
38       .into_iter()
39       .map(|m| ObjectId::<ApubPerson>::from(m.moderator.actor_id))
40       .collect();
41     Ok(GroupModerators {
42       r#type: OrderedCollectionType::OrderedCollection,
43       id: generate_moderators_url(&owner.actor_id)?.into(),
44       ordered_items,
45     })
46   }
47
48   #[tracing::instrument(skip_all)]
49   async fn verify(
50     group_moderators: &GroupModerators,
51     expected_domain: &Url,
52     _data: &Data<Self::DataType>,
53   ) -> Result<(), LemmyError> {
54     verify_domains_match(&group_moderators.id, expected_domain)?;
55     Ok(())
56   }
57
58   #[tracing::instrument(skip_all)]
59   async fn from_json(
60     apub: Self::Kind,
61     owner: &Self::Owner,
62     data: &Data<Self::DataType>,
63   ) -> Result<Self, LemmyError> {
64     let community_id = owner.id;
65     let current_moderators =
66       CommunityModeratorView::for_community(&mut data.pool(), community_id).await?;
67     // Remove old mods from database which arent in the moderators collection anymore
68     for mod_user in &current_moderators {
69       let mod_id = ObjectId::from(mod_user.moderator.actor_id.clone());
70       if !apub.ordered_items.contains(&mod_id) {
71         let community_moderator_form = CommunityModeratorForm {
72           community_id: mod_user.community.id,
73           person_id: mod_user.moderator.id,
74         };
75         CommunityModerator::leave(&mut data.pool(), &community_moderator_form).await?;
76       }
77     }
78
79     // Add new mods to database which have been added to moderators collection
80     for mod_id in apub.ordered_items {
81       let mod_user: ApubPerson = mod_id.dereference(data).await?;
82
83       if !current_moderators
84         .iter()
85         .map(|c| c.moderator.actor_id.clone())
86         .any(|x| x == mod_user.actor_id)
87       {
88         let community_moderator_form = CommunityModeratorForm {
89           community_id: owner.id,
90           person_id: mod_user.id,
91         };
92         CommunityModerator::join(&mut data.pool(), &community_moderator_form).await?;
93       }
94     }
95
96     // This return value is unused, so just set an empty vec
97     Ok(ApubCommunityModerators(Vec::new()))
98   }
99 }
100
101 #[cfg(test)]
102 mod tests {
103   #![allow(clippy::unwrap_used)]
104   #![allow(clippy::indexing_slicing)]
105
106   use super::*;
107   use crate::{
108     objects::{
109       community::tests::parse_lemmy_community,
110       person::tests::parse_lemmy_person,
111       tests::init_context,
112     },
113     protocol::tests::file_to_json_object,
114   };
115   use lemmy_db_schema::{
116     source::{
117       community::Community,
118       instance::Instance,
119       person::{Person, PersonInsertForm},
120       site::Site,
121     },
122     traits::Crud,
123   };
124   use serial_test::serial;
125
126   #[tokio::test]
127   #[serial]
128   async fn test_parse_lemmy_community_moderators() {
129     let context = init_context().await;
130     let (new_mod, site) = parse_lemmy_person(&context).await;
131     let community = parse_lemmy_community(&context).await;
132     let community_id = community.id;
133
134     let inserted_instance =
135       Instance::read_or_create(&mut context.pool(), "my_domain.tld".to_string())
136         .await
137         .unwrap();
138
139     let old_mod = PersonInsertForm::builder()
140       .name("holly".into())
141       .public_key("pubkey".to_string())
142       .instance_id(inserted_instance.id)
143       .build();
144
145     let old_mod = Person::create(&mut context.pool(), &old_mod).await.unwrap();
146     let community_moderator_form = CommunityModeratorForm {
147       community_id: community.id,
148       person_id: old_mod.id,
149     };
150
151     CommunityModerator::join(&mut context.pool(), &community_moderator_form)
152       .await
153       .unwrap();
154
155     assert_eq!(site.actor_id.to_string(), "https://enterprise.lemmy.ml/");
156
157     let json: GroupModerators =
158       file_to_json_object("assets/lemmy/collections/group_moderators.json").unwrap();
159     let url = Url::parse("https://enterprise.lemmy.ml/c/tenforward").unwrap();
160     ApubCommunityModerators::verify(&json, &url, &context)
161       .await
162       .unwrap();
163     ApubCommunityModerators::from_json(json, &community, &context)
164       .await
165       .unwrap();
166     assert_eq!(context.request_count(), 0);
167
168     let current_moderators =
169       CommunityModeratorView::for_community(&mut context.pool(), community_id)
170         .await
171         .unwrap();
172
173     assert_eq!(current_moderators.len(), 1);
174     assert_eq!(current_moderators[0].moderator.id, new_mod.id);
175
176     Person::delete(&mut context.pool(), old_mod.id)
177       .await
178       .unwrap();
179     Person::delete(&mut context.pool(), new_mod.id)
180       .await
181       .unwrap();
182     Community::delete(&mut context.pool(), community.id)
183       .await
184       .unwrap();
185     Site::delete(&mut context.pool(), site.id).await.unwrap();
186     Instance::delete(&mut context.pool(), inserted_instance.id)
187       .await
188       .unwrap();
189   }
190 }