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