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