]> Untitled Git - lemmy.git/blob - server/src/api/post.rs
Merge branch 'admin_settings' into dev
[lemmy.git] / server / src / api / post.rs
1 use super::*;
2 use diesel::PgConnection;
3 use std::str::FromStr;
4
5 #[derive(Serialize, Deserialize)]
6 pub struct CreatePost {
7   name: String,
8   url: Option<String>,
9   body: Option<String>,
10   nsfw: bool,
11   pub community_id: i32,
12   auth: String,
13 }
14
15 #[derive(Serialize, Deserialize, Clone)]
16 pub struct PostResponse {
17   pub post: PostView,
18 }
19
20 #[derive(Serialize, Deserialize)]
21 pub struct GetPost {
22   pub id: i32,
23   auth: Option<String>,
24 }
25
26 #[derive(Serialize, Deserialize)]
27 pub struct GetPostResponse {
28   post: PostView,
29   comments: Vec<CommentView>,
30   community: CommunityView,
31   moderators: Vec<CommunityModeratorView>,
32   admins: Vec<UserView>,
33   pub online: usize,
34 }
35
36 #[derive(Serialize, Deserialize)]
37 pub struct GetPosts {
38   type_: String,
39   sort: String,
40   page: Option<i64>,
41   limit: Option<i64>,
42   pub community_id: Option<i32>,
43   auth: Option<String>,
44 }
45
46 #[derive(Serialize, Deserialize)]
47 pub struct GetPostsResponse {
48   posts: Vec<PostView>,
49 }
50
51 #[derive(Serialize, Deserialize)]
52 pub struct CreatePostLike {
53   post_id: i32,
54   score: i16,
55   auth: String,
56 }
57
58 #[derive(Serialize, Deserialize)]
59 pub struct EditPost {
60   pub edit_id: i32,
61   creator_id: i32,
62   community_id: i32,
63   name: String,
64   url: Option<String>,
65   body: Option<String>,
66   removed: Option<bool>,
67   deleted: Option<bool>,
68   nsfw: bool,
69   locked: Option<bool>,
70   stickied: Option<bool>,
71   reason: Option<String>,
72   auth: String,
73 }
74
75 #[derive(Serialize, Deserialize)]
76 pub struct SavePost {
77   post_id: i32,
78   save: bool,
79   auth: String,
80 }
81
82 impl Perform<PostResponse> for Oper<CreatePost> {
83   fn perform(&self, conn: &PgConnection) -> Result<PostResponse, Error> {
84     let data: &CreatePost = &self.data;
85
86     let claims = match Claims::decode(&data.auth) {
87       Ok(claims) => claims.claims,
88       Err(_e) => return Err(APIError::err("not_logged_in").into()),
89     };
90
91     if let Err(slurs) = slur_check(&data.name) {
92       return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
93     }
94
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());
98       }
99     }
100
101     let user_id = claims.id;
102
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());
106     }
107
108     // Check for a site ban
109     if UserView::read(&conn, user_id)?.banned {
110       return Err(APIError::err("site_ban").into());
111     }
112
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());
116
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,
122       creator_id: user_id,
123       removed: None,
124       deleted: None,
125       nsfw: data.nsfw,
126       locked: None,
127       stickied: None,
128       updated: None,
129       embed_title: iframely_title,
130       embed_description: iframely_description,
131       embed_html: iframely_html,
132       thumbnail_url: pictshare_thumbnail,
133     };
134
135     let inserted_post = match Post::create(&conn, &post_form) {
136       Ok(post) => post,
137       Err(e) => {
138         let err_type = if e.to_string() == "value too long for type character varying(200)" {
139           "post_title_too_long"
140         } else {
141           "couldnt_create_post"
142         };
143
144         return Err(APIError::err(err_type).into());
145       }
146     };
147
148     // They like their own post by default
149     let like_form = PostLikeForm {
150       post_id: inserted_post.id,
151       user_id,
152       score: 1,
153     };
154
155     // Only add the like if the score isnt 0
156     let _inserted_like = match PostLike::like(&conn, &like_form) {
157       Ok(like) => like,
158       Err(_e) => return Err(APIError::err("couldnt_like_post").into()),
159     };
160
161     // Refetch the view
162     let post_view = match PostView::read(&conn, inserted_post.id, Some(user_id)) {
163       Ok(post) => post,
164       Err(_e) => return Err(APIError::err("couldnt_find_post").into()),
165     };
166
167     Ok(PostResponse { post: post_view })
168   }
169 }
170
171 impl Perform<GetPostResponse> for Oper<GetPost> {
172   fn perform(&self, conn: &PgConnection) -> Result<GetPostResponse, Error> {
173     let data: &GetPost = &self.data;
174
175     let user_id: Option<i32> = match &data.auth {
176       Some(auth) => match Claims::decode(&auth) {
177         Ok(claims) => {
178           let user_id = claims.claims.id;
179           Some(user_id)
180         }
181         Err(_e) => None,
182       },
183       None => None,
184     };
185
186     let post_view = match PostView::read(&conn, data.id, user_id) {
187       Ok(post) => post,
188       Err(_e) => return Err(APIError::err("couldnt_find_post").into()),
189     };
190
191     let comments = CommentQueryBuilder::create(&conn)
192       .for_post_id(data.id)
193       .my_user_id(user_id)
194       .limit(9999)
195       .list()?;
196
197     let community = CommunityView::read(&conn, post_view.community_id, user_id)?;
198
199     let moderators = CommunityModeratorView::for_community(&conn, post_view.community_id)?;
200
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);
206
207     // Return the jwt
208     Ok(GetPostResponse {
209       post: post_view,
210       comments,
211       community,
212       moderators,
213       admins,
214       online: 0,
215     })
216   }
217 }
218
219 impl Perform<GetPostsResponse> for Oper<GetPosts> {
220   fn perform(&self, conn: &PgConnection) -> Result<GetPostsResponse, Error> {
221     let data: &GetPosts = &self.data;
222
223     let user_claims: Option<Claims> = match &data.auth {
224       Some(auth) => match Claims::decode(&auth) {
225         Ok(claims) => Some(claims.claims),
226         Err(_e) => None,
227       },
228       None => None,
229     };
230
231     let user_id = match &user_claims {
232       Some(claims) => Some(claims.id),
233       None => None,
234     };
235
236     let show_nsfw = match &user_claims {
237       Some(claims) => claims.show_nsfw,
238       None => false,
239     };
240
241     let type_ = ListingType::from_str(&data.type_)?;
242     let sort = SortType::from_str(&data.sort)?;
243
244     let posts = match PostQueryBuilder::create(&conn)
245       .listing_type(type_)
246       .sort(&sort)
247       .show_nsfw(show_nsfw)
248       .for_community_id(data.community_id)
249       .my_user_id(user_id)
250       .page(data.page)
251       .limit(data.limit)
252       .list()
253     {
254       Ok(posts) => posts,
255       Err(_e) => return Err(APIError::err("couldnt_get_posts").into()),
256     };
257
258     Ok(GetPostsResponse { posts })
259   }
260 }
261
262 impl Perform<PostResponse> for Oper<CreatePostLike> {
263   fn perform(&self, conn: &PgConnection) -> Result<PostResponse, Error> {
264     let data: &CreatePostLike = &self.data;
265
266     let claims = match Claims::decode(&data.auth) {
267       Ok(claims) => claims.claims,
268       Err(_e) => return Err(APIError::err("not_logged_in").into()),
269     };
270
271     let user_id = claims.id;
272
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());
278       }
279     }
280
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());
285     }
286
287     // Check for a site ban
288     if UserView::read(&conn, user_id)?.banned {
289       return Err(APIError::err("site_ban").into());
290     }
291
292     let like_form = PostLikeForm {
293       post_id: data.post_id,
294       user_id,
295       score: data.score,
296     };
297
298     // Remove any likes first
299     PostLike::remove(&conn, &like_form)?;
300
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);
303     if do_add {
304       let _inserted_like = match PostLike::like(&conn, &like_form) {
305         Ok(like) => like,
306         Err(_e) => return Err(APIError::err("couldnt_like_post").into()),
307       };
308     }
309
310     let post_view = match PostView::read(&conn, data.post_id, Some(user_id)) {
311       Ok(post) => post,
312       Err(_e) => return Err(APIError::err("couldnt_find_post").into()),
313     };
314
315     // just output the score
316     Ok(PostResponse { post: post_view })
317   }
318 }
319
320 impl Perform<PostResponse> for Oper<EditPost> {
321   fn perform(&self, conn: &PgConnection) -> Result<PostResponse, Error> {
322     let data: &EditPost = &self.data;
323
324     if let Err(slurs) = slur_check(&data.name) {
325       return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
326     }
327
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());
331       }
332     }
333
334     let claims = match Claims::decode(&data.auth) {
335       Ok(claims) => claims.claims,
336       Err(_e) => return Err(APIError::err("not_logged_in").into()),
337     };
338
339     let user_id = claims.id;
340
341     // Verify its the creator or a mod or admin
342     let mut editors: Vec<i32> = vec![data.creator_id];
343     editors.append(
344       &mut CommunityModeratorView::for_community(&conn, data.community_id)?
345         .into_iter()
346         .map(|m| m.user_id)
347         .collect(),
348     );
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());
352     }
353
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());
357     }
358
359     // Check for a site ban
360     if UserView::read(&conn, user_id)?.banned {
361       return Err(APIError::err("site_ban").into());
362     }
363
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());
367
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(),
376       nsfw: data.nsfw,
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,
384     };
385
386     let _updated_post = match Post::update(&conn, data.edit_id, &post_form) {
387       Ok(post) => post,
388       Err(e) => {
389         let err_type = if e.to_string() == "value too long for type character varying(200)" {
390           "post_title_too_long"
391         } else {
392           "couldnt_update_post"
393         };
394
395         return Err(APIError::err(err_type).into());
396       }
397     };
398
399     // Mod tables
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(),
406       };
407       ModRemovePost::create(&conn, &form)?;
408     }
409
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),
415       };
416       ModLockPost::create(&conn, &form)?;
417     }
418
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),
424       };
425       ModStickyPost::create(&conn, &form)?;
426     }
427
428     let post_view = PostView::read(&conn, data.edit_id, Some(user_id))?;
429
430     Ok(PostResponse { post: post_view })
431   }
432 }
433
434 impl Perform<PostResponse> for Oper<SavePost> {
435   fn perform(&self, conn: &PgConnection) -> Result<PostResponse, Error> {
436     let data: &SavePost = &self.data;
437
438     let claims = match Claims::decode(&data.auth) {
439       Ok(claims) => claims.claims,
440       Err(_e) => return Err(APIError::err("not_logged_in").into()),
441     };
442
443     let user_id = claims.id;
444
445     let post_saved_form = PostSavedForm {
446       post_id: data.post_id,
447       user_id,
448     };
449
450     if data.save {
451       match PostSaved::save(&conn, &post_saved_form) {
452         Ok(post) => post,
453         Err(_e) => return Err(APIError::err("couldnt_save_post").into()),
454       };
455     } else {
456       match PostSaved::unsave(&conn, &post_saved_form) {
457         Ok(post) => post,
458         Err(_e) => return Err(APIError::err("couldnt_save_post").into()),
459       };
460     }
461
462     let post_view = PostView::read(&conn, data.post_id, Some(user_id))?;
463
464     Ok(PostResponse { post: post_view })
465   }
466 }