3 collect_moderated_communities,
10 use actix_web::web::Data;
11 use lemmy_apub::{ApubLikeableType, ApubObjectType};
13 source::comment::Comment_,
15 comment_report_view::{CommentReportQueryBuilder, CommentReportView},
16 comment_view::{CommentQueryBuilder, CommentView},
26 use lemmy_db_schema::source::{comment::*, comment_report::*, moderator::*, post::Post, user::*};
27 use lemmy_structs::{blocking, comment::*, send_local_notifs};
29 apub::{make_apub_endpoint, EndpointType},
30 utils::{remove_slurs, scrape_text_for_mentions},
35 use lemmy_websocket::{
36 messages::{SendComment, SendModRoomMessage, SendUserRoomMessage},
40 use std::str::FromStr;
42 #[async_trait::async_trait(?Send)]
43 impl Perform for CreateComment {
44 type Response = CommentResponse;
48 context: &Data<LemmyContext>,
49 websocket_id: Option<ConnectionId>,
50 ) -> Result<CommentResponse, LemmyError> {
51 let data: &CreateComment = &self;
52 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
54 let content_slurs_removed = remove_slurs(&data.content.to_owned());
56 let comment_form = CommentForm {
57 content: content_slurs_removed,
58 parent_id: data.parent_id.to_owned(),
59 post_id: data.post_id,
70 // Check for a community ban
71 let post_id = data.post_id;
72 let post = get_post(post_id, context.pool()).await?;
74 check_community_ban(user.id, post.community_id, context.pool()).await?;
76 // Check if post is locked, no new comments
78 return Err(APIError::err("locked").into());
82 let comment_form2 = comment_form.clone();
83 let inserted_comment = match blocking(context.pool(), move |conn| {
84 Comment::create(&conn, &comment_form2)
88 Ok(comment) => comment,
89 Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
92 // Necessary to update the ap_id
93 let inserted_comment_id = inserted_comment.id;
94 let updated_comment: Comment = match blocking(context.pool(), move |conn| {
96 make_apub_endpoint(EndpointType::Comment, &inserted_comment_id.to_string()).to_string();
97 Comment::update_ap_id(&conn, inserted_comment_id, apub_id)
101 Ok(comment) => comment,
102 Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
105 updated_comment.send_create(&user, context).await?;
107 // Scan the comment for user mentions, add those rows
108 let mentions = scrape_text_for_mentions(&comment_form.content);
109 let recipient_ids = send_local_notifs(
111 updated_comment.clone(),
119 // You like your own comment by default
120 let like_form = CommentLikeForm {
121 comment_id: inserted_comment.id,
122 post_id: data.post_id,
127 let like = move |conn: &'_ _| CommentLike::like(&conn, &like_form);
128 if blocking(context.pool(), like).await?.is_err() {
129 return Err(APIError::err("couldnt_like_comment").into());
132 updated_comment.send_like(&user, context).await?;
134 let user_id = user.id;
135 let comment_view = blocking(context.pool(), move |conn| {
136 CommentView::read(&conn, inserted_comment.id, Some(user_id))
140 let mut res = CommentResponse {
143 form_id: data.form_id.to_owned(),
146 context.chat_server().do_send(SendComment {
147 op: UserOperation::CreateComment,
148 comment: res.clone(),
152 // strip out the recipient_ids, so that
153 // users don't get double notifs
154 res.recipient_ids = Vec::new();
160 #[async_trait::async_trait(?Send)]
161 impl Perform for EditComment {
162 type Response = CommentResponse;
166 context: &Data<LemmyContext>,
167 websocket_id: Option<ConnectionId>,
168 ) -> Result<CommentResponse, LemmyError> {
169 let data: &EditComment = &self;
170 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
172 let edit_id = data.edit_id;
173 let orig_comment = blocking(context.pool(), move |conn| {
174 CommentView::read(&conn, edit_id, None)
178 check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
180 // Verify that only the creator can edit
181 if user.id != orig_comment.creator.id {
182 return Err(APIError::err("no_comment_edit_allowed").into());
186 let content_slurs_removed = remove_slurs(&data.content.to_owned());
187 let edit_id = data.edit_id;
188 let updated_comment = match blocking(context.pool(), move |conn| {
189 Comment::update_content(conn, edit_id, &content_slurs_removed)
193 Ok(comment) => comment,
194 Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
197 // Send the apub update
198 updated_comment.send_update(&user, context).await?;
200 // Do the mentions / recipients
201 let post_id = orig_comment.post.id;
202 let post = get_post(post_id, context.pool()).await?;
204 let updated_comment_content = updated_comment.content.to_owned();
205 let mentions = scrape_text_for_mentions(&updated_comment_content);
206 let recipient_ids = send_local_notifs(
216 let edit_id = data.edit_id;
217 let user_id = user.id;
218 let comment_view = blocking(context.pool(), move |conn| {
219 CommentView::read(conn, edit_id, Some(user_id))
223 let mut res = CommentResponse {
226 form_id: data.form_id.to_owned(),
229 context.chat_server().do_send(SendComment {
230 op: UserOperation::EditComment,
231 comment: res.clone(),
235 // strip out the recipient_ids, so that
236 // users don't get double notifs
237 res.recipient_ids = Vec::new();
243 #[async_trait::async_trait(?Send)]
244 impl Perform for DeleteComment {
245 type Response = CommentResponse;
249 context: &Data<LemmyContext>,
250 websocket_id: Option<ConnectionId>,
251 ) -> Result<CommentResponse, LemmyError> {
252 let data: &DeleteComment = &self;
253 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
255 let edit_id = data.edit_id;
256 let orig_comment = blocking(context.pool(), move |conn| {
257 CommentView::read(&conn, edit_id, None)
261 check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
263 // Verify that only the creator can delete
264 if user.id != orig_comment.creator.id {
265 return Err(APIError::err("no_comment_edit_allowed").into());
269 let deleted = data.deleted;
270 let updated_comment = match blocking(context.pool(), move |conn| {
271 Comment::update_deleted(conn, edit_id, deleted)
275 Ok(comment) => comment,
276 Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
279 // Send the apub message
281 updated_comment.send_delete(&user, context).await?;
283 updated_comment.send_undo_delete(&user, context).await?;
287 let edit_id = data.edit_id;
288 let user_id = user.id;
289 let comment_view = blocking(context.pool(), move |conn| {
290 CommentView::read(conn, edit_id, Some(user_id))
294 // Build the recipients
295 let post_id = comment_view.post.id;
296 let post = get_post(post_id, context.pool()).await?;
297 let mentions = vec![];
298 let recipient_ids = send_local_notifs(
308 let mut res = CommentResponse {
314 context.chat_server().do_send(SendComment {
315 op: UserOperation::DeleteComment,
316 comment: res.clone(),
320 // strip out the recipient_ids, so that
321 // users don't get double notifs
322 res.recipient_ids = Vec::new();
328 #[async_trait::async_trait(?Send)]
329 impl Perform for RemoveComment {
330 type Response = CommentResponse;
334 context: &Data<LemmyContext>,
335 websocket_id: Option<ConnectionId>,
336 ) -> Result<CommentResponse, LemmyError> {
337 let data: &RemoveComment = &self;
338 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
340 let edit_id = data.edit_id;
341 let orig_comment = blocking(context.pool(), move |conn| {
342 CommentView::read(&conn, edit_id, None)
346 check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
348 // Verify that only a mod or admin can remove
349 is_mod_or_admin(context.pool(), user.id, orig_comment.community.id).await?;
352 let removed = data.removed;
353 let updated_comment = match blocking(context.pool(), move |conn| {
354 Comment::update_removed(conn, edit_id, removed)
358 Ok(comment) => comment,
359 Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
363 let form = ModRemoveCommentForm {
364 mod_user_id: user.id,
365 comment_id: data.edit_id,
366 removed: Some(removed),
367 reason: data.reason.to_owned(),
369 blocking(context.pool(), move |conn| {
370 ModRemoveComment::create(conn, &form)
374 // Send the apub message
376 updated_comment.send_remove(&user, context).await?;
378 updated_comment.send_undo_remove(&user, context).await?;
382 let edit_id = data.edit_id;
383 let user_id = user.id;
384 let comment_view = blocking(context.pool(), move |conn| {
385 CommentView::read(conn, edit_id, Some(user_id))
389 // Build the recipients
390 let post_id = comment_view.post.id;
391 let post = get_post(post_id, context.pool()).await?;
392 let mentions = vec![];
393 let recipient_ids = send_local_notifs(
403 let mut res = CommentResponse {
409 context.chat_server().do_send(SendComment {
410 op: UserOperation::RemoveComment,
411 comment: res.clone(),
415 // strip out the recipient_ids, so that
416 // users don't get double notifs
417 res.recipient_ids = Vec::new();
423 #[async_trait::async_trait(?Send)]
424 impl Perform for MarkCommentAsRead {
425 type Response = CommentResponse;
429 context: &Data<LemmyContext>,
430 _websocket_id: Option<ConnectionId>,
431 ) -> Result<CommentResponse, LemmyError> {
432 let data: &MarkCommentAsRead = &self;
433 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
435 let edit_id = data.edit_id;
436 let orig_comment = blocking(context.pool(), move |conn| {
437 CommentView::read(&conn, edit_id, None)
441 check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
443 // Verify that only the recipient can mark as read
444 // Needs to fetch the parent comment / post to get the recipient
445 let parent_id = orig_comment.comment.parent_id;
448 let parent_comment = blocking(context.pool(), move |conn| {
449 CommentView::read(&conn, pid, None)
452 if user.id != parent_comment.creator.id {
453 return Err(APIError::err("no_comment_edit_allowed").into());
457 let parent_post_id = orig_comment.post.id;
459 blocking(context.pool(), move |conn| Post::read(conn, parent_post_id)).await??;
460 if user.id != parent_post.creator_id {
461 return Err(APIError::err("no_comment_edit_allowed").into());
466 // Do the mark as read
467 let read = data.read;
468 match blocking(context.pool(), move |conn| {
469 Comment::update_read(conn, edit_id, read)
473 Ok(comment) => comment,
474 Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
478 let edit_id = data.edit_id;
479 let user_id = user.id;
480 let comment_view = blocking(context.pool(), move |conn| {
481 CommentView::read(conn, edit_id, Some(user_id))
485 let res = CommentResponse {
487 recipient_ids: Vec::new(),
495 #[async_trait::async_trait(?Send)]
496 impl Perform for SaveComment {
497 type Response = CommentResponse;
501 context: &Data<LemmyContext>,
502 _websocket_id: Option<ConnectionId>,
503 ) -> Result<CommentResponse, LemmyError> {
504 let data: &SaveComment = &self;
505 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
507 let comment_saved_form = CommentSavedForm {
508 comment_id: data.comment_id,
513 let save_comment = move |conn: &'_ _| CommentSaved::save(conn, &comment_saved_form);
514 if blocking(context.pool(), save_comment).await?.is_err() {
515 return Err(APIError::err("couldnt_save_comment").into());
518 let unsave_comment = move |conn: &'_ _| CommentSaved::unsave(conn, &comment_saved_form);
519 if blocking(context.pool(), unsave_comment).await?.is_err() {
520 return Err(APIError::err("couldnt_save_comment").into());
524 let comment_id = data.comment_id;
525 let user_id = user.id;
526 let comment_view = blocking(context.pool(), move |conn| {
527 CommentView::read(conn, comment_id, Some(user_id))
533 recipient_ids: Vec::new(),
539 #[async_trait::async_trait(?Send)]
540 impl Perform for CreateCommentLike {
541 type Response = CommentResponse;
545 context: &Data<LemmyContext>,
546 websocket_id: Option<ConnectionId>,
547 ) -> Result<CommentResponse, LemmyError> {
548 let data: &CreateCommentLike = &self;
549 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
551 let mut recipient_ids = Vec::new();
553 // Don't do a downvote if site has downvotes disabled
554 if data.score == -1 {
555 let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
556 if !site_view.site.enable_downvotes {
557 return Err(APIError::err("downvotes_disabled").into());
561 let comment_id = data.comment_id;
562 let orig_comment = blocking(context.pool(), move |conn| {
563 CommentView::read(&conn, comment_id, None)
567 let post_id = orig_comment.post.id;
568 let post = get_post(post_id, context.pool()).await?;
569 check_community_ban(user.id, post.community_id, context.pool()).await?;
571 let comment_id = data.comment_id;
572 let comment = blocking(context.pool(), move |conn| Comment::read(conn, comment_id)).await??;
574 // Add to recipient ids
575 match comment.parent_id {
578 blocking(context.pool(), move |conn| Comment::read(conn, parent_id)).await??;
579 if parent_comment.creator_id != user.id {
580 let parent_user = blocking(context.pool(), move |conn| {
581 User_::read(conn, parent_comment.creator_id)
584 recipient_ids.push(parent_user.id);
588 recipient_ids.push(post.creator_id);
592 let like_form = CommentLikeForm {
593 comment_id: data.comment_id,
599 // Remove any likes first
600 let user_id = user.id;
601 blocking(context.pool(), move |conn| {
602 CommentLike::remove(conn, user_id, comment_id)
606 // Only add the like if the score isnt 0
607 let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
609 let like_form2 = like_form.clone();
610 let like = move |conn: &'_ _| CommentLike::like(conn, &like_form2);
611 if blocking(context.pool(), like).await?.is_err() {
612 return Err(APIError::err("couldnt_like_comment").into());
615 if like_form.score == 1 {
616 comment.send_like(&user, context).await?;
617 } else if like_form.score == -1 {
618 comment.send_dislike(&user, context).await?;
621 comment.send_undo_like(&user, context).await?;
624 // Have to refetch the comment to get the current state
625 let comment_id = data.comment_id;
626 let user_id = user.id;
627 let liked_comment = blocking(context.pool(), move |conn| {
628 CommentView::read(conn, comment_id, Some(user_id))
632 let mut res = CommentResponse {
633 comment_view: liked_comment,
638 context.chat_server().do_send(SendComment {
639 op: UserOperation::CreateCommentLike,
640 comment: res.clone(),
644 // strip out the recipient_ids, so that
645 // users don't get double notifs
646 res.recipient_ids = Vec::new();
652 #[async_trait::async_trait(?Send)]
653 impl Perform for GetComments {
654 type Response = GetCommentsResponse;
658 context: &Data<LemmyContext>,
659 _websocket_id: Option<ConnectionId>,
660 ) -> Result<GetCommentsResponse, LemmyError> {
661 let data: &GetComments = &self;
662 let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
663 let user_id = user.map(|u| u.id);
665 let type_ = ListingType::from_str(&data.type_)?;
666 let sort = SortType::from_str(&data.sort)?;
668 let community_id = data.community_id;
669 let community_name = data.community_name.to_owned();
670 let page = data.page;
671 let limit = data.limit;
672 let comments = blocking(context.pool(), move |conn| {
673 CommentQueryBuilder::create(conn)
676 .community_id(community_id)
677 .community_name(community_name)
684 let comments = match comments {
685 Ok(comments) => comments,
686 Err(_) => return Err(APIError::err("couldnt_get_comments").into()),
689 Ok(GetCommentsResponse { comments })
693 /// Creates a comment report and notifies the moderators of the community
694 #[async_trait::async_trait(?Send)]
695 impl Perform for CreateCommentReport {
696 type Response = CreateCommentReportResponse;
700 context: &Data<LemmyContext>,
701 websocket_id: Option<ConnectionId>,
702 ) -> Result<CreateCommentReportResponse, LemmyError> {
703 let data: &CreateCommentReport = &self;
704 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
706 // check size of report and check for whitespace
707 let reason = data.reason.trim();
708 if reason.is_empty() {
709 return Err(APIError::err("report_reason_required").into());
711 if reason.len() > 1000 {
712 return Err(APIError::err("report_too_long").into());
715 let user_id = user.id;
716 let comment_id = data.comment_id;
717 let comment_view = blocking(context.pool(), move |conn| {
718 CommentView::read(&conn, comment_id, None)
722 check_community_ban(user_id, comment_view.community.id, context.pool()).await?;
724 let report_form = CommentReportForm {
727 original_comment_text: comment_view.comment.content,
728 reason: data.reason.to_owned(),
731 let report = match blocking(context.pool(), move |conn| {
732 CommentReport::report(conn, &report_form)
736 Ok(report) => report,
737 Err(_e) => return Err(APIError::err("couldnt_create_report").into()),
740 let res = CreateCommentReportResponse { success: true };
742 context.chat_server().do_send(SendUserRoomMessage {
743 op: UserOperation::CreateCommentReport,
744 response: res.clone(),
745 recipient_id: user.id,
749 context.chat_server().do_send(SendModRoomMessage {
750 op: UserOperation::CreateCommentReport,
752 community_id: comment_view.community.id,
760 /// Resolves or unresolves a comment report and notifies the moderators of the community
761 #[async_trait::async_trait(?Send)]
762 impl Perform for ResolveCommentReport {
763 type Response = ResolveCommentReportResponse;
767 context: &Data<LemmyContext>,
768 websocket_id: Option<ConnectionId>,
769 ) -> Result<ResolveCommentReportResponse, LemmyError> {
770 let data: &ResolveCommentReport = &self;
771 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
773 let report_id = data.report_id;
774 let report = blocking(context.pool(), move |conn| {
775 CommentReportView::read(&conn, report_id)
779 let user_id = user.id;
780 is_mod_or_admin(context.pool(), user_id, report.community.id).await?;
782 let resolved = data.resolved;
783 let resolve_fun = move |conn: &'_ _| {
785 CommentReport::resolve(conn, report_id, user_id)
787 CommentReport::unresolve(conn, report_id, user_id)
791 if blocking(context.pool(), resolve_fun).await?.is_err() {
792 return Err(APIError::err("couldnt_resolve_report").into());
795 let report_id = data.report_id;
796 let res = ResolveCommentReportResponse {
801 context.chat_server().do_send(SendModRoomMessage {
802 op: UserOperation::ResolveCommentReport,
803 response: res.clone(),
804 community_id: report.community.id,
812 /// Lists comment reports for a community if an id is supplied
813 /// or returns all comment reports for communities a user moderates
814 #[async_trait::async_trait(?Send)]
815 impl Perform for ListCommentReports {
816 type Response = ListCommentReportsResponse;
820 context: &Data<LemmyContext>,
821 websocket_id: Option<ConnectionId>,
822 ) -> Result<ListCommentReportsResponse, LemmyError> {
823 let data: &ListCommentReports = &self;
824 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
826 let user_id = user.id;
827 let community_id = data.community;
829 collect_moderated_communities(user_id, community_id, context.pool()).await?;
831 let page = data.page;
832 let limit = data.limit;
833 let comments = blocking(context.pool(), move |conn| {
834 CommentReportQueryBuilder::create(conn)
835 .community_ids(community_ids)
842 let res = ListCommentReportsResponse { comments };
844 context.chat_server().do_send(SendUserRoomMessage {
845 op: UserOperation::ListCommentReports,
846 response: res.clone(),
847 recipient_id: user.id,