]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/deletion/delete.rs
Extract Activitypub logic into separate library (#2288)
[lemmy.git] / crates / apub / src / activities / deletion / delete.rs
1 use crate::{
2   activities::{
3     community::announce::GetCommunity,
4     deletion::{receive_delete_action, verify_delete_activity, DeletableObjects},
5     generate_activity_id,
6   },
7   local_instance,
8   objects::{community::ApubCommunity, person::ApubPerson},
9   protocol::{
10     activities::deletion::delete::Delete,
11     objects::tombstone::Tombstone,
12     IdOrNestedObject,
13   },
14 };
15 use activitypub_federation::{core::object_id::ObjectId, data::Data, traits::ActivityHandler};
16 use activitystreams_kinds::activity::DeleteType;
17 use anyhow::anyhow;
18 use lemmy_api_common::utils::blocking;
19 use lemmy_db_schema::{
20   source::{
21     comment::Comment,
22     community::Community,
23     moderator::{
24       ModRemoveComment,
25       ModRemoveCommentForm,
26       ModRemoveCommunity,
27       ModRemoveCommunityForm,
28       ModRemovePost,
29       ModRemovePostForm,
30     },
31     person::Person,
32     post::Post,
33   },
34   traits::Crud,
35 };
36 use lemmy_utils::error::LemmyError;
37 use lemmy_websocket::{
38   send::{send_comment_ws_message_simple, send_community_ws_message, send_post_ws_message},
39   LemmyContext,
40   UserOperationCrud,
41 };
42 use url::Url;
43
44 #[async_trait::async_trait(?Send)]
45 impl ActivityHandler for Delete {
46   type DataType = LemmyContext;
47   type Error = LemmyError;
48
49   fn id(&self) -> &Url {
50     &self.id
51   }
52
53   fn actor(&self) -> &Url {
54     self.actor.inner()
55   }
56
57   #[tracing::instrument(skip_all)]
58   async fn verify(
59     &self,
60     context: &Data<LemmyContext>,
61     request_counter: &mut i32,
62   ) -> Result<(), LemmyError> {
63     verify_delete_activity(self, self.summary.is_some(), context, request_counter).await?;
64     Ok(())
65   }
66
67   #[tracing::instrument(skip_all)]
68   async fn receive(
69     self,
70     context: &Data<LemmyContext>,
71     request_counter: &mut i32,
72   ) -> Result<(), LemmyError> {
73     if let Some(reason) = self.summary {
74       // We set reason to empty string if it doesn't exist, to distinguish between delete and
75       // remove. Here we change it back to option, so we don't write it to db.
76       let reason = if reason.is_empty() {
77         None
78       } else {
79         Some(reason)
80       };
81       receive_remove_action(
82         &self
83           .actor
84           .dereference::<LemmyError>(context, local_instance(context), request_counter)
85           .await?,
86         self.object.id(),
87         reason,
88         context,
89       )
90       .await
91     } else {
92       receive_delete_action(
93         self.object.id(),
94         &self.actor,
95         true,
96         context,
97         request_counter,
98       )
99       .await
100     }
101   }
102 }
103
104 impl Delete {
105   pub(in crate::activities::deletion) fn new(
106     actor: &Person,
107     object: DeletableObjects,
108     to: Url,
109     community: Option<&Community>,
110     summary: Option<String>,
111     context: &LemmyContext,
112   ) -> Result<Delete, LemmyError> {
113     let id = generate_activity_id(
114       DeleteType::Delete,
115       &context.settings().get_protocol_and_hostname(),
116     )?;
117     let cc: Option<Url> = community.map(|c| c.actor_id.clone().into());
118     Ok(Delete {
119       actor: ObjectId::new(actor.actor_id.clone()),
120       to: vec![to],
121       object: IdOrNestedObject::NestedObject(Tombstone {
122         id: object.id(),
123         kind: Default::default(),
124       }),
125       cc: cc.into_iter().collect(),
126       kind: DeleteType::Delete,
127       summary,
128       id,
129       unparsed: Default::default(),
130     })
131   }
132 }
133
134 #[tracing::instrument(skip_all)]
135 pub(in crate::activities) async fn receive_remove_action(
136   actor: &ApubPerson,
137   object: &Url,
138   reason: Option<String>,
139   context: &LemmyContext,
140 ) -> Result<(), LemmyError> {
141   use UserOperationCrud::*;
142   match DeletableObjects::read_from_db(object, context).await? {
143     DeletableObjects::Community(community) => {
144       if community.local {
145         return Err(LemmyError::from_message(
146           "Only local admin can remove community",
147         ));
148       }
149       let form = ModRemoveCommunityForm {
150         mod_person_id: actor.id,
151         community_id: community.id,
152         removed: Some(true),
153         reason,
154         expires: None,
155       };
156       blocking(context.pool(), move |conn| {
157         ModRemoveCommunity::create(conn, &form)
158       })
159       .await??;
160       let deleted_community = blocking(context.pool(), move |conn| {
161         Community::update_removed(conn, community.id, true)
162       })
163       .await??;
164
165       send_community_ws_message(deleted_community.id, RemoveCommunity, None, None, context).await?;
166     }
167     DeletableObjects::Post(post) => {
168       let form = ModRemovePostForm {
169         mod_person_id: actor.id,
170         post_id: post.id,
171         removed: Some(true),
172         reason,
173       };
174       blocking(context.pool(), move |conn| {
175         ModRemovePost::create(conn, &form)
176       })
177       .await??;
178       let removed_post = blocking(context.pool(), move |conn| {
179         Post::update_removed(conn, post.id, true)
180       })
181       .await??;
182
183       send_post_ws_message(removed_post.id, RemovePost, None, None, context).await?;
184     }
185     DeletableObjects::Comment(comment) => {
186       let form = ModRemoveCommentForm {
187         mod_person_id: actor.id,
188         comment_id: comment.id,
189         removed: Some(true),
190         reason,
191       };
192       blocking(context.pool(), move |conn| {
193         ModRemoveComment::create(conn, &form)
194       })
195       .await??;
196       let removed_comment = blocking(context.pool(), move |conn| {
197         Comment::update_removed(conn, comment.id, true)
198       })
199       .await??;
200
201       send_comment_ws_message_simple(removed_comment.id, RemoveComment, context).await?;
202     }
203     DeletableObjects::PrivateMessage(_) => unimplemented!(),
204   }
205   Ok(())
206 }
207
208 #[async_trait::async_trait(?Send)]
209 impl GetCommunity for Delete {
210   #[tracing::instrument(skip_all)]
211   async fn get_community(
212     &self,
213     context: &LemmyContext,
214     _request_counter: &mut i32,
215   ) -> Result<ApubCommunity, LemmyError> {
216     let community_id = match DeletableObjects::read_from_db(self.object.id(), context).await? {
217       DeletableObjects::Community(c) => c.id,
218       DeletableObjects::Comment(c) => {
219         let post = blocking(context.pool(), move |conn| Post::read(conn, c.post_id)).await??;
220         post.community_id
221       }
222       DeletableObjects::Post(p) => p.community_id,
223       DeletableObjects::PrivateMessage(_) => {
224         return Err(anyhow!("Private message is not part of community").into())
225       }
226     };
227     let community = blocking(context.pool(), move |conn| {
228       Community::read(conn, community_id)
229     })
230     .await??;
231     Ok(community.into())
232   }
233 }