]> Untitled Git - lemmy.git/blob - lemmy_apub/src/inbox/receive_for_community.rs
Merge pull request #1328 from LemmyNet/move_views_to_diesel
[lemmy.git] / lemmy_apub / src / inbox / receive_for_community.rs
1 use crate::{
2   activities::receive::{
3     comment::{
4       receive_create_comment,
5       receive_delete_comment,
6       receive_dislike_comment,
7       receive_like_comment,
8       receive_remove_comment,
9       receive_update_comment,
10     },
11     comment_undo::{
12       receive_undo_delete_comment,
13       receive_undo_dislike_comment,
14       receive_undo_like_comment,
15       receive_undo_remove_comment,
16     },
17     post::{
18       receive_create_post,
19       receive_delete_post,
20       receive_dislike_post,
21       receive_like_post,
22       receive_remove_post,
23       receive_update_post,
24     },
25     post_undo::{
26       receive_undo_delete_post,
27       receive_undo_dislike_post,
28       receive_undo_like_post,
29       receive_undo_remove_post,
30     },
31     receive_unhandled_activity,
32     verify_activity_domains_valid,
33   },
34   fetcher::objects::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
35   find_post_or_comment_by_id,
36   inbox::is_addressed_to_public,
37   PostOrComment,
38 };
39 use activitystreams::{
40   activity::{Create, Delete, Dislike, Like, Remove, Undo, Update},
41   base::AnyBase,
42   prelude::*,
43 };
44 use anyhow::Context;
45 use diesel::result::Error::NotFound;
46 use lemmy_db_queries::Crud;
47 use lemmy_db_schema::source::site::Site;
48 use lemmy_structs::blocking;
49 use lemmy_utils::{location_info, LemmyError};
50 use lemmy_websocket::LemmyContext;
51 use url::Url;
52
53 /// This file is for post/comment activities received by the community, and for post/comment
54 ///       activities announced by the community and received by the user.
55
56 /// A post or comment being created
57 pub(in crate::inbox) async fn receive_create_for_community(
58   context: &LemmyContext,
59   activity: AnyBase,
60   expected_domain: &Url,
61   request_counter: &mut i32,
62 ) -> Result<(), LemmyError> {
63   let create = Create::from_any_base(activity)?.context(location_info!())?;
64   verify_activity_domains_valid(&create, &expected_domain, true)?;
65   is_addressed_to_public(&create)?;
66
67   match create.object().as_single_kind_str() {
68     Some("Page") => receive_create_post(create, context, request_counter).await,
69     Some("Note") => receive_create_comment(create, context, request_counter).await,
70     _ => receive_unhandled_activity(create),
71   }
72 }
73
74 /// A post or comment being edited
75 pub(in crate::inbox) async fn receive_update_for_community(
76   context: &LemmyContext,
77   activity: AnyBase,
78   expected_domain: &Url,
79   request_counter: &mut i32,
80 ) -> Result<(), LemmyError> {
81   let update = Update::from_any_base(activity)?.context(location_info!())?;
82   verify_activity_domains_valid(&update, &expected_domain, true)?;
83   is_addressed_to_public(&update)?;
84
85   match update.object().as_single_kind_str() {
86     Some("Page") => receive_update_post(update, context, request_counter).await,
87     Some("Note") => receive_update_comment(update, context, request_counter).await,
88     _ => receive_unhandled_activity(update),
89   }
90 }
91
92 /// A post or comment being upvoted
93 pub(in crate::inbox) async fn receive_like_for_community(
94   context: &LemmyContext,
95   activity: AnyBase,
96   expected_domain: &Url,
97   request_counter: &mut i32,
98 ) -> Result<(), LemmyError> {
99   let like = Like::from_any_base(activity)?.context(location_info!())?;
100   verify_activity_domains_valid(&like, &expected_domain, false)?;
101   is_addressed_to_public(&like)?;
102
103   let object_id = get_like_object_id(&like)?;
104   match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
105     PostOrComment::Post(post) => receive_like_post(like, post, context, request_counter).await,
106     PostOrComment::Comment(comment) => {
107       receive_like_comment(like, comment, context, request_counter).await
108     }
109   }
110 }
111
112 /// A post or comment being downvoted
113 pub(in crate::inbox) async fn receive_dislike_for_community(
114   context: &LemmyContext,
115   activity: AnyBase,
116   expected_domain: &Url,
117   request_counter: &mut i32,
118 ) -> Result<(), LemmyError> {
119   let enable_downvotes = blocking(context.pool(), move |conn| {
120     Site::read(conn, 1).map(|s| s.enable_downvotes)
121   })
122   .await??;
123   if !enable_downvotes {
124     return Ok(());
125   }
126
127   let dislike = Dislike::from_any_base(activity)?.context(location_info!())?;
128   verify_activity_domains_valid(&dislike, &expected_domain, false)?;
129   is_addressed_to_public(&dislike)?;
130
131   let object_id = get_like_object_id(&dislike)?;
132   match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
133     PostOrComment::Post(post) => {
134       receive_dislike_post(dislike, post, context, request_counter).await
135     }
136     PostOrComment::Comment(comment) => {
137       receive_dislike_comment(dislike, comment, context, request_counter).await
138     }
139   }
140 }
141
142 /// A post or comment being deleted by its creator
143 pub(in crate::inbox) async fn receive_delete_for_community(
144   context: &LemmyContext,
145   activity: AnyBase,
146   expected_domain: &Url,
147 ) -> Result<(), LemmyError> {
148   let delete = Delete::from_any_base(activity)?.context(location_info!())?;
149   verify_activity_domains_valid(&delete, &expected_domain, true)?;
150   is_addressed_to_public(&delete)?;
151
152   let object = delete
153     .object()
154     .to_owned()
155     .single_xsd_any_uri()
156     .context(location_info!())?;
157
158   match find_post_or_comment_by_id(context, object).await {
159     Ok(PostOrComment::Post(p)) => receive_delete_post(context, p).await,
160     Ok(PostOrComment::Comment(c)) => receive_delete_comment(context, c).await,
161     // if we dont have the object, no need to do anything
162     Err(_) => Ok(()),
163   }
164 }
165
166 /// A post or comment being removed by a mod/admin
167 pub(in crate::inbox) async fn receive_remove_for_community(
168   context: &LemmyContext,
169   activity: AnyBase,
170   expected_domain: &Url,
171 ) -> Result<(), LemmyError> {
172   let remove = Remove::from_any_base(activity)?.context(location_info!())?;
173   verify_activity_domains_valid(&remove, &expected_domain, false)?;
174   is_addressed_to_public(&remove)?;
175
176   let cc = remove
177     .cc()
178     .map(|c| c.as_many())
179     .flatten()
180     .context(location_info!())?;
181   let community_id = cc
182     .first()
183     .map(|c| c.as_xsd_any_uri())
184     .flatten()
185     .context(location_info!())?;
186
187   let object = remove
188     .object()
189     .to_owned()
190     .single_xsd_any_uri()
191     .context(location_info!())?;
192
193   // Ensure that remove activity comes from the same domain as the community
194   remove.id(community_id.domain().context(location_info!())?)?;
195
196   match find_post_or_comment_by_id(context, object).await {
197     Ok(PostOrComment::Post(p)) => receive_remove_post(context, remove, p).await,
198     Ok(PostOrComment::Comment(c)) => receive_remove_comment(context, remove, c).await,
199     // if we dont have the object, no need to do anything
200     Err(_) => Ok(()),
201   }
202 }
203
204 /// A post/comment action being reverted (either a delete, remove, upvote or downvote)
205 pub(in crate::inbox) async fn receive_undo_for_community(
206   context: &LemmyContext,
207   activity: AnyBase,
208   expected_domain: &Url,
209   request_counter: &mut i32,
210 ) -> Result<(), LemmyError> {
211   let undo = Undo::from_any_base(activity)?.context(location_info!())?;
212   verify_activity_domains_valid(&undo, &expected_domain.to_owned(), true)?;
213   is_addressed_to_public(&undo)?;
214
215   match undo.object().as_single_kind_str() {
216     Some("Delete") => receive_undo_delete_for_community(context, undo, expected_domain).await,
217     Some("Remove") => receive_undo_remove_for_community(context, undo, expected_domain).await,
218     Some("Like") => {
219       receive_undo_like_for_community(context, undo, expected_domain, request_counter).await
220     }
221     Some("Dislike") => {
222       receive_undo_dislike_for_community(context, undo, expected_domain, request_counter).await
223     }
224     _ => receive_unhandled_activity(undo),
225   }
226 }
227
228 /// A post or comment deletion being reverted
229 pub(in crate::inbox) async fn receive_undo_delete_for_community(
230   context: &LemmyContext,
231   undo: Undo,
232   expected_domain: &Url,
233 ) -> Result<(), LemmyError> {
234   let delete = Delete::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
235     .context(location_info!())?;
236   verify_activity_domains_valid(&delete, &expected_domain, true)?;
237   is_addressed_to_public(&delete)?;
238
239   let object = delete
240     .object()
241     .to_owned()
242     .single_xsd_any_uri()
243     .context(location_info!())?;
244   match find_post_or_comment_by_id(context, object).await {
245     Ok(PostOrComment::Post(p)) => receive_undo_delete_post(context, p).await,
246     Ok(PostOrComment::Comment(c)) => receive_undo_delete_comment(context, c).await,
247     // if we dont have the object, no need to do anything
248     Err(_) => Ok(()),
249   }
250 }
251
252 /// A post or comment removal being reverted
253 pub(in crate::inbox) async fn receive_undo_remove_for_community(
254   context: &LemmyContext,
255   undo: Undo,
256   expected_domain: &Url,
257 ) -> Result<(), LemmyError> {
258   let remove = Remove::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
259     .context(location_info!())?;
260   verify_activity_domains_valid(&remove, &expected_domain, false)?;
261   is_addressed_to_public(&remove)?;
262
263   let object = remove
264     .object()
265     .to_owned()
266     .single_xsd_any_uri()
267     .context(location_info!())?;
268   match find_post_or_comment_by_id(context, object).await {
269     Ok(PostOrComment::Post(p)) => receive_undo_remove_post(context, p).await,
270     Ok(PostOrComment::Comment(c)) => receive_undo_remove_comment(context, c).await,
271     // if we dont have the object, no need to do anything
272     Err(_) => Ok(()),
273   }
274 }
275
276 /// A post or comment upvote being reverted
277 pub(in crate::inbox) async fn receive_undo_like_for_community(
278   context: &LemmyContext,
279   undo: Undo,
280   expected_domain: &Url,
281   request_counter: &mut i32,
282 ) -> Result<(), LemmyError> {
283   let like = Like::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
284     .context(location_info!())?;
285   verify_activity_domains_valid(&like, &expected_domain, false)?;
286   is_addressed_to_public(&like)?;
287
288   let object_id = get_like_object_id(&like)?;
289   match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
290     PostOrComment::Post(post) => {
291       receive_undo_like_post(&like, post, context, request_counter).await
292     }
293     PostOrComment::Comment(comment) => {
294       receive_undo_like_comment(&like, comment, context, request_counter).await
295     }
296   }
297 }
298
299 /// A post or comment downvote being reverted
300 pub(in crate::inbox) async fn receive_undo_dislike_for_community(
301   context: &LemmyContext,
302   undo: Undo,
303   expected_domain: &Url,
304   request_counter: &mut i32,
305 ) -> Result<(), LemmyError> {
306   let dislike = Dislike::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
307     .context(location_info!())?;
308   verify_activity_domains_valid(&dislike, &expected_domain, false)?;
309   is_addressed_to_public(&dislike)?;
310
311   let object_id = get_like_object_id(&dislike)?;
312   match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
313     PostOrComment::Post(post) => {
314       receive_undo_dislike_post(&dislike, post, context, request_counter).await
315     }
316     PostOrComment::Comment(comment) => {
317       receive_undo_dislike_comment(&dislike, comment, context, request_counter).await
318     }
319   }
320 }
321
322 async fn fetch_post_or_comment_by_id(
323   apub_id: &Url,
324   context: &LemmyContext,
325   request_counter: &mut i32,
326 ) -> Result<PostOrComment, LemmyError> {
327   if let Ok(post) = get_or_fetch_and_insert_post(apub_id, context, request_counter).await {
328     return Ok(PostOrComment::Post(post));
329   }
330
331   if let Ok(comment) = get_or_fetch_and_insert_comment(apub_id, context, request_counter).await {
332     return Ok(PostOrComment::Comment(comment));
333   }
334
335   Err(NotFound.into())
336 }
337
338 fn get_like_object_id<Activity>(like_or_dislike: &Activity) -> Result<Url, LemmyError>
339 where
340   Activity: ActorAndObjectRefExt,
341 {
342   // TODO: For backwards compatibility with older Lemmy versions where like.object contains a full
343   //       post/comment. This can be removed after some time, using
344   //       `activity.oject().as_single_xsd_any_uri()` instead.
345   let object = like_or_dislike.object();
346   if let Some(xsd_uri) = object.as_single_xsd_any_uri() {
347     Ok(xsd_uri.to_owned())
348   } else {
349     Ok(
350       object
351         .to_owned()
352         .one()
353         .context(location_info!())?
354         .id()
355         .context(location_info!())?
356         .to_owned(),
357     )
358   }
359 }