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