]> Untitled Git - lemmy.git/blob - crates/apub/src/collections/community_moderators.rs
336bbfb12471aaa26497de42c23e75c03d080aa6
[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   use super::*;
104   use crate::{
105     objects::{
106       community::tests::parse_lemmy_community,
107       person::tests::parse_lemmy_person,
108       tests::init_context,
109     },
110     protocol::tests::file_to_json_object,
111   };
112   use lemmy_db_schema::{
113     source::{
114       community::Community,
115       instance::Instance,
116       person::{Person, PersonInsertForm},
117       site::Site,
118     },
119     traits::Crud,
120   };
121   use serial_test::serial;
122
123   #[tokio::test]
124   #[serial]
125   async fn test_parse_lemmy_community_moderators() {
126     let context = init_context().await;
127     let (new_mod, site) = parse_lemmy_person(&context).await;
128     let community = parse_lemmy_community(&context).await;
129     let community_id = community.id;
130
131     let inserted_instance =
132       Instance::read_or_create(&mut context.pool(), "my_domain.tld".to_string())
133         .await
134         .unwrap();
135
136     let old_mod = PersonInsertForm::builder()
137       .name("holly".into())
138       .public_key("pubkey".to_string())
139       .instance_id(inserted_instance.id)
140       .build();
141
142     let old_mod = Person::create(&mut context.pool(), &old_mod).await.unwrap();
143     let community_moderator_form = CommunityModeratorForm {
144       community_id: community.id,
145       person_id: old_mod.id,
146     };
147
148     CommunityModerator::join(&mut context.pool(), &community_moderator_form)
149       .await
150       .unwrap();
151
152     assert_eq!(site.actor_id.to_string(), "https://enterprise.lemmy.ml/");
153
154     let json: GroupModerators =
155       file_to_json_object("assets/lemmy/collections/group_moderators.json").unwrap();
156     let url = Url::parse("https://enterprise.lemmy.ml/c/tenforward").unwrap();
157     ApubCommunityModerators::verify(&json, &url, &context)
158       .await
159       .unwrap();
160     ApubCommunityModerators::from_json(json, &community, &context)
161       .await
162       .unwrap();
163     assert_eq!(context.request_count(), 0);
164
165     let current_moderators =
166       CommunityModeratorView::for_community(&mut context.pool(), community_id)
167         .await
168         .unwrap();
169
170     assert_eq!(current_moderators.len(), 1);
171     assert_eq!(current_moderators[0].moderator.id, new_mod.id);
172
173     Person::delete(&mut context.pool(), old_mod.id)
174       .await
175       .unwrap();
176     Person::delete(&mut context.pool(), new_mod.id)
177       .await
178       .unwrap();
179     Community::delete(&mut context.pool(), community.id)
180       .await
181       .unwrap();
182     Site::delete(&mut context.pool(), site.id).await.unwrap();
183     Instance::delete(&mut context.pool(), inserted_instance.id)
184       .await
185       .unwrap();
186   }
187 }