]> Untitled Git - lemmy.git/blob - server/src/api/post.rs
Merge remote-tracking branch 'upstream/master'
[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     let post_form = PostForm {
114       name: data.name.to_owned(),
115       url: data.url.to_owned(),
116       body: data.body.to_owned(),
117       community_id: data.community_id,
118       creator_id: user_id,
119       removed: None,
120       deleted: None,
121       nsfw: data.nsfw,
122       locked: None,
123       stickied: None,
124       updated: None,
125     };
126
127     let inserted_post = match Post::create(&conn, &post_form) {
128       Ok(post) => post,
129       Err(_e) => return Err(APIError::err("couldnt_create_post").into()),
130     };
131
132     // They like their own post by default
133     let like_form = PostLikeForm {
134       post_id: inserted_post.id,
135       user_id,
136       score: 1,
137     };
138
139     // Only add the like if the score isnt 0
140     let _inserted_like = match PostLike::like(&conn, &like_form) {
141       Ok(like) => like,
142       Err(_e) => return Err(APIError::err("couldnt_like_post").into()),
143     };
144
145     // Refetch the view
146     let post_view = match PostView::read(&conn, inserted_post.id, Some(user_id)) {
147       Ok(post) => post,
148       Err(_e) => return Err(APIError::err("couldnt_find_post").into()),
149     };
150
151     Ok(PostResponse { post: post_view })
152   }
153 }
154
155 impl Perform<GetPostResponse> for Oper<GetPost> {
156   fn perform(&self, conn: &PgConnection) -> Result<GetPostResponse, Error> {
157     let data: &GetPost = &self.data;
158
159     let user_id: Option<i32> = match &data.auth {
160       Some(auth) => match Claims::decode(&auth) {
161         Ok(claims) => {
162           let user_id = claims.claims.id;
163           Some(user_id)
164         }
165         Err(_e) => None,
166       },
167       None => None,
168     };
169
170     let post_view = match PostView::read(&conn, data.id, user_id) {
171       Ok(post) => post,
172       Err(_e) => return Err(APIError::err("couldnt_find_post").into()),
173     };
174
175     let comments = CommentQueryBuilder::create(&conn)
176       .for_post_id(data.id)
177       .my_user_id(user_id)
178       .limit(9999)
179       .list()?;
180
181     let community = CommunityView::read(&conn, post_view.community_id, user_id)?;
182
183     let moderators = CommunityModeratorView::for_community(&conn, post_view.community_id)?;
184
185     let site_creator_id = Site::read(&conn, 1)?.creator_id;
186     let mut admins = UserView::admins(&conn)?;
187     let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
188     let creator_user = admins.remove(creator_index);
189     admins.insert(0, creator_user);
190
191     // Return the jwt
192     Ok(GetPostResponse {
193       post: post_view,
194       comments,
195       community,
196       moderators,
197       admins,
198       online: 0,
199     })
200   }
201 }
202
203 impl Perform<GetPostsResponse> for Oper<GetPosts> {
204   fn perform(&self, conn: &PgConnection) -> Result<GetPostsResponse, Error> {
205     let data: &GetPosts = &self.data;
206
207     let user_claims: Option<Claims> = match &data.auth {
208       Some(auth) => match Claims::decode(&auth) {
209         Ok(claims) => Some(claims.claims),
210         Err(_e) => None,
211       },
212       None => None,
213     };
214
215     let user_id = match &user_claims {
216       Some(claims) => Some(claims.id),
217       None => None,
218     };
219
220     let show_nsfw = match &user_claims {
221       Some(claims) => claims.show_nsfw,
222       None => false,
223     };
224
225     let type_ = ListingType::from_str(&data.type_)?;
226     let sort = SortType::from_str(&data.sort)?;
227
228     let posts = match PostQueryBuilder::create(&conn)
229       .listing_type(type_)
230       .sort(&sort)
231       .show_nsfw(show_nsfw)
232       .for_community_id(data.community_id)
233       .my_user_id(user_id)
234       .page(data.page)
235       .limit(data.limit)
236       .list()
237     {
238       Ok(posts) => posts,
239       Err(_e) => return Err(APIError::err("couldnt_get_posts").into()),
240     };
241
242     Ok(GetPostsResponse { posts })
243   }
244 }
245
246 impl Perform<PostResponse> for Oper<CreatePostLike> {
247   fn perform(&self, conn: &PgConnection) -> Result<PostResponse, Error> {
248     let data: &CreatePostLike = &self.data;
249
250     let claims = match Claims::decode(&data.auth) {
251       Ok(claims) => claims.claims,
252       Err(_e) => return Err(APIError::err("not_logged_in").into()),
253     };
254
255     let user_id = claims.id;
256
257     // Don't do a downvote if site has downvotes disabled
258     if data.score == -1 {
259       let site = SiteView::read(&conn)?;
260       if !site.enable_downvotes {
261         return Err(APIError::err("downvotes_disabled").into());
262       }
263     }
264
265     // Check for a community ban
266     let post = Post::read(&conn, data.post_id)?;
267     if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() {
268       return Err(APIError::err("community_ban").into());
269     }
270
271     // Check for a site ban
272     if UserView::read(&conn, user_id)?.banned {
273       return Err(APIError::err("site_ban").into());
274     }
275
276     let like_form = PostLikeForm {
277       post_id: data.post_id,
278       user_id,
279       score: data.score,
280     };
281
282     // Remove any likes first
283     PostLike::remove(&conn, &like_form)?;
284
285     // Only add the like if the score isnt 0
286     let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
287     if do_add {
288       let _inserted_like = match PostLike::like(&conn, &like_form) {
289         Ok(like) => like,
290         Err(_e) => return Err(APIError::err("couldnt_like_post").into()),
291       };
292     }
293
294     let post_view = match PostView::read(&conn, data.post_id, Some(user_id)) {
295       Ok(post) => post,
296       Err(_e) => return Err(APIError::err("couldnt_find_post").into()),
297     };
298
299     // just output the score
300     Ok(PostResponse { post: post_view })
301   }
302 }
303
304 impl Perform<PostResponse> for Oper<EditPost> {
305   fn perform(&self, conn: &PgConnection) -> Result<PostResponse, Error> {
306     let data: &EditPost = &self.data;
307
308     if let Err(slurs) = slur_check(&data.name) {
309       return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
310     }
311
312     if let Some(body) = &data.body {
313       if let Err(slurs) = slur_check(body) {
314         return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
315       }
316     }
317
318     let claims = match Claims::decode(&data.auth) {
319       Ok(claims) => claims.claims,
320       Err(_e) => return Err(APIError::err("not_logged_in").into()),
321     };
322
323     let user_id = claims.id;
324
325     // Verify its the creator or a mod or admin
326     let mut editors: Vec<i32> = vec![data.creator_id];
327     editors.append(
328       &mut CommunityModeratorView::for_community(&conn, data.community_id)?
329         .into_iter()
330         .map(|m| m.user_id)
331         .collect(),
332     );
333     editors.append(&mut UserView::admins(&conn)?.into_iter().map(|a| a.id).collect());
334     if !editors.contains(&user_id) {
335       return Err(APIError::err("no_post_edit_allowed").into());
336     }
337
338     // Check for a community ban
339     if CommunityUserBanView::get(&conn, user_id, data.community_id).is_ok() {
340       return Err(APIError::err("community_ban").into());
341     }
342
343     // Check for a site ban
344     if UserView::read(&conn, user_id)?.banned {
345       return Err(APIError::err("site_ban").into());
346     }
347
348     let post_form = PostForm {
349       name: data.name.to_owned(),
350       url: data.url.to_owned(),
351       body: data.body.to_owned(),
352       creator_id: data.creator_id.to_owned(),
353       community_id: data.community_id,
354       removed: data.removed.to_owned(),
355       deleted: data.deleted.to_owned(),
356       nsfw: data.nsfw,
357       locked: data.locked.to_owned(),
358       stickied: data.stickied.to_owned(),
359       updated: Some(naive_now()),
360     };
361
362     let _updated_post = match Post::update(&conn, data.edit_id, &post_form) {
363       Ok(post) => post,
364       Err(_e) => return Err(APIError::err("couldnt_update_post").into()),
365     };
366
367     // Mod tables
368     if let Some(removed) = data.removed.to_owned() {
369       let form = ModRemovePostForm {
370         mod_user_id: user_id,
371         post_id: data.edit_id,
372         removed: Some(removed),
373         reason: data.reason.to_owned(),
374       };
375       ModRemovePost::create(&conn, &form)?;
376     }
377
378     if let Some(locked) = data.locked.to_owned() {
379       let form = ModLockPostForm {
380         mod_user_id: user_id,
381         post_id: data.edit_id,
382         locked: Some(locked),
383       };
384       ModLockPost::create(&conn, &form)?;
385     }
386
387     if let Some(stickied) = data.stickied.to_owned() {
388       let form = ModStickyPostForm {
389         mod_user_id: user_id,
390         post_id: data.edit_id,
391         stickied: Some(stickied),
392       };
393       ModStickyPost::create(&conn, &form)?;
394     }
395
396     let post_view = PostView::read(&conn, data.edit_id, Some(user_id))?;
397
398     Ok(PostResponse { post: post_view })
399   }
400 }
401
402 impl Perform<PostResponse> for Oper<SavePost> {
403   fn perform(&self, conn: &PgConnection) -> Result<PostResponse, Error> {
404     let data: &SavePost = &self.data;
405
406     let claims = match Claims::decode(&data.auth) {
407       Ok(claims) => claims.claims,
408       Err(_e) => return Err(APIError::err("not_logged_in").into()),
409     };
410
411     let user_id = claims.id;
412
413     let post_saved_form = PostSavedForm {
414       post_id: data.post_id,
415       user_id,
416     };
417
418     if data.save {
419       match PostSaved::save(&conn, &post_saved_form) {
420         Ok(post) => post,
421         Err(_e) => return Err(APIError::err("couldnt_save_post").into()),
422       };
423     } else {
424       match PostSaved::unsave(&conn, &post_saved_form) {
425         Ok(post) => post,
426         Err(_e) => return Err(APIError::err("couldnt_save_post").into()),
427       };
428     }
429
430     let post_view = PostView::read(&conn, data.post_id, Some(user_id))?;
431
432     Ok(PostResponse { post: post_view })
433   }
434 }