]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/post/create_or_update.rs
Rewrite delete activities (#1699)
[lemmy.git] / crates / apub / src / activities / post / create_or_update.rs
1 use crate::{
2   activities::{
3     community::announce::AnnouncableActivities,
4     extract_community,
5     generate_activity_id,
6     verify_activity,
7     verify_mod_action,
8     verify_person_in_community,
9     CreateOrUpdateType,
10   },
11   activity_queue::send_to_community_new,
12   extensions::context::lemmy_context,
13   fetcher::person::get_or_fetch_and_upsert_person,
14   objects::{post::Page, FromApub, ToApub},
15   ActorType,
16 };
17 use anyhow::anyhow;
18 use lemmy_api_common::blocking;
19 use lemmy_apub_lib::{
20   values::PublicUrl,
21   verify_domains_match,
22   verify_urls_match,
23   ActivityCommonFields,
24   ActivityHandler,
25 };
26 use lemmy_db_queries::Crud;
27 use lemmy_db_schema::source::{community::Community, person::Person, post::Post};
28 use lemmy_utils::LemmyError;
29 use lemmy_websocket::{send::send_post_ws_message, LemmyContext, UserOperationCrud};
30 use url::Url;
31
32 #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
33 #[serde(rename_all = "camelCase")]
34 pub struct CreateOrUpdatePost {
35   to: PublicUrl,
36   object: Page,
37   cc: [Url; 1],
38   #[serde(rename = "type")]
39   kind: CreateOrUpdateType,
40   #[serde(flatten)]
41   common: ActivityCommonFields,
42 }
43
44 impl CreateOrUpdatePost {
45   pub async fn send(
46     post: &Post,
47     actor: &Person,
48     kind: CreateOrUpdateType,
49     context: &LemmyContext,
50   ) -> Result<(), LemmyError> {
51     let community_id = post.community_id;
52     let community = blocking(context.pool(), move |conn| {
53       Community::read(conn, community_id)
54     })
55     .await??;
56
57     let id = generate_activity_id(kind.clone())?;
58     let create_or_update = CreateOrUpdatePost {
59       to: PublicUrl::Public,
60       object: post.to_apub(context.pool()).await?,
61       cc: [community.actor_id()],
62       kind,
63       common: ActivityCommonFields {
64         context: lemmy_context(),
65         id: id.clone(),
66         actor: actor.actor_id(),
67         unparsed: Default::default(),
68       },
69     };
70
71     let activity = AnnouncableActivities::CreateOrUpdatePost(Box::new(create_or_update));
72     send_to_community_new(activity, &id, actor, &community, vec![], context).await
73   }
74 }
75
76 #[async_trait::async_trait(?Send)]
77 impl ActivityHandler for CreateOrUpdatePost {
78   async fn verify(
79     &self,
80     context: &LemmyContext,
81     request_counter: &mut i32,
82   ) -> Result<(), LemmyError> {
83     verify_activity(self.common())?;
84     let community = extract_community(&self.cc, context, request_counter).await?;
85     let community_id = community.actor_id();
86     verify_person_in_community(&self.common.actor, &community_id, context, request_counter).await?;
87     match self.kind {
88       CreateOrUpdateType::Create => {
89         verify_domains_match(&self.common.actor, self.object.id_unchecked())?;
90         verify_urls_match(&self.common.actor, &self.object.attributed_to)?;
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(anyhow!("New post cannot be stickied or locked").into());
99         }
100       }
101       CreateOrUpdateType::Update => {
102         let is_mod_action = self.object.is_mod_action(context.pool()).await?;
103         if is_mod_action {
104           verify_mod_action(&self.common.actor, community_id, context).await?;
105         } else {
106           verify_domains_match(&self.common.actor, self.object.id_unchecked())?;
107           verify_urls_match(&self.common.actor, &self.object.attributed_to)?;
108         }
109       }
110     }
111     self.object.verify(context, request_counter).await?;
112     Ok(())
113   }
114
115   async fn receive(
116     self,
117     context: &LemmyContext,
118     request_counter: &mut i32,
119   ) -> Result<(), LemmyError> {
120     let actor =
121       get_or_fetch_and_upsert_person(&self.common.actor, context, request_counter).await?;
122     let post = Post::from_apub(&self.object, context, &actor.actor_id(), request_counter).await?;
123
124     let notif_type = match self.kind {
125       CreateOrUpdateType::Create => UserOperationCrud::CreatePost,
126       CreateOrUpdateType::Update => UserOperationCrud::EditPost,
127     };
128     send_post_ws_message(post.id, notif_type, None, None, context).await?;
129     Ok(())
130   }
131
132   fn common(&self) -> &ActivityCommonFields {
133     &self.common
134   }
135 }