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