]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/deletion/mod.rs
Merge crates db_schema and db_queries
[lemmy.git] / crates / apub / src / activities / deletion / mod.rs
1 use crate::{
2   activities::{
3     deletion::{delete::Delete, undo_delete::UndoDelete},
4     verify_mod_action,
5     verify_person_in_community,
6   },
7   fetcher::object_id::ObjectId,
8 };
9 use diesel::PgConnection;
10 use lemmy_api_common::blocking;
11 use lemmy_apub_lib::{
12   traits::{ActivityFields, ActorType, ApubObject},
13   verify::verify_domains_match,
14 };
15 use lemmy_db_schema::source::{comment::Comment, community::Community, person::Person, post::Post};
16 use lemmy_utils::LemmyError;
17 use lemmy_websocket::{
18   send::{send_comment_ws_message_simple, send_community_ws_message, send_post_ws_message},
19   LemmyContext,
20   UserOperationCrud,
21 };
22 use url::Url;
23
24 pub mod delete;
25 pub mod undo_delete;
26
27 pub async fn send_apub_delete(
28   actor: &Person,
29   community: &Community,
30   object_id: Url,
31   deleted: bool,
32   context: &LemmyContext,
33 ) -> Result<(), LemmyError> {
34   if deleted {
35     Delete::send(actor, community, object_id, None, context).await
36   } else {
37     UndoDelete::send(actor, community, object_id, None, context).await
38   }
39 }
40
41 // TODO: remove reason is actually optional in lemmy. we set an empty string in that case, but its
42 //       ugly
43 pub async fn send_apub_remove(
44   actor: &Person,
45   community: &Community,
46   object_id: Url,
47   reason: String,
48   removed: bool,
49   context: &LemmyContext,
50 ) -> Result<(), LemmyError> {
51   if removed {
52     Delete::send(actor, community, object_id, Some(reason), context).await
53   } else {
54     UndoDelete::send(actor, community, object_id, Some(reason), context).await
55   }
56 }
57
58 pub enum DeletableObjects {
59   Community(Box<Community>),
60   Comment(Box<Comment>),
61   Post(Box<Post>),
62 }
63
64 impl DeletableObjects {
65   pub(crate) async fn read_from_db(
66     ap_id: &Url,
67     context: &LemmyContext,
68   ) -> Result<DeletableObjects, LemmyError> {
69     if let Some(c) =
70       DeletableObjects::read_type_from_db::<Community>(ap_id.clone(), context).await?
71     {
72       return Ok(DeletableObjects::Community(Box::new(c)));
73     }
74     if let Some(p) = DeletableObjects::read_type_from_db::<Post>(ap_id.clone(), context).await? {
75       return Ok(DeletableObjects::Post(Box::new(p)));
76     }
77     if let Some(c) = DeletableObjects::read_type_from_db::<Comment>(ap_id.clone(), context).await? {
78       return Ok(DeletableObjects::Comment(Box::new(c)));
79     }
80     Err(diesel::NotFound.into())
81   }
82
83   // TODO: a method like this should be provided by fetcher module
84   async fn read_type_from_db<Type>(
85     ap_id: Url,
86     context: &LemmyContext,
87   ) -> Result<Option<Type>, LemmyError>
88   where
89     Type: ApubObject<DataType = PgConnection> + Send + 'static,
90   {
91     blocking(context.pool(), move |conn| {
92       Type::read_from_apub_id(conn, ap_id)
93     })
94     .await?
95   }
96 }
97
98 pub(in crate::activities) async fn verify_delete_activity(
99   object: &Url,
100   activity: &dyn ActivityFields,
101   community_id: &ObjectId<Community>,
102   is_mod_action: bool,
103   context: &LemmyContext,
104   request_counter: &mut i32,
105 ) -> Result<(), LemmyError> {
106   let object = DeletableObjects::read_from_db(object, context).await?;
107   let actor = ObjectId::new(activity.actor().clone());
108   match object {
109     DeletableObjects::Community(c) => {
110       if c.local {
111         // can only do this check for local community, in remote case it would try to fetch the
112         // deleted community (which fails)
113         verify_person_in_community(&actor, community_id, context, request_counter).await?;
114       }
115       // community deletion is always a mod (or admin) action
116       verify_mod_action(
117         &actor,
118         ObjectId::new(c.actor_id()),
119         context,
120         request_counter,
121       )
122       .await?;
123     }
124     DeletableObjects::Post(p) => {
125       verify_delete_activity_post_or_comment(
126         activity,
127         &p.ap_id.into(),
128         community_id,
129         is_mod_action,
130         context,
131         request_counter,
132       )
133       .await?;
134     }
135     DeletableObjects::Comment(c) => {
136       verify_delete_activity_post_or_comment(
137         activity,
138         &c.ap_id.into(),
139         community_id,
140         is_mod_action,
141         context,
142         request_counter,
143       )
144       .await?;
145     }
146   }
147   Ok(())
148 }
149
150 async fn verify_delete_activity_post_or_comment(
151   activity: &dyn ActivityFields,
152   object_id: &Url,
153   community_id: &ObjectId<Community>,
154   is_mod_action: bool,
155   context: &LemmyContext,
156   request_counter: &mut i32,
157 ) -> Result<(), LemmyError> {
158   let actor = ObjectId::new(activity.actor().clone());
159   verify_person_in_community(&actor, community_id, context, request_counter).await?;
160   if is_mod_action {
161     verify_mod_action(&actor, community_id.clone(), context, request_counter).await?;
162   } else {
163     // domain of post ap_id and post.creator ap_id are identical, so we just check the former
164     verify_domains_match(activity.actor(), object_id)?;
165   }
166   Ok(())
167 }
168
169 struct WebsocketMessages {
170   community: UserOperationCrud,
171   post: UserOperationCrud,
172   comment: UserOperationCrud,
173 }
174
175 /// Write deletion or restoring of an object to the database, and send websocket message.
176 /// TODO: we should do something similar for receive_remove_action(), but its much more complicated
177 ///       because of the mod log
178 async fn receive_delete_action(
179   object: &Url,
180   actor: &ObjectId<Person>,
181   ws_messages: WebsocketMessages,
182   deleted: bool,
183   context: &LemmyContext,
184   request_counter: &mut i32,
185 ) -> Result<(), LemmyError> {
186   match DeletableObjects::read_from_db(object, context).await? {
187     DeletableObjects::Community(community) => {
188       if community.local {
189         let mod_ = actor.dereference(context, request_counter).await?;
190         let object = community.actor_id();
191         send_apub_delete(&mod_, &community.clone(), object, true, context).await?;
192       }
193
194       let community = blocking(context.pool(), move |conn| {
195         Community::update_deleted(conn, community.id, deleted)
196       })
197       .await??;
198       send_community_ws_message(community.id, ws_messages.community, None, None, context).await?;
199     }
200     DeletableObjects::Post(post) => {
201       let deleted_post = blocking(context.pool(), move |conn| {
202         Post::update_deleted(conn, post.id, deleted)
203       })
204       .await??;
205       send_post_ws_message(deleted_post.id, ws_messages.post, None, None, context).await?;
206     }
207     DeletableObjects::Comment(comment) => {
208       let deleted_comment = blocking(context.pool(), move |conn| {
209         Comment::update_deleted(conn, comment.id, deleted)
210       })
211       .await??;
212       send_comment_ws_message_simple(deleted_comment.id, ws_messages.comment, context).await?;
213     }
214   }
215   Ok(())
216 }