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