]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/create_or_update/post.rs
Error enum fixed (#3487)
[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(context.pool(), person_id).await?.into();
113     let community: ApubCommunity = Community::read(context.pool(), community_id).await?.into();
114
115     let create_or_update =
116       CreateOrUpdatePage::new(post, &person, &community, kind, context).await?;
117     let is_mod_action = create_or_update.object.is_mod_action(context).await?;
118     let activity = AnnouncableActivities::CreateOrUpdatePost(create_or_update);
119     send_activity_in_community(
120       activity,
121       &person,
122       &community,
123       vec![],
124       is_mod_action,
125       context,
126     )
127     .await?;
128     Ok(())
129   }
130 }
131
132 #[async_trait::async_trait]
133 impl ActivityHandler for CreateOrUpdatePage {
134   type DataType = LemmyContext;
135   type Error = LemmyError;
136
137   fn id(&self) -> &Url {
138     &self.id
139   }
140
141   fn actor(&self) -> &Url {
142     self.actor.inner()
143   }
144
145   #[tracing::instrument(skip_all)]
146   async fn verify(&self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
147     verify_is_public(&self.to, &self.cc)?;
148     let community = self.community(context).await?;
149     verify_person_in_community(&self.actor, &community, context).await?;
150     check_community_deleted_or_removed(&community)?;
151
152     match self.kind {
153       CreateOrUpdateType::Create => {
154         verify_domains_match(self.actor.inner(), self.object.id.inner())?;
155         verify_urls_match(self.actor.inner(), self.object.creator()?.inner())?;
156         // Check that the post isnt locked, as that isnt possible for newly created posts.
157         // However, when fetching a remote post we generate a new create activity with the current
158         // locked value, so this check may fail. So only check if its a local community,
159         // because then we will definitely receive all create and update activities separately.
160         let is_locked = self.object.comments_enabled == Some(false);
161         if community.local && is_locked {
162           return Err(LemmyErrorType::NewPostCannotBeLocked)?;
163         }
164       }
165       CreateOrUpdateType::Update => {
166         let is_mod_action = self.object.is_mod_action(context).await?;
167         if is_mod_action {
168           verify_mod_action(&self.actor, self.object.id.inner(), community.id, context).await?;
169         } else {
170           verify_domains_match(self.actor.inner(), self.object.id.inner())?;
171           verify_urls_match(self.actor.inner(), self.object.creator()?.inner())?;
172         }
173       }
174     }
175     ApubPost::verify(&self.object, self.actor.inner(), context).await?;
176     Ok(())
177   }
178
179   #[tracing::instrument(skip_all)]
180   async fn receive(self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
181     insert_activity(&self.id, &self, false, false, context).await?;
182     let post = ApubPost::from_json(self.object, context).await?;
183
184     // author likes their own post by default
185     let like_form = PostLikeForm {
186       post_id: post.id,
187       person_id: post.creator_id,
188       score: 1,
189     };
190     PostLike::like(context.pool(), &like_form).await?;
191
192     // Calculate initial hot_rank for post
193     PostAggregates::update_hot_rank(context.pool(), post.id).await?;
194
195     Ok(())
196   }
197 }