]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/create_or_update/post.rs
907e23907890499715748a9fa4f26fc8079919a0
[lemmy.git] / crates / apub / src / activities / create_or_update / post.rs
1 use crate::{
2   activities::{
3     check_community_deleted_or_removed,
4     community::{announce::GetCommunity, send_activity_in_community},
5     generate_activity_id,
6     verify_activity,
7     verify_is_public,
8     verify_mod_action,
9     verify_person_in_community,
10   },
11   activity_lists::AnnouncableActivities,
12   objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
13   protocol::activities::{create_or_update::post::CreateOrUpdatePost, CreateOrUpdateType},
14 };
15 use activitystreams_kinds::public;
16 use lemmy_api_common::blocking;
17 use lemmy_apub_lib::{
18   data::Data,
19   object_id::ObjectId,
20   traits::{ActivityHandler, ActorType, ApubObject},
21   verify::{verify_domains_match, verify_urls_match},
22 };
23 use lemmy_db_schema::{source::community::Community, traits::Crud};
24 use lemmy_utils::LemmyError;
25 use lemmy_websocket::{send::send_post_ws_message, LemmyContext, UserOperationCrud};
26
27 impl CreateOrUpdatePost {
28   pub(crate) async fn new(
29     post: ApubPost,
30     actor: &ApubPerson,
31     community: &ApubCommunity,
32     kind: CreateOrUpdateType,
33     context: &LemmyContext,
34   ) -> Result<CreateOrUpdatePost, LemmyError> {
35     let id = generate_activity_id(
36       kind.clone(),
37       &context.settings().get_protocol_and_hostname(),
38     )?;
39     Ok(CreateOrUpdatePost {
40       actor: ObjectId::new(actor.actor_id()),
41       to: vec![public()],
42       object: post.into_apub(context).await?,
43       cc: vec![community.actor_id()],
44       kind,
45       id: id.clone(),
46       unparsed: Default::default(),
47     })
48   }
49
50   #[tracing::instrument(skip_all)]
51   pub async fn send(
52     post: ApubPost,
53     actor: &ApubPerson,
54     kind: CreateOrUpdateType,
55     context: &LemmyContext,
56   ) -> Result<(), LemmyError> {
57     let community_id = post.community_id;
58     let community: ApubCommunity = blocking(context.pool(), move |conn| {
59       Community::read(conn, community_id)
60     })
61     .await??
62     .into();
63
64     let create_or_update = CreateOrUpdatePost::new(post, actor, &community, kind, context).await?;
65     let id = create_or_update.id.clone();
66     let activity = AnnouncableActivities::CreateOrUpdatePost(Box::new(create_or_update));
67     send_activity_in_community(activity, &id, actor, &community, vec![], context).await
68   }
69 }
70
71 #[async_trait::async_trait(?Send)]
72 impl ActivityHandler for CreateOrUpdatePost {
73   type DataType = LemmyContext;
74
75   #[tracing::instrument(skip_all)]
76   async fn verify(
77     &self,
78     context: &Data<LemmyContext>,
79     request_counter: &mut i32,
80   ) -> Result<(), LemmyError> {
81     verify_is_public(&self.to, &self.cc)?;
82     verify_activity(&self.id, self.actor.inner(), &context.settings())?;
83     let community = self.get_community(context, request_counter).await?;
84     verify_person_in_community(&self.actor, &community, context, request_counter).await?;
85     check_community_deleted_or_removed(&community)?;
86
87     match self.kind {
88       CreateOrUpdateType::Create => {
89         verify_domains_match(self.actor.inner(), self.object.id.inner())?;
90         verify_urls_match(self.actor.inner(), self.object.attributed_to.inner())?;
91         // Check that the post isnt locked or stickied, as that isnt possible for newly created posts.
92         // However, when fetching a remote post we generate a new create activity with the current
93         // locked/stickied value, so this check may fail. So only check if its a local community,
94         // because then we will definitely receive all create and update activities separately.
95         let is_stickied_or_locked =
96           self.object.stickied == Some(true) || self.object.comments_enabled == Some(false);
97         if community.local && is_stickied_or_locked {
98           return Err(LemmyError::from_message(
99             "New post cannot be stickied or locked",
100           ));
101         }
102       }
103       CreateOrUpdateType::Update => {
104         let is_mod_action = self.object.is_mod_action(context).await?;
105         if is_mod_action {
106           verify_mod_action(
107             &self.actor,
108             self.object.id.inner(),
109             &community,
110             context,
111             request_counter,
112           )
113           .await?;
114         } else {
115           verify_domains_match(self.actor.inner(), self.object.id.inner())?;
116           verify_urls_match(self.actor.inner(), self.object.attributed_to.inner())?;
117         }
118       }
119     }
120     ApubPost::verify(&self.object, self.actor.inner(), context, request_counter).await?;
121     Ok(())
122   }
123
124   #[tracing::instrument(skip_all)]
125   async fn receive(
126     self,
127     context: &Data<LemmyContext>,
128     request_counter: &mut i32,
129   ) -> Result<(), LemmyError> {
130     let post = ApubPost::from_apub(self.object, context, request_counter).await?;
131
132     let notif_type = match self.kind {
133       CreateOrUpdateType::Create => UserOperationCrud::CreatePost,
134       CreateOrUpdateType::Update => UserOperationCrud::EditPost,
135     };
136     send_post_ws_message(post.id, notif_type, None, None, context).await?;
137     Ok(())
138   }
139 }
140
141 #[async_trait::async_trait(?Send)]
142 impl GetCommunity for CreateOrUpdatePost {
143   #[tracing::instrument(skip_all)]
144   async fn get_community(
145     &self,
146     context: &LemmyContext,
147     request_counter: &mut i32,
148   ) -> Result<ApubCommunity, LemmyError> {
149     self
150       .object
151       .extract_community(context, request_counter)
152       .await
153   }
154 }