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