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