]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/community/collection_add.rs
Implement separate mod activities for feature, lock post (#2716)
[lemmy.git] / crates / apub / src / activities / community / collection_add.rs
1 use crate::{
2   activities::{
3     community::send_activity_in_community,
4     generate_activity_id,
5     verify_is_public,
6     verify_mod_action,
7     verify_person_in_community,
8   },
9   activity_lists::AnnouncableActivities,
10   local_instance,
11   objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
12   protocol::{
13     activities::{
14       community::{collection_add::CollectionAdd, collection_remove::CollectionRemove},
15       create_or_update::page::CreateOrUpdatePage,
16       CreateOrUpdateType,
17     },
18     InCommunity,
19   },
20   ActorType,
21   SendActivity,
22 };
23 use activitypub_federation::{
24   core::object_id::ObjectId,
25   data::Data,
26   traits::{ActivityHandler, Actor},
27 };
28 use activitystreams_kinds::{activity::AddType, public};
29 use lemmy_api_common::{
30   community::{AddModToCommunity, AddModToCommunityResponse},
31   context::LemmyContext,
32   post::{FeaturePost, PostResponse},
33   utils::{generate_featured_url, generate_moderators_url, get_local_user_view_from_jwt},
34 };
35 use lemmy_db_schema::{
36   impls::community::CollectionType,
37   source::{
38     community::{Community, CommunityModerator, CommunityModeratorForm},
39     moderator::{ModAddCommunity, ModAddCommunityForm},
40     person::Person,
41     post::{Post, PostUpdateForm},
42   },
43   traits::{Crud, Joinable},
44 };
45 use lemmy_utils::error::LemmyError;
46 use url::Url;
47
48 impl CollectionAdd {
49   #[tracing::instrument(skip_all)]
50   pub async fn send_add_mod(
51     community: &ApubCommunity,
52     added_mod: &ApubPerson,
53     actor: &ApubPerson,
54     context: &LemmyContext,
55   ) -> Result<(), LemmyError> {
56     let id = generate_activity_id(
57       AddType::Add,
58       &context.settings().get_protocol_and_hostname(),
59     )?;
60     let add = CollectionAdd {
61       actor: ObjectId::new(actor.actor_id()),
62       to: vec![public()],
63       object: added_mod.actor_id(),
64       target: generate_moderators_url(&community.actor_id)?.into(),
65       cc: vec![community.actor_id()],
66       kind: AddType::Add,
67       id: id.clone(),
68       audience: Some(ObjectId::new(community.actor_id())),
69     };
70
71     let activity = AnnouncableActivities::CollectionAdd(add);
72     let inboxes = vec![added_mod.shared_inbox_or_inbox()];
73     send_activity_in_community(activity, actor, community, inboxes, true, context).await
74   }
75
76   pub async fn send_add_featured_post(
77     community: &ApubCommunity,
78     featured_post: &ApubPost,
79     actor: &ApubPerson,
80     context: &LemmyContext,
81   ) -> Result<(), LemmyError> {
82     let id = generate_activity_id(
83       AddType::Add,
84       &context.settings().get_protocol_and_hostname(),
85     )?;
86     let add = CollectionAdd {
87       actor: ObjectId::new(actor.actor_id()),
88       to: vec![public()],
89       object: featured_post.ap_id.clone().into(),
90       target: generate_featured_url(&community.actor_id)?.into(),
91       cc: vec![community.actor_id()],
92       kind: AddType::Add,
93       id: id.clone(),
94       audience: Some(ObjectId::new(community.actor_id())),
95     };
96     let activity = AnnouncableActivities::CollectionAdd(add);
97     send_activity_in_community(activity, actor, community, vec![], true, context).await
98   }
99 }
100
101 #[async_trait::async_trait(?Send)]
102 impl ActivityHandler for CollectionAdd {
103   type DataType = LemmyContext;
104   type Error = LemmyError;
105
106   fn id(&self) -> &Url {
107     &self.id
108   }
109
110   fn actor(&self) -> &Url {
111     self.actor.inner()
112   }
113
114   #[tracing::instrument(skip_all)]
115   async fn verify(
116     &self,
117     context: &Data<LemmyContext>,
118     request_counter: &mut i32,
119   ) -> Result<(), LemmyError> {
120     verify_is_public(&self.to, &self.cc)?;
121     let community = self.community(context, request_counter).await?;
122     verify_person_in_community(&self.actor, &community, context, request_counter).await?;
123     verify_mod_action(
124       &self.actor,
125       &self.object,
126       community.id,
127       context,
128       request_counter,
129     )
130     .await?;
131     Ok(())
132   }
133
134   #[tracing::instrument(skip_all)]
135   async fn receive(
136     self,
137     context: &Data<LemmyContext>,
138     request_counter: &mut i32,
139   ) -> Result<(), LemmyError> {
140     let (community, collection_type) =
141       Community::get_by_collection_url(context.pool(), &self.target.into()).await?;
142     match collection_type {
143       CollectionType::Moderators => {
144         let new_mod = ObjectId::<ApubPerson>::new(self.object)
145           .dereference(context, local_instance(context).await, request_counter)
146           .await?;
147
148         // If we had to refetch the community while parsing the activity, then the new mod has already
149         // been added. Skip it here as it would result in a duplicate key error.
150         let new_mod_id = new_mod.id;
151         let moderated_communities =
152           CommunityModerator::get_person_moderated_communities(context.pool(), new_mod_id).await?;
153         if !moderated_communities.contains(&community.id) {
154           let form = CommunityModeratorForm {
155             community_id: community.id,
156             person_id: new_mod.id,
157           };
158           CommunityModerator::join(context.pool(), &form).await?;
159
160           // write mod log
161           let actor = self
162             .actor
163             .dereference(context, local_instance(context).await, request_counter)
164             .await?;
165           let form = ModAddCommunityForm {
166             mod_person_id: actor.id,
167             other_person_id: new_mod.id,
168             community_id: community.id,
169             removed: Some(false),
170           };
171           ModAddCommunity::create(context.pool(), &form).await?;
172         }
173         // TODO: send websocket notification about added mod
174       }
175       CollectionType::Featured => {
176         let post = ObjectId::<ApubPost>::new(self.object)
177           .dereference(context, local_instance(context).await, request_counter)
178           .await?;
179         let form = PostUpdateForm::builder()
180           .featured_community(Some(true))
181           .build();
182         Post::update(context.pool(), post.id, &form).await?;
183       }
184     }
185     Ok(())
186   }
187 }
188
189 #[async_trait::async_trait(?Send)]
190 impl SendActivity for AddModToCommunity {
191   type Response = AddModToCommunityResponse;
192
193   async fn send_activity(
194     request: &Self,
195     _response: &Self::Response,
196     context: &LemmyContext,
197   ) -> Result<(), LemmyError> {
198     let local_user_view =
199       get_local_user_view_from_jwt(&request.auth, context.pool(), context.secret()).await?;
200     let community: ApubCommunity = Community::read(context.pool(), request.community_id)
201       .await?
202       .into();
203     let updated_mod: ApubPerson = Person::read(context.pool(), request.person_id)
204       .await?
205       .into();
206     if request.added {
207       CollectionAdd::send_add_mod(
208         &community,
209         &updated_mod,
210         &local_user_view.person.into(),
211         context,
212       )
213       .await
214     } else {
215       CollectionRemove::send_remove_mod(
216         &community,
217         &updated_mod,
218         &local_user_view.person.into(),
219         context,
220       )
221       .await
222     }
223   }
224 }
225
226 #[async_trait::async_trait(?Send)]
227 impl SendActivity for FeaturePost {
228   type Response = PostResponse;
229
230   async fn send_activity(
231     request: &Self,
232     response: &Self::Response,
233     context: &LemmyContext,
234   ) -> Result<(), LemmyError> {
235     let local_user_view =
236       get_local_user_view_from_jwt(&request.auth, context.pool(), context.secret()).await?;
237     // Deprecated, for backwards compatibility with 0.17
238     CreateOrUpdatePage::send(
239       &response.post_view.post,
240       local_user_view.person.id,
241       CreateOrUpdateType::Update,
242       context,
243     )
244     .await?;
245     let community = Community::read(context.pool(), response.post_view.community.id)
246       .await?
247       .into();
248     let post = response.post_view.post.clone().into();
249     let person = local_user_view.person.into();
250     if request.featured {
251       CollectionAdd::send_add_featured_post(&community, &post, &person, context).await
252     } else {
253       CollectionRemove::send_remove_featured_post(&community, &post, &person, context).await
254     }
255   }
256 }