]> Untitled Git - lemmy.git/blob - lemmy_api/src/post.rs
Fixing some clippy warnings.
[lemmy.git] / lemmy_api / src / post.rs
1 use crate::{
2   check_community_ban,
3   check_downvotes_enabled,
4   check_optional_url,
5   collect_moderated_communities,
6   get_user_from_jwt,
7   get_user_from_jwt_opt,
8   is_mod_or_admin,
9   Perform,
10 };
11 use actix_web::web::Data;
12 use lemmy_apub::{ApubLikeableType, ApubObjectType};
13 use lemmy_db::{
14   naive_now,
15   source::{
16     moderator::*,
17     post::*,
18     post_report::{PostReport, PostReportForm},
19   },
20   views::{
21     comment_view::CommentQueryBuilder,
22     community::community_moderator_view::CommunityModeratorView,
23     post_report_view::{PostReportQueryBuilder, PostReportView},
24     post_view::{PostQueryBuilder, PostView},
25   },
26   Crud,
27   Likeable,
28   ListingType,
29   Reportable,
30   Saveable,
31   SortType,
32 };
33 use lemmy_structs::{blocking, post::*};
34 use lemmy_utils::{
35   apub::{make_apub_endpoint, EndpointType},
36   request::fetch_iframely_and_pictrs_data,
37   utils::{check_slurs, check_slurs_opt, is_valid_post_title},
38   APIError,
39   ConnectionId,
40   LemmyError,
41 };
42 use lemmy_websocket::{
43   messages::{GetPostUsersOnline, JoinPostRoom, SendModRoomMessage, SendPost, SendUserRoomMessage},
44   LemmyContext,
45   UserOperation,
46 };
47 use std::str::FromStr;
48
49 #[async_trait::async_trait(?Send)]
50 impl Perform for CreatePost {
51   type Response = PostResponse;
52
53   async fn perform(
54     &self,
55     context: &Data<LemmyContext>,
56     websocket_id: Option<ConnectionId>,
57   ) -> Result<PostResponse, LemmyError> {
58     let data: &CreatePost = &self;
59     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
60
61     check_slurs(&data.name)?;
62     check_slurs_opt(&data.body)?;
63
64     if !is_valid_post_title(&data.name) {
65       return Err(APIError::err("invalid_post_title").into());
66     }
67
68     check_community_ban(user.id, data.community_id, context.pool()).await?;
69
70     check_optional_url(&Some(data.url.to_owned()))?;
71
72     // Fetch Iframely and pictrs cached image
73     let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
74       fetch_iframely_and_pictrs_data(context.client(), data.url.to_owned()).await;
75
76     let post_form = PostForm {
77       name: data.name.trim().to_owned(),
78       url: data.url.to_owned(),
79       body: data.body.to_owned(),
80       community_id: data.community_id,
81       creator_id: user.id,
82       removed: None,
83       deleted: None,
84       nsfw: data.nsfw,
85       locked: None,
86       stickied: None,
87       updated: None,
88       embed_title: iframely_title,
89       embed_description: iframely_description,
90       embed_html: iframely_html,
91       thumbnail_url: pictrs_thumbnail,
92       ap_id: None,
93       local: true,
94       published: None,
95     };
96
97     let inserted_post =
98       match blocking(context.pool(), move |conn| Post::create(conn, &post_form)).await? {
99         Ok(post) => post,
100         Err(e) => {
101           let err_type = if e.to_string() == "value too long for type character varying(200)" {
102             "post_title_too_long"
103           } else {
104             "couldnt_create_post"
105           };
106
107           return Err(APIError::err(err_type).into());
108         }
109       };
110
111     let inserted_post_id = inserted_post.id;
112     let updated_post = match blocking(context.pool(), move |conn| {
113       let apub_id =
114         make_apub_endpoint(EndpointType::Post, &inserted_post_id.to_string()).to_string();
115       Post::update_ap_id(conn, inserted_post_id, apub_id)
116     })
117     .await?
118     {
119       Ok(post) => post,
120       Err(_e) => return Err(APIError::err("couldnt_create_post").into()),
121     };
122
123     updated_post.send_create(&user, context).await?;
124
125     // They like their own post by default
126     let like_form = PostLikeForm {
127       post_id: inserted_post.id,
128       user_id: user.id,
129       score: 1,
130     };
131
132     let like = move |conn: &'_ _| PostLike::like(conn, &like_form);
133     if blocking(context.pool(), like).await?.is_err() {
134       return Err(APIError::err("couldnt_like_post").into());
135     }
136
137     updated_post.send_like(&user, context).await?;
138
139     // Refetch the view
140     let inserted_post_id = inserted_post.id;
141     let post_view = match blocking(context.pool(), move |conn| {
142       PostView::read(conn, inserted_post_id, Some(user.id))
143     })
144     .await?
145     {
146       Ok(post) => post,
147       Err(_e) => return Err(APIError::err("couldnt_find_post").into()),
148     };
149
150     let res = PostResponse { post_view };
151
152     context.chat_server().do_send(SendPost {
153       op: UserOperation::CreatePost,
154       post: res.clone(),
155       websocket_id,
156     });
157
158     Ok(res)
159   }
160 }
161
162 #[async_trait::async_trait(?Send)]
163 impl Perform for GetPost {
164   type Response = GetPostResponse;
165
166   async fn perform(
167     &self,
168     context: &Data<LemmyContext>,
169     _websocket_id: Option<ConnectionId>,
170   ) -> Result<GetPostResponse, LemmyError> {
171     let data: &GetPost = &self;
172     let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
173     let user_id = user.map(|u| u.id);
174
175     let id = data.id;
176     let post_view = match blocking(context.pool(), move |conn| {
177       PostView::read(conn, id, user_id)
178     })
179     .await?
180     {
181       Ok(post) => post,
182       Err(_e) => return Err(APIError::err("couldnt_find_post").into()),
183     };
184
185     let id = data.id;
186     let comments = blocking(context.pool(), move |conn| {
187       CommentQueryBuilder::create(conn)
188         .my_user_id(user_id)
189         .post_id(id)
190         .limit(9999)
191         .list()
192     })
193     .await??;
194
195     let community_id = post_view.community.id;
196     let moderators = blocking(context.pool(), move |conn| {
197       CommunityModeratorView::for_community(conn, community_id)
198     })
199     .await??;
200
201     let online = context
202       .chat_server()
203       .send(GetPostUsersOnline { post_id: data.id })
204       .await
205       .unwrap_or(1);
206
207     // Return the jwt
208     Ok(GetPostResponse {
209       post_view,
210       comments,
211       moderators,
212       online,
213     })
214   }
215 }
216
217 #[async_trait::async_trait(?Send)]
218 impl Perform for GetPosts {
219   type Response = GetPostsResponse;
220
221   async fn perform(
222     &self,
223     context: &Data<LemmyContext>,
224     _websocket_id: Option<ConnectionId>,
225   ) -> Result<GetPostsResponse, LemmyError> {
226     let data: &GetPosts = &self;
227     let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
228
229     let user_id = match &user {
230       Some(user) => Some(user.id),
231       None => None,
232     };
233
234     let show_nsfw = match &user {
235       Some(user) => user.show_nsfw,
236       None => false,
237     };
238
239     let type_ = ListingType::from_str(&data.type_)?;
240     let sort = SortType::from_str(&data.sort)?;
241
242     let page = data.page;
243     let limit = data.limit;
244     let community_id = data.community_id;
245     let community_name = data.community_name.to_owned();
246     let posts = match blocking(context.pool(), move |conn| {
247       PostQueryBuilder::create(conn)
248         .listing_type(&type_)
249         .sort(&sort)
250         .show_nsfw(show_nsfw)
251         .community_id(community_id)
252         .community_name(community_name)
253         .my_user_id(user_id)
254         .page(page)
255         .limit(limit)
256         .list()
257     })
258     .await?
259     {
260       Ok(posts) => posts,
261       Err(_e) => return Err(APIError::err("couldnt_get_posts").into()),
262     };
263
264     Ok(GetPostsResponse { posts })
265   }
266 }
267
268 #[async_trait::async_trait(?Send)]
269 impl Perform for CreatePostLike {
270   type Response = PostResponse;
271
272   async fn perform(
273     &self,
274     context: &Data<LemmyContext>,
275     websocket_id: Option<ConnectionId>,
276   ) -> Result<PostResponse, LemmyError> {
277     let data: &CreatePostLike = &self;
278     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
279
280     // Don't do a downvote if site has downvotes disabled
281     check_downvotes_enabled(data.score, context.pool()).await?;
282
283     // Check for a community ban
284     let post_id = data.post_id;
285     let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
286
287     check_community_ban(user.id, post.community_id, context.pool()).await?;
288
289     let like_form = PostLikeForm {
290       post_id: data.post_id,
291       user_id: user.id,
292       score: data.score,
293     };
294
295     // Remove any likes first
296     let user_id = user.id;
297     blocking(context.pool(), move |conn| {
298       PostLike::remove(conn, user_id, post_id)
299     })
300     .await??;
301
302     // Only add the like if the score isnt 0
303     let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
304     if do_add {
305       let like_form2 = like_form.clone();
306       let like = move |conn: &'_ _| PostLike::like(conn, &like_form2);
307       if blocking(context.pool(), like).await?.is_err() {
308         return Err(APIError::err("couldnt_like_post").into());
309       }
310
311       if like_form.score == 1 {
312         post.send_like(&user, context).await?;
313       } else if like_form.score == -1 {
314         post.send_dislike(&user, context).await?;
315       }
316     } else {
317       post.send_undo_like(&user, context).await?;
318     }
319
320     let post_id = data.post_id;
321     let user_id = user.id;
322     let post_view = match blocking(context.pool(), move |conn| {
323       PostView::read(conn, post_id, Some(user_id))
324     })
325     .await?
326     {
327       Ok(post) => post,
328       Err(_e) => return Err(APIError::err("couldnt_find_post").into()),
329     };
330
331     let res = PostResponse { post_view };
332
333     context.chat_server().do_send(SendPost {
334       op: UserOperation::CreatePostLike,
335       post: res.clone(),
336       websocket_id,
337     });
338
339     Ok(res)
340   }
341 }
342
343 #[async_trait::async_trait(?Send)]
344 impl Perform for EditPost {
345   type Response = PostResponse;
346
347   async fn perform(
348     &self,
349     context: &Data<LemmyContext>,
350     websocket_id: Option<ConnectionId>,
351   ) -> Result<PostResponse, LemmyError> {
352     let data: &EditPost = &self;
353     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
354
355     check_slurs(&data.name)?;
356     check_slurs_opt(&data.body)?;
357
358     if !is_valid_post_title(&data.name) {
359       return Err(APIError::err("invalid_post_title").into());
360     }
361
362     let edit_id = data.edit_id;
363     let orig_post = blocking(context.pool(), move |conn| Post::read(conn, edit_id)).await??;
364
365     check_community_ban(user.id, orig_post.community_id, context.pool()).await?;
366
367     // Verify that only the creator can edit
368     if !Post::is_post_creator(user.id, orig_post.creator_id) {
369       return Err(APIError::err("no_post_edit_allowed").into());
370     }
371
372     // Fetch Iframely and Pictrs cached image
373     let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
374       fetch_iframely_and_pictrs_data(context.client(), data.url.to_owned()).await;
375
376     let post_form = PostForm {
377       name: data.name.trim().to_owned(),
378       url: data.url.to_owned(),
379       body: data.body.to_owned(),
380       nsfw: data.nsfw,
381       creator_id: orig_post.creator_id.to_owned(),
382       community_id: orig_post.community_id,
383       removed: Some(orig_post.removed),
384       deleted: Some(orig_post.deleted),
385       locked: Some(orig_post.locked),
386       stickied: Some(orig_post.stickied),
387       updated: Some(naive_now()),
388       embed_title: iframely_title,
389       embed_description: iframely_description,
390       embed_html: iframely_html,
391       thumbnail_url: pictrs_thumbnail,
392       ap_id: Some(orig_post.ap_id),
393       local: orig_post.local,
394       published: None,
395     };
396
397     let edit_id = data.edit_id;
398     let res = blocking(context.pool(), move |conn| {
399       Post::update(conn, edit_id, &post_form)
400     })
401     .await?;
402     let updated_post: Post = match res {
403       Ok(post) => post,
404       Err(e) => {
405         let err_type = if e.to_string() == "value too long for type character varying(200)" {
406           "post_title_too_long"
407         } else {
408           "couldnt_update_post"
409         };
410
411         return Err(APIError::err(err_type).into());
412       }
413     };
414
415     // Send apub update
416     updated_post.send_update(&user, context).await?;
417
418     let edit_id = data.edit_id;
419     let post_view = blocking(context.pool(), move |conn| {
420       PostView::read(conn, edit_id, Some(user.id))
421     })
422     .await??;
423
424     let res = PostResponse { post_view };
425
426     context.chat_server().do_send(SendPost {
427       op: UserOperation::EditPost,
428       post: res.clone(),
429       websocket_id,
430     });
431
432     Ok(res)
433   }
434 }
435
436 #[async_trait::async_trait(?Send)]
437 impl Perform for DeletePost {
438   type Response = PostResponse;
439
440   async fn perform(
441     &self,
442     context: &Data<LemmyContext>,
443     websocket_id: Option<ConnectionId>,
444   ) -> Result<PostResponse, LemmyError> {
445     let data: &DeletePost = &self;
446     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
447
448     let edit_id = data.edit_id;
449     let orig_post = blocking(context.pool(), move |conn| Post::read(conn, edit_id)).await??;
450
451     check_community_ban(user.id, orig_post.community_id, context.pool()).await?;
452
453     // Verify that only the creator can delete
454     if !Post::is_post_creator(user.id, orig_post.creator_id) {
455       return Err(APIError::err("no_post_edit_allowed").into());
456     }
457
458     // Update the post
459     let edit_id = data.edit_id;
460     let deleted = data.deleted;
461     let updated_post = blocking(context.pool(), move |conn| {
462       Post::update_deleted(conn, edit_id, deleted)
463     })
464     .await??;
465
466     // apub updates
467     if deleted {
468       updated_post.send_delete(&user, context).await?;
469     } else {
470       updated_post.send_undo_delete(&user, context).await?;
471     }
472
473     // Refetch the post
474     let edit_id = data.edit_id;
475     let post_view = blocking(context.pool(), move |conn| {
476       PostView::read(conn, edit_id, Some(user.id))
477     })
478     .await??;
479
480     let res = PostResponse { post_view };
481
482     context.chat_server().do_send(SendPost {
483       op: UserOperation::DeletePost,
484       post: res.clone(),
485       websocket_id,
486     });
487
488     Ok(res)
489   }
490 }
491
492 #[async_trait::async_trait(?Send)]
493 impl Perform for RemovePost {
494   type Response = PostResponse;
495
496   async fn perform(
497     &self,
498     context: &Data<LemmyContext>,
499     websocket_id: Option<ConnectionId>,
500   ) -> Result<PostResponse, LemmyError> {
501     let data: &RemovePost = &self;
502     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
503
504     let edit_id = data.edit_id;
505     let orig_post = blocking(context.pool(), move |conn| Post::read(conn, edit_id)).await??;
506
507     check_community_ban(user.id, orig_post.community_id, context.pool()).await?;
508
509     // Verify that only the mods can remove
510     is_mod_or_admin(context.pool(), user.id, orig_post.community_id).await?;
511
512     // Update the post
513     let edit_id = data.edit_id;
514     let removed = data.removed;
515     let updated_post = blocking(context.pool(), move |conn| {
516       Post::update_removed(conn, edit_id, removed)
517     })
518     .await??;
519
520     // Mod tables
521     let form = ModRemovePostForm {
522       mod_user_id: user.id,
523       post_id: data.edit_id,
524       removed: Some(removed),
525       reason: data.reason.to_owned(),
526     };
527     blocking(context.pool(), move |conn| {
528       ModRemovePost::create(conn, &form)
529     })
530     .await??;
531
532     // apub updates
533     if removed {
534       updated_post.send_remove(&user, context).await?;
535     } else {
536       updated_post.send_undo_remove(&user, context).await?;
537     }
538
539     // Refetch the post
540     let edit_id = data.edit_id;
541     let user_id = user.id;
542     let post_view = blocking(context.pool(), move |conn| {
543       PostView::read(conn, edit_id, Some(user_id))
544     })
545     .await??;
546
547     let res = PostResponse { post_view };
548
549     context.chat_server().do_send(SendPost {
550       op: UserOperation::RemovePost,
551       post: res.clone(),
552       websocket_id,
553     });
554
555     Ok(res)
556   }
557 }
558
559 #[async_trait::async_trait(?Send)]
560 impl Perform for LockPost {
561   type Response = PostResponse;
562
563   async fn perform(
564     &self,
565     context: &Data<LemmyContext>,
566     websocket_id: Option<ConnectionId>,
567   ) -> Result<PostResponse, LemmyError> {
568     let data: &LockPost = &self;
569     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
570
571     let edit_id = data.edit_id;
572     let orig_post = blocking(context.pool(), move |conn| Post::read(conn, edit_id)).await??;
573
574     check_community_ban(user.id, orig_post.community_id, context.pool()).await?;
575
576     // Verify that only the mods can lock
577     is_mod_or_admin(context.pool(), user.id, orig_post.community_id).await?;
578
579     // Update the post
580     let edit_id = data.edit_id;
581     let locked = data.locked;
582     let updated_post = blocking(context.pool(), move |conn| {
583       Post::update_locked(conn, edit_id, locked)
584     })
585     .await??;
586
587     // Mod tables
588     let form = ModLockPostForm {
589       mod_user_id: user.id,
590       post_id: data.edit_id,
591       locked: Some(locked),
592     };
593     blocking(context.pool(), move |conn| ModLockPost::create(conn, &form)).await??;
594
595     // apub updates
596     updated_post.send_update(&user, context).await?;
597
598     // Refetch the post
599     let edit_id = data.edit_id;
600     let post_view = blocking(context.pool(), move |conn| {
601       PostView::read(conn, edit_id, Some(user.id))
602     })
603     .await??;
604
605     let res = PostResponse { post_view };
606
607     context.chat_server().do_send(SendPost {
608       op: UserOperation::LockPost,
609       post: res.clone(),
610       websocket_id,
611     });
612
613     Ok(res)
614   }
615 }
616
617 #[async_trait::async_trait(?Send)]
618 impl Perform for StickyPost {
619   type Response = PostResponse;
620
621   async fn perform(
622     &self,
623     context: &Data<LemmyContext>,
624     websocket_id: Option<ConnectionId>,
625   ) -> Result<PostResponse, LemmyError> {
626     let data: &StickyPost = &self;
627     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
628
629     let edit_id = data.edit_id;
630     let orig_post = blocking(context.pool(), move |conn| Post::read(conn, edit_id)).await??;
631
632     check_community_ban(user.id, orig_post.community_id, context.pool()).await?;
633
634     // Verify that only the mods can sticky
635     is_mod_or_admin(context.pool(), user.id, orig_post.community_id).await?;
636
637     // Update the post
638     let edit_id = data.edit_id;
639     let stickied = data.stickied;
640     let updated_post = blocking(context.pool(), move |conn| {
641       Post::update_stickied(conn, edit_id, stickied)
642     })
643     .await??;
644
645     // Mod tables
646     let form = ModStickyPostForm {
647       mod_user_id: user.id,
648       post_id: data.edit_id,
649       stickied: Some(stickied),
650     };
651     blocking(context.pool(), move |conn| {
652       ModStickyPost::create(conn, &form)
653     })
654     .await??;
655
656     // Apub updates
657     // TODO stickied should pry work like locked for ease of use
658     updated_post.send_update(&user, context).await?;
659
660     // Refetch the post
661     let edit_id = data.edit_id;
662     let post_view = blocking(context.pool(), move |conn| {
663       PostView::read(conn, edit_id, Some(user.id))
664     })
665     .await??;
666
667     let res = PostResponse { post_view };
668
669     context.chat_server().do_send(SendPost {
670       op: UserOperation::StickyPost,
671       post: res.clone(),
672       websocket_id,
673     });
674
675     Ok(res)
676   }
677 }
678
679 #[async_trait::async_trait(?Send)]
680 impl Perform for SavePost {
681   type Response = PostResponse;
682
683   async fn perform(
684     &self,
685     context: &Data<LemmyContext>,
686     _websocket_id: Option<ConnectionId>,
687   ) -> Result<PostResponse, LemmyError> {
688     let data: &SavePost = &self;
689     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
690
691     let post_saved_form = PostSavedForm {
692       post_id: data.post_id,
693       user_id: user.id,
694     };
695
696     if data.save {
697       let save = move |conn: &'_ _| PostSaved::save(conn, &post_saved_form);
698       if blocking(context.pool(), save).await?.is_err() {
699         return Err(APIError::err("couldnt_save_post").into());
700       }
701     } else {
702       let unsave = move |conn: &'_ _| PostSaved::unsave(conn, &post_saved_form);
703       if blocking(context.pool(), unsave).await?.is_err() {
704         return Err(APIError::err("couldnt_save_post").into());
705       }
706     }
707
708     let post_id = data.post_id;
709     let user_id = user.id;
710     let post_view = blocking(context.pool(), move |conn| {
711       PostView::read(conn, post_id, Some(user_id))
712     })
713     .await??;
714
715     Ok(PostResponse { post_view })
716   }
717 }
718
719 #[async_trait::async_trait(?Send)]
720 impl Perform for PostJoin {
721   type Response = PostJoinResponse;
722
723   async fn perform(
724     &self,
725     context: &Data<LemmyContext>,
726     websocket_id: Option<ConnectionId>,
727   ) -> Result<PostJoinResponse, LemmyError> {
728     let data: &PostJoin = &self;
729
730     if let Some(ws_id) = websocket_id {
731       context.chat_server().do_send(JoinPostRoom {
732         post_id: data.post_id,
733         id: ws_id,
734       });
735     }
736
737     Ok(PostJoinResponse { joined: true })
738   }
739 }
740
741 /// Creates a post report and notifies the moderators of the community
742 #[async_trait::async_trait(?Send)]
743 impl Perform for CreatePostReport {
744   type Response = CreatePostReportResponse;
745
746   async fn perform(
747     &self,
748     context: &Data<LemmyContext>,
749     websocket_id: Option<ConnectionId>,
750   ) -> Result<CreatePostReportResponse, LemmyError> {
751     let data: &CreatePostReport = &self;
752     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
753
754     // check size of report and check for whitespace
755     let reason = data.reason.trim();
756     if reason.is_empty() {
757       return Err(APIError::err("report_reason_required").into());
758     }
759     if reason.len() > 1000 {
760       return Err(APIError::err("report_too_long").into());
761     }
762
763     let user_id = user.id;
764     let post_id = data.post_id;
765     let post_view = blocking(context.pool(), move |conn| {
766       PostView::read(&conn, post_id, None)
767     })
768     .await??;
769
770     check_community_ban(user_id, post_view.community.id, context.pool()).await?;
771
772     let report_form = PostReportForm {
773       creator_id: user_id,
774       post_id,
775       original_post_name: post_view.post.name,
776       original_post_url: post_view.post.url,
777       original_post_body: post_view.post.body,
778       reason: data.reason.to_owned(),
779     };
780
781     let report = match blocking(context.pool(), move |conn| {
782       PostReport::report(conn, &report_form)
783     })
784     .await?
785     {
786       Ok(report) => report,
787       Err(_e) => return Err(APIError::err("couldnt_create_report").into()),
788     };
789
790     let res = CreatePostReportResponse { success: true };
791
792     context.chat_server().do_send(SendUserRoomMessage {
793       op: UserOperation::CreatePostReport,
794       response: res.clone(),
795       recipient_id: user.id,
796       websocket_id,
797     });
798
799     context.chat_server().do_send(SendModRoomMessage {
800       op: UserOperation::CreatePostReport,
801       response: report,
802       community_id: post_view.community.id,
803       websocket_id,
804     });
805
806     Ok(res)
807   }
808 }
809
810 /// Resolves or unresolves a post report and notifies the moderators of the community
811 #[async_trait::async_trait(?Send)]
812 impl Perform for ResolvePostReport {
813   type Response = ResolvePostReportResponse;
814
815   async fn perform(
816     &self,
817     context: &Data<LemmyContext>,
818     websocket_id: Option<ConnectionId>,
819   ) -> Result<ResolvePostReportResponse, LemmyError> {
820     let data: &ResolvePostReport = &self;
821     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
822
823     let report_id = data.report_id;
824     let report = blocking(context.pool(), move |conn| {
825       PostReportView::read(&conn, report_id)
826     })
827     .await??;
828
829     let user_id = user.id;
830     is_mod_or_admin(context.pool(), user_id, report.community.id).await?;
831
832     let resolved = data.resolved;
833     let resolve_fun = move |conn: &'_ _| {
834       if resolved {
835         PostReport::resolve(conn, report_id, user_id)
836       } else {
837         PostReport::unresolve(conn, report_id, user_id)
838       }
839     };
840
841     let res = ResolvePostReportResponse {
842       report_id,
843       resolved: true,
844     };
845
846     if blocking(context.pool(), resolve_fun).await?.is_err() {
847       return Err(APIError::err("couldnt_resolve_report").into());
848     };
849
850     context.chat_server().do_send(SendModRoomMessage {
851       op: UserOperation::ResolvePostReport,
852       response: res.clone(),
853       community_id: report.community.id,
854       websocket_id,
855     });
856
857     Ok(res)
858   }
859 }
860
861 /// Lists post reports for a community if an id is supplied
862 /// or returns all post reports for communities a user moderates
863 #[async_trait::async_trait(?Send)]
864 impl Perform for ListPostReports {
865   type Response = ListPostReportsResponse;
866
867   async fn perform(
868     &self,
869     context: &Data<LemmyContext>,
870     websocket_id: Option<ConnectionId>,
871   ) -> Result<ListPostReportsResponse, LemmyError> {
872     let data: &ListPostReports = &self;
873     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
874
875     let user_id = user.id;
876     let community_id = data.community;
877     let community_ids =
878       collect_moderated_communities(user_id, community_id, context.pool()).await?;
879
880     let page = data.page;
881     let limit = data.limit;
882     let posts = blocking(context.pool(), move |conn| {
883       PostReportQueryBuilder::create(conn)
884         .community_ids(community_ids)
885         .page(page)
886         .limit(limit)
887         .list()
888     })
889     .await??;
890
891     let res = ListPostReportsResponse { posts };
892
893     context.chat_server().do_send(SendUserRoomMessage {
894       op: UserOperation::ListPostReports,
895       response: res.clone(),
896       recipient_id: user.id,
897       websocket_id,
898     });
899
900     Ok(res)
901   }
902 }