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