10 apub::{ApubLikeableType, ApubObjectType},
12 messages::{JoinCommunityRoom, SendComment},
17 use actix_web::web::Data;
18 use lemmy_api_structs::{blocking, comment::*, send_local_notifs};
33 apub::{make_apub_endpoint, EndpointType},
34 utils::{remove_slurs, scrape_text_for_mentions},
39 use std::str::FromStr;
41 #[async_trait::async_trait(?Send)]
42 impl Perform for CreateComment {
43 type Response = CommentResponse;
47 context: &Data<LemmyContext>,
48 websocket_id: Option<ConnectionId>,
49 ) -> Result<CommentResponse, LemmyError> {
50 let data: &CreateComment = &self;
51 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
53 let content_slurs_removed = remove_slurs(&data.content.to_owned());
55 let comment_form = CommentForm {
56 content: content_slurs_removed,
57 parent_id: data.parent_id.to_owned(),
58 post_id: data.post_id,
69 // Check for a community ban
70 let post_id = data.post_id;
71 let post = get_post(post_id, context.pool()).await?;
73 check_community_ban(user.id, post.community_id, context.pool()).await?;
75 // Check if post is locked, no new comments
77 return Err(APIError::err("locked").into());
81 let comment_form2 = comment_form.clone();
82 let inserted_comment = match blocking(context.pool(), move |conn| {
83 Comment::create(&conn, &comment_form2)
87 Ok(comment) => comment,
88 Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
91 // Necessary to update the ap_id
92 let inserted_comment_id = inserted_comment.id;
93 let updated_comment: Comment = match blocking(context.pool(), move |conn| {
95 make_apub_endpoint(EndpointType::Comment, &inserted_comment_id.to_string()).to_string();
96 Comment::update_ap_id(&conn, inserted_comment_id, apub_id)
100 Ok(comment) => comment,
101 Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
104 updated_comment.send_create(&user, context).await?;
106 // Scan the comment for user mentions, add those rows
107 let mentions = scrape_text_for_mentions(&comment_form.content);
108 let recipient_ids = send_local_notifs(
110 updated_comment.clone(),
118 // You like your own comment by default
119 let like_form = CommentLikeForm {
120 comment_id: inserted_comment.id,
121 post_id: data.post_id,
126 let like = move |conn: &'_ _| CommentLike::like(&conn, &like_form);
127 if blocking(context.pool(), like).await?.is_err() {
128 return Err(APIError::err("couldnt_like_comment").into());
131 updated_comment.send_like(&user, context).await?;
133 let user_id = user.id;
134 let comment_view = blocking(context.pool(), move |conn| {
135 CommentView::read(&conn, inserted_comment.id, Some(user_id))
139 let mut res = CommentResponse {
140 comment: comment_view,
142 form_id: data.form_id.to_owned(),
145 context.chat_server().do_send(SendComment {
146 op: UserOperation::CreateComment,
147 comment: res.clone(),
151 // strip out the recipient_ids, so that
152 // users don't get double notifs
153 res.recipient_ids = Vec::new();
159 #[async_trait::async_trait(?Send)]
160 impl Perform for EditComment {
161 type Response = CommentResponse;
165 context: &Data<LemmyContext>,
166 websocket_id: Option<ConnectionId>,
167 ) -> Result<CommentResponse, LemmyError> {
168 let data: &EditComment = &self;
169 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
171 let edit_id = data.edit_id;
172 let orig_comment = blocking(context.pool(), move |conn| {
173 CommentView::read(&conn, edit_id, None)
177 check_community_ban(user.id, orig_comment.community_id, context.pool()).await?;
179 // Verify that only the creator can edit
180 if user.id != orig_comment.creator_id {
181 return Err(APIError::err("no_comment_edit_allowed").into());
185 let content_slurs_removed = remove_slurs(&data.content.to_owned());
186 let edit_id = data.edit_id;
187 let updated_comment = match blocking(context.pool(), move |conn| {
188 Comment::update_content(conn, edit_id, &content_slurs_removed)
192 Ok(comment) => comment,
193 Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
196 // Send the apub update
197 updated_comment.send_update(&user, context).await?;
199 // Do the mentions / recipients
200 let post_id = orig_comment.post_id;
201 let post = get_post(post_id, context.pool()).await?;
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 {
223 comment: comment_view,
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
236 res.recipient_ids = Vec::new();
242 #[async_trait::async_trait(?Send)]
243 impl Perform for DeleteComment {
244 type Response = CommentResponse;
248 context: &Data<LemmyContext>,
249 websocket_id: Option<ConnectionId>,
250 ) -> Result<CommentResponse, LemmyError> {
251 let data: &DeleteComment = &self;
252 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
254 let edit_id = data.edit_id;
255 let orig_comment = blocking(context.pool(), move |conn| {
256 CommentView::read(&conn, edit_id, None)
260 check_community_ban(user.id, orig_comment.community_id, context.pool()).await?;
262 // Verify that only the creator can delete
263 if user.id != orig_comment.creator_id {
264 return Err(APIError::err("no_comment_edit_allowed").into());
268 let deleted = data.deleted;
269 let updated_comment = match blocking(context.pool(), move |conn| {
270 Comment::update_deleted(conn, edit_id, deleted)
274 Ok(comment) => comment,
275 Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
278 // Send the apub message
280 updated_comment.send_delete(&user, context).await?;
282 updated_comment.send_undo_delete(&user, context).await?;
286 let edit_id = data.edit_id;
287 let user_id = user.id;
288 let comment_view = blocking(context.pool(), move |conn| {
289 CommentView::read(conn, edit_id, Some(user_id))
293 // Build the recipients
294 let post_id = comment_view.post_id;
295 let post = get_post(post_id, context.pool()).await?;
296 let mentions = vec![];
297 let recipient_ids = send_local_notifs(
307 let mut res = CommentResponse {
308 comment: comment_view,
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
321 res.recipient_ids = Vec::new();
327 #[async_trait::async_trait(?Send)]
328 impl Perform for RemoveComment {
329 type Response = CommentResponse;
333 context: &Data<LemmyContext>,
334 websocket_id: Option<ConnectionId>,
335 ) -> Result<CommentResponse, LemmyError> {
336 let data: &RemoveComment = &self;
337 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
339 let edit_id = data.edit_id;
340 let orig_comment = blocking(context.pool(), move |conn| {
341 CommentView::read(&conn, edit_id, None)
345 check_community_ban(user.id, orig_comment.community_id, context.pool()).await?;
347 // Verify that only a mod or admin can remove
348 is_mod_or_admin(context.pool(), user.id, orig_comment.community_id).await?;
351 let removed = data.removed;
352 let updated_comment = match blocking(context.pool(), move |conn| {
353 Comment::update_removed(conn, edit_id, removed)
357 Ok(comment) => comment,
358 Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
362 let form = ModRemoveCommentForm {
363 mod_user_id: user.id,
364 comment_id: data.edit_id,
365 removed: Some(removed),
366 reason: data.reason.to_owned(),
368 blocking(context.pool(), move |conn| {
369 ModRemoveComment::create(conn, &form)
373 // Send the apub message
375 updated_comment.send_remove(&user, context).await?;
377 updated_comment.send_undo_remove(&user, context).await?;
381 let edit_id = data.edit_id;
382 let user_id = user.id;
383 let comment_view = blocking(context.pool(), move |conn| {
384 CommentView::read(conn, edit_id, Some(user_id))
388 // Build the recipients
389 let post_id = comment_view.post_id;
390 let post = get_post(post_id, context.pool()).await?;
391 let mentions = vec![];
392 let recipient_ids = send_local_notifs(
402 let mut res = CommentResponse {
403 comment: comment_view,
408 context.chat_server().do_send(SendComment {
409 op: UserOperation::RemoveComment,
410 comment: res.clone(),
414 // strip out the recipient_ids, so that
415 // users don't get double notifs
416 res.recipient_ids = Vec::new();
422 #[async_trait::async_trait(?Send)]
423 impl Perform for MarkCommentAsRead {
424 type Response = CommentResponse;
428 context: &Data<LemmyContext>,
429 _websocket_id: Option<ConnectionId>,
430 ) -> Result<CommentResponse, LemmyError> {
431 let data: &MarkCommentAsRead = &self;
432 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
434 let edit_id = data.edit_id;
435 let orig_comment = blocking(context.pool(), move |conn| {
436 CommentView::read(&conn, edit_id, None)
440 check_community_ban(user.id, orig_comment.community_id, context.pool()).await?;
442 // Verify that only the recipient can mark as read
443 // Needs to fetch the parent comment / post to get the recipient
444 let parent_id = orig_comment.parent_id;
447 let parent_comment = blocking(context.pool(), move |conn| {
448 CommentView::read(&conn, pid, None)
451 if user.id != parent_comment.creator_id {
452 return Err(APIError::err("no_comment_edit_allowed").into());
456 let parent_post_id = orig_comment.post_id;
458 blocking(context.pool(), move |conn| Post::read(conn, parent_post_id)).await??;
459 if user.id != parent_post.creator_id {
460 return Err(APIError::err("no_comment_edit_allowed").into());
465 // Do the mark as read
466 let read = data.read;
467 match blocking(context.pool(), move |conn| {
468 Comment::update_read(conn, edit_id, read)
472 Ok(comment) => comment,
473 Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
477 let edit_id = data.edit_id;
478 let user_id = user.id;
479 let comment_view = blocking(context.pool(), move |conn| {
480 CommentView::read(conn, edit_id, Some(user_id))
484 let res = CommentResponse {
485 comment: comment_view,
486 recipient_ids: Vec::new(),
494 #[async_trait::async_trait(?Send)]
495 impl Perform for SaveComment {
496 type Response = CommentResponse;
500 context: &Data<LemmyContext>,
501 _websocket_id: Option<ConnectionId>,
502 ) -> Result<CommentResponse, LemmyError> {
503 let data: &SaveComment = &self;
504 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
506 let comment_saved_form = CommentSavedForm {
507 comment_id: data.comment_id,
512 let save_comment = move |conn: &'_ _| CommentSaved::save(conn, &comment_saved_form);
513 if blocking(context.pool(), save_comment).await?.is_err() {
514 return Err(APIError::err("couldnt_save_comment").into());
517 let unsave_comment = move |conn: &'_ _| CommentSaved::unsave(conn, &comment_saved_form);
518 if blocking(context.pool(), unsave_comment).await?.is_err() {
519 return Err(APIError::err("couldnt_save_comment").into());
523 let comment_id = data.comment_id;
524 let user_id = user.id;
525 let comment_view = blocking(context.pool(), move |conn| {
526 CommentView::read(conn, comment_id, Some(user_id))
531 comment: comment_view,
532 recipient_ids: Vec::new(),
538 #[async_trait::async_trait(?Send)]
539 impl Perform for CreateCommentLike {
540 type Response = CommentResponse;
544 context: &Data<LemmyContext>,
545 websocket_id: Option<ConnectionId>,
546 ) -> Result<CommentResponse, LemmyError> {
547 let data: &CreateCommentLike = &self;
548 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
550 let mut recipient_ids = Vec::new();
552 // Don't do a downvote if site has downvotes disabled
553 if data.score == -1 {
554 let site = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
555 if !site.enable_downvotes {
556 return Err(APIError::err("downvotes_disabled").into());
560 let comment_id = data.comment_id;
561 let orig_comment = blocking(context.pool(), move |conn| {
562 CommentView::read(&conn, comment_id, None)
566 let post_id = orig_comment.post_id;
567 let post = get_post(post_id, context.pool()).await?;
568 check_community_ban(user.id, post.community_id, context.pool()).await?;
570 let comment_id = data.comment_id;
571 let comment = blocking(context.pool(), move |conn| Comment::read(conn, comment_id)).await??;
573 // Add to recipient ids
574 match comment.parent_id {
577 blocking(context.pool(), move |conn| Comment::read(conn, parent_id)).await??;
578 if parent_comment.creator_id != user.id {
579 let parent_user = blocking(context.pool(), move |conn| {
580 User_::read(conn, parent_comment.creator_id)
583 recipient_ids.push(parent_user.id);
587 recipient_ids.push(post.creator_id);
591 let like_form = CommentLikeForm {
592 comment_id: data.comment_id,
598 // Remove any likes first
599 let user_id = user.id;
600 blocking(context.pool(), move |conn| {
601 CommentLike::remove(conn, user_id, comment_id)
605 // Only add the like if the score isnt 0
606 let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
608 let like_form2 = like_form.clone();
609 let like = move |conn: &'_ _| CommentLike::like(conn, &like_form2);
610 if blocking(context.pool(), like).await?.is_err() {
611 return Err(APIError::err("couldnt_like_comment").into());
614 if like_form.score == 1 {
615 comment.send_like(&user, context).await?;
616 } else if like_form.score == -1 {
617 comment.send_dislike(&user, context).await?;
620 comment.send_undo_like(&user, context).await?;
623 // Have to refetch the comment to get the current state
624 let comment_id = data.comment_id;
625 let user_id = user.id;
626 let liked_comment = blocking(context.pool(), move |conn| {
627 CommentView::read(conn, comment_id, Some(user_id))
631 let mut res = CommentResponse {
632 comment: liked_comment,
637 context.chat_server().do_send(SendComment {
638 op: UserOperation::CreateCommentLike,
639 comment: res.clone(),
643 // strip out the recipient_ids, so that
644 // users don't get double notifs
645 res.recipient_ids = Vec::new();
651 #[async_trait::async_trait(?Send)]
652 impl Perform for GetComments {
653 type Response = GetCommentsResponse;
657 context: &Data<LemmyContext>,
658 websocket_id: Option<ConnectionId>,
659 ) -> Result<GetCommentsResponse, LemmyError> {
660 let data: &GetComments = &self;
661 let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
662 let user_id = user.map(|u| u.id);
664 let type_ = ListingType::from_str(&data.type_)?;
665 let sort = SortType::from_str(&data.sort)?;
667 let community_id = data.community_id;
668 let page = data.page;
669 let limit = data.limit;
670 let comments = blocking(context.pool(), move |conn| {
671 CommentQueryBuilder::create(conn)
674 .for_community_id(community_id)
681 let comments = match comments {
682 Ok(comments) => comments,
683 Err(_) => return Err(APIError::err("couldnt_get_comments").into()),
686 if let Some(id) = websocket_id {
687 // You don't need to join the specific community room, bc this is already handled by
689 if data.community_id.is_none() {
690 // 0 is the "all" community
691 context.chat_server().do_send(JoinCommunityRoom {
698 Ok(GetCommentsResponse { comments })