2 use actix_web::web::Data;
3 use lemmy_api_common::{
6 check_community_deleted_or_removed,
7 check_downvotes_enabled,
8 get_local_user_view_from_jwt,
15 fetcher::post_or_comment::PostOrComment,
16 objects::post::ApubPost,
17 protocol::activities::{
18 create_or_update::post::CreateOrUpdatePost,
21 vote::{Vote, VoteType},
26 use lemmy_db_schema::{
27 source::{moderator::*, post::*},
28 traits::{Crud, Likeable, Saveable},
30 use lemmy_db_views::post_view::PostView;
31 use lemmy_utils::{request::fetch_site_metadata, ConnectionId, LemmyError};
32 use lemmy_websocket::{send::send_post_ws_message, LemmyContext, UserOperation};
33 use std::convert::TryInto;
35 #[async_trait::async_trait(?Send)]
36 impl Perform for CreatePostLike {
37 type Response = PostResponse;
39 #[tracing::instrument(skip(context, websocket_id))]
42 context: &Data<LemmyContext>,
43 websocket_id: Option<ConnectionId>,
44 ) -> Result<PostResponse, LemmyError> {
45 let data: &CreatePostLike = self;
47 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
49 // Don't do a downvote if site has downvotes disabled
50 check_downvotes_enabled(data.score, context.pool()).await?;
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))
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?;
61 let like_form = PostLikeForm {
62 post_id: data.post_id,
63 person_id: local_user_view.person.id,
67 // Remove any likes first
68 let person_id = local_user_view.person.id;
69 blocking(context.pool(), move |conn| {
70 PostLike::remove(conn, person_id, post_id)
74 let community_id = post.community_id;
75 let object = PostOrComment::Post(Box::new(post));
77 // Only add the like if the score isnt 0
78 let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
80 let like_form2 = like_form.clone();
81 let like = move |conn: &'_ _| PostLike::like(conn, &like_form2);
82 blocking(context.pool(), like)
84 .map_err(LemmyError::from)
85 .map_err(|e| e.with_message("couldnt_like_post"))?;
89 &local_user_view.person.clone().into(),
91 like_form.score.try_into()?,
96 // API doesn't distinguish between Undo/Like and Undo/Dislike
99 &local_user_view.person.clone().into(),
107 // Mark the post as read
108 mark_post_as_read(person_id, post_id, context.pool()).await?;
110 send_post_ws_message(
112 UserOperation::CreatePostLike,
114 Some(local_user_view.person.id),
121 #[async_trait::async_trait(?Send)]
122 impl Perform for MarkPostAsRead {
123 type Response = PostResponse;
125 #[tracing::instrument(skip(context, _websocket_id))]
128 context: &Data<LemmyContext>,
129 _websocket_id: Option<ConnectionId>,
130 ) -> Result<Self::Response, LemmyError> {
132 let local_user_view =
133 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
135 let post_id = data.post_id;
136 let person_id = local_user_view.person.id;
138 // Mark the post as read / unread
140 mark_post_as_read(person_id, post_id, context.pool()).await?;
142 mark_post_as_unread(person_id, post_id, context.pool()).await?;
146 let post_view = blocking(context.pool(), move |conn| {
147 PostView::read(conn, post_id, Some(person_id))
151 let res = Self::Response { post_view };
157 #[async_trait::async_trait(?Send)]
158 impl Perform for LockPost {
159 type Response = PostResponse;
161 #[tracing::instrument(skip(context, websocket_id))]
164 context: &Data<LemmyContext>,
165 websocket_id: Option<ConnectionId>,
166 ) -> Result<PostResponse, LemmyError> {
167 let data: &LockPost = self;
168 let local_user_view =
169 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
171 let post_id = data.post_id;
172 let orig_post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
175 local_user_view.person.id,
176 orig_post.community_id,
180 check_community_deleted_or_removed(orig_post.community_id, context.pool()).await?;
182 // Verify that only the mods can lock
185 local_user_view.person.id,
186 orig_post.community_id,
191 let post_id = data.post_id;
192 let locked = data.locked;
193 let updated_post: ApubPost = blocking(context.pool(), move |conn| {
194 Post::update_locked(conn, post_id, locked)
200 let form = ModLockPostForm {
201 mod_person_id: local_user_view.person.id,
202 post_id: data.post_id,
203 locked: Some(locked),
205 blocking(context.pool(), move |conn| ModLockPost::create(conn, &form)).await??;
208 CreateOrUpdatePost::send(
210 &local_user_view.person.clone().into(),
211 CreateOrUpdateType::Update,
216 send_post_ws_message(
218 UserOperation::LockPost,
220 Some(local_user_view.person.id),
227 #[async_trait::async_trait(?Send)]
228 impl Perform for StickyPost {
229 type Response = PostResponse;
231 #[tracing::instrument(skip(context, websocket_id))]
234 context: &Data<LemmyContext>,
235 websocket_id: Option<ConnectionId>,
236 ) -> Result<PostResponse, LemmyError> {
237 let data: &StickyPost = self;
238 let local_user_view =
239 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
241 let post_id = data.post_id;
242 let orig_post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
245 local_user_view.person.id,
246 orig_post.community_id,
250 check_community_deleted_or_removed(orig_post.community_id, context.pool()).await?;
252 // Verify that only the mods can sticky
255 local_user_view.person.id,
256 orig_post.community_id,
261 let post_id = data.post_id;
262 let stickied = data.stickied;
263 let updated_post: ApubPost = blocking(context.pool(), move |conn| {
264 Post::update_stickied(conn, post_id, stickied)
270 let form = ModStickyPostForm {
271 mod_person_id: local_user_view.person.id,
272 post_id: data.post_id,
273 stickied: Some(stickied),
275 blocking(context.pool(), move |conn| {
276 ModStickyPost::create(conn, &form)
281 // TODO stickied should pry work like locked for ease of use
282 CreateOrUpdatePost::send(
284 &local_user_view.person.clone().into(),
285 CreateOrUpdateType::Update,
290 send_post_ws_message(
292 UserOperation::StickyPost,
294 Some(local_user_view.person.id),
301 #[async_trait::async_trait(?Send)]
302 impl Perform for SavePost {
303 type Response = PostResponse;
305 #[tracing::instrument(skip(context, _websocket_id))]
308 context: &Data<LemmyContext>,
309 _websocket_id: Option<ConnectionId>,
310 ) -> Result<PostResponse, LemmyError> {
311 let data: &SavePost = self;
312 let local_user_view =
313 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
315 let post_saved_form = PostSavedForm {
316 post_id: data.post_id,
317 person_id: local_user_view.person.id,
321 let save = move |conn: &'_ _| PostSaved::save(conn, &post_saved_form);
322 blocking(context.pool(), save)
324 .map_err(LemmyError::from)
325 .map_err(|e| e.with_message("couldnt_save_post"))?;
327 let unsave = move |conn: &'_ _| PostSaved::unsave(conn, &post_saved_form);
328 blocking(context.pool(), unsave)
330 .map_err(LemmyError::from)
331 .map_err(|e| e.with_message("couldnt_save_post"))?;
334 let post_id = data.post_id;
335 let person_id = local_user_view.person.id;
336 let post_view = blocking(context.pool(), move |conn| {
337 PostView::read(conn, post_id, Some(person_id))
341 // Mark the post as read
342 mark_post_as_read(person_id, post_id, context.pool()).await?;
344 Ok(PostResponse { post_view })
348 #[async_trait::async_trait(?Send)]
349 impl Perform for GetSiteMetadata {
350 type Response = GetSiteMetadataResponse;
352 #[tracing::instrument(skip(context, _websocket_id))]
355 context: &Data<LemmyContext>,
356 _websocket_id: Option<ConnectionId>,
357 ) -> Result<GetSiteMetadataResponse, LemmyError> {
358 let data: &Self = self;
360 let metadata = fetch_site_metadata(context.client(), &data.url).await?;
362 Ok(GetSiteMetadataResponse { metadata })