]> Untitled Git - lemmy.git/blob - crates/api/src/post.rs
Rewrite voting (#1685)
[lemmy.git] / crates / api / src / post.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   get_local_user_view_from_jwt,
8   is_mod_or_admin,
9   mark_post_as_read,
10   post::*,
11 };
12 use lemmy_apub::{
13   activities::{
14     post::create_or_update::CreateOrUpdatePost,
15     voting::{
16       undo_vote::UndoVote,
17       vote::{Vote, VoteType},
18     },
19     CreateOrUpdateType,
20   },
21   PostOrComment,
22 };
23 use lemmy_db_queries::{source::post::Post_, Crud, Likeable, Saveable};
24 use lemmy_db_schema::source::{moderator::*, post::*};
25 use lemmy_db_views::post_view::PostView;
26 use lemmy_utils::{ApiError, ConnectionId, LemmyError};
27 use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperation};
28 use std::convert::TryInto;
29
30 #[async_trait::async_trait(?Send)]
31 impl Perform for CreatePostLike {
32   type Response = PostResponse;
33
34   async fn perform(
35     &self,
36     context: &Data<LemmyContext>,
37     websocket_id: Option<ConnectionId>,
38   ) -> Result<PostResponse, LemmyError> {
39     let data: &CreatePostLike = self;
40     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
41
42     // Don't do a downvote if site has downvotes disabled
43     check_downvotes_enabled(data.score, context.pool()).await?;
44
45     // Check for a community ban
46     let post_id = data.post_id;
47     let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
48
49     check_community_ban(local_user_view.person.id, post.community_id, context.pool()).await?;
50
51     let like_form = PostLikeForm {
52       post_id: data.post_id,
53       person_id: local_user_view.person.id,
54       score: data.score,
55     };
56
57     // Remove any likes first
58     let person_id = local_user_view.person.id;
59     blocking(context.pool(), move |conn| {
60       PostLike::remove(conn, person_id, post_id)
61     })
62     .await??;
63
64     let community_id = post.community_id;
65     let object = PostOrComment::Post(Box::new(post));
66
67     // Only add the like if the score isnt 0
68     let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
69     if do_add {
70       let like_form2 = like_form.clone();
71       let like = move |conn: &'_ _| PostLike::like(conn, &like_form2);
72       if blocking(context.pool(), like).await?.is_err() {
73         return Err(ApiError::err("couldnt_like_post").into());
74       }
75
76       Vote::send(
77         &object,
78         &local_user_view.person,
79         community_id,
80         like_form.score.try_into()?,
81         context,
82       )
83       .await?;
84     } else {
85       // API doesn't distinguish between Undo/Like and Undo/Dislike
86       UndoVote::send(
87         &object,
88         &local_user_view.person,
89         community_id,
90         VoteType::Like,
91         context,
92       )
93       .await?;
94     }
95
96     // Mark the post as read
97     mark_post_as_read(person_id, post_id, context.pool()).await?;
98
99     let post_id = data.post_id;
100     let person_id = local_user_view.person.id;
101     let post_view = blocking(context.pool(), move |conn| {
102       PostView::read(conn, post_id, Some(person_id))
103     })
104     .await?
105     .map_err(|_| ApiError::err("couldnt_find_post"))?;
106
107     let res = PostResponse { post_view };
108
109     context.chat_server().do_send(SendPost {
110       op: UserOperation::CreatePostLike,
111       post: res.clone(),
112       websocket_id,
113     });
114
115     Ok(res)
116   }
117 }
118
119 #[async_trait::async_trait(?Send)]
120 impl Perform for LockPost {
121   type Response = PostResponse;
122
123   async fn perform(
124     &self,
125     context: &Data<LemmyContext>,
126     websocket_id: Option<ConnectionId>,
127   ) -> Result<PostResponse, LemmyError> {
128     let data: &LockPost = self;
129     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
130
131     let post_id = data.post_id;
132     let orig_post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
133
134     check_community_ban(
135       local_user_view.person.id,
136       orig_post.community_id,
137       context.pool(),
138     )
139     .await?;
140
141     // Verify that only the mods can lock
142     is_mod_or_admin(
143       context.pool(),
144       local_user_view.person.id,
145       orig_post.community_id,
146     )
147     .await?;
148
149     // Update the post
150     let post_id = data.post_id;
151     let locked = data.locked;
152     let updated_post = blocking(context.pool(), move |conn| {
153       Post::update_locked(conn, post_id, locked)
154     })
155     .await??;
156
157     // Mod tables
158     let form = ModLockPostForm {
159       mod_person_id: local_user_view.person.id,
160       post_id: data.post_id,
161       locked: Some(locked),
162     };
163     blocking(context.pool(), move |conn| ModLockPost::create(conn, &form)).await??;
164
165     // apub updates
166     CreateOrUpdatePost::send(
167       &updated_post,
168       &local_user_view.person,
169       CreateOrUpdateType::Update,
170       context,
171     )
172     .await?;
173
174     // Refetch the post
175     let post_id = data.post_id;
176     let post_view = blocking(context.pool(), move |conn| {
177       PostView::read(conn, post_id, Some(local_user_view.person.id))
178     })
179     .await??;
180
181     let res = PostResponse { post_view };
182
183     context.chat_server().do_send(SendPost {
184       op: UserOperation::LockPost,
185       post: res.clone(),
186       websocket_id,
187     });
188
189     Ok(res)
190   }
191 }
192
193 #[async_trait::async_trait(?Send)]
194 impl Perform for StickyPost {
195   type Response = PostResponse;
196
197   async fn perform(
198     &self,
199     context: &Data<LemmyContext>,
200     websocket_id: Option<ConnectionId>,
201   ) -> Result<PostResponse, LemmyError> {
202     let data: &StickyPost = self;
203     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
204
205     let post_id = data.post_id;
206     let orig_post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
207
208     check_community_ban(
209       local_user_view.person.id,
210       orig_post.community_id,
211       context.pool(),
212     )
213     .await?;
214
215     // Verify that only the mods can sticky
216     is_mod_or_admin(
217       context.pool(),
218       local_user_view.person.id,
219       orig_post.community_id,
220     )
221     .await?;
222
223     // Update the post
224     let post_id = data.post_id;
225     let stickied = data.stickied;
226     let updated_post = blocking(context.pool(), move |conn| {
227       Post::update_stickied(conn, post_id, stickied)
228     })
229     .await??;
230
231     // Mod tables
232     let form = ModStickyPostForm {
233       mod_person_id: local_user_view.person.id,
234       post_id: data.post_id,
235       stickied: Some(stickied),
236     };
237     blocking(context.pool(), move |conn| {
238       ModStickyPost::create(conn, &form)
239     })
240     .await??;
241
242     // Apub updates
243     // TODO stickied should pry work like locked for ease of use
244     CreateOrUpdatePost::send(
245       &updated_post,
246       &local_user_view.person,
247       CreateOrUpdateType::Update,
248       context,
249     )
250     .await?;
251
252     // Refetch the post
253     let post_id = data.post_id;
254     let post_view = blocking(context.pool(), move |conn| {
255       PostView::read(conn, post_id, Some(local_user_view.person.id))
256     })
257     .await??;
258
259     let res = PostResponse { post_view };
260
261     context.chat_server().do_send(SendPost {
262       op: UserOperation::StickyPost,
263       post: res.clone(),
264       websocket_id,
265     });
266
267     Ok(res)
268   }
269 }
270
271 #[async_trait::async_trait(?Send)]
272 impl Perform for SavePost {
273   type Response = PostResponse;
274
275   async fn perform(
276     &self,
277     context: &Data<LemmyContext>,
278     _websocket_id: Option<ConnectionId>,
279   ) -> Result<PostResponse, LemmyError> {
280     let data: &SavePost = self;
281     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
282
283     let post_saved_form = PostSavedForm {
284       post_id: data.post_id,
285       person_id: local_user_view.person.id,
286     };
287
288     if data.save {
289       let save = move |conn: &'_ _| PostSaved::save(conn, &post_saved_form);
290       if blocking(context.pool(), save).await?.is_err() {
291         return Err(ApiError::err("couldnt_save_post").into());
292       }
293     } else {
294       let unsave = move |conn: &'_ _| PostSaved::unsave(conn, &post_saved_form);
295       if blocking(context.pool(), unsave).await?.is_err() {
296         return Err(ApiError::err("couldnt_save_post").into());
297       }
298     }
299
300     let post_id = data.post_id;
301     let person_id = local_user_view.person.id;
302     let post_view = blocking(context.pool(), move |conn| {
303       PostView::read(conn, post_id, Some(person_id))
304     })
305     .await??;
306
307     // Mark the post as read
308     mark_post_as_read(person_id, post_id, context.pool()).await?;
309
310     Ok(PostResponse { post_view })
311   }
312 }