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