2 use actix_web::web::Data;
3 use lemmy_api_common::{
6 check_community_deleted_or_removed,
7 check_downvotes_enabled,
9 get_local_user_view_from_jwt,
16 fetcher::post_or_comment::PostOrComment,
17 objects::post::ApubPost,
18 protocol::activities::{
19 create_or_update::post::CreateOrUpdatePost,
22 vote::{Vote, VoteType},
27 use lemmy_db_schema::{
28 source::{moderator::*, post::*},
29 traits::{Crud, Likeable, Saveable},
31 use lemmy_db_views::post_view::PostView;
32 use lemmy_utils::{request::fetch_site_metadata, ConnectionId, LemmyError};
33 use lemmy_websocket::{send::send_post_ws_message, LemmyContext, UserOperation};
34 use std::convert::TryInto;
36 #[async_trait::async_trait(?Send)]
37 impl Perform for CreatePostLike {
38 type Response = PostResponse;
40 #[tracing::instrument(skip(context, websocket_id))]
43 context: &Data<LemmyContext>,
44 websocket_id: Option<ConnectionId>,
45 ) -> Result<PostResponse, LemmyError> {
46 let data: &CreatePostLike = self;
48 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
50 // Don't do a downvote if site has downvotes disabled
51 check_downvotes_enabled(data.score, context.pool()).await?;
53 // Check for a community ban
54 let post_id = data.post_id;
55 let post: ApubPost = blocking(context.pool(), move |conn| Post::read(conn, post_id))
59 check_community_ban(local_user_view.person.id, post.community_id, context.pool()).await?;
60 check_community_deleted_or_removed(post.community_id, context.pool()).await?;
62 check_person_block(local_user_view.person.id, post.creator_id, context.pool()).await?;
64 let like_form = PostLikeForm {
65 post_id: data.post_id,
66 person_id: local_user_view.person.id,
70 // Remove any likes first
71 let person_id = local_user_view.person.id;
72 blocking(context.pool(), move |conn| {
73 PostLike::remove(conn, person_id, post_id)
77 let community_id = post.community_id;
78 let object = PostOrComment::Post(Box::new(post));
80 // Only add the like if the score isnt 0
81 let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
83 let like_form2 = like_form.clone();
84 let like = move |conn: &'_ _| PostLike::like(conn, &like_form2);
85 blocking(context.pool(), like)
87 .map_err(LemmyError::from)
88 .map_err(|e| e.with_message("couldnt_like_post"))?;
92 &local_user_view.person.clone().into(),
94 like_form.score.try_into()?,
99 // API doesn't distinguish between Undo/Like and Undo/Dislike
102 &local_user_view.person.clone().into(),
110 // Mark the post as read
111 mark_post_as_read(person_id, post_id, context.pool()).await?;
113 send_post_ws_message(
115 UserOperation::CreatePostLike,
117 Some(local_user_view.person.id),
124 #[async_trait::async_trait(?Send)]
125 impl Perform for MarkPostAsRead {
126 type Response = PostResponse;
128 #[tracing::instrument(skip(context, _websocket_id))]
131 context: &Data<LemmyContext>,
132 _websocket_id: Option<ConnectionId>,
133 ) -> Result<Self::Response, LemmyError> {
135 let local_user_view =
136 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
138 let post_id = data.post_id;
139 let person_id = local_user_view.person.id;
141 // Mark the post as read / unread
143 mark_post_as_read(person_id, post_id, context.pool()).await?;
145 mark_post_as_unread(person_id, post_id, context.pool()).await?;
149 let post_view = blocking(context.pool(), move |conn| {
150 PostView::read(conn, post_id, Some(person_id))
154 let res = Self::Response { post_view };
160 #[async_trait::async_trait(?Send)]
161 impl Perform for LockPost {
162 type Response = PostResponse;
164 #[tracing::instrument(skip(context, websocket_id))]
167 context: &Data<LemmyContext>,
168 websocket_id: Option<ConnectionId>,
169 ) -> Result<PostResponse, LemmyError> {
170 let data: &LockPost = self;
171 let local_user_view =
172 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
174 let post_id = data.post_id;
175 let orig_post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
178 local_user_view.person.id,
179 orig_post.community_id,
183 check_community_deleted_or_removed(orig_post.community_id, context.pool()).await?;
185 // Verify that only the mods can lock
188 local_user_view.person.id,
189 orig_post.community_id,
194 let post_id = data.post_id;
195 let locked = data.locked;
196 let updated_post: ApubPost = blocking(context.pool(), move |conn| {
197 Post::update_locked(conn, post_id, locked)
203 let form = ModLockPostForm {
204 mod_person_id: local_user_view.person.id,
205 post_id: data.post_id,
206 locked: Some(locked),
208 blocking(context.pool(), move |conn| ModLockPost::create(conn, &form)).await??;
211 CreateOrUpdatePost::send(
213 &local_user_view.person.clone().into(),
214 CreateOrUpdateType::Update,
219 send_post_ws_message(
221 UserOperation::LockPost,
223 Some(local_user_view.person.id),
230 #[async_trait::async_trait(?Send)]
231 impl Perform for StickyPost {
232 type Response = PostResponse;
234 #[tracing::instrument(skip(context, websocket_id))]
237 context: &Data<LemmyContext>,
238 websocket_id: Option<ConnectionId>,
239 ) -> Result<PostResponse, LemmyError> {
240 let data: &StickyPost = self;
241 let local_user_view =
242 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
244 let post_id = data.post_id;
245 let orig_post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
248 local_user_view.person.id,
249 orig_post.community_id,
253 check_community_deleted_or_removed(orig_post.community_id, context.pool()).await?;
255 // Verify that only the mods can sticky
258 local_user_view.person.id,
259 orig_post.community_id,
264 let post_id = data.post_id;
265 let stickied = data.stickied;
266 let updated_post: ApubPost = blocking(context.pool(), move |conn| {
267 Post::update_stickied(conn, post_id, stickied)
273 let form = ModStickyPostForm {
274 mod_person_id: local_user_view.person.id,
275 post_id: data.post_id,
276 stickied: Some(stickied),
278 blocking(context.pool(), move |conn| {
279 ModStickyPost::create(conn, &form)
284 // TODO stickied should pry work like locked for ease of use
285 CreateOrUpdatePost::send(
287 &local_user_view.person.clone().into(),
288 CreateOrUpdateType::Update,
293 send_post_ws_message(
295 UserOperation::StickyPost,
297 Some(local_user_view.person.id),
304 #[async_trait::async_trait(?Send)]
305 impl Perform for SavePost {
306 type Response = PostResponse;
308 #[tracing::instrument(skip(context, _websocket_id))]
311 context: &Data<LemmyContext>,
312 _websocket_id: Option<ConnectionId>,
313 ) -> Result<PostResponse, LemmyError> {
314 let data: &SavePost = self;
315 let local_user_view =
316 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
318 let post_saved_form = PostSavedForm {
319 post_id: data.post_id,
320 person_id: local_user_view.person.id,
324 let save = move |conn: &'_ _| PostSaved::save(conn, &post_saved_form);
325 blocking(context.pool(), save)
327 .map_err(LemmyError::from)
328 .map_err(|e| e.with_message("couldnt_save_post"))?;
330 let unsave = move |conn: &'_ _| PostSaved::unsave(conn, &post_saved_form);
331 blocking(context.pool(), unsave)
333 .map_err(LemmyError::from)
334 .map_err(|e| e.with_message("couldnt_save_post"))?;
337 let post_id = data.post_id;
338 let person_id = local_user_view.person.id;
339 let post_view = blocking(context.pool(), move |conn| {
340 PostView::read(conn, post_id, Some(person_id))
344 // Mark the post as read
345 mark_post_as_read(person_id, post_id, context.pool()).await?;
347 Ok(PostResponse { post_view })
351 #[async_trait::async_trait(?Send)]
352 impl Perform for GetSiteMetadata {
353 type Response = GetSiteMetadataResponse;
355 #[tracing::instrument(skip(context, _websocket_id))]
358 context: &Data<LemmyContext>,
359 _websocket_id: Option<ConnectionId>,
360 ) -> Result<GetSiteMetadataResponse, LemmyError> {
361 let data: &Self = self;
363 let metadata = fetch_site_metadata(context.client(), &data.url).await?;
365 Ok(GetSiteMetadataResponse { metadata })