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