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