]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/deletion/delete.rs
Change activity.cc to Vec<Url> in public activities
[lemmy.git] / crates / apub / src / activities / deletion / delete.rs
1 use crate::{
2   activities::{
3     community::{
4       announce::{AnnouncableActivities, GetCommunity},
5       send_to_community,
6     },
7     deletion::{
8       receive_delete_action,
9       verify_delete_activity,
10       DeletableObjects,
11       WebsocketMessages,
12     },
13     generate_activity_id,
14     verify_activity,
15     verify_is_public,
16   },
17   context::lemmy_context,
18   fetcher::object_id::ObjectId,
19   objects::{community::ApubCommunity, person::ApubPerson},
20 };
21 use activitystreams::{
22   activity::kind::DeleteType,
23   base::AnyBase,
24   primitives::OneOrMany,
25   public,
26   unparsed::Unparsed,
27 };
28 use anyhow::anyhow;
29 use lemmy_api_common::blocking;
30 use lemmy_apub_lib::{
31   data::Data,
32   traits::{ActivityFields, ActivityHandler, ActorType},
33 };
34 use lemmy_db_schema::{
35   source::{
36     comment::Comment,
37     community::Community,
38     moderator::{
39       ModRemoveComment,
40       ModRemoveCommentForm,
41       ModRemoveCommunity,
42       ModRemoveCommunityForm,
43       ModRemovePost,
44       ModRemovePostForm,
45     },
46     post::Post,
47   },
48   traits::Crud,
49 };
50 use lemmy_utils::LemmyError;
51 use lemmy_websocket::{
52   send::{send_comment_ws_message_simple, send_community_ws_message, send_post_ws_message},
53   LemmyContext,
54   UserOperationCrud,
55 };
56 use serde::{Deserialize, Serialize};
57 use serde_with::skip_serializing_none;
58 use url::Url;
59
60 /// This is very confusing, because there are four distinct cases to handle:
61 /// - user deletes their post
62 /// - user deletes their comment
63 /// - remote community mod deletes local community
64 /// - remote community deletes itself (triggered by a mod)
65 ///
66 /// TODO: we should probably change how community deletions work to simplify this. Probably by
67 /// wrapping it in an announce just like other activities, instead of having the community send it.
68 #[skip_serializing_none]
69 #[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
70 #[serde(rename_all = "camelCase")]
71 pub struct Delete {
72   actor: ObjectId<ApubPerson>,
73   to: Vec<Url>,
74   pub(in crate::activities::deletion) object: Url,
75   pub(in crate::activities::deletion) cc: Vec<Url>,
76   #[serde(rename = "type")]
77   kind: DeleteType,
78   /// If summary is present, this is a mod action (Remove in Lemmy terms). Otherwise, its a user
79   /// deleting their own content.
80   pub(in crate::activities::deletion) summary: Option<String>,
81   id: Url,
82   #[serde(rename = "@context")]
83   context: OneOrMany<AnyBase>,
84   #[serde(flatten)]
85   unparsed: Unparsed,
86 }
87
88 #[async_trait::async_trait(?Send)]
89 impl ActivityHandler for Delete {
90   type DataType = LemmyContext;
91   async fn verify(
92     &self,
93     context: &Data<LemmyContext>,
94     request_counter: &mut i32,
95   ) -> Result<(), LemmyError> {
96     verify_is_public(&self.to)?;
97     verify_activity(self, &context.settings())?;
98     let community = self.get_community(context, request_counter).await?;
99     verify_delete_activity(
100       &self.object,
101       self,
102       &community,
103       self.summary.is_some(),
104       context,
105       request_counter,
106     )
107     .await?;
108     Ok(())
109   }
110
111   async fn receive(
112     self,
113     context: &Data<LemmyContext>,
114     request_counter: &mut i32,
115   ) -> Result<(), LemmyError> {
116     if let Some(reason) = self.summary {
117       // We set reason to empty string if it doesn't exist, to distinguish between delete and
118       // remove. Here we change it back to option, so we don't write it to db.
119       let reason = if reason.is_empty() {
120         None
121       } else {
122         Some(reason)
123       };
124       receive_remove_action(&self.actor, &self.object, reason, context, request_counter).await
125     } else {
126       receive_delete_action(
127         &self.object,
128         &self.actor,
129         WebsocketMessages {
130           community: UserOperationCrud::DeleteCommunity,
131           post: UserOperationCrud::DeletePost,
132           comment: UserOperationCrud::DeleteComment,
133         },
134         true,
135         context,
136         request_counter,
137       )
138       .await
139     }
140   }
141 }
142
143 impl Delete {
144   pub(in crate::activities::deletion) fn new(
145     actor: &ApubPerson,
146     community: &ApubCommunity,
147     object_id: Url,
148     summary: Option<String>,
149     context: &LemmyContext,
150   ) -> Result<Delete, LemmyError> {
151     Ok(Delete {
152       actor: ObjectId::new(actor.actor_id()),
153       to: vec![public()],
154       object: object_id,
155       cc: vec![community.actor_id()],
156       kind: DeleteType::Delete,
157       summary,
158       id: generate_activity_id(
159         DeleteType::Delete,
160         &context.settings().get_protocol_and_hostname(),
161       )?,
162       context: lemmy_context(),
163       unparsed: Default::default(),
164     })
165   }
166   pub(in crate::activities::deletion) async fn send(
167     actor: &ApubPerson,
168     community: &ApubCommunity,
169     object_id: Url,
170     summary: Option<String>,
171     context: &LemmyContext,
172   ) -> Result<(), LemmyError> {
173     let delete = Delete::new(actor, community, object_id, summary, context)?;
174     let delete_id = delete.id.clone();
175
176     let activity = AnnouncableActivities::Delete(delete);
177     send_to_community(activity, &delete_id, actor, community, vec![], context).await
178   }
179 }
180
181 pub(in crate::activities) async fn receive_remove_action(
182   actor: &ObjectId<ApubPerson>,
183   object: &Url,
184   reason: Option<String>,
185   context: &LemmyContext,
186   request_counter: &mut i32,
187 ) -> Result<(), LemmyError> {
188   let actor = actor.dereference(context, request_counter).await?;
189   use UserOperationCrud::*;
190   match DeletableObjects::read_from_db(object, context).await? {
191     DeletableObjects::Community(community) => {
192       if community.local {
193         return Err(anyhow!("Only local admin can remove community").into());
194       }
195       let form = ModRemoveCommunityForm {
196         mod_person_id: actor.id,
197         community_id: community.id,
198         removed: Some(true),
199         reason,
200         expires: None,
201       };
202       blocking(context.pool(), move |conn| {
203         ModRemoveCommunity::create(conn, &form)
204       })
205       .await??;
206       let deleted_community = blocking(context.pool(), move |conn| {
207         Community::update_removed(conn, community.id, true)
208       })
209       .await??;
210
211       send_community_ws_message(deleted_community.id, RemoveCommunity, None, None, context).await?;
212     }
213     DeletableObjects::Post(post) => {
214       let form = ModRemovePostForm {
215         mod_person_id: actor.id,
216         post_id: post.id,
217         removed: Some(true),
218         reason,
219       };
220       blocking(context.pool(), move |conn| {
221         ModRemovePost::create(conn, &form)
222       })
223       .await??;
224       let removed_post = blocking(context.pool(), move |conn| {
225         Post::update_removed(conn, post.id, true)
226       })
227       .await??;
228
229       send_post_ws_message(removed_post.id, RemovePost, None, None, context).await?;
230     }
231     DeletableObjects::Comment(comment) => {
232       let form = ModRemoveCommentForm {
233         mod_person_id: actor.id,
234         comment_id: comment.id,
235         removed: Some(true),
236         reason,
237       };
238       blocking(context.pool(), move |conn| {
239         ModRemoveComment::create(conn, &form)
240       })
241       .await??;
242       let removed_comment = blocking(context.pool(), move |conn| {
243         Comment::update_removed(conn, comment.id, true)
244       })
245       .await??;
246
247       send_comment_ws_message_simple(removed_comment.id, RemoveComment, context).await?;
248     }
249   }
250   Ok(())
251 }
252
253 #[async_trait::async_trait(?Send)]
254 impl GetCommunity for Delete {
255   async fn get_community(
256     &self,
257     context: &LemmyContext,
258     _request_counter: &mut i32,
259   ) -> Result<ApubCommunity, LemmyError> {
260     let community_id = match DeletableObjects::read_from_db(&self.object, context).await? {
261       DeletableObjects::Community(c) => c.id,
262       DeletableObjects::Comment(c) => {
263         let post = blocking(context.pool(), move |conn| Post::read(conn, c.post_id)).await??;
264         post.community_id
265       }
266       DeletableObjects::Post(p) => p.community_id,
267     };
268     let community = blocking(context.pool(), move |conn| {
269       Community::read(conn, community_id)
270     })
271     .await??;
272     Ok(community.into())
273   }
274 }