3 check_downvotes_enabled,
4 collect_moderated_communities,
11 use actix_web::web::Data;
12 use lemmy_apub::{ApubLikeableType, ApubObjectType};
14 source::comment::Comment_,
16 comment_report_view::{CommentReportQueryBuilder, CommentReportView},
17 comment_view::{CommentQueryBuilder, CommentView},
26 use lemmy_db_schema::source::{comment::*, comment_report::*, moderator::*};
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 post_id = post.id;
109 let mentions = scrape_text_for_mentions(&comment_form.content);
110 let recipient_ids = send_local_notifs(
112 updated_comment.clone(),
120 // You like your own comment by default
121 let like_form = CommentLikeForm {
122 comment_id: inserted_comment.id,
128 let like = move |conn: &'_ _| CommentLike::like(&conn, &like_form);
129 if blocking(context.pool(), like).await?.is_err() {
130 return Err(APIError::err("couldnt_like_comment").into());
133 updated_comment.send_like(&user, context).await?;
135 let user_id = user.id;
136 let comment_view = blocking(context.pool(), move |conn| {
137 CommentView::read(&conn, inserted_comment.id, Some(user_id))
141 let mut res = CommentResponse {
144 form_id: data.form_id.to_owned(),
147 context.chat_server().do_send(SendComment {
148 op: UserOperation::CreateComment,
149 comment: res.clone(),
153 // strip out the recipient_ids, so that
154 // users don't get double notifs
155 // TODO Do this in a different way
156 res.recipient_ids = Vec::new();
162 #[async_trait::async_trait(?Send)]
163 impl Perform for EditComment {
164 type Response = CommentResponse;
168 context: &Data<LemmyContext>,
169 websocket_id: Option<ConnectionId>,
170 ) -> Result<CommentResponse, LemmyError> {
171 let data: &EditComment = &self;
172 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
174 let edit_id = data.edit_id;
175 let orig_comment = blocking(context.pool(), move |conn| {
176 CommentView::read(&conn, edit_id, None)
180 check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
182 // Verify that only the creator can edit
183 if user.id != orig_comment.creator.id {
184 return Err(APIError::err("no_comment_edit_allowed").into());
188 let content_slurs_removed = remove_slurs(&data.content.to_owned());
189 let edit_id = data.edit_id;
190 let updated_comment = match blocking(context.pool(), move |conn| {
191 Comment::update_content(conn, edit_id, &content_slurs_removed)
195 Ok(comment) => comment,
196 Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
199 // Send the apub update
200 updated_comment.send_update(&user, context).await?;
202 // Do the mentions / recipients
203 let updated_comment_content = updated_comment.content.to_owned();
204 let mentions = scrape_text_for_mentions(&updated_comment_content);
205 let recipient_ids = send_local_notifs(
215 let edit_id = data.edit_id;
216 let user_id = user.id;
217 let comment_view = blocking(context.pool(), move |conn| {
218 CommentView::read(conn, edit_id, Some(user_id))
222 let mut res = CommentResponse {
225 form_id: data.form_id.to_owned(),
228 context.chat_server().do_send(SendComment {
229 op: UserOperation::EditComment,
230 comment: res.clone(),
234 // strip out the recipient_ids, so that
235 // 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 comment_view_2 = comment_view.clone();
296 let mentions = vec![];
297 let recipient_ids = send_local_notifs(
307 let mut res = CommentResponse {
310 form_id: None, // TODO a comment delete might clear forms?
313 context.chat_server().do_send(SendComment {
314 op: UserOperation::DeleteComment,
315 comment: res.clone(),
319 // strip out the recipient_ids, so that
320 // 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 comment_view_2 = comment_view.clone();
392 let mentions = vec![];
393 let recipient_ids = send_local_notifs(
403 let mut res = CommentResponse {
406 form_id: None, // TODO maybe this might clear other forms
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
418 res.recipient_ids = Vec::new();
424 #[async_trait::async_trait(?Send)]
425 impl Perform for MarkCommentAsRead {
426 type Response = CommentResponse;
430 context: &Data<LemmyContext>,
431 _websocket_id: Option<ConnectionId>,
432 ) -> Result<CommentResponse, LemmyError> {
433 let data: &MarkCommentAsRead = &self;
434 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
436 let comment_id = data.comment_id;
437 let orig_comment = blocking(context.pool(), move |conn| {
438 CommentView::read(&conn, comment_id, None)
442 check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
444 // Verify that only the recipient can mark as read
445 if user.id != orig_comment.get_recipient_id() {
446 return Err(APIError::err("no_comment_edit_allowed").into());
449 // Do the mark as read
450 let read = data.read;
451 match blocking(context.pool(), move |conn| {
452 Comment::update_read(conn, comment_id, read)
456 Ok(comment) => comment,
457 Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
461 let edit_id = data.comment_id;
462 let user_id = user.id;
463 let comment_view = blocking(context.pool(), move |conn| {
464 CommentView::read(conn, edit_id, Some(user_id))
468 let res = CommentResponse {
470 recipient_ids: Vec::new(),
478 #[async_trait::async_trait(?Send)]
479 impl Perform for SaveComment {
480 type Response = CommentResponse;
484 context: &Data<LemmyContext>,
485 _websocket_id: Option<ConnectionId>,
486 ) -> Result<CommentResponse, LemmyError> {
487 let data: &SaveComment = &self;
488 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
490 let comment_saved_form = CommentSavedForm {
491 comment_id: data.comment_id,
496 let save_comment = move |conn: &'_ _| CommentSaved::save(conn, &comment_saved_form);
497 if blocking(context.pool(), save_comment).await?.is_err() {
498 return Err(APIError::err("couldnt_save_comment").into());
501 let unsave_comment = move |conn: &'_ _| CommentSaved::unsave(conn, &comment_saved_form);
502 if blocking(context.pool(), unsave_comment).await?.is_err() {
503 return Err(APIError::err("couldnt_save_comment").into());
507 let comment_id = data.comment_id;
508 let user_id = user.id;
509 let comment_view = blocking(context.pool(), move |conn| {
510 CommentView::read(conn, comment_id, Some(user_id))
516 recipient_ids: Vec::new(),
522 #[async_trait::async_trait(?Send)]
523 impl Perform for CreateCommentLike {
524 type Response = CommentResponse;
528 context: &Data<LemmyContext>,
529 websocket_id: Option<ConnectionId>,
530 ) -> Result<CommentResponse, LemmyError> {
531 let data: &CreateCommentLike = &self;
532 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
534 let mut recipient_ids = Vec::new();
536 // Don't do a downvote if site has downvotes disabled
537 check_downvotes_enabled(data.score, context.pool()).await?;
539 let comment_id = data.comment_id;
540 let orig_comment = blocking(context.pool(), move |conn| {
541 CommentView::read(&conn, comment_id, None)
545 check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
547 // Add parent user to recipients
548 recipient_ids.push(orig_comment.get_recipient_id());
550 let like_form = CommentLikeForm {
551 comment_id: data.comment_id,
552 post_id: orig_comment.post.id,
557 // Remove any likes first
558 let user_id = user.id;
559 blocking(context.pool(), move |conn| {
560 CommentLike::remove(conn, user_id, comment_id)
564 // Only add the like if the score isnt 0
565 let comment = orig_comment.comment;
566 let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
568 let like_form2 = like_form.clone();
569 let like = move |conn: &'_ _| CommentLike::like(conn, &like_form2);
570 if blocking(context.pool(), like).await?.is_err() {
571 return Err(APIError::err("couldnt_like_comment").into());
574 if like_form.score == 1 {
575 comment.send_like(&user, context).await?;
576 } else if like_form.score == -1 {
577 comment.send_dislike(&user, context).await?;
580 comment.send_undo_like(&user, context).await?;
583 // Have to refetch the comment to get the current state
584 let comment_id = data.comment_id;
585 let user_id = user.id;
586 let liked_comment = blocking(context.pool(), move |conn| {
587 CommentView::read(conn, comment_id, Some(user_id))
591 let mut res = CommentResponse {
592 comment_view: liked_comment,
597 context.chat_server().do_send(SendComment {
598 op: UserOperation::CreateCommentLike,
599 comment: res.clone(),
603 // strip out the recipient_ids, so that
604 // users don't get double notifs
605 res.recipient_ids = Vec::new();
612 #[async_trait::async_trait(?Send)]
613 impl Perform for GetComments {
614 type Response = GetCommentsResponse;
618 context: &Data<LemmyContext>,
619 _websocket_id: Option<ConnectionId>,
620 ) -> Result<GetCommentsResponse, LemmyError> {
621 let data: &GetComments = &self;
622 let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
623 let user_id = user.map(|u| u.id);
625 let type_ = ListingType::from_str(&data.type_)?;
626 let sort = SortType::from_str(&data.sort)?;
628 let community_id = data.community_id;
629 let community_name = data.community_name.to_owned();
630 let page = data.page;
631 let limit = data.limit;
632 let comments = blocking(context.pool(), move |conn| {
633 CommentQueryBuilder::create(conn)
636 .community_id(community_id)
637 .community_name(community_name)
644 let comments = match comments {
645 Ok(comments) => comments,
646 Err(_) => return Err(APIError::err("couldnt_get_comments").into()),
649 Ok(GetCommentsResponse { comments })
653 /// Creates a comment report and notifies the moderators of the community
654 #[async_trait::async_trait(?Send)]
655 impl Perform for CreateCommentReport {
656 type Response = CreateCommentReportResponse;
660 context: &Data<LemmyContext>,
661 websocket_id: Option<ConnectionId>,
662 ) -> Result<CreateCommentReportResponse, LemmyError> {
663 let data: &CreateCommentReport = &self;
664 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
666 // check size of report and check for whitespace
667 let reason = data.reason.trim();
668 if reason.is_empty() {
669 return Err(APIError::err("report_reason_required").into());
671 if reason.len() > 1000 {
672 return Err(APIError::err("report_too_long").into());
675 let user_id = user.id;
676 let comment_id = data.comment_id;
677 let comment_view = blocking(context.pool(), move |conn| {
678 CommentView::read(&conn, comment_id, None)
682 check_community_ban(user_id, comment_view.community.id, context.pool()).await?;
684 let report_form = CommentReportForm {
687 original_comment_text: comment_view.comment.content,
688 reason: data.reason.to_owned(),
691 let report = match blocking(context.pool(), move |conn| {
692 CommentReport::report(conn, &report_form)
696 Ok(report) => report,
697 Err(_e) => return Err(APIError::err("couldnt_create_report").into()),
700 let res = CreateCommentReportResponse { success: true };
702 context.chat_server().do_send(SendUserRoomMessage {
703 op: UserOperation::CreateCommentReport,
704 response: res.clone(),
705 recipient_id: user.id,
709 context.chat_server().do_send(SendModRoomMessage {
710 op: UserOperation::CreateCommentReport,
712 community_id: comment_view.community.id,
720 /// Resolves or unresolves a comment report and notifies the moderators of the community
721 #[async_trait::async_trait(?Send)]
722 impl Perform for ResolveCommentReport {
723 type Response = ResolveCommentReportResponse;
727 context: &Data<LemmyContext>,
728 websocket_id: Option<ConnectionId>,
729 ) -> Result<ResolveCommentReportResponse, LemmyError> {
730 let data: &ResolveCommentReport = &self;
731 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
733 let report_id = data.report_id;
734 let report = blocking(context.pool(), move |conn| {
735 CommentReportView::read(&conn, report_id)
739 let user_id = user.id;
740 is_mod_or_admin(context.pool(), user_id, report.community.id).await?;
742 let resolved = data.resolved;
743 let resolve_fun = move |conn: &'_ _| {
745 CommentReport::resolve(conn, report_id, user_id)
747 CommentReport::unresolve(conn, report_id, user_id)
751 if blocking(context.pool(), resolve_fun).await?.is_err() {
752 return Err(APIError::err("couldnt_resolve_report").into());
755 let report_id = data.report_id;
756 let res = ResolveCommentReportResponse {
761 context.chat_server().do_send(SendModRoomMessage {
762 op: UserOperation::ResolveCommentReport,
763 response: res.clone(),
764 community_id: report.community.id,
772 /// Lists comment reports for a community if an id is supplied
773 /// or returns all comment reports for communities a user moderates
774 #[async_trait::async_trait(?Send)]
775 impl Perform for ListCommentReports {
776 type Response = ListCommentReportsResponse;
780 context: &Data<LemmyContext>,
781 websocket_id: Option<ConnectionId>,
782 ) -> Result<ListCommentReportsResponse, LemmyError> {
783 let data: &ListCommentReports = &self;
784 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
786 let user_id = user.id;
787 let community_id = data.community;
789 collect_moderated_communities(user_id, community_id, context.pool()).await?;
791 let page = data.page;
792 let limit = data.limit;
793 let comments = blocking(context.pool(), move |conn| {
794 CommentReportQueryBuilder::create(conn)
795 .community_ids(community_ids)
802 let res = ListCommentReportsResponse { comments };
804 context.chat_server().do_send(SendUserRoomMessage {
805 op: UserOperation::ListCommentReports,
806 response: res.clone(),
807 recipient_id: user.id,