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