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