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