2 api::{APIError, Oper, Perform},
3 apub::{ApubLikeableType, ApubObjectType},
20 fetch_iframely_and_pictshare_data,
25 server::{JoinCommunityRoom, JoinPostRoom, SendPost},
31 r2d2::{ConnectionManager, Pool},
35 use serde::{Deserialize, Serialize};
36 use std::str::FromStr;
38 #[derive(Serialize, Deserialize, Debug)]
39 pub struct CreatePost {
44 pub community_id: i32,
48 #[derive(Serialize, Deserialize, Clone)]
49 pub struct PostResponse {
53 #[derive(Serialize, Deserialize)]
59 #[derive(Serialize, Deserialize)]
60 pub struct GetPostResponse {
62 comments: Vec<CommentView>,
63 community: CommunityView,
64 moderators: Vec<CommunityModeratorView>,
65 admins: Vec<UserView>,
69 #[derive(Serialize, Deserialize, Debug)]
75 pub community_id: Option<i32>,
79 #[derive(Serialize, Deserialize, Debug)]
80 pub struct GetPostsResponse {
81 pub posts: Vec<PostView>,
84 #[derive(Serialize, Deserialize)]
85 pub struct CreatePostLike {
91 #[derive(Serialize, Deserialize)]
99 removed: Option<bool>,
100 deleted: Option<bool>,
102 locked: Option<bool>,
103 stickied: Option<bool>,
104 reason: Option<String>,
108 #[derive(Serialize, Deserialize)]
109 pub struct SavePost {
115 impl Perform for Oper<CreatePost> {
116 type Response = PostResponse;
120 pool: Pool<ConnectionManager<PgConnection>>,
121 websocket_info: Option<WebsocketInfo>,
122 ) -> Result<PostResponse, Error> {
123 let data: &CreatePost = &self.data;
125 let claims = match Claims::decode(&data.auth) {
126 Ok(claims) => claims.claims,
127 Err(_e) => return Err(APIError::err("not_logged_in").into()),
130 if let Err(slurs) = slur_check(&data.name) {
131 return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
134 if let Some(body) = &data.body {
135 if let Err(slurs) = slur_check(body) {
136 return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
140 let user_id = claims.id;
142 let conn = pool.get()?;
144 // Check for a community ban
145 if CommunityUserBanView::get(&conn, user_id, data.community_id).is_ok() {
146 return Err(APIError::err("community_ban").into());
149 // Check for a site ban
150 let user = User_::read(&conn, user_id)?;
152 return Err(APIError::err("site_ban").into());
155 // Fetch Iframely and Pictshare cached image
156 let (iframely_title, iframely_description, iframely_html, pictshare_thumbnail) =
157 fetch_iframely_and_pictshare_data(data.url.to_owned());
159 let post_form = PostForm {
160 name: data.name.to_owned(),
161 url: data.url.to_owned(),
162 body: data.body.to_owned(),
163 community_id: data.community_id,
171 embed_title: iframely_title,
172 embed_description: iframely_description,
173 embed_html: iframely_html,
174 thumbnail_url: pictshare_thumbnail,
175 ap_id: "changeme".into(),
180 let inserted_post = match Post::create(&conn, &post_form) {
183 let err_type = if e.to_string() == "value too long for type character varying(200)" {
184 "post_title_too_long"
186 "couldnt_create_post"
189 return Err(APIError::err(err_type).into());
193 let updated_post = match Post::update_ap_id(&conn, inserted_post.id) {
195 Err(_e) => return Err(APIError::err("couldnt_create_post").into()),
198 updated_post.send_create(&user, &conn)?;
200 // They like their own post by default
201 let like_form = PostLikeForm {
202 post_id: inserted_post.id,
207 let _inserted_like = match PostLike::like(&conn, &like_form) {
209 Err(_e) => return Err(APIError::err("couldnt_like_post").into()),
212 updated_post.send_like(&user, &conn)?;
215 let post_view = match PostView::read(&conn, inserted_post.id, Some(user_id)) {
217 Err(_e) => return Err(APIError::err("couldnt_find_post").into()),
220 let res = PostResponse { post: post_view };
222 if let Some(ws) = websocket_info {
223 ws.chatserver.do_send(SendPost {
224 op: UserOperation::CreatePost,
234 impl Perform for Oper<GetPost> {
235 type Response = GetPostResponse;
239 pool: Pool<ConnectionManager<PgConnection>>,
240 websocket_info: Option<WebsocketInfo>,
241 ) -> Result<GetPostResponse, Error> {
242 let data: &GetPost = &self.data;
244 let user_id: Option<i32> = match &data.auth {
245 Some(auth) => match Claims::decode(&auth) {
247 let user_id = claims.claims.id;
255 let conn = pool.get()?;
257 let post_view = match PostView::read(&conn, data.id, user_id) {
259 Err(_e) => return Err(APIError::err("couldnt_find_post").into()),
262 let comments = CommentQueryBuilder::create(&conn)
263 .for_post_id(data.id)
268 let community = CommunityView::read(&conn, post_view.community_id, user_id)?;
270 let moderators = CommunityModeratorView::for_community(&conn, post_view.community_id)?;
272 let site_creator_id = Site::read(&conn, 1)?.creator_id;
273 let mut admins = UserView::admins(&conn)?;
274 let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
275 let creator_user = admins.remove(creator_index);
276 admins.insert(0, creator_user);
278 let online = if let Some(ws) = websocket_info {
279 if let Some(id) = ws.id {
280 ws.chatserver.do_send(JoinPostRoom {
289 // ws.chatserver.send(GetPostUsersOnline {post_id: data.id}).await.unwrap()
291 // Runtime::new().unwrap().block_on(fut)
308 impl Perform for Oper<GetPosts> {
309 type Response = GetPostsResponse;
313 pool: Pool<ConnectionManager<PgConnection>>,
314 websocket_info: Option<WebsocketInfo>,
315 ) -> Result<GetPostsResponse, Error> {
316 let data: &GetPosts = &self.data;
318 let user_claims: Option<Claims> = match &data.auth {
319 Some(auth) => match Claims::decode(&auth) {
320 Ok(claims) => Some(claims.claims),
326 let user_id = match &user_claims {
327 Some(claims) => Some(claims.id),
331 let show_nsfw = match &user_claims {
332 Some(claims) => claims.show_nsfw,
336 let type_ = ListingType::from_str(&data.type_)?;
337 let sort = SortType::from_str(&data.sort)?;
339 let conn = pool.get()?;
341 let posts = match PostQueryBuilder::create(&conn)
344 .show_nsfw(show_nsfw)
345 .for_community_id(data.community_id)
352 Err(_e) => return Err(APIError::err("couldnt_get_posts").into()),
355 if let Some(ws) = websocket_info {
356 // You don't need to join the specific community room, bc this is already handled by
358 if data.community_id.is_none() {
359 if let Some(id) = ws.id {
360 // 0 is the "all" community
361 ws.chatserver.do_send(JoinCommunityRoom {
369 Ok(GetPostsResponse { posts })
373 impl Perform for Oper<CreatePostLike> {
374 type Response = PostResponse;
378 pool: Pool<ConnectionManager<PgConnection>>,
379 websocket_info: Option<WebsocketInfo>,
380 ) -> Result<PostResponse, Error> {
381 let data: &CreatePostLike = &self.data;
383 let claims = match Claims::decode(&data.auth) {
384 Ok(claims) => claims.claims,
385 Err(_e) => return Err(APIError::err("not_logged_in").into()),
388 let user_id = claims.id;
390 let conn = pool.get()?;
392 // Don't do a downvote if site has downvotes disabled
393 if data.score == -1 {
394 let site = SiteView::read(&conn)?;
395 if !site.enable_downvotes {
396 return Err(APIError::err("downvotes_disabled").into());
400 // Check for a community ban
401 let post = Post::read(&conn, data.post_id)?;
402 if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() {
403 return Err(APIError::err("community_ban").into());
406 // Check for a site ban
407 let user = User_::read(&conn, user_id)?;
409 return Err(APIError::err("site_ban").into());
412 let like_form = PostLikeForm {
413 post_id: data.post_id,
418 // Remove any likes first
419 PostLike::remove(&conn, &like_form)?;
421 // Only add the like if the score isnt 0
422 let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
424 let _inserted_like = match PostLike::like(&conn, &like_form) {
426 Err(_e) => return Err(APIError::err("couldnt_like_post").into()),
429 if like_form.score == 1 {
430 post.send_like(&user, &conn)?;
431 } else if like_form.score == -1 {
432 post.send_dislike(&user, &conn)?;
435 post.send_undo_like(&user, &conn)?;
438 let post_view = match PostView::read(&conn, data.post_id, Some(user_id)) {
440 Err(_e) => return Err(APIError::err("couldnt_find_post").into()),
443 let res = PostResponse { post: post_view };
445 if let Some(ws) = websocket_info {
446 ws.chatserver.do_send(SendPost {
447 op: UserOperation::CreatePostLike,
457 impl Perform for Oper<EditPost> {
458 type Response = PostResponse;
462 pool: Pool<ConnectionManager<PgConnection>>,
463 websocket_info: Option<WebsocketInfo>,
464 ) -> Result<PostResponse, Error> {
465 let data: &EditPost = &self.data;
467 if let Err(slurs) = slur_check(&data.name) {
468 return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
471 if let Some(body) = &data.body {
472 if let Err(slurs) = slur_check(body) {
473 return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
477 let claims = match Claims::decode(&data.auth) {
478 Ok(claims) => claims.claims,
479 Err(_e) => return Err(APIError::err("not_logged_in").into()),
482 let user_id = claims.id;
484 let conn = pool.get()?;
486 // Verify its the creator or a mod or admin
487 let mut editors: Vec<i32> = vec![data.creator_id];
489 &mut CommunityModeratorView::for_community(&conn, data.community_id)?
494 editors.append(&mut UserView::admins(&conn)?.into_iter().map(|a| a.id).collect());
495 if !editors.contains(&user_id) {
496 return Err(APIError::err("no_post_edit_allowed").into());
499 // Check for a community ban
500 if CommunityUserBanView::get(&conn, user_id, data.community_id).is_ok() {
501 return Err(APIError::err("community_ban").into());
504 // Check for a site ban
505 let user = User_::read(&conn, user_id)?;
507 return Err(APIError::err("site_ban").into());
510 // Fetch Iframely and Pictshare cached image
511 let (iframely_title, iframely_description, iframely_html, pictshare_thumbnail) =
512 fetch_iframely_and_pictshare_data(data.url.to_owned());
514 let read_post = Post::read(&conn, data.edit_id)?;
516 let post_form = PostForm {
517 name: data.name.to_owned(),
518 url: data.url.to_owned(),
519 body: data.body.to_owned(),
520 creator_id: data.creator_id.to_owned(),
521 community_id: data.community_id,
522 removed: data.removed.to_owned(),
523 deleted: data.deleted.to_owned(),
525 locked: data.locked.to_owned(),
526 stickied: data.stickied.to_owned(),
527 updated: Some(naive_now()),
528 embed_title: iframely_title,
529 embed_description: iframely_description,
530 embed_html: iframely_html,
531 thumbnail_url: pictshare_thumbnail,
532 ap_id: read_post.ap_id,
533 local: read_post.local,
537 let updated_post = match Post::update(&conn, data.edit_id, &post_form) {
540 let err_type = if e.to_string() == "value too long for type character varying(200)" {
541 "post_title_too_long"
543 "couldnt_update_post"
546 return Err(APIError::err(err_type).into());
551 if let Some(removed) = data.removed.to_owned() {
552 let form = ModRemovePostForm {
553 mod_user_id: user_id,
554 post_id: data.edit_id,
555 removed: Some(removed),
556 reason: data.reason.to_owned(),
558 ModRemovePost::create(&conn, &form)?;
561 if let Some(locked) = data.locked.to_owned() {
562 let form = ModLockPostForm {
563 mod_user_id: user_id,
564 post_id: data.edit_id,
565 locked: Some(locked),
567 ModLockPost::create(&conn, &form)?;
570 if let Some(stickied) = data.stickied.to_owned() {
571 let form = ModStickyPostForm {
572 mod_user_id: user_id,
573 post_id: data.edit_id,
574 stickied: Some(stickied),
576 ModStickyPost::create(&conn, &form)?;
579 if let Some(deleted) = data.deleted.to_owned() {
581 updated_post.send_delete(&user, &conn)?;
583 updated_post.send_undo_delete(&user, &conn)?;
585 } else if let Some(removed) = data.removed.to_owned() {
587 updated_post.send_remove(&user, &conn)?;
589 updated_post.send_undo_remove(&user, &conn)?;
592 updated_post.send_update(&user, &conn)?;
595 let post_view = PostView::read(&conn, data.edit_id, Some(user_id))?;
597 let res = PostResponse { post: post_view };
599 if let Some(ws) = websocket_info {
600 ws.chatserver.do_send(SendPost {
601 op: UserOperation::EditPost,
611 impl Perform for Oper<SavePost> {
612 type Response = PostResponse;
616 pool: Pool<ConnectionManager<PgConnection>>,
617 _websocket_info: Option<WebsocketInfo>,
618 ) -> Result<PostResponse, Error> {
619 let data: &SavePost = &self.data;
621 let claims = match Claims::decode(&data.auth) {
622 Ok(claims) => claims.claims,
623 Err(_e) => return Err(APIError::err("not_logged_in").into()),
626 let user_id = claims.id;
628 let post_saved_form = PostSavedForm {
629 post_id: data.post_id,
633 let conn = pool.get()?;
636 match PostSaved::save(&conn, &post_saved_form) {
638 Err(_e) => return Err(APIError::err("couldnt_save_post").into()),
641 match PostSaved::unsave(&conn, &post_saved_form) {
643 Err(_e) => return Err(APIError::err("couldnt_save_post").into()),
647 let post_view = PostView::read(&conn, data.post_id, Some(user_id))?;
649 Ok(PostResponse { post: post_view })