]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/create_or_update/post.rs
Add support for Featured Posts (#2585)
[lemmy.git] / crates / apub / src / activities / create_or_update / post.rs
1 use crate::{
2   activities::{
3     check_community_deleted_or_removed,
4     community::send_activity_in_community,
5     generate_activity_id,
6     verify_is_public,
7     verify_mod_action,
8     verify_person_in_community,
9   },
10   activity_lists::AnnouncableActivities,
11   objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
12   protocol::{
13     activities::{create_or_update::page::CreateOrUpdatePage, CreateOrUpdateType},
14     InCommunity,
15   },
16   ActorType,
17   SendActivity,
18 };
19 use activitypub_federation::{
20   core::object_id::ObjectId,
21   data::Data,
22   traits::{ActivityHandler, ApubObject},
23   utils::{verify_domains_match, verify_urls_match},
24 };
25 use activitystreams_kinds::public;
26 use lemmy_api_common::{
27   context::LemmyContext,
28   post::{CreatePost, EditPost, FeaturePost, LockPost, PostResponse},
29   utils::get_local_user_view_from_jwt,
30   websocket::{send::send_post_ws_message, UserOperationCrud},
31 };
32 use lemmy_db_schema::{
33   newtypes::PersonId,
34   source::{
35     community::Community,
36     person::Person,
37     post::{Post, PostLike, PostLikeForm},
38   },
39   traits::{Crud, Likeable},
40 };
41 use lemmy_utils::error::LemmyError;
42 use url::Url;
43
44 #[async_trait::async_trait(?Send)]
45 impl SendActivity for CreatePost {
46   type Response = PostResponse;
47
48   async fn send_activity(
49     _request: &Self,
50     response: &Self::Response,
51     context: &LemmyContext,
52   ) -> Result<(), LemmyError> {
53     CreateOrUpdatePage::send(
54       &response.post_view.post,
55       response.post_view.creator.id,
56       CreateOrUpdateType::Create,
57       context,
58     )
59     .await
60   }
61 }
62
63 #[async_trait::async_trait(?Send)]
64 impl SendActivity for EditPost {
65   type Response = PostResponse;
66
67   async fn send_activity(
68     _request: &Self,
69     response: &Self::Response,
70     context: &LemmyContext,
71   ) -> Result<(), LemmyError> {
72     CreateOrUpdatePage::send(
73       &response.post_view.post,
74       response.post_view.creator.id,
75       CreateOrUpdateType::Update,
76       context,
77     )
78     .await
79   }
80 }
81
82 #[async_trait::async_trait(?Send)]
83 impl SendActivity for LockPost {
84   type Response = PostResponse;
85
86   async fn send_activity(
87     request: &Self,
88     response: &Self::Response,
89     context: &LemmyContext,
90   ) -> Result<(), LemmyError> {
91     let local_user_view =
92       get_local_user_view_from_jwt(&request.auth, context.pool(), context.secret()).await?;
93     CreateOrUpdatePage::send(
94       &response.post_view.post,
95       local_user_view.person.id,
96       CreateOrUpdateType::Update,
97       context,
98     )
99     .await
100   }
101 }
102
103 #[async_trait::async_trait(?Send)]
104 impl SendActivity for FeaturePost {
105   type Response = PostResponse;
106
107   async fn send_activity(
108     request: &Self,
109     response: &Self::Response,
110     context: &LemmyContext,
111   ) -> Result<(), LemmyError> {
112     let local_user_view =
113       get_local_user_view_from_jwt(&request.auth, context.pool(), context.secret()).await?;
114     CreateOrUpdatePage::send(
115       &response.post_view.post,
116       local_user_view.person.id,
117       CreateOrUpdateType::Update,
118       context,
119     )
120     .await
121   }
122 }
123
124 impl CreateOrUpdatePage {
125   pub(crate) async fn new(
126     post: ApubPost,
127     actor: &ApubPerson,
128     community: &ApubCommunity,
129     kind: CreateOrUpdateType,
130     context: &LemmyContext,
131   ) -> Result<CreateOrUpdatePage, LemmyError> {
132     let id = generate_activity_id(
133       kind.clone(),
134       &context.settings().get_protocol_and_hostname(),
135     )?;
136     Ok(CreateOrUpdatePage {
137       actor: ObjectId::new(actor.actor_id()),
138       to: vec![public()],
139       object: post.into_apub(context).await?,
140       cc: vec![community.actor_id()],
141       kind,
142       id: id.clone(),
143       audience: Some(ObjectId::new(community.actor_id())),
144     })
145   }
146
147   #[tracing::instrument(skip_all)]
148   async fn send(
149     post: &Post,
150     person_id: PersonId,
151     kind: CreateOrUpdateType,
152     context: &LemmyContext,
153   ) -> Result<(), LemmyError> {
154     let post = ApubPost(post.clone());
155     let community_id = post.community_id;
156     let person: ApubPerson = Person::read(context.pool(), person_id).await?.into();
157     let community: ApubCommunity = Community::read(context.pool(), community_id).await?.into();
158
159     let create_or_update =
160       CreateOrUpdatePage::new(post, &person, &community, kind, context).await?;
161     let is_mod_action = create_or_update.object.is_mod_action(context).await?;
162     let activity = AnnouncableActivities::CreateOrUpdatePost(create_or_update);
163     send_activity_in_community(
164       activity,
165       &person,
166       &community,
167       vec![],
168       is_mod_action,
169       context,
170     )
171     .await?;
172     Ok(())
173   }
174 }
175
176 #[async_trait::async_trait(?Send)]
177 impl ActivityHandler for CreateOrUpdatePage {
178   type DataType = LemmyContext;
179   type Error = LemmyError;
180
181   fn id(&self) -> &Url {
182     &self.id
183   }
184
185   fn actor(&self) -> &Url {
186     self.actor.inner()
187   }
188
189   #[tracing::instrument(skip_all)]
190   async fn verify(
191     &self,
192     context: &Data<LemmyContext>,
193     request_counter: &mut i32,
194   ) -> Result<(), LemmyError> {
195     verify_is_public(&self.to, &self.cc)?;
196     let community = self.community(context, request_counter).await?;
197     verify_person_in_community(&self.actor, &community, context, request_counter).await?;
198     check_community_deleted_or_removed(&community)?;
199
200     match self.kind {
201       CreateOrUpdateType::Create => {
202         verify_domains_match(self.actor.inner(), self.object.id.inner())?;
203         verify_urls_match(self.actor.inner(), self.object.creator()?.inner())?;
204         // Check that the post isnt locked or stickied, as that isnt possible for newly created posts.
205         // However, when fetching a remote post we generate a new create activity with the current
206         // locked/stickied value, so this check may fail. So only check if its a local community,
207         // because then we will definitely receive all create and update activities separately.
208         let is_featured_or_locked =
209           self.object.stickied == Some(true) || self.object.comments_enabled == Some(false);
210         if community.local && is_featured_or_locked {
211           return Err(LemmyError::from_message(
212             "New post cannot be stickied or locked",
213           ));
214         }
215       }
216       CreateOrUpdateType::Update => {
217         let is_mod_action = self.object.is_mod_action(context).await?;
218         if is_mod_action {
219           verify_mod_action(
220             &self.actor,
221             self.object.id.inner(),
222             community.id,
223             context,
224             request_counter,
225           )
226           .await?;
227         } else {
228           verify_domains_match(self.actor.inner(), self.object.id.inner())?;
229           verify_urls_match(self.actor.inner(), self.object.creator()?.inner())?;
230         }
231       }
232     }
233     ApubPost::verify(&self.object, self.actor.inner(), context, request_counter).await?;
234     Ok(())
235   }
236
237   #[tracing::instrument(skip_all)]
238   async fn receive(
239     self,
240     context: &Data<LemmyContext>,
241     request_counter: &mut i32,
242   ) -> Result<(), LemmyError> {
243     let post = ApubPost::from_apub(self.object, context, request_counter).await?;
244
245     // author likes their own post by default
246     let like_form = PostLikeForm {
247       post_id: post.id,
248       person_id: post.creator_id,
249       score: 1,
250     };
251     PostLike::like(context.pool(), &like_form).await?;
252
253     let notif_type = match self.kind {
254       CreateOrUpdateType::Create => UserOperationCrud::CreatePost,
255       CreateOrUpdateType::Update => UserOperationCrud::EditPost,
256     };
257     send_post_ws_message(post.id, notif_type, None, None, context).await?;
258     Ok(())
259   }
260 }