]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/post/create_or_update.rs
Moving settings and secrets to context.
[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(
63       kind.clone(),
64       &context.settings().get_protocol_and_hostname(),
65     )?;
66     let create_or_update = CreateOrUpdatePost {
67       actor: ObjectId::new(actor.actor_id()),
68       to: [PublicUrl::Public],
69       object: post.to_apub(context.pool()).await?,
70       cc: [ObjectId::new(community.actor_id())],
71       kind,
72       id: id.clone(),
73       context: lemmy_context(),
74       unparsed: Default::default(),
75     };
76
77     let activity = AnnouncableActivities::CreateOrUpdatePost(Box::new(create_or_update));
78     send_to_community_new(activity, &id, actor, &community, vec![], context).await
79   }
80 }
81
82 #[async_trait::async_trait(?Send)]
83 impl ActivityHandler for CreateOrUpdatePost {
84   async fn verify(
85     &self,
86     context: &LemmyContext,
87     request_counter: &mut i32,
88   ) -> Result<(), LemmyError> {
89     verify_activity(self, &context.settings())?;
90     let community = self.cc[0].dereference(context, request_counter).await?;
91     verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?;
92     match self.kind {
93       CreateOrUpdateType::Create => {
94         verify_domains_match(self.actor.inner(), self.object.id_unchecked())?;
95         verify_urls_match(self.actor(), self.object.attributed_to.inner())?;
96         // Check that the post isnt locked or stickied, as that isnt possible for newly created posts.
97         // However, when fetching a remote post we generate a new create activity with the current
98         // locked/stickied value, so this check may fail. So only check if its a local community,
99         // because then we will definitely receive all create and update activities separately.
100         let is_stickied_or_locked =
101           self.object.stickied == Some(true) || self.object.comments_enabled == Some(false);
102         if community.local && is_stickied_or_locked {
103           return Err(anyhow!("New post cannot be stickied or locked").into());
104         }
105       }
106       CreateOrUpdateType::Update => {
107         let is_mod_action = self.object.is_mod_action(context.pool()).await?;
108         if is_mod_action {
109           verify_mod_action(&self.actor, self.cc[0].clone(), context).await?;
110         } else {
111           verify_domains_match(self.actor.inner(), self.object.id_unchecked())?;
112           verify_urls_match(self.actor(), self.object.attributed_to.inner())?;
113         }
114       }
115     }
116     self.object.verify(context, request_counter).await?;
117     Ok(())
118   }
119
120   async fn receive(
121     self,
122     context: &LemmyContext,
123     request_counter: &mut i32,
124   ) -> Result<(), LemmyError> {
125     let actor = self.actor.dereference(context, request_counter).await?;
126     let post = Post::from_apub(&self.object, context, &actor.actor_id(), request_counter).await?;
127
128     let notif_type = match self.kind {
129       CreateOrUpdateType::Create => UserOperationCrud::CreatePost,
130       CreateOrUpdateType::Update => UserOperationCrud::EditPost,
131     };
132     send_post_ws_message(post.id, notif_type, None, None, context).await?;
133     Ok(())
134   }
135 }