]> Untitled Git - lemmy.git/blob - crates/api/src/comment.rs
Rework error handling (fixes #1714) (#2135)
[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(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;
62
63     // Refetch it
64     let comment_id = data.comment_id;
65     let person_id = local_user_view.person.id;
66     let comment_view = blocking(context.pool(), move |conn| {
67       CommentView::read(conn, comment_id, Some(person_id))
68     })
69     .await??;
70
71     let res = CommentResponse {
72       comment_view,
73       recipient_ids: Vec::new(),
74       form_id: None,
75     };
76
77     Ok(res)
78   }
79 }
80
81 #[async_trait::async_trait(?Send)]
82 impl Perform for SaveComment {
83   type Response = CommentResponse;
84
85   #[tracing::instrument(skip(context, _websocket_id))]
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| LemmyError::from_error_message(e, "couldnt_save_comment"))?;
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| LemmyError::from_error_message(e, "couldnt_save_comment"))?;
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   #[tracing::instrument(skip(context, websocket_id))]
132   async fn perform(
133     &self,
134     context: &Data<LemmyContext>,
135     websocket_id: Option<ConnectionId>,
136   ) -> Result<CommentResponse, LemmyError> {
137     let data: &CreateCommentLike = self;
138     let local_user_view =
139       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
140
141     let mut recipient_ids = Vec::<LocalUserId>::new();
142
143     // Don't do a downvote if site has downvotes disabled
144     check_downvotes_enabled(data.score, context.pool()).await?;
145
146     let comment_id = data.comment_id;
147     let orig_comment = blocking(context.pool(), move |conn| {
148       CommentView::read(conn, comment_id, None)
149     })
150     .await??;
151
152     check_community_ban(
153       local_user_view.person.id,
154       orig_comment.community.id,
155       context.pool(),
156     )
157     .await?;
158
159     // Add parent user to recipients
160     let recipient_id = orig_comment.get_recipient_id();
161     if let Ok(local_recipient) = blocking(context.pool(), move |conn| {
162       LocalUserView::read_person(conn, recipient_id)
163     })
164     .await?
165     {
166       recipient_ids.push(local_recipient.local_user.id);
167     }
168
169     let like_form = CommentLikeForm {
170       comment_id: data.comment_id,
171       post_id: orig_comment.post.id,
172       person_id: local_user_view.person.id,
173       score: data.score,
174     };
175
176     // Remove any likes first
177     let person_id = local_user_view.person.id;
178     blocking(context.pool(), move |conn| {
179       CommentLike::remove(conn, person_id, comment_id)
180     })
181     .await??;
182
183     // Only add the like if the score isnt 0
184     let comment = orig_comment.comment;
185     let object = PostOrComment::Comment(Box::new(comment.into()));
186     let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
187     if do_add {
188       let like_form2 = like_form.clone();
189       let like = move |conn: &'_ _| CommentLike::like(conn, &like_form2);
190       blocking(context.pool(), like)
191         .await?
192         .map_err(|e| LemmyError::from_error_message(e, "couldnt_like_comment"))?;
193
194       Vote::send(
195         &object,
196         &local_user_view.person.clone().into(),
197         orig_comment.community.id,
198         like_form.score.try_into()?,
199         context,
200       )
201       .await?;
202     } else {
203       // API doesn't distinguish between Undo/Like and Undo/Dislike
204       UndoVote::send(
205         &object,
206         &local_user_view.person.clone().into(),
207         orig_comment.community.id,
208         VoteType::Like,
209         context,
210       )
211       .await?;
212     }
213
214     send_comment_ws_message(
215       data.comment_id,
216       UserOperation::CreateCommentLike,
217       websocket_id,
218       None,
219       Some(local_user_view.person.id),
220       recipient_ids,
221       context,
222     )
223     .await
224   }
225 }