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