3 check_downvotes_enabled,
4 collect_moderated_communities,
11 use actix_web::web::Data;
12 use lemmy_apub::{ApubLikeableType, ApubObjectType};
15 comment::{Comment, CommentForm, CommentLike, CommentLikeForm, CommentSaved, CommentSavedForm},
16 comment_report::{CommentReport, CommentReportForm},
17 moderator::{ModRemoveComment, ModRemoveCommentForm},
20 comment_report_view::{CommentReportQueryBuilder, CommentReportView},
21 comment_view::{CommentQueryBuilder, CommentView},
30 use lemmy_structs::{blocking, comment::*, send_local_notifs};
32 apub::{make_apub_endpoint, EndpointType},
33 utils::{remove_slurs, scrape_text_for_mentions},
38 use lemmy_websocket::{
39 messages::{SendComment, SendModRoomMessage, SendUserRoomMessage},
43 use std::str::FromStr;
45 #[async_trait::async_trait(?Send)]
46 impl Perform for CreateComment {
47 type Response = CommentResponse;
51 context: &Data<LemmyContext>,
52 websocket_id: Option<ConnectionId>,
53 ) -> Result<CommentResponse, LemmyError> {
54 let data: &CreateComment = &self;
55 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
57 let content_slurs_removed = remove_slurs(&data.content.to_owned());
59 let comment_form = CommentForm {
60 content: content_slurs_removed,
61 parent_id: data.parent_id.to_owned(),
62 post_id: data.post_id,
73 // Check for a community ban
74 let post_id = data.post_id;
75 let post = get_post(post_id, context.pool()).await?;
77 check_community_ban(user.id, post.community_id, context.pool()).await?;
79 // Check if post is locked, no new comments
81 return Err(APIError::err("locked").into());
85 let comment_form2 = comment_form.clone();
86 let inserted_comment = match blocking(context.pool(), move |conn| {
87 Comment::create(&conn, &comment_form2)
91 Ok(comment) => comment,
92 Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
95 // Necessary to update the ap_id
96 let inserted_comment_id = inserted_comment.id;
97 let updated_comment: Comment = match blocking(context.pool(), move |conn| {
99 make_apub_endpoint(EndpointType::Comment, &inserted_comment_id.to_string()).to_string();
100 Comment::update_ap_id(&conn, inserted_comment_id, apub_id)
104 Ok(comment) => comment,
105 Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
108 updated_comment.send_create(&user, context).await?;
110 // Scan the comment for user mentions, add those rows
111 let post_id = post.id;
112 let mentions = scrape_text_for_mentions(&comment_form.content);
113 let recipient_ids = send_local_notifs(
115 updated_comment.clone(),
123 // You like your own comment by default
124 let like_form = CommentLikeForm {
125 comment_id: inserted_comment.id,
131 let like = move |conn: &'_ _| CommentLike::like(&conn, &like_form);
132 if blocking(context.pool(), like).await?.is_err() {
133 return Err(APIError::err("couldnt_like_comment").into());
136 updated_comment.send_like(&user, context).await?;
138 let user_id = user.id;
139 let comment_view = blocking(context.pool(), move |conn| {
140 CommentView::read(&conn, inserted_comment.id, Some(user_id))
144 let mut res = CommentResponse {
147 form_id: data.form_id.to_owned(),
150 context.chat_server().do_send(SendComment {
151 op: UserOperation::CreateComment,
152 comment: res.clone(),
156 // strip out the recipient_ids, so that
157 // users don't get double notifs
158 // TODO Do this in a different way
159 res.recipient_ids = Vec::new();
165 #[async_trait::async_trait(?Send)]
166 impl Perform for EditComment {
167 type Response = CommentResponse;
171 context: &Data<LemmyContext>,
172 websocket_id: Option<ConnectionId>,
173 ) -> Result<CommentResponse, LemmyError> {
174 let data: &EditComment = &self;
175 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
177 let edit_id = data.edit_id;
178 let orig_comment = blocking(context.pool(), move |conn| {
179 CommentView::read(&conn, edit_id, None)
183 check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
185 // Verify that only the creator can edit
186 if user.id != orig_comment.creator.id {
187 return Err(APIError::err("no_comment_edit_allowed").into());
191 let content_slurs_removed = remove_slurs(&data.content.to_owned());
192 let edit_id = data.edit_id;
193 let updated_comment = match blocking(context.pool(), move |conn| {
194 Comment::update_content(conn, edit_id, &content_slurs_removed)
198 Ok(comment) => comment,
199 Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
202 // Send the apub update
203 updated_comment.send_update(&user, context).await?;
205 // Do the mentions / recipients
206 let updated_comment_content = updated_comment.content.to_owned();
207 let mentions = scrape_text_for_mentions(&updated_comment_content);
208 let recipient_ids = send_local_notifs(
218 let edit_id = data.edit_id;
219 let user_id = user.id;
220 let comment_view = blocking(context.pool(), move |conn| {
221 CommentView::read(conn, edit_id, Some(user_id))
225 let mut res = CommentResponse {
228 form_id: data.form_id.to_owned(),
231 context.chat_server().do_send(SendComment {
232 op: UserOperation::EditComment,
233 comment: res.clone(),
237 // strip out the recipient_ids, so that
238 // users don't get double notifs
240 res.recipient_ids = Vec::new();
246 #[async_trait::async_trait(?Send)]
247 impl Perform for DeleteComment {
248 type Response = CommentResponse;
252 context: &Data<LemmyContext>,
253 websocket_id: Option<ConnectionId>,
254 ) -> Result<CommentResponse, LemmyError> {
255 let data: &DeleteComment = &self;
256 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
258 let edit_id = data.edit_id;
259 let orig_comment = blocking(context.pool(), move |conn| {
260 CommentView::read(&conn, edit_id, None)
264 check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
266 // Verify that only the creator can delete
267 if user.id != orig_comment.creator.id {
268 return Err(APIError::err("no_comment_edit_allowed").into());
272 let deleted = data.deleted;
273 let updated_comment = match blocking(context.pool(), move |conn| {
274 Comment::update_deleted(conn, edit_id, deleted)
278 Ok(comment) => comment,
279 Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
282 // Send the apub message
284 updated_comment.send_delete(&user, context).await?;
286 updated_comment.send_undo_delete(&user, context).await?;
290 let edit_id = data.edit_id;
291 let user_id = user.id;
292 let comment_view = blocking(context.pool(), move |conn| {
293 CommentView::read(conn, edit_id, Some(user_id))
297 // Build the recipients
298 let comment_view_2 = comment_view.clone();
299 let mentions = vec![];
300 let recipient_ids = send_local_notifs(
310 let mut res = CommentResponse {
313 form_id: None, // TODO a comment delete might clear forms?
316 context.chat_server().do_send(SendComment {
317 op: UserOperation::DeleteComment,
318 comment: res.clone(),
322 // strip out the recipient_ids, so that
323 // users don't get double notifs
325 res.recipient_ids = Vec::new();
331 #[async_trait::async_trait(?Send)]
332 impl Perform for RemoveComment {
333 type Response = CommentResponse;
337 context: &Data<LemmyContext>,
338 websocket_id: Option<ConnectionId>,
339 ) -> Result<CommentResponse, LemmyError> {
340 let data: &RemoveComment = &self;
341 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
343 let edit_id = data.edit_id;
344 let orig_comment = blocking(context.pool(), move |conn| {
345 CommentView::read(&conn, edit_id, None)
349 check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
351 // Verify that only a mod or admin can remove
352 is_mod_or_admin(context.pool(), user.id, orig_comment.community.id).await?;
355 let removed = data.removed;
356 let updated_comment = match blocking(context.pool(), move |conn| {
357 Comment::update_removed(conn, edit_id, removed)
361 Ok(comment) => comment,
362 Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
366 let form = ModRemoveCommentForm {
367 mod_user_id: user.id,
368 comment_id: data.edit_id,
369 removed: Some(removed),
370 reason: data.reason.to_owned(),
372 blocking(context.pool(), move |conn| {
373 ModRemoveComment::create(conn, &form)
377 // Send the apub message
379 updated_comment.send_remove(&user, context).await?;
381 updated_comment.send_undo_remove(&user, context).await?;
385 let edit_id = data.edit_id;
386 let user_id = user.id;
387 let comment_view = blocking(context.pool(), move |conn| {
388 CommentView::read(conn, edit_id, Some(user_id))
392 // Build the recipients
393 let comment_view_2 = comment_view.clone();
395 let mentions = vec![];
396 let recipient_ids = send_local_notifs(
406 let mut res = CommentResponse {
409 form_id: None, // TODO maybe this might clear other forms
412 context.chat_server().do_send(SendComment {
413 op: UserOperation::RemoveComment,
414 comment: res.clone(),
418 // strip out the recipient_ids, so that
419 // users don't get double notifs
421 res.recipient_ids = Vec::new();
427 #[async_trait::async_trait(?Send)]
428 impl Perform for MarkCommentAsRead {
429 type Response = CommentResponse;
433 context: &Data<LemmyContext>,
434 _websocket_id: Option<ConnectionId>,
435 ) -> Result<CommentResponse, LemmyError> {
436 let data: &MarkCommentAsRead = &self;
437 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
439 let comment_id = data.comment_id;
440 let orig_comment = blocking(context.pool(), move |conn| {
441 CommentView::read(&conn, comment_id, None)
445 check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
447 // Verify that only the recipient can mark as read
448 if user.id != orig_comment.get_recipient_id() {
449 return Err(APIError::err("no_comment_edit_allowed").into());
452 // Do the mark as read
453 let read = data.read;
454 match blocking(context.pool(), move |conn| {
455 Comment::update_read(conn, comment_id, read)
459 Ok(comment) => comment,
460 Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
464 let edit_id = data.comment_id;
465 let user_id = user.id;
466 let comment_view = blocking(context.pool(), move |conn| {
467 CommentView::read(conn, edit_id, Some(user_id))
471 let res = CommentResponse {
473 recipient_ids: Vec::new(),
481 #[async_trait::async_trait(?Send)]
482 impl Perform for SaveComment {
483 type Response = CommentResponse;
487 context: &Data<LemmyContext>,
488 _websocket_id: Option<ConnectionId>,
489 ) -> Result<CommentResponse, LemmyError> {
490 let data: &SaveComment = &self;
491 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
493 let comment_saved_form = CommentSavedForm {
494 comment_id: data.comment_id,
499 let save_comment = move |conn: &'_ _| CommentSaved::save(conn, &comment_saved_form);
500 if blocking(context.pool(), save_comment).await?.is_err() {
501 return Err(APIError::err("couldnt_save_comment").into());
504 let unsave_comment = move |conn: &'_ _| CommentSaved::unsave(conn, &comment_saved_form);
505 if blocking(context.pool(), unsave_comment).await?.is_err() {
506 return Err(APIError::err("couldnt_save_comment").into());
510 let comment_id = data.comment_id;
511 let user_id = user.id;
512 let comment_view = blocking(context.pool(), move |conn| {
513 CommentView::read(conn, comment_id, Some(user_id))
519 recipient_ids: Vec::new(),
525 #[async_trait::async_trait(?Send)]
526 impl Perform for CreateCommentLike {
527 type Response = CommentResponse;
531 context: &Data<LemmyContext>,
532 websocket_id: Option<ConnectionId>,
533 ) -> Result<CommentResponse, LemmyError> {
534 let data: &CreateCommentLike = &self;
535 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
537 let mut recipient_ids = Vec::new();
539 // Don't do a downvote if site has downvotes disabled
540 check_downvotes_enabled(data.score, context.pool()).await?;
542 let comment_id = data.comment_id;
543 let orig_comment = blocking(context.pool(), move |conn| {
544 CommentView::read(&conn, comment_id, None)
548 check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
550 // Add parent user to recipients
551 recipient_ids.push(orig_comment.get_recipient_id());
553 let like_form = CommentLikeForm {
554 comment_id: data.comment_id,
555 post_id: orig_comment.post.id,
560 // Remove any likes first
561 let user_id = user.id;
562 blocking(context.pool(), move |conn| {
563 CommentLike::remove(conn, user_id, comment_id)
567 // Only add the like if the score isnt 0
568 let comment = orig_comment.comment;
569 let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
571 let like_form2 = like_form.clone();
572 let like = move |conn: &'_ _| CommentLike::like(conn, &like_form2);
573 if blocking(context.pool(), like).await?.is_err() {
574 return Err(APIError::err("couldnt_like_comment").into());
577 if like_form.score == 1 {
578 comment.send_like(&user, context).await?;
579 } else if like_form.score == -1 {
580 comment.send_dislike(&user, context).await?;
583 comment.send_undo_like(&user, context).await?;
586 // Have to refetch the comment to get the current state
587 let comment_id = data.comment_id;
588 let user_id = user.id;
589 let liked_comment = blocking(context.pool(), move |conn| {
590 CommentView::read(conn, comment_id, Some(user_id))
594 let mut res = CommentResponse {
595 comment_view: liked_comment,
600 context.chat_server().do_send(SendComment {
601 op: UserOperation::CreateCommentLike,
602 comment: res.clone(),
606 // strip out the recipient_ids, so that
607 // users don't get double notifs
608 res.recipient_ids = Vec::new();
615 #[async_trait::async_trait(?Send)]
616 impl Perform for GetComments {
617 type Response = GetCommentsResponse;
621 context: &Data<LemmyContext>,
622 _websocket_id: Option<ConnectionId>,
623 ) -> Result<GetCommentsResponse, LemmyError> {
624 let data: &GetComments = &self;
625 let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
626 let user_id = user.map(|u| u.id);
628 let type_ = ListingType::from_str(&data.type_)?;
629 let sort = SortType::from_str(&data.sort)?;
631 let community_id = data.community_id;
632 let community_name = data.community_name.to_owned();
633 let page = data.page;
634 let limit = data.limit;
635 let comments = blocking(context.pool(), move |conn| {
636 CommentQueryBuilder::create(conn)
639 .community_id(community_id)
640 .community_name(community_name)
647 let comments = match comments {
648 Ok(comments) => comments,
649 Err(_) => return Err(APIError::err("couldnt_get_comments").into()),
652 Ok(GetCommentsResponse { comments })
656 /// Creates a comment report and notifies the moderators of the community
657 #[async_trait::async_trait(?Send)]
658 impl Perform for CreateCommentReport {
659 type Response = CreateCommentReportResponse;
663 context: &Data<LemmyContext>,
664 websocket_id: Option<ConnectionId>,
665 ) -> Result<CreateCommentReportResponse, LemmyError> {
666 let data: &CreateCommentReport = &self;
667 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
669 // check size of report and check for whitespace
670 let reason = data.reason.trim();
671 if reason.is_empty() {
672 return Err(APIError::err("report_reason_required").into());
674 if reason.len() > 1000 {
675 return Err(APIError::err("report_too_long").into());
678 let user_id = user.id;
679 let comment_id = data.comment_id;
680 let comment_view = blocking(context.pool(), move |conn| {
681 CommentView::read(&conn, comment_id, None)
685 check_community_ban(user_id, comment_view.community.id, context.pool()).await?;
687 let report_form = CommentReportForm {
690 original_comment_text: comment_view.comment.content,
691 reason: data.reason.to_owned(),
694 let report = match blocking(context.pool(), move |conn| {
695 CommentReport::report(conn, &report_form)
699 Ok(report) => report,
700 Err(_e) => return Err(APIError::err("couldnt_create_report").into()),
703 let res = CreateCommentReportResponse { success: true };
705 context.chat_server().do_send(SendUserRoomMessage {
706 op: UserOperation::CreateCommentReport,
707 response: res.clone(),
708 recipient_id: user.id,
712 context.chat_server().do_send(SendModRoomMessage {
713 op: UserOperation::CreateCommentReport,
715 community_id: comment_view.community.id,
723 /// Resolves or unresolves a comment report and notifies the moderators of the community
724 #[async_trait::async_trait(?Send)]
725 impl Perform for ResolveCommentReport {
726 type Response = ResolveCommentReportResponse;
730 context: &Data<LemmyContext>,
731 websocket_id: Option<ConnectionId>,
732 ) -> Result<ResolveCommentReportResponse, LemmyError> {
733 let data: &ResolveCommentReport = &self;
734 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
736 let report_id = data.report_id;
737 let report = blocking(context.pool(), move |conn| {
738 CommentReportView::read(&conn, report_id)
742 let user_id = user.id;
743 is_mod_or_admin(context.pool(), user_id, report.community.id).await?;
745 let resolved = data.resolved;
746 let resolve_fun = move |conn: &'_ _| {
748 CommentReport::resolve(conn, report_id, user_id)
750 CommentReport::unresolve(conn, report_id, user_id)
754 if blocking(context.pool(), resolve_fun).await?.is_err() {
755 return Err(APIError::err("couldnt_resolve_report").into());
758 let report_id = data.report_id;
759 let res = ResolveCommentReportResponse {
764 context.chat_server().do_send(SendModRoomMessage {
765 op: UserOperation::ResolveCommentReport,
766 response: res.clone(),
767 community_id: report.community.id,
775 /// Lists comment reports for a community if an id is supplied
776 /// or returns all comment reports for communities a user moderates
777 #[async_trait::async_trait(?Send)]
778 impl Perform for ListCommentReports {
779 type Response = ListCommentReportsResponse;
783 context: &Data<LemmyContext>,
784 websocket_id: Option<ConnectionId>,
785 ) -> Result<ListCommentReportsResponse, LemmyError> {
786 let data: &ListCommentReports = &self;
787 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
789 let user_id = user.id;
790 let community_id = data.community;
792 collect_moderated_communities(user_id, community_id, context.pool()).await?;
794 let page = data.page;
795 let limit = data.limit;
796 let comments = blocking(context.pool(), move |conn| {
797 CommentReportQueryBuilder::create(conn)
798 .community_ids(community_ids)
805 let res = ListCommentReportsResponse { comments };
807 context.chat_server().do_send(SendUserRoomMessage {
808 op: UserOperation::ListCommentReports,
809 response: res.clone(),
810 recipient_id: user.id,