]> Untitled Git - lemmy.git/blob - server/src/apub/inbox/activities/undo.rs
bf2073860e49239eeb582fcfe1de1f36fb74af28
[lemmy.git] / server / src / apub / inbox / activities / undo.rs
1 use crate::{
2   apub::{
3     fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
4     inbox::shared_inbox::{
5       announce_if_community_is_local,
6       get_user_from_activity,
7       receive_unhandled_activity,
8     },
9     ActorType,
10     FromApub,
11     GroupExt,
12     PageExt,
13   },
14   websocket::{
15     messages::{SendComment, SendCommunityRoomMessage, SendPost},
16     UserOperation,
17   },
18   LemmyContext,
19 };
20 use activitystreams::{
21   activity::*,
22   base::{AnyBase, AsBase},
23   object::Note,
24   prelude::*,
25 };
26 use actix_web::HttpResponse;
27 use anyhow::{anyhow, Context};
28 use lemmy_api_structs::{
29   blocking,
30   comment::CommentResponse,
31   community::CommunityResponse,
32   post::PostResponse,
33 };
34 use lemmy_db::{
35   comment::{Comment, CommentForm, CommentLike},
36   comment_view::CommentView,
37   community::{Community, CommunityForm},
38   community_view::CommunityView,
39   naive_now,
40   post::{Post, PostForm, PostLike},
41   post_view::PostView,
42   Crud,
43   Likeable,
44 };
45 use lemmy_utils::{location_info, LemmyError};
46
47 pub async fn receive_undo(
48   activity: AnyBase,
49   context: &LemmyContext,
50 ) -> Result<HttpResponse, LemmyError> {
51   let undo = Undo::from_any_base(activity)?.context(location_info!())?;
52   match undo.object().as_single_kind_str() {
53     Some("Delete") => receive_undo_delete(undo, context).await,
54     Some("Remove") => receive_undo_remove(undo, context).await,
55     Some("Like") => receive_undo_like(undo, context).await,
56     Some("Dislike") => receive_undo_dislike(undo, context).await,
57     _ => receive_unhandled_activity(undo),
58   }
59 }
60
61 fn check_is_undo_valid<T, A>(outer_activity: &Undo, inner_activity: &T) -> Result<(), LemmyError>
62 where
63   T: AsBase<A> + ActorAndObjectRef,
64 {
65   let outer_actor = outer_activity.actor()?;
66   let outer_actor_uri = outer_actor
67     .as_single_xsd_any_uri()
68     .context(location_info!())?;
69
70   let inner_actor = inner_activity.actor()?;
71   let inner_actor_uri = inner_actor
72     .as_single_xsd_any_uri()
73     .context(location_info!())?;
74
75   if outer_actor_uri.domain() != inner_actor_uri.domain() {
76     Err(anyhow!("Cant undo activities from a different instance").into())
77   } else {
78     Ok(())
79   }
80 }
81
82 async fn receive_undo_delete(
83   undo: Undo,
84   context: &LemmyContext,
85 ) -> Result<HttpResponse, LemmyError> {
86   let delete = Delete::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
87     .context(location_info!())?;
88   check_is_undo_valid(&undo, &delete)?;
89   let type_ = delete
90     .object()
91     .as_single_kind_str()
92     .context(location_info!())?;
93   match type_ {
94     "Note" => receive_undo_delete_comment(undo, &delete, context).await,
95     "Page" => receive_undo_delete_post(undo, &delete, context).await,
96     "Group" => receive_undo_delete_community(undo, &delete, context).await,
97     d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
98   }
99 }
100
101 async fn receive_undo_remove(
102   undo: Undo,
103   context: &LemmyContext,
104 ) -> Result<HttpResponse, LemmyError> {
105   let remove = Remove::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
106     .context(location_info!())?;
107   check_is_undo_valid(&undo, &remove)?;
108
109   let type_ = remove
110     .object()
111     .as_single_kind_str()
112     .context(location_info!())?;
113   match type_ {
114     "Note" => receive_undo_remove_comment(undo, &remove, context).await,
115     "Page" => receive_undo_remove_post(undo, &remove, context).await,
116     "Group" => receive_undo_remove_community(undo, &remove, context).await,
117     d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
118   }
119 }
120
121 async fn receive_undo_like(undo: Undo, context: &LemmyContext) -> Result<HttpResponse, LemmyError> {
122   let like = Like::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
123     .context(location_info!())?;
124   check_is_undo_valid(&undo, &like)?;
125
126   let type_ = like
127     .object()
128     .as_single_kind_str()
129     .context(location_info!())?;
130   match type_ {
131     "Note" => receive_undo_like_comment(undo, &like, context).await,
132     "Page" => receive_undo_like_post(undo, &like, context).await,
133     d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
134   }
135 }
136
137 async fn receive_undo_dislike(
138   undo: Undo,
139   context: &LemmyContext,
140 ) -> Result<HttpResponse, LemmyError> {
141   let dislike = Dislike::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
142     .context(location_info!())?;
143   check_is_undo_valid(&undo, &dislike)?;
144
145   let type_ = dislike
146     .object()
147     .as_single_kind_str()
148     .context(location_info!())?;
149   match type_ {
150     "Note" => receive_undo_dislike_comment(undo, &dislike, context).await,
151     "Page" => receive_undo_dislike_post(undo, &dislike, context).await,
152     d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
153   }
154 }
155
156 async fn receive_undo_delete_comment(
157   undo: Undo,
158   delete: &Delete,
159   context: &LemmyContext,
160 ) -> Result<HttpResponse, LemmyError> {
161   let user = get_user_from_activity(delete, context).await?;
162   let note = Note::from_any_base(delete.object().to_owned().one().context(location_info!())?)?
163     .context(location_info!())?;
164
165   let comment_ap_id = CommentForm::from_apub(&note, context, Some(user.actor_id()?))
166     .await?
167     .get_ap_id()?;
168
169   let comment = get_or_fetch_and_insert_comment(&comment_ap_id, context).await?;
170
171   let comment_form = CommentForm {
172     content: comment.content.to_owned(),
173     parent_id: comment.parent_id,
174     post_id: comment.post_id,
175     creator_id: comment.creator_id,
176     removed: None,
177     deleted: Some(false),
178     read: None,
179     published: None,
180     updated: Some(naive_now()),
181     ap_id: Some(comment.ap_id),
182     local: comment.local,
183   };
184   let comment_id = comment.id;
185   blocking(context.pool(), move |conn| {
186     Comment::update(conn, comment_id, &comment_form)
187   })
188   .await??;
189
190   // Refetch the view
191   let comment_id = comment.id;
192   let comment_view = blocking(context.pool(), move |conn| {
193     CommentView::read(conn, comment_id, None)
194   })
195   .await??;
196
197   // TODO get those recipient actor ids from somewhere
198   let recipient_ids = vec![];
199   let res = CommentResponse {
200     comment: comment_view,
201     recipient_ids,
202     form_id: None,
203   };
204
205   context.chat_server().do_send(SendComment {
206     op: UserOperation::EditComment,
207     comment: res,
208     websocket_id: None,
209   });
210
211   announce_if_community_is_local(undo, &user, context).await?;
212   Ok(HttpResponse::Ok().finish())
213 }
214
215 async fn receive_undo_remove_comment(
216   undo: Undo,
217   remove: &Remove,
218   context: &LemmyContext,
219 ) -> Result<HttpResponse, LemmyError> {
220   let mod_ = get_user_from_activity(remove, context).await?;
221   let note = Note::from_any_base(remove.object().to_owned().one().context(location_info!())?)?
222     .context(location_info!())?;
223
224   let comment_ap_id = CommentForm::from_apub(&note, context, None)
225     .await?
226     .get_ap_id()?;
227
228   let comment = get_or_fetch_and_insert_comment(&comment_ap_id, context).await?;
229
230   let comment_form = CommentForm {
231     content: comment.content.to_owned(),
232     parent_id: comment.parent_id,
233     post_id: comment.post_id,
234     creator_id: comment.creator_id,
235     removed: Some(false),
236     deleted: None,
237     read: None,
238     published: None,
239     updated: Some(naive_now()),
240     ap_id: Some(comment.ap_id),
241     local: comment.local,
242   };
243   let comment_id = comment.id;
244   blocking(context.pool(), move |conn| {
245     Comment::update(conn, comment_id, &comment_form)
246   })
247   .await??;
248
249   // Refetch the view
250   let comment_id = comment.id;
251   let comment_view = blocking(context.pool(), move |conn| {
252     CommentView::read(conn, comment_id, None)
253   })
254   .await??;
255
256   // TODO get those recipient actor ids from somewhere
257   let recipient_ids = vec![];
258   let res = CommentResponse {
259     comment: comment_view,
260     recipient_ids,
261     form_id: None,
262   };
263
264   context.chat_server().do_send(SendComment {
265     op: UserOperation::EditComment,
266     comment: res,
267     websocket_id: None,
268   });
269
270   announce_if_community_is_local(undo, &mod_, context).await?;
271   Ok(HttpResponse::Ok().finish())
272 }
273
274 async fn receive_undo_delete_post(
275   undo: Undo,
276   delete: &Delete,
277   context: &LemmyContext,
278 ) -> Result<HttpResponse, LemmyError> {
279   let user = get_user_from_activity(delete, context).await?;
280   let page = PageExt::from_any_base(delete.object().to_owned().one().context(location_info!())?)?
281     .context(location_info!())?;
282
283   let post_ap_id = PostForm::from_apub(&page, context, Some(user.actor_id()?))
284     .await?
285     .get_ap_id()?;
286
287   let post = get_or_fetch_and_insert_post(&post_ap_id, context).await?;
288
289   let post_form = PostForm {
290     name: post.name.to_owned(),
291     url: post.url.to_owned(),
292     body: post.body.to_owned(),
293     creator_id: post.creator_id.to_owned(),
294     community_id: post.community_id,
295     removed: None,
296     deleted: Some(false),
297     nsfw: post.nsfw,
298     locked: None,
299     stickied: None,
300     updated: Some(naive_now()),
301     embed_title: post.embed_title,
302     embed_description: post.embed_description,
303     embed_html: post.embed_html,
304     thumbnail_url: post.thumbnail_url,
305     ap_id: Some(post.ap_id),
306     local: post.local,
307     published: None,
308   };
309   let post_id = post.id;
310   blocking(context.pool(), move |conn| {
311     Post::update(conn, post_id, &post_form)
312   })
313   .await??;
314
315   // Refetch the view
316   let post_id = post.id;
317   let post_view = blocking(context.pool(), move |conn| {
318     PostView::read(conn, post_id, None)
319   })
320   .await??;
321
322   let res = PostResponse { post: post_view };
323
324   context.chat_server().do_send(SendPost {
325     op: UserOperation::EditPost,
326     post: res,
327     websocket_id: None,
328   });
329
330   announce_if_community_is_local(undo, &user, context).await?;
331   Ok(HttpResponse::Ok().finish())
332 }
333
334 async fn receive_undo_remove_post(
335   undo: Undo,
336   remove: &Remove,
337   context: &LemmyContext,
338 ) -> Result<HttpResponse, LemmyError> {
339   let mod_ = get_user_from_activity(remove, context).await?;
340   let page = PageExt::from_any_base(remove.object().to_owned().one().context(location_info!())?)?
341     .context(location_info!())?;
342
343   let post_ap_id = PostForm::from_apub(&page, context, None)
344     .await?
345     .get_ap_id()?;
346
347   let post = get_or_fetch_and_insert_post(&post_ap_id, context).await?;
348
349   let post_form = PostForm {
350     name: post.name.to_owned(),
351     url: post.url.to_owned(),
352     body: post.body.to_owned(),
353     creator_id: post.creator_id.to_owned(),
354     community_id: post.community_id,
355     removed: Some(false),
356     deleted: None,
357     nsfw: post.nsfw,
358     locked: None,
359     stickied: None,
360     updated: Some(naive_now()),
361     embed_title: post.embed_title,
362     embed_description: post.embed_description,
363     embed_html: post.embed_html,
364     thumbnail_url: post.thumbnail_url,
365     ap_id: Some(post.ap_id),
366     local: post.local,
367     published: None,
368   };
369   let post_id = post.id;
370   blocking(context.pool(), move |conn| {
371     Post::update(conn, post_id, &post_form)
372   })
373   .await??;
374
375   // Refetch the view
376   let post_id = post.id;
377   let post_view = blocking(context.pool(), move |conn| {
378     PostView::read(conn, post_id, None)
379   })
380   .await??;
381
382   let res = PostResponse { post: post_view };
383
384   context.chat_server().do_send(SendPost {
385     op: UserOperation::EditPost,
386     post: res,
387     websocket_id: None,
388   });
389
390   announce_if_community_is_local(undo, &mod_, context).await?;
391   Ok(HttpResponse::Ok().finish())
392 }
393
394 async fn receive_undo_delete_community(
395   undo: Undo,
396   delete: &Delete,
397   context: &LemmyContext,
398 ) -> Result<HttpResponse, LemmyError> {
399   let user = get_user_from_activity(delete, context).await?;
400   let group = GroupExt::from_any_base(delete.object().to_owned().one().context(location_info!())?)?
401     .context(location_info!())?;
402
403   let community_actor_id = CommunityForm::from_apub(&group, context, Some(user.actor_id()?))
404     .await?
405     .actor_id
406     .context(location_info!())?;
407
408   let community = blocking(context.pool(), move |conn| {
409     Community::read_from_actor_id(conn, &community_actor_id)
410   })
411   .await??;
412
413   let community_form = CommunityForm {
414     name: community.name.to_owned(),
415     title: community.title.to_owned(),
416     description: community.description.to_owned(),
417     category_id: community.category_id, // Note: need to keep this due to foreign key constraint
418     creator_id: community.creator_id,   // Note: need to keep this due to foreign key constraint
419     removed: None,
420     published: None,
421     updated: Some(naive_now()),
422     deleted: Some(false),
423     nsfw: community.nsfw,
424     actor_id: Some(community.actor_id),
425     local: community.local,
426     private_key: community.private_key,
427     public_key: community.public_key,
428     last_refreshed_at: None,
429     icon: Some(community.icon.to_owned()),
430     banner: Some(community.banner.to_owned()),
431   };
432
433   let community_id = community.id;
434   blocking(context.pool(), move |conn| {
435     Community::update(conn, community_id, &community_form)
436   })
437   .await??;
438
439   let community_id = community.id;
440   let res = CommunityResponse {
441     community: blocking(context.pool(), move |conn| {
442       CommunityView::read(conn, community_id, None)
443     })
444     .await??,
445   };
446
447   let community_id = res.community.id;
448
449   context.chat_server().do_send(SendCommunityRoomMessage {
450     op: UserOperation::EditCommunity,
451     response: res,
452     community_id,
453     websocket_id: None,
454   });
455
456   announce_if_community_is_local(undo, &user, context).await?;
457   Ok(HttpResponse::Ok().finish())
458 }
459
460 async fn receive_undo_remove_community(
461   undo: Undo,
462   remove: &Remove,
463   context: &LemmyContext,
464 ) -> Result<HttpResponse, LemmyError> {
465   let mod_ = get_user_from_activity(remove, context).await?;
466   let group = GroupExt::from_any_base(remove.object().to_owned().one().context(location_info!())?)?
467     .context(location_info!())?;
468
469   let community_actor_id = CommunityForm::from_apub(&group, context, Some(mod_.actor_id()?))
470     .await?
471     .actor_id
472     .context(location_info!())?;
473
474   let community = blocking(context.pool(), move |conn| {
475     Community::read_from_actor_id(conn, &community_actor_id)
476   })
477   .await??;
478
479   let community_form = CommunityForm {
480     name: community.name.to_owned(),
481     title: community.title.to_owned(),
482     description: community.description.to_owned(),
483     category_id: community.category_id, // Note: need to keep this due to foreign key constraint
484     creator_id: community.creator_id,   // Note: need to keep this due to foreign key constraint
485     removed: Some(false),
486     published: None,
487     updated: Some(naive_now()),
488     deleted: None,
489     nsfw: community.nsfw,
490     actor_id: Some(community.actor_id),
491     local: community.local,
492     private_key: community.private_key,
493     public_key: community.public_key,
494     last_refreshed_at: None,
495     icon: Some(community.icon.to_owned()),
496     banner: Some(community.banner.to_owned()),
497   };
498
499   let community_id = community.id;
500   blocking(context.pool(), move |conn| {
501     Community::update(conn, community_id, &community_form)
502   })
503   .await??;
504
505   let community_id = community.id;
506   let res = CommunityResponse {
507     community: blocking(context.pool(), move |conn| {
508       CommunityView::read(conn, community_id, None)
509     })
510     .await??,
511   };
512
513   let community_id = res.community.id;
514
515   context.chat_server().do_send(SendCommunityRoomMessage {
516     op: UserOperation::EditCommunity,
517     response: res,
518     community_id,
519     websocket_id: None,
520   });
521
522   announce_if_community_is_local(undo, &mod_, context).await?;
523   Ok(HttpResponse::Ok().finish())
524 }
525
526 async fn receive_undo_like_comment(
527   undo: Undo,
528   like: &Like,
529   context: &LemmyContext,
530 ) -> Result<HttpResponse, LemmyError> {
531   let user = get_user_from_activity(like, context).await?;
532   let note = Note::from_any_base(like.object().to_owned().one().context(location_info!())?)?
533     .context(location_info!())?;
534
535   let comment = CommentForm::from_apub(&note, context, None).await?;
536
537   let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context)
538     .await?
539     .id;
540
541   let user_id = user.id;
542   blocking(context.pool(), move |conn| {
543     CommentLike::remove(conn, user_id, comment_id)
544   })
545   .await??;
546
547   // Refetch the view
548   let comment_view = blocking(context.pool(), move |conn| {
549     CommentView::read(conn, comment_id, None)
550   })
551   .await??;
552
553   // TODO get those recipient actor ids from somewhere
554   let recipient_ids = vec![];
555   let res = CommentResponse {
556     comment: comment_view,
557     recipient_ids,
558     form_id: None,
559   };
560
561   context.chat_server().do_send(SendComment {
562     op: UserOperation::CreateCommentLike,
563     comment: res,
564     websocket_id: None,
565   });
566
567   announce_if_community_is_local(undo, &user, context).await?;
568   Ok(HttpResponse::Ok().finish())
569 }
570
571 async fn receive_undo_like_post(
572   undo: Undo,
573   like: &Like,
574   context: &LemmyContext,
575 ) -> Result<HttpResponse, LemmyError> {
576   let user = get_user_from_activity(like, context).await?;
577   let page = PageExt::from_any_base(like.object().to_owned().one().context(location_info!())?)?
578     .context(location_info!())?;
579
580   let post = PostForm::from_apub(&page, context, None).await?;
581
582   let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context)
583     .await?
584     .id;
585
586   let user_id = user.id;
587   blocking(context.pool(), move |conn| {
588     PostLike::remove(conn, user_id, post_id)
589   })
590   .await??;
591
592   // Refetch the view
593   let post_view = blocking(context.pool(), move |conn| {
594     PostView::read(conn, post_id, None)
595   })
596   .await??;
597
598   let res = PostResponse { post: post_view };
599
600   context.chat_server().do_send(SendPost {
601     op: UserOperation::CreatePostLike,
602     post: res,
603     websocket_id: None,
604   });
605
606   announce_if_community_is_local(undo, &user, context).await?;
607   Ok(HttpResponse::Ok().finish())
608 }
609
610 async fn receive_undo_dislike_comment(
611   undo: Undo,
612   dislike: &Dislike,
613   context: &LemmyContext,
614 ) -> Result<HttpResponse, LemmyError> {
615   let user = get_user_from_activity(dislike, context).await?;
616   let note = Note::from_any_base(
617     dislike
618       .object()
619       .to_owned()
620       .one()
621       .context(location_info!())?,
622   )?
623   .context(location_info!())?;
624
625   let comment = CommentForm::from_apub(&note, context, None).await?;
626
627   let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context)
628     .await?
629     .id;
630
631   let user_id = user.id;
632   blocking(context.pool(), move |conn| {
633     CommentLike::remove(conn, user_id, comment_id)
634   })
635   .await??;
636
637   // Refetch the view
638   let comment_view = blocking(context.pool(), move |conn| {
639     CommentView::read(conn, comment_id, None)
640   })
641   .await??;
642
643   // TODO get those recipient actor ids from somewhere
644   let recipient_ids = vec![];
645   let res = CommentResponse {
646     comment: comment_view,
647     recipient_ids,
648     form_id: None,
649   };
650
651   context.chat_server().do_send(SendComment {
652     op: UserOperation::CreateCommentLike,
653     comment: res,
654     websocket_id: None,
655   });
656
657   announce_if_community_is_local(undo, &user, context).await?;
658   Ok(HttpResponse::Ok().finish())
659 }
660
661 async fn receive_undo_dislike_post(
662   undo: Undo,
663   dislike: &Dislike,
664   context: &LemmyContext,
665 ) -> Result<HttpResponse, LemmyError> {
666   let user = get_user_from_activity(dislike, context).await?;
667   let page = PageExt::from_any_base(
668     dislike
669       .object()
670       .to_owned()
671       .one()
672       .context(location_info!())?,
673   )?
674   .context(location_info!())?;
675
676   let post = PostForm::from_apub(&page, context, None).await?;
677
678   let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context)
679     .await?
680     .id;
681
682   let user_id = user.id;
683   blocking(context.pool(), move |conn| {
684     PostLike::remove(conn, user_id, post_id)
685   })
686   .await??;
687
688   // Refetch the view
689   let post_view = blocking(context.pool(), move |conn| {
690     PostView::read(conn, post_id, None)
691   })
692   .await??;
693
694   let res = PostResponse { post: post_view };
695
696   context.chat_server().do_send(SendPost {
697     op: UserOperation::CreatePostLike,
698     post: res,
699     websocket_id: None,
700   });
701
702   announce_if_community_is_local(undo, &user, context).await?;
703   Ok(HttpResponse::Ok().finish())
704 }