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