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