]> Untitled Git - lemmy.git/blob - crates/api/src/comment.rs
bd8d3a8e9b1efe6c5bb6b938f08900a355d8d4b7
[lemmy.git] / crates / api / src / comment.rs
1 use std::convert::TryInto;
2
3 use actix_web::web::Data;
4
5 use lemmy_api_common::{
6   blocking,
7   check_community_ban,
8   check_downvotes_enabled,
9   comment::*,
10   get_local_user_view_from_jwt,
11 };
12 use lemmy_apub::{
13   fetcher::post_or_comment::PostOrComment,
14   protocol::activities::voting::{
15     undo_vote::UndoVote,
16     vote::{Vote, VoteType},
17   },
18 };
19 use lemmy_db_schema::{
20   newtypes::LocalUserId,
21   source::comment::*,
22   traits::{Likeable, Saveable},
23 };
24 use lemmy_db_views::{comment_view::CommentView, local_user_view::LocalUserView};
25 use lemmy_utils::{ConnectionId, LemmyError};
26 use lemmy_websocket::{send::send_comment_ws_message, LemmyContext, UserOperation};
27
28 use crate::Perform;
29
30 #[async_trait::async_trait(?Send)]
31 impl Perform for MarkCommentAsRead {
32   type Response = CommentResponse;
33
34   #[tracing::instrument(skip(context, _websocket_id))]
35   async fn perform(
36     &self,
37     context: &Data<LemmyContext>,
38     _websocket_id: Option<ConnectionId>,
39   ) -> Result<CommentResponse, LemmyError> {
40     let data: &MarkCommentAsRead = self;
41     let local_user_view =
42       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
43
44     let comment_id = data.comment_id;
45     let orig_comment = blocking(context.pool(), move |conn| {
46       CommentView::read(conn, comment_id, None)
47     })
48     .await??;
49
50     // Verify that only the recipient can mark as read
51     if local_user_view.person.id != orig_comment.get_recipient_id() {
52       return Err(LemmyError::from_message("no_comment_edit_allowed"));
53     }
54
55     // Do the mark as read
56     let read = data.read;
57     blocking(context.pool(), move |conn| {
58       Comment::update_read(conn, comment_id, read)
59     })
60     .await?
61     .map_err(LemmyError::from)
62     .map_err(|e| e.with_message("couldnt_update_comment"))?;
63
64     // Refetch it
65     let comment_id = data.comment_id;
66     let person_id = local_user_view.person.id;
67     let comment_view = blocking(context.pool(), move |conn| {
68       CommentView::read(conn, comment_id, Some(person_id))
69     })
70     .await??;
71
72     let res = CommentResponse {
73       comment_view,
74       recipient_ids: Vec::new(),
75       form_id: None,
76     };
77
78     Ok(res)
79   }
80 }
81
82 #[async_trait::async_trait(?Send)]
83 impl Perform for SaveComment {
84   type Response = CommentResponse;
85
86   #[tracing::instrument(skip(context, _websocket_id))]
87   async fn perform(
88     &self,
89     context: &Data<LemmyContext>,
90     _websocket_id: Option<ConnectionId>,
91   ) -> Result<CommentResponse, LemmyError> {
92     let data: &SaveComment = self;
93     let local_user_view =
94       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
95
96     let comment_saved_form = CommentSavedForm {
97       comment_id: data.comment_id,
98       person_id: local_user_view.person.id,
99     };
100
101     if data.save {
102       let save_comment = move |conn: &'_ _| CommentSaved::save(conn, &comment_saved_form);
103       blocking(context.pool(), save_comment)
104         .await?
105         .map_err(LemmyError::from)
106         .map_err(|e| e.with_message("couldnt_save_comment"))?;
107     } else {
108       let unsave_comment = move |conn: &'_ _| CommentSaved::unsave(conn, &comment_saved_form);
109       blocking(context.pool(), unsave_comment)
110         .await?
111         .map_err(LemmyError::from)
112         .map_err(|e| e.with_message("couldnt_save_comment"))?;
113     }
114
115     let comment_id = data.comment_id;
116     let person_id = local_user_view.person.id;
117     let comment_view = blocking(context.pool(), move |conn| {
118       CommentView::read(conn, comment_id, Some(person_id))
119     })
120     .await??;
121
122     Ok(CommentResponse {
123       comment_view,
124       recipient_ids: Vec::new(),
125       form_id: None,
126     })
127   }
128 }
129
130 #[async_trait::async_trait(?Send)]
131 impl Perform for CreateCommentLike {
132   type Response = CommentResponse;
133
134   #[tracing::instrument(skip(context, websocket_id))]
135   async fn perform(
136     &self,
137     context: &Data<LemmyContext>,
138     websocket_id: Option<ConnectionId>,
139   ) -> Result<CommentResponse, LemmyError> {
140     let data: &CreateCommentLike = self;
141     let local_user_view =
142       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
143
144     let mut recipient_ids = Vec::<LocalUserId>::new();
145
146     // Don't do a downvote if site has downvotes disabled
147     check_downvotes_enabled(data.score, context.pool()).await?;
148
149     let comment_id = data.comment_id;
150     let orig_comment = blocking(context.pool(), move |conn| {
151       CommentView::read(conn, comment_id, None)
152     })
153     .await??;
154
155     check_community_ban(
156       local_user_view.person.id,
157       orig_comment.community.id,
158       context.pool(),
159     )
160     .await?;
161
162     // Add parent user to recipients
163     let recipient_id = orig_comment.get_recipient_id();
164     if let Ok(local_recipient) = blocking(context.pool(), move |conn| {
165       LocalUserView::read_person(conn, recipient_id)
166     })
167     .await?
168     {
169       recipient_ids.push(local_recipient.local_user.id);
170     }
171
172     let like_form = CommentLikeForm {
173       comment_id: data.comment_id,
174       post_id: orig_comment.post.id,
175       person_id: local_user_view.person.id,
176       score: data.score,
177     };
178
179     // Remove any likes first
180     let person_id = local_user_view.person.id;
181     blocking(context.pool(), move |conn| {
182       CommentLike::remove(conn, person_id, comment_id)
183     })
184     .await??;
185
186     // Only add the like if the score isnt 0
187     let comment = orig_comment.comment;
188     let object = PostOrComment::Comment(Box::new(comment.into()));
189     let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
190     if do_add {
191       let like_form2 = like_form.clone();
192       let like = move |conn: &'_ _| CommentLike::like(conn, &like_form2);
193       blocking(context.pool(), like)
194         .await?
195         .map_err(LemmyError::from)
196         .map_err(|e| e.with_message("couldnt_like_comment"))?;
197
198       Vote::send(
199         &object,
200         &local_user_view.person.clone().into(),
201         orig_comment.community.id,
202         like_form.score.try_into()?,
203         context,
204       )
205       .await?;
206     } else {
207       // API doesn't distinguish between Undo/Like and Undo/Dislike
208       UndoVote::send(
209         &object,
210         &local_user_view.person.clone().into(),
211         orig_comment.community.id,
212         VoteType::Like,
213         context,
214       )
215       .await?;
216     }
217
218     send_comment_ws_message(
219       data.comment_id,
220       UserOperation::CreateCommentLike,
221       websocket_id,
222       None,
223       Some(local_user_view.person.id),
224       recipient_ids,
225       context,
226     )
227     .await
228   }
229 }