2 use diesel::PgConnection;
5 #[derive(Serialize, Deserialize)]
6 pub struct CreatePost {
11 pub community_id: i32,
15 #[derive(Serialize, Deserialize, Clone)]
16 pub struct PostResponse {
20 #[derive(Serialize, Deserialize)]
26 #[derive(Serialize, Deserialize)]
27 pub struct GetPostResponse {
29 comments: Vec<CommentView>,
30 community: CommunityView,
31 moderators: Vec<CommunityModeratorView>,
32 admins: Vec<UserView>,
36 #[derive(Serialize, Deserialize)]
42 pub community_id: Option<i32>,
46 #[derive(Serialize, Deserialize)]
47 pub struct GetPostsResponse {
51 #[derive(Serialize, Deserialize)]
52 pub struct CreatePostLike {
58 #[derive(Serialize, Deserialize)]
66 removed: Option<bool>,
67 deleted: Option<bool>,
70 stickied: Option<bool>,
71 reason: Option<String>,
75 #[derive(Serialize, Deserialize)]
82 impl Perform<PostResponse> for Oper<CreatePost> {
83 fn perform(&self, conn: &PgConnection) -> Result<PostResponse, Error> {
84 let data: &CreatePost = &self.data;
86 let claims = match Claims::decode(&data.auth) {
87 Ok(claims) => claims.claims,
88 Err(_e) => return Err(APIError::err("not_logged_in").into()),
91 if let Err(slurs) = slur_check(&data.name) {
92 return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
95 if let Some(body) = &data.body {
96 if let Err(slurs) = slur_check(body) {
97 return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
101 let user_id = claims.id;
103 // Check for a community ban
104 if CommunityUserBanView::get(&conn, user_id, data.community_id).is_ok() {
105 return Err(APIError::err("community_ban").into());
108 // Check for a site ban
109 if UserView::read(&conn, user_id)?.banned {
110 return Err(APIError::err("site_ban").into());
113 // Fetch Iframely and Pictshare cached image
114 let (iframely_title, iframely_description, iframely_html, pictshare_thumbnail) =
115 fetch_iframely_and_pictshare_data(data.url.to_owned());
117 let post_form = PostForm {
118 name: data.name.to_owned(),
119 url: data.url.to_owned(),
120 body: data.body.to_owned(),
121 community_id: data.community_id,
129 embed_title: iframely_title,
130 embed_description: iframely_description,
131 embed_html: iframely_html,
132 thumbnail_url: pictshare_thumbnail,
135 let inserted_post = match Post::create(&conn, &post_form) {
138 let err_type = if e.to_string() == "value too long for type character varying(200)" {
139 "post_title_too_long"
141 "couldnt_create_post"
144 return Err(APIError::err(err_type).into());
148 // They like their own post by default
149 let like_form = PostLikeForm {
150 post_id: inserted_post.id,
155 // Only add the like if the score isnt 0
156 let _inserted_like = match PostLike::like(&conn, &like_form) {
158 Err(_e) => return Err(APIError::err("couldnt_like_post").into()),
162 let post_view = match PostView::read(&conn, inserted_post.id, Some(user_id)) {
164 Err(_e) => return Err(APIError::err("couldnt_find_post").into()),
167 Ok(PostResponse { post: post_view })
171 impl Perform<GetPostResponse> for Oper<GetPost> {
172 fn perform(&self, conn: &PgConnection) -> Result<GetPostResponse, Error> {
173 let data: &GetPost = &self.data;
175 let user_id: Option<i32> = match &data.auth {
176 Some(auth) => match Claims::decode(&auth) {
178 let user_id = claims.claims.id;
186 let post_view = match PostView::read(&conn, data.id, user_id) {
188 Err(_e) => return Err(APIError::err("couldnt_find_post").into()),
191 let comments = CommentQueryBuilder::create(&conn)
192 .for_post_id(data.id)
197 let community = CommunityView::read(&conn, post_view.community_id, user_id)?;
199 let moderators = CommunityModeratorView::for_community(&conn, post_view.community_id)?;
201 let site_creator_id = Site::read(&conn, 1)?.creator_id;
202 let mut admins = UserView::admins(&conn)?;
203 let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
204 let creator_user = admins.remove(creator_index);
205 admins.insert(0, creator_user);
219 impl Perform<GetPostsResponse> for Oper<GetPosts> {
220 fn perform(&self, conn: &PgConnection) -> Result<GetPostsResponse, Error> {
221 let data: &GetPosts = &self.data;
223 let user_claims: Option<Claims> = match &data.auth {
224 Some(auth) => match Claims::decode(&auth) {
225 Ok(claims) => Some(claims.claims),
231 let user_id = match &user_claims {
232 Some(claims) => Some(claims.id),
236 let show_nsfw = match &user_claims {
237 Some(claims) => claims.show_nsfw,
241 let type_ = ListingType::from_str(&data.type_)?;
242 let sort = SortType::from_str(&data.sort)?;
244 let posts = match PostQueryBuilder::create(&conn)
247 .show_nsfw(show_nsfw)
248 .for_community_id(data.community_id)
255 Err(_e) => return Err(APIError::err("couldnt_get_posts").into()),
258 Ok(GetPostsResponse { posts })
262 impl Perform<PostResponse> for Oper<CreatePostLike> {
263 fn perform(&self, conn: &PgConnection) -> Result<PostResponse, Error> {
264 let data: &CreatePostLike = &self.data;
266 let claims = match Claims::decode(&data.auth) {
267 Ok(claims) => claims.claims,
268 Err(_e) => return Err(APIError::err("not_logged_in").into()),
271 let user_id = claims.id;
273 // Don't do a downvote if site has downvotes disabled
274 if data.score == -1 {
275 let site = SiteView::read(&conn)?;
276 if !site.enable_downvotes {
277 return Err(APIError::err("downvotes_disabled").into());
281 // Check for a community ban
282 let post = Post::read(&conn, data.post_id)?;
283 if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() {
284 return Err(APIError::err("community_ban").into());
287 // Check for a site ban
288 if UserView::read(&conn, user_id)?.banned {
289 return Err(APIError::err("site_ban").into());
292 let like_form = PostLikeForm {
293 post_id: data.post_id,
298 // Remove any likes first
299 PostLike::remove(&conn, &like_form)?;
301 // Only add the like if the score isnt 0
302 let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
304 let _inserted_like = match PostLike::like(&conn, &like_form) {
306 Err(_e) => return Err(APIError::err("couldnt_like_post").into()),
310 let post_view = match PostView::read(&conn, data.post_id, Some(user_id)) {
312 Err(_e) => return Err(APIError::err("couldnt_find_post").into()),
315 // just output the score
316 Ok(PostResponse { post: post_view })
320 impl Perform<PostResponse> for Oper<EditPost> {
321 fn perform(&self, conn: &PgConnection) -> Result<PostResponse, Error> {
322 let data: &EditPost = &self.data;
324 if let Err(slurs) = slur_check(&data.name) {
325 return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
328 if let Some(body) = &data.body {
329 if let Err(slurs) = slur_check(body) {
330 return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
334 let claims = match Claims::decode(&data.auth) {
335 Ok(claims) => claims.claims,
336 Err(_e) => return Err(APIError::err("not_logged_in").into()),
339 let user_id = claims.id;
341 // Verify its the creator or a mod or admin
342 let mut editors: Vec<i32> = vec![data.creator_id];
344 &mut CommunityModeratorView::for_community(&conn, data.community_id)?
349 editors.append(&mut UserView::admins(&conn)?.into_iter().map(|a| a.id).collect());
350 if !editors.contains(&user_id) {
351 return Err(APIError::err("no_post_edit_allowed").into());
354 // Check for a community ban
355 if CommunityUserBanView::get(&conn, user_id, data.community_id).is_ok() {
356 return Err(APIError::err("community_ban").into());
359 // Check for a site ban
360 if UserView::read(&conn, user_id)?.banned {
361 return Err(APIError::err("site_ban").into());
364 // Fetch Iframely and Pictshare cached image
365 let (iframely_title, iframely_description, iframely_html, pictshare_thumbnail) =
366 fetch_iframely_and_pictshare_data(data.url.to_owned());
368 let post_form = PostForm {
369 name: data.name.to_owned(),
370 url: data.url.to_owned(),
371 body: data.body.to_owned(),
372 creator_id: data.creator_id.to_owned(),
373 community_id: data.community_id,
374 removed: data.removed.to_owned(),
375 deleted: data.deleted.to_owned(),
377 locked: data.locked.to_owned(),
378 stickied: data.stickied.to_owned(),
379 updated: Some(naive_now()),
380 embed_title: iframely_title,
381 embed_description: iframely_description,
382 embed_html: iframely_html,
383 thumbnail_url: pictshare_thumbnail,
386 let _updated_post = match Post::update(&conn, data.edit_id, &post_form) {
389 let err_type = if e.to_string() == "value too long for type character varying(200)" {
390 "post_title_too_long"
392 "couldnt_update_post"
395 return Err(APIError::err(err_type).into());
400 if let Some(removed) = data.removed.to_owned() {
401 let form = ModRemovePostForm {
402 mod_user_id: user_id,
403 post_id: data.edit_id,
404 removed: Some(removed),
405 reason: data.reason.to_owned(),
407 ModRemovePost::create(&conn, &form)?;
410 if let Some(locked) = data.locked.to_owned() {
411 let form = ModLockPostForm {
412 mod_user_id: user_id,
413 post_id: data.edit_id,
414 locked: Some(locked),
416 ModLockPost::create(&conn, &form)?;
419 if let Some(stickied) = data.stickied.to_owned() {
420 let form = ModStickyPostForm {
421 mod_user_id: user_id,
422 post_id: data.edit_id,
423 stickied: Some(stickied),
425 ModStickyPost::create(&conn, &form)?;
428 let post_view = PostView::read(&conn, data.edit_id, Some(user_id))?;
430 Ok(PostResponse { post: post_view })
434 impl Perform<PostResponse> for Oper<SavePost> {
435 fn perform(&self, conn: &PgConnection) -> Result<PostResponse, Error> {
436 let data: &SavePost = &self.data;
438 let claims = match Claims::decode(&data.auth) {
439 Ok(claims) => claims.claims,
440 Err(_e) => return Err(APIError::err("not_logged_in").into()),
443 let user_id = claims.id;
445 let post_saved_form = PostSavedForm {
446 post_id: data.post_id,
451 match PostSaved::save(&conn, &post_saved_form) {
453 Err(_e) => return Err(APIError::err("couldnt_save_post").into()),
456 match PostSaved::unsave(&conn, &post_saved_form) {
458 Err(_e) => return Err(APIError::err("couldnt_save_post").into()),
462 let post_view = PostView::read(&conn, data.post_id, Some(user_id))?;
464 Ok(PostResponse { post: post_view })