]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/community/collection_add.rs
afe8387f2c6e68e5e9e4bb7eba572afed69df7b1
[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, 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 = local_user_view_from_jwt(&request.auth, context).await?;
181     let community: ApubCommunity = Community::read(context.pool(), request.community_id)
182       .await?
183       .into();
184     let updated_mod: ApubPerson = Person::read(context.pool(), request.person_id)
185       .await?
186       .into();
187     if request.added {
188       CollectionAdd::send_add_mod(
189         &community,
190         &updated_mod,
191         &local_user_view.person.into(),
192         context,
193       )
194       .await
195     } else {
196       CollectionRemove::send_remove_mod(
197         &community,
198         &updated_mod,
199         &local_user_view.person.into(),
200         context,
201       )
202       .await
203     }
204   }
205 }
206
207 #[async_trait::async_trait]
208 impl SendActivity for FeaturePost {
209   type Response = PostResponse;
210
211   async fn send_activity(
212     request: &Self,
213     response: &Self::Response,
214     context: &Data<LemmyContext>,
215   ) -> Result<(), LemmyError> {
216     let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
217     // Deprecated, for backwards compatibility with 0.17
218     CreateOrUpdatePage::send(
219       &response.post_view.post,
220       local_user_view.person.id,
221       CreateOrUpdateType::Update,
222       context,
223     )
224     .await?;
225     let community = Community::read(context.pool(), response.post_view.community.id)
226       .await?
227       .into();
228     let post = response.post_view.post.clone().into();
229     let person = local_user_view.person.into();
230     if request.featured {
231       CollectionAdd::send_add_featured_post(&community, &post, &person, context).await
232     } else {
233       CollectionRemove::send_remove_featured_post(&community, &post, &person, context).await
234     }
235   }
236 }