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