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