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