10 apub::{ApubLikeableType, ApubObjectType},
11 websocket::{messages::SendComment, UserOperation},
14 use actix_web::web::Data;
15 use lemmy_api_structs::{blocking, comment::*, send_local_notifs};
30 apub::{make_apub_endpoint, EndpointType},
31 utils::{remove_slurs, scrape_text_for_mentions},
36 use std::str::FromStr;
38 #[async_trait::async_trait(?Send)]
39 impl Perform for CreateComment {
40 type Response = CommentResponse;
44 context: &Data<LemmyContext>,
45 websocket_id: Option<ConnectionId>,
46 ) -> Result<CommentResponse, LemmyError> {
47 let data: &CreateComment = &self;
48 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
50 let content_slurs_removed = remove_slurs(&data.content.to_owned());
52 let comment_form = CommentForm {
53 content: content_slurs_removed,
54 parent_id: data.parent_id.to_owned(),
55 post_id: data.post_id,
66 // Check for a community ban
67 let post_id = data.post_id;
68 let post = get_post(post_id, context.pool()).await?;
70 check_community_ban(user.id, post.community_id, context.pool()).await?;
72 // Check if post is locked, no new comments
74 return Err(APIError::err("locked").into());
78 let comment_form2 = comment_form.clone();
79 let inserted_comment = match blocking(context.pool(), move |conn| {
80 Comment::create(&conn, &comment_form2)
84 Ok(comment) => comment,
85 Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
88 // Necessary to update the ap_id
89 let inserted_comment_id = inserted_comment.id;
90 let updated_comment: Comment = match blocking(context.pool(), move |conn| {
92 make_apub_endpoint(EndpointType::Comment, &inserted_comment_id.to_string()).to_string();
93 Comment::update_ap_id(&conn, inserted_comment_id, apub_id)
97 Ok(comment) => comment,
98 Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
101 updated_comment.send_create(&user, context).await?;
103 // Scan the comment for user mentions, add those rows
104 let mentions = scrape_text_for_mentions(&comment_form.content);
105 let recipient_ids = send_local_notifs(
107 updated_comment.clone(),
115 // You like your own comment by default
116 let like_form = CommentLikeForm {
117 comment_id: inserted_comment.id,
118 post_id: data.post_id,
123 let like = move |conn: &'_ _| CommentLike::like(&conn, &like_form);
124 if blocking(context.pool(), like).await?.is_err() {
125 return Err(APIError::err("couldnt_like_comment").into());
128 updated_comment.send_like(&user, context).await?;
130 let user_id = user.id;
131 let comment_view = blocking(context.pool(), move |conn| {
132 CommentView::read(&conn, inserted_comment.id, Some(user_id))
136 let mut res = CommentResponse {
137 comment: comment_view,
139 form_id: data.form_id.to_owned(),
142 context.chat_server().do_send(SendComment {
143 op: UserOperation::CreateComment,
144 comment: res.clone(),
148 // strip out the recipient_ids, so that
149 // users don't get double notifs
150 res.recipient_ids = Vec::new();
156 #[async_trait::async_trait(?Send)]
157 impl Perform for EditComment {
158 type Response = CommentResponse;
162 context: &Data<LemmyContext>,
163 websocket_id: Option<ConnectionId>,
164 ) -> Result<CommentResponse, LemmyError> {
165 let data: &EditComment = &self;
166 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
168 let edit_id = data.edit_id;
169 let orig_comment = blocking(context.pool(), move |conn| {
170 CommentView::read(&conn, edit_id, None)
174 check_community_ban(user.id, orig_comment.community_id, context.pool()).await?;
176 // Verify that only the creator can edit
177 if user.id != orig_comment.creator_id {
178 return Err(APIError::err("no_comment_edit_allowed").into());
182 let content_slurs_removed = remove_slurs(&data.content.to_owned());
183 let edit_id = data.edit_id;
184 let updated_comment = match blocking(context.pool(), move |conn| {
185 Comment::update_content(conn, edit_id, &content_slurs_removed)
189 Ok(comment) => comment,
190 Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
193 // Send the apub update
194 updated_comment.send_update(&user, context).await?;
196 // Do the mentions / recipients
197 let post_id = orig_comment.post_id;
198 let post = get_post(post_id, context.pool()).await?;
200 let updated_comment_content = updated_comment.content.to_owned();
201 let mentions = scrape_text_for_mentions(&updated_comment_content);
202 let recipient_ids = send_local_notifs(
212 let edit_id = data.edit_id;
213 let user_id = user.id;
214 let comment_view = blocking(context.pool(), move |conn| {
215 CommentView::read(conn, edit_id, Some(user_id))
219 let mut res = CommentResponse {
220 comment: comment_view,
222 form_id: data.form_id.to_owned(),
225 context.chat_server().do_send(SendComment {
226 op: UserOperation::EditComment,
227 comment: res.clone(),
231 // strip out the recipient_ids, so that
232 // users don't get double notifs
233 res.recipient_ids = Vec::new();
239 #[async_trait::async_trait(?Send)]
240 impl Perform for DeleteComment {
241 type Response = CommentResponse;
245 context: &Data<LemmyContext>,
246 websocket_id: Option<ConnectionId>,
247 ) -> Result<CommentResponse, LemmyError> {
248 let data: &DeleteComment = &self;
249 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
251 let edit_id = data.edit_id;
252 let orig_comment = blocking(context.pool(), move |conn| {
253 CommentView::read(&conn, edit_id, None)
257 check_community_ban(user.id, orig_comment.community_id, context.pool()).await?;
259 // Verify that only the creator can delete
260 if user.id != orig_comment.creator_id {
261 return Err(APIError::err("no_comment_edit_allowed").into());
265 let deleted = data.deleted;
266 let updated_comment = match blocking(context.pool(), move |conn| {
267 Comment::update_deleted(conn, edit_id, deleted)
271 Ok(comment) => comment,
272 Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
275 // Send the apub message
277 updated_comment.send_delete(&user, context).await?;
279 updated_comment.send_undo_delete(&user, context).await?;
283 let edit_id = data.edit_id;
284 let user_id = user.id;
285 let comment_view = blocking(context.pool(), move |conn| {
286 CommentView::read(conn, edit_id, Some(user_id))
290 // Build the recipients
291 let post_id = comment_view.post_id;
292 let post = get_post(post_id, context.pool()).await?;
293 let mentions = vec![];
294 let recipient_ids = send_local_notifs(
304 let mut res = CommentResponse {
305 comment: comment_view,
310 context.chat_server().do_send(SendComment {
311 op: UserOperation::DeleteComment,
312 comment: res.clone(),
316 // strip out the recipient_ids, so that
317 // users don't get double notifs
318 res.recipient_ids = Vec::new();
324 #[async_trait::async_trait(?Send)]
325 impl Perform for RemoveComment {
326 type Response = CommentResponse;
330 context: &Data<LemmyContext>,
331 websocket_id: Option<ConnectionId>,
332 ) -> Result<CommentResponse, LemmyError> {
333 let data: &RemoveComment = &self;
334 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
336 let edit_id = data.edit_id;
337 let orig_comment = blocking(context.pool(), move |conn| {
338 CommentView::read(&conn, edit_id, None)
342 check_community_ban(user.id, orig_comment.community_id, context.pool()).await?;
344 // Verify that only a mod or admin can remove
345 is_mod_or_admin(context.pool(), user.id, orig_comment.community_id).await?;
348 let removed = data.removed;
349 let updated_comment = match blocking(context.pool(), move |conn| {
350 Comment::update_removed(conn, edit_id, removed)
354 Ok(comment) => comment,
355 Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
359 let form = ModRemoveCommentForm {
360 mod_user_id: user.id,
361 comment_id: data.edit_id,
362 removed: Some(removed),
363 reason: data.reason.to_owned(),
365 blocking(context.pool(), move |conn| {
366 ModRemoveComment::create(conn, &form)
370 // Send the apub message
372 updated_comment.send_remove(&user, context).await?;
374 updated_comment.send_undo_remove(&user, context).await?;
378 let edit_id = data.edit_id;
379 let user_id = user.id;
380 let comment_view = blocking(context.pool(), move |conn| {
381 CommentView::read(conn, edit_id, Some(user_id))
385 // Build the recipients
386 let post_id = comment_view.post_id;
387 let post = get_post(post_id, context.pool()).await?;
388 let mentions = vec![];
389 let recipient_ids = send_local_notifs(
399 let mut res = CommentResponse {
400 comment: comment_view,
405 context.chat_server().do_send(SendComment {
406 op: UserOperation::RemoveComment,
407 comment: res.clone(),
411 // strip out the recipient_ids, so that
412 // users don't get double notifs
413 res.recipient_ids = Vec::new();
419 #[async_trait::async_trait(?Send)]
420 impl Perform for MarkCommentAsRead {
421 type Response = CommentResponse;
425 context: &Data<LemmyContext>,
426 _websocket_id: Option<ConnectionId>,
427 ) -> Result<CommentResponse, LemmyError> {
428 let data: &MarkCommentAsRead = &self;
429 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
431 let edit_id = data.edit_id;
432 let orig_comment = blocking(context.pool(), move |conn| {
433 CommentView::read(&conn, edit_id, None)
437 check_community_ban(user.id, orig_comment.community_id, context.pool()).await?;
439 // Verify that only the recipient can mark as read
440 // Needs to fetch the parent comment / post to get the recipient
441 let parent_id = orig_comment.parent_id;
444 let parent_comment = blocking(context.pool(), move |conn| {
445 CommentView::read(&conn, pid, None)
448 if user.id != parent_comment.creator_id {
449 return Err(APIError::err("no_comment_edit_allowed").into());
453 let parent_post_id = orig_comment.post_id;
455 blocking(context.pool(), move |conn| Post::read(conn, parent_post_id)).await??;
456 if user.id != parent_post.creator_id {
457 return Err(APIError::err("no_comment_edit_allowed").into());
462 // Do the mark as read
463 let read = data.read;
464 match blocking(context.pool(), move |conn| {
465 Comment::update_read(conn, edit_id, read)
469 Ok(comment) => comment,
470 Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
474 let edit_id = data.edit_id;
475 let user_id = user.id;
476 let comment_view = blocking(context.pool(), move |conn| {
477 CommentView::read(conn, edit_id, Some(user_id))
481 let res = CommentResponse {
482 comment: comment_view,
483 recipient_ids: Vec::new(),
491 #[async_trait::async_trait(?Send)]
492 impl Perform for SaveComment {
493 type Response = CommentResponse;
497 context: &Data<LemmyContext>,
498 _websocket_id: Option<ConnectionId>,
499 ) -> Result<CommentResponse, LemmyError> {
500 let data: &SaveComment = &self;
501 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
503 let comment_saved_form = CommentSavedForm {
504 comment_id: data.comment_id,
509 let save_comment = move |conn: &'_ _| CommentSaved::save(conn, &comment_saved_form);
510 if blocking(context.pool(), save_comment).await?.is_err() {
511 return Err(APIError::err("couldnt_save_comment").into());
514 let unsave_comment = move |conn: &'_ _| CommentSaved::unsave(conn, &comment_saved_form);
515 if blocking(context.pool(), unsave_comment).await?.is_err() {
516 return Err(APIError::err("couldnt_save_comment").into());
520 let comment_id = data.comment_id;
521 let user_id = user.id;
522 let comment_view = blocking(context.pool(), move |conn| {
523 CommentView::read(conn, comment_id, Some(user_id))
528 comment: comment_view,
529 recipient_ids: Vec::new(),
535 #[async_trait::async_trait(?Send)]
536 impl Perform for CreateCommentLike {
537 type Response = CommentResponse;
541 context: &Data<LemmyContext>,
542 websocket_id: Option<ConnectionId>,
543 ) -> Result<CommentResponse, LemmyError> {
544 let data: &CreateCommentLike = &self;
545 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
547 let mut recipient_ids = Vec::new();
549 // Don't do a downvote if site has downvotes disabled
550 if data.score == -1 {
551 let site = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
552 if !site.enable_downvotes {
553 return Err(APIError::err("downvotes_disabled").into());
557 let comment_id = data.comment_id;
558 let orig_comment = blocking(context.pool(), move |conn| {
559 CommentView::read(&conn, comment_id, None)
563 let post_id = orig_comment.post_id;
564 let post = get_post(post_id, context.pool()).await?;
565 check_community_ban(user.id, post.community_id, context.pool()).await?;
567 let comment_id = data.comment_id;
568 let comment = blocking(context.pool(), move |conn| Comment::read(conn, comment_id)).await??;
570 // Add to recipient ids
571 match comment.parent_id {
574 blocking(context.pool(), move |conn| Comment::read(conn, parent_id)).await??;
575 if parent_comment.creator_id != user.id {
576 let parent_user = blocking(context.pool(), move |conn| {
577 User_::read(conn, parent_comment.creator_id)
580 recipient_ids.push(parent_user.id);
584 recipient_ids.push(post.creator_id);
588 let like_form = CommentLikeForm {
589 comment_id: data.comment_id,
595 // Remove any likes first
596 let user_id = user.id;
597 blocking(context.pool(), move |conn| {
598 CommentLike::remove(conn, user_id, comment_id)
602 // Only add the like if the score isnt 0
603 let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
605 let like_form2 = like_form.clone();
606 let like = move |conn: &'_ _| CommentLike::like(conn, &like_form2);
607 if blocking(context.pool(), like).await?.is_err() {
608 return Err(APIError::err("couldnt_like_comment").into());
611 if like_form.score == 1 {
612 comment.send_like(&user, context).await?;
613 } else if like_form.score == -1 {
614 comment.send_dislike(&user, context).await?;
617 comment.send_undo_like(&user, context).await?;
620 // Have to refetch the comment to get the current state
621 let comment_id = data.comment_id;
622 let user_id = user.id;
623 let liked_comment = blocking(context.pool(), move |conn| {
624 CommentView::read(conn, comment_id, Some(user_id))
628 let mut res = CommentResponse {
629 comment: liked_comment,
634 context.chat_server().do_send(SendComment {
635 op: UserOperation::CreateCommentLike,
636 comment: res.clone(),
640 // strip out the recipient_ids, so that
641 // users don't get double notifs
642 res.recipient_ids = Vec::new();
648 #[async_trait::async_trait(?Send)]
649 impl Perform for GetComments {
650 type Response = GetCommentsResponse;
654 context: &Data<LemmyContext>,
655 _websocket_id: Option<ConnectionId>,
656 ) -> Result<GetCommentsResponse, LemmyError> {
657 let data: &GetComments = &self;
658 let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
659 let user_id = user.map(|u| u.id);
661 let type_ = ListingType::from_str(&data.type_)?;
662 let sort = SortType::from_str(&data.sort)?;
664 let community_id = data.community_id;
665 let community_name = data.community_name.to_owned();
666 let page = data.page;
667 let limit = data.limit;
668 let comments = blocking(context.pool(), move |conn| {
669 CommentQueryBuilder::create(conn)
672 .for_community_id(community_id)
673 .for_community_name(community_name)
680 let comments = match comments {
681 Ok(comments) => comments,
682 Err(_) => return Err(APIError::err("couldnt_get_comments").into()),
685 Ok(GetCommentsResponse { comments })