9 use actix_web::web::Data;
10 use lemmy_apub::{ApubLikeableType, ApubObjectType};
24 use lemmy_structs::{blocking, comment::*, send_local_notifs};
26 apub::{make_apub_endpoint, EndpointType},
27 utils::{remove_slurs, scrape_text_for_mentions},
32 use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation};
33 use std::str::FromStr;
35 #[async_trait::async_trait(?Send)]
36 impl Perform for CreateComment {
37 type Response = CommentResponse;
41 context: &Data<LemmyContext>,
42 websocket_id: Option<ConnectionId>,
43 ) -> Result<CommentResponse, LemmyError> {
44 let data: &CreateComment = &self;
45 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
47 let content_slurs_removed = remove_slurs(&data.content.to_owned());
49 let comment_form = CommentForm {
50 content: content_slurs_removed,
51 parent_id: data.parent_id.to_owned(),
52 post_id: data.post_id,
63 // Check for a community ban
64 let post_id = data.post_id;
65 let post = get_post(post_id, context.pool()).await?;
67 check_community_ban(user.id, post.community_id, context.pool()).await?;
69 // Check if post is locked, no new comments
71 return Err(APIError::err("locked").into());
75 let comment_form2 = comment_form.clone();
76 let inserted_comment = match blocking(context.pool(), move |conn| {
77 Comment::create(&conn, &comment_form2)
81 Ok(comment) => comment,
82 Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
85 // Necessary to update the ap_id
86 let inserted_comment_id = inserted_comment.id;
87 let updated_comment: Comment = match blocking(context.pool(), move |conn| {
89 make_apub_endpoint(EndpointType::Comment, &inserted_comment_id.to_string()).to_string();
90 Comment::update_ap_id(&conn, inserted_comment_id, apub_id)
94 Ok(comment) => comment,
95 Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
98 updated_comment.send_create(&user, context).await?;
100 // Scan the comment for user mentions, add those rows
101 let mentions = scrape_text_for_mentions(&comment_form.content);
102 let recipient_ids = send_local_notifs(
104 updated_comment.clone(),
112 // You like your own comment by default
113 let like_form = CommentLikeForm {
114 comment_id: inserted_comment.id,
115 post_id: data.post_id,
120 let like = move |conn: &'_ _| CommentLike::like(&conn, &like_form);
121 if blocking(context.pool(), like).await?.is_err() {
122 return Err(APIError::err("couldnt_like_comment").into());
125 updated_comment.send_like(&user, context).await?;
127 let user_id = user.id;
128 let comment_view = blocking(context.pool(), move |conn| {
129 CommentView::read(&conn, inserted_comment.id, Some(user_id))
133 let mut res = CommentResponse {
134 comment: comment_view,
136 form_id: data.form_id.to_owned(),
139 context.chat_server().do_send(SendComment {
140 op: UserOperation::CreateComment,
141 comment: res.clone(),
145 // strip out the recipient_ids, so that
146 // users don't get double notifs
147 res.recipient_ids = Vec::new();
153 #[async_trait::async_trait(?Send)]
154 impl Perform for EditComment {
155 type Response = CommentResponse;
159 context: &Data<LemmyContext>,
160 websocket_id: Option<ConnectionId>,
161 ) -> Result<CommentResponse, LemmyError> {
162 let data: &EditComment = &self;
163 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
165 let edit_id = data.edit_id;
166 let orig_comment = blocking(context.pool(), move |conn| {
167 CommentView::read(&conn, edit_id, None)
171 check_community_ban(user.id, orig_comment.community_id, context.pool()).await?;
173 // Verify that only the creator can edit
174 if user.id != orig_comment.creator_id {
175 return Err(APIError::err("no_comment_edit_allowed").into());
179 let content_slurs_removed = remove_slurs(&data.content.to_owned());
180 let edit_id = data.edit_id;
181 let updated_comment = match blocking(context.pool(), move |conn| {
182 Comment::update_content(conn, edit_id, &content_slurs_removed)
186 Ok(comment) => comment,
187 Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
190 // Send the apub update
191 updated_comment.send_update(&user, context).await?;
193 // Do the mentions / recipients
194 let post_id = orig_comment.post_id;
195 let post = get_post(post_id, context.pool()).await?;
197 let updated_comment_content = updated_comment.content.to_owned();
198 let mentions = scrape_text_for_mentions(&updated_comment_content);
199 let recipient_ids = send_local_notifs(
209 let edit_id = data.edit_id;
210 let user_id = user.id;
211 let comment_view = blocking(context.pool(), move |conn| {
212 CommentView::read(conn, edit_id, Some(user_id))
216 let mut res = CommentResponse {
217 comment: comment_view,
219 form_id: data.form_id.to_owned(),
222 context.chat_server().do_send(SendComment {
223 op: UserOperation::EditComment,
224 comment: res.clone(),
228 // strip out the recipient_ids, so that
229 // users don't get double notifs
230 res.recipient_ids = Vec::new();
236 #[async_trait::async_trait(?Send)]
237 impl Perform for DeleteComment {
238 type Response = CommentResponse;
242 context: &Data<LemmyContext>,
243 websocket_id: Option<ConnectionId>,
244 ) -> Result<CommentResponse, LemmyError> {
245 let data: &DeleteComment = &self;
246 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
248 let edit_id = data.edit_id;
249 let orig_comment = blocking(context.pool(), move |conn| {
250 CommentView::read(&conn, edit_id, None)
254 check_community_ban(user.id, orig_comment.community_id, context.pool()).await?;
256 // Verify that only the creator can delete
257 if user.id != orig_comment.creator_id {
258 return Err(APIError::err("no_comment_edit_allowed").into());
262 let deleted = data.deleted;
263 let updated_comment = match blocking(context.pool(), move |conn| {
264 Comment::update_deleted(conn, edit_id, deleted)
268 Ok(comment) => comment,
269 Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
272 // Send the apub message
274 updated_comment.send_delete(&user, context).await?;
276 updated_comment.send_undo_delete(&user, context).await?;
280 let edit_id = data.edit_id;
281 let user_id = user.id;
282 let comment_view = blocking(context.pool(), move |conn| {
283 CommentView::read(conn, edit_id, Some(user_id))
287 // Build the recipients
288 let post_id = comment_view.post_id;
289 let post = get_post(post_id, context.pool()).await?;
290 let mentions = vec![];
291 let recipient_ids = send_local_notifs(
301 let mut res = CommentResponse {
302 comment: comment_view,
307 context.chat_server().do_send(SendComment {
308 op: UserOperation::DeleteComment,
309 comment: res.clone(),
313 // strip out the recipient_ids, so that
314 // users don't get double notifs
315 res.recipient_ids = Vec::new();
321 #[async_trait::async_trait(?Send)]
322 impl Perform for RemoveComment {
323 type Response = CommentResponse;
327 context: &Data<LemmyContext>,
328 websocket_id: Option<ConnectionId>,
329 ) -> Result<CommentResponse, LemmyError> {
330 let data: &RemoveComment = &self;
331 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
333 let edit_id = data.edit_id;
334 let orig_comment = blocking(context.pool(), move |conn| {
335 CommentView::read(&conn, edit_id, None)
339 check_community_ban(user.id, orig_comment.community_id, context.pool()).await?;
341 // Verify that only a mod or admin can remove
342 is_mod_or_admin(context.pool(), user.id, orig_comment.community_id).await?;
345 let removed = data.removed;
346 let updated_comment = match blocking(context.pool(), move |conn| {
347 Comment::update_removed(conn, edit_id, removed)
351 Ok(comment) => comment,
352 Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
356 let form = ModRemoveCommentForm {
357 mod_user_id: user.id,
358 comment_id: data.edit_id,
359 removed: Some(removed),
360 reason: data.reason.to_owned(),
362 blocking(context.pool(), move |conn| {
363 ModRemoveComment::create(conn, &form)
367 // Send the apub message
369 updated_comment.send_remove(&user, context).await?;
371 updated_comment.send_undo_remove(&user, context).await?;
375 let edit_id = data.edit_id;
376 let user_id = user.id;
377 let comment_view = blocking(context.pool(), move |conn| {
378 CommentView::read(conn, edit_id, Some(user_id))
382 // Build the recipients
383 let post_id = comment_view.post_id;
384 let post = get_post(post_id, context.pool()).await?;
385 let mentions = vec![];
386 let recipient_ids = send_local_notifs(
396 let mut res = CommentResponse {
397 comment: comment_view,
402 context.chat_server().do_send(SendComment {
403 op: UserOperation::RemoveComment,
404 comment: res.clone(),
408 // strip out the recipient_ids, so that
409 // users don't get double notifs
410 res.recipient_ids = Vec::new();
416 #[async_trait::async_trait(?Send)]
417 impl Perform for MarkCommentAsRead {
418 type Response = CommentResponse;
422 context: &Data<LemmyContext>,
423 _websocket_id: Option<ConnectionId>,
424 ) -> Result<CommentResponse, LemmyError> {
425 let data: &MarkCommentAsRead = &self;
426 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
428 let edit_id = data.edit_id;
429 let orig_comment = blocking(context.pool(), move |conn| {
430 CommentView::read(&conn, edit_id, None)
434 check_community_ban(user.id, orig_comment.community_id, context.pool()).await?;
436 // Verify that only the recipient can mark as read
437 // Needs to fetch the parent comment / post to get the recipient
438 let parent_id = orig_comment.parent_id;
441 let parent_comment = blocking(context.pool(), move |conn| {
442 CommentView::read(&conn, pid, None)
445 if user.id != parent_comment.creator_id {
446 return Err(APIError::err("no_comment_edit_allowed").into());
450 let parent_post_id = orig_comment.post_id;
452 blocking(context.pool(), move |conn| Post::read(conn, parent_post_id)).await??;
453 if user.id != parent_post.creator_id {
454 return Err(APIError::err("no_comment_edit_allowed").into());
459 // Do the mark as read
460 let read = data.read;
461 match blocking(context.pool(), move |conn| {
462 Comment::update_read(conn, edit_id, read)
466 Ok(comment) => comment,
467 Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
471 let edit_id = data.edit_id;
472 let user_id = user.id;
473 let comment_view = blocking(context.pool(), move |conn| {
474 CommentView::read(conn, edit_id, Some(user_id))
478 let res = CommentResponse {
479 comment: comment_view,
480 recipient_ids: Vec::new(),
488 #[async_trait::async_trait(?Send)]
489 impl Perform for SaveComment {
490 type Response = CommentResponse;
494 context: &Data<LemmyContext>,
495 _websocket_id: Option<ConnectionId>,
496 ) -> Result<CommentResponse, LemmyError> {
497 let data: &SaveComment = &self;
498 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
500 let comment_saved_form = CommentSavedForm {
501 comment_id: data.comment_id,
506 let save_comment = move |conn: &'_ _| CommentSaved::save(conn, &comment_saved_form);
507 if blocking(context.pool(), save_comment).await?.is_err() {
508 return Err(APIError::err("couldnt_save_comment").into());
511 let unsave_comment = move |conn: &'_ _| CommentSaved::unsave(conn, &comment_saved_form);
512 if blocking(context.pool(), unsave_comment).await?.is_err() {
513 return Err(APIError::err("couldnt_save_comment").into());
517 let comment_id = data.comment_id;
518 let user_id = user.id;
519 let comment_view = blocking(context.pool(), move |conn| {
520 CommentView::read(conn, comment_id, Some(user_id))
525 comment: comment_view,
526 recipient_ids: Vec::new(),
532 #[async_trait::async_trait(?Send)]
533 impl Perform for CreateCommentLike {
534 type Response = CommentResponse;
538 context: &Data<LemmyContext>,
539 websocket_id: Option<ConnectionId>,
540 ) -> Result<CommentResponse, LemmyError> {
541 let data: &CreateCommentLike = &self;
542 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
544 let mut recipient_ids = Vec::new();
546 // Don't do a downvote if site has downvotes disabled
547 if data.score == -1 {
548 let site = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
549 if !site.enable_downvotes {
550 return Err(APIError::err("downvotes_disabled").into());
554 let comment_id = data.comment_id;
555 let orig_comment = blocking(context.pool(), move |conn| {
556 CommentView::read(&conn, comment_id, None)
560 let post_id = orig_comment.post_id;
561 let post = get_post(post_id, context.pool()).await?;
562 check_community_ban(user.id, post.community_id, context.pool()).await?;
564 let comment_id = data.comment_id;
565 let comment = blocking(context.pool(), move |conn| Comment::read(conn, comment_id)).await??;
567 // Add to recipient ids
568 match comment.parent_id {
571 blocking(context.pool(), move |conn| Comment::read(conn, parent_id)).await??;
572 if parent_comment.creator_id != user.id {
573 let parent_user = blocking(context.pool(), move |conn| {
574 User_::read(conn, parent_comment.creator_id)
577 recipient_ids.push(parent_user.id);
581 recipient_ids.push(post.creator_id);
585 let like_form = CommentLikeForm {
586 comment_id: data.comment_id,
592 // Remove any likes first
593 let user_id = user.id;
594 blocking(context.pool(), move |conn| {
595 CommentLike::remove(conn, user_id, comment_id)
599 // Only add the like if the score isnt 0
600 let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
602 let like_form2 = like_form.clone();
603 let like = move |conn: &'_ _| CommentLike::like(conn, &like_form2);
604 if blocking(context.pool(), like).await?.is_err() {
605 return Err(APIError::err("couldnt_like_comment").into());
608 if like_form.score == 1 {
609 comment.send_like(&user, context).await?;
610 } else if like_form.score == -1 {
611 comment.send_dislike(&user, context).await?;
614 comment.send_undo_like(&user, context).await?;
617 // Have to refetch the comment to get the current state
618 let comment_id = data.comment_id;
619 let user_id = user.id;
620 let liked_comment = blocking(context.pool(), move |conn| {
621 CommentView::read(conn, comment_id, Some(user_id))
625 let mut res = CommentResponse {
626 comment: liked_comment,
631 context.chat_server().do_send(SendComment {
632 op: UserOperation::CreateCommentLike,
633 comment: res.clone(),
637 // strip out the recipient_ids, so that
638 // users don't get double notifs
639 res.recipient_ids = Vec::new();
645 #[async_trait::async_trait(?Send)]
646 impl Perform for GetComments {
647 type Response = GetCommentsResponse;
651 context: &Data<LemmyContext>,
652 _websocket_id: Option<ConnectionId>,
653 ) -> Result<GetCommentsResponse, LemmyError> {
654 let data: &GetComments = &self;
655 let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
656 let user_id = user.map(|u| u.id);
658 let type_ = ListingType::from_str(&data.type_)?;
659 let sort = SortType::from_str(&data.sort)?;
661 let community_id = data.community_id;
662 let community_name = data.community_name.to_owned();
663 let page = data.page;
664 let limit = data.limit;
665 let comments = blocking(context.pool(), move |conn| {
666 CommentQueryBuilder::create(conn)
669 .for_community_id(community_id)
670 .for_community_name(community_name)
677 let comments = match comments {
678 Ok(comments) => comments,
679 Err(_) => return Err(APIError::err("couldnt_get_comments").into()),
682 Ok(GetCommentsResponse { comments })