]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/community/collection_add.rs
Split activity table into sent and received parts (fixes #3103) (#3583)
[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_received_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     insert_received_activity(&self.id, context).await?;
112     verify_is_public(&self.to, &self.cc)?;
113     let community = self.community(context).await?;
114     verify_person_in_community(&self.actor, &community, context).await?;
115     verify_mod_action(&self.actor, &self.object, community.id, context).await?;
116     Ok(())
117   }
118
119   #[tracing::instrument(skip_all)]
120   async fn receive(self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
121     let (community, collection_type) =
122       Community::get_by_collection_url(&mut 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(&mut context.pool(), new_mod_id)
134             .await?;
135         if !moderated_communities.contains(&community.id) {
136           let form = CommunityModeratorForm {
137             community_id: community.id,
138             person_id: new_mod.id,
139           };
140           CommunityModerator::join(&mut context.pool(), &form).await?;
141
142           // write mod log
143           let actor = self.actor.dereference(context).await?;
144           let form = ModAddCommunityForm {
145             mod_person_id: actor.id,
146             other_person_id: new_mod.id,
147             community_id: community.id,
148             removed: Some(false),
149           };
150           ModAddCommunity::create(&mut context.pool(), &form).await?;
151         }
152         // TODO: send websocket notification about added mod
153       }
154       CollectionType::Featured => {
155         let post = ObjectId::<ApubPost>::from(self.object)
156           .dereference(context)
157           .await?;
158         let form = PostUpdateForm::builder()
159           .featured_community(Some(true))
160           .build();
161         Post::update(&mut context.pool(), post.id, &form).await?;
162       }
163     }
164     Ok(())
165   }
166 }
167
168 #[async_trait::async_trait]
169 impl SendActivity for AddModToCommunity {
170   type Response = AddModToCommunityResponse;
171
172   async fn send_activity(
173     request: &Self,
174     _response: &Self::Response,
175     context: &Data<LemmyContext>,
176   ) -> Result<(), LemmyError> {
177     let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
178     let community: ApubCommunity = Community::read(&mut context.pool(), request.community_id)
179       .await?
180       .into();
181     let updated_mod: ApubPerson = Person::read(&mut context.pool(), request.person_id)
182       .await?
183       .into();
184     if request.added {
185       CollectionAdd::send_add_mod(
186         &community,
187         &updated_mod,
188         &local_user_view.person.into(),
189         context,
190       )
191       .await
192     } else {
193       CollectionRemove::send_remove_mod(
194         &community,
195         &updated_mod,
196         &local_user_view.person.into(),
197         context,
198       )
199       .await
200     }
201   }
202 }
203
204 #[async_trait::async_trait]
205 impl SendActivity for FeaturePost {
206   type Response = PostResponse;
207
208   async fn send_activity(
209     request: &Self,
210     response: &Self::Response,
211     context: &Data<LemmyContext>,
212   ) -> Result<(), LemmyError> {
213     let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
214     let community = Community::read(&mut context.pool(), response.post_view.community.id)
215       .await?
216       .into();
217     let post = response.post_view.post.clone().into();
218     let person = local_user_view.person.into();
219     if request.featured {
220       CollectionAdd::send_add_featured_post(&community, &post, &person, context).await
221     } else {
222       CollectionRemove::send_remove_featured_post(&community, &post, &person, context).await
223     }
224   }
225 }