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