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