]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/create_or_update/post.rs
Migrate towards using page.attachment field for url (ref #2144) (#2182)
[lemmy.git] / crates / apub / src / activities / create_or_update / post.rs
1 use crate::{
2   activities::{
3     check_community_deleted_or_removed,
4     community::{announce::GetCommunity, send_activity_in_community},
5     generate_activity_id,
6     verify_activity,
7     verify_is_public,
8     verify_mod_action,
9     verify_person_in_community,
10   },
11   activity_lists::AnnouncableActivities,
12   objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
13   protocol::activities::{create_or_update::post::CreateOrUpdatePost, CreateOrUpdateType},
14 };
15 use activitystreams_kinds::public;
16 use lemmy_api_common::blocking;
17 use lemmy_apub_lib::{
18   data::Data,
19   object_id::ObjectId,
20   traits::{ActivityHandler, ActorType, ApubObject},
21   verify::{verify_domains_match, verify_urls_match},
22 };
23 use lemmy_db_schema::{source::community::Community, traits::Crud};
24 use lemmy_utils::LemmyError;
25 use lemmy_websocket::{send::send_post_ws_message, LemmyContext, UserOperationCrud};
26
27 impl CreateOrUpdatePost {
28   pub(crate) async fn new(
29     post: ApubPost,
30     actor: &ApubPerson,
31     community: &ApubCommunity,
32     kind: CreateOrUpdateType,
33     context: &LemmyContext,
34   ) -> Result<CreateOrUpdatePost, LemmyError> {
35     let id = generate_activity_id(
36       kind.clone(),
37       &context.settings().get_protocol_and_hostname(),
38     )?;
39     Ok(CreateOrUpdatePost {
40       actor: ObjectId::new(actor.actor_id()),
41       to: vec![public()],
42       object: post.into_apub(context).await?,
43       cc: vec![community.actor_id()],
44       kind,
45       id: id.clone(),
46       unparsed: Default::default(),
47     })
48   }
49
50   #[tracing::instrument(skip_all)]
51   pub async fn send(
52     post: ApubPost,
53     actor: &ApubPerson,
54     kind: CreateOrUpdateType,
55     context: &LemmyContext,
56   ) -> Result<(), LemmyError> {
57     let community_id = post.community_id;
58     let community: ApubCommunity = blocking(context.pool(), move |conn| {
59       Community::read(conn, community_id)
60     })
61     .await??
62     .into();
63
64     let create_or_update = CreateOrUpdatePost::new(post, actor, &community, kind, context).await?;
65     let id = create_or_update.id.clone();
66     let activity = AnnouncableActivities::CreateOrUpdatePost(Box::new(create_or_update));
67     send_activity_in_community(activity, &id, actor, &community, vec![], context).await
68   }
69 }
70
71 #[async_trait::async_trait(?Send)]
72 impl ActivityHandler for CreateOrUpdatePost {
73   type DataType = LemmyContext;
74
75   #[tracing::instrument(skip_all)]
76   async fn verify(
77     &self,
78     context: &Data<LemmyContext>,
79     request_counter: &mut i32,
80   ) -> Result<(), LemmyError> {
81     verify_is_public(&self.to, &self.cc)?;
82     verify_activity(&self.id, self.actor.inner(), &context.settings())?;
83     let community = self.get_community(context, request_counter).await?;
84     verify_person_in_community(&self.actor, &community, context, request_counter).await?;
85     check_community_deleted_or_removed(&community)?;
86
87     match self.kind {
88       CreateOrUpdateType::Create => {
89         verify_domains_match(self.actor.inner(), self.object.id.inner())?;
90         verify_urls_match(self.actor.inner(), self.object.attributed_to.inner())?;
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(LemmyError::from_message(
99             "New post cannot be stickied or locked",
100           ));
101         }
102       }
103       CreateOrUpdateType::Update => {
104         let is_mod_action = self.object.is_mod_action(context).await?;
105         if is_mod_action {
106           verify_mod_action(&self.actor, &community, context, request_counter).await?;
107         } else {
108           verify_domains_match(self.actor.inner(), self.object.id.inner())?;
109           verify_urls_match(self.actor.inner(), self.object.attributed_to.inner())?;
110         }
111       }
112     }
113     ApubPost::verify(&self.object, self.actor.inner(), context, request_counter).await?;
114     Ok(())
115   }
116
117   #[tracing::instrument(skip_all)]
118   async fn receive(
119     self,
120     context: &Data<LemmyContext>,
121     request_counter: &mut i32,
122   ) -> Result<(), LemmyError> {
123     let post = ApubPost::from_apub(self.object, context, 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 }
133
134 #[async_trait::async_trait(?Send)]
135 impl GetCommunity for CreateOrUpdatePost {
136   #[tracing::instrument(skip_all)]
137   async fn get_community(
138     &self,
139     context: &LemmyContext,
140     request_counter: &mut i32,
141   ) -> Result<ApubCommunity, LemmyError> {
142     self
143       .object
144       .extract_community(context, request_counter)
145       .await
146   }
147 }