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