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