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