]> Untitled Git - lemmy.git/blob - server/src/api/community.rs
Version v0.6.11
[lemmy.git] / server / src / api / community.rs
1 use super::*;
2 use diesel::PgConnection;
3 use std::str::FromStr;
4
5 #[derive(Serialize, Deserialize)]
6 pub struct GetCommunity {
7   id: Option<i32>,
8   name: Option<String>,
9   auth: Option<String>,
10 }
11
12 #[derive(Serialize, Deserialize)]
13 pub struct GetCommunityResponse {
14   pub community: CommunityView,
15   moderators: Vec<CommunityModeratorView>,
16   admins: Vec<UserView>,
17   pub online: usize,
18 }
19
20 #[derive(Serialize, Deserialize)]
21 pub struct CreateCommunity {
22   name: String,
23   title: String,
24   description: Option<String>,
25   category_id: i32,
26   nsfw: bool,
27   auth: String,
28 }
29
30 #[derive(Serialize, Deserialize, Clone)]
31 pub struct CommunityResponse {
32   pub community: CommunityView,
33 }
34
35 #[derive(Serialize, Deserialize)]
36 pub struct ListCommunities {
37   sort: String,
38   page: Option<i64>,
39   limit: Option<i64>,
40   auth: Option<String>,
41 }
42
43 #[derive(Serialize, Deserialize)]
44 pub struct ListCommunitiesResponse {
45   communities: Vec<CommunityView>,
46 }
47
48 #[derive(Serialize, Deserialize, Clone)]
49 pub struct BanFromCommunity {
50   pub community_id: i32,
51   user_id: i32,
52   ban: bool,
53   reason: Option<String>,
54   expires: Option<i64>,
55   auth: String,
56 }
57
58 #[derive(Serialize, Deserialize)]
59 pub struct BanFromCommunityResponse {
60   user: UserView,
61   banned: bool,
62 }
63
64 #[derive(Serialize, Deserialize)]
65 pub struct AddModToCommunity {
66   pub community_id: i32,
67   user_id: i32,
68   added: bool,
69   auth: String,
70 }
71
72 #[derive(Serialize, Deserialize)]
73 pub struct AddModToCommunityResponse {
74   moderators: Vec<CommunityModeratorView>,
75 }
76
77 #[derive(Serialize, Deserialize)]
78 pub struct EditCommunity {
79   pub edit_id: i32,
80   name: String,
81   title: String,
82   description: Option<String>,
83   category_id: i32,
84   removed: Option<bool>,
85   deleted: Option<bool>,
86   nsfw: bool,
87   reason: Option<String>,
88   expires: Option<i64>,
89   auth: String,
90 }
91
92 #[derive(Serialize, Deserialize)]
93 pub struct FollowCommunity {
94   community_id: i32,
95   follow: bool,
96   auth: String,
97 }
98
99 #[derive(Serialize, Deserialize)]
100 pub struct GetFollowedCommunities {
101   auth: String,
102 }
103
104 #[derive(Serialize, Deserialize)]
105 pub struct GetFollowedCommunitiesResponse {
106   communities: Vec<CommunityFollowerView>,
107 }
108
109 #[derive(Serialize, Deserialize)]
110 pub struct TransferCommunity {
111   community_id: i32,
112   user_id: i32,
113   auth: String,
114 }
115
116 impl Perform<GetCommunityResponse> for Oper<GetCommunity> {
117   fn perform(&self, conn: &PgConnection) -> Result<GetCommunityResponse, Error> {
118     let data: &GetCommunity = &self.data;
119
120     let user_id: Option<i32> = match &data.auth {
121       Some(auth) => match Claims::decode(&auth) {
122         Ok(claims) => {
123           let user_id = claims.claims.id;
124           Some(user_id)
125         }
126         Err(_e) => None,
127       },
128       None => None,
129     };
130
131     let community_id = match data.id {
132       Some(id) => id,
133       None => {
134         match Community::read_from_name(
135           &conn,
136           data.name.to_owned().unwrap_or_else(|| "main".to_string()),
137         ) {
138           Ok(community) => community.id,
139           Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
140         }
141       }
142     };
143
144     let community_view = match CommunityView::read(&conn, community_id, user_id) {
145       Ok(community) => community,
146       Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
147     };
148
149     let moderators = match CommunityModeratorView::for_community(&conn, community_id) {
150       Ok(moderators) => moderators,
151       Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
152     };
153
154     let site_creator_id = Site::read(&conn, 1)?.creator_id;
155     let mut admins = UserView::admins(&conn)?;
156     let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
157     let creator_user = admins.remove(creator_index);
158     admins.insert(0, creator_user);
159
160     // Return the jwt
161     Ok(GetCommunityResponse {
162       community: community_view,
163       moderators,
164       admins,
165       online: 0,
166     })
167   }
168 }
169
170 impl Perform<CommunityResponse> for Oper<CreateCommunity> {
171   fn perform(&self, conn: &PgConnection) -> Result<CommunityResponse, Error> {
172     let data: &CreateCommunity = &self.data;
173
174     let claims = match Claims::decode(&data.auth) {
175       Ok(claims) => claims.claims,
176       Err(_e) => return Err(APIError::err("not_logged_in").into()),
177     };
178
179     if let Err(slurs) = slur_check(&data.name) {
180       return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
181     }
182
183     if let Err(slurs) = slur_check(&data.title) {
184       return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
185     }
186
187     if let Some(description) = &data.description {
188       if let Err(slurs) = slur_check(description) {
189         return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
190       }
191     }
192
193     let user_id = claims.id;
194
195     // Check for a site ban
196     if UserView::read(&conn, user_id)?.banned {
197       return Err(APIError::err("site_ban").into());
198     }
199
200     // When you create a community, make sure the user becomes a moderator and a follower
201     let community_form = CommunityForm {
202       name: data.name.to_owned(),
203       title: data.title.to_owned(),
204       description: data.description.to_owned(),
205       category_id: data.category_id,
206       creator_id: user_id,
207       removed: None,
208       deleted: None,
209       nsfw: data.nsfw,
210       updated: None,
211     };
212
213     let inserted_community = match Community::create(&conn, &community_form) {
214       Ok(community) => community,
215       Err(_e) => return Err(APIError::err("community_already_exists").into()),
216     };
217
218     let community_moderator_form = CommunityModeratorForm {
219       community_id: inserted_community.id,
220       user_id,
221     };
222
223     let _inserted_community_moderator =
224       match CommunityModerator::join(&conn, &community_moderator_form) {
225         Ok(user) => user,
226         Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()),
227       };
228
229     let community_follower_form = CommunityFollowerForm {
230       community_id: inserted_community.id,
231       user_id,
232     };
233
234     let _inserted_community_follower =
235       match CommunityFollower::follow(&conn, &community_follower_form) {
236         Ok(user) => user,
237         Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
238       };
239
240     let community_view = CommunityView::read(&conn, inserted_community.id, Some(user_id))?;
241
242     Ok(CommunityResponse {
243       community: community_view,
244     })
245   }
246 }
247
248 impl Perform<CommunityResponse> for Oper<EditCommunity> {
249   fn perform(&self, conn: &PgConnection) -> Result<CommunityResponse, Error> {
250     let data: &EditCommunity = &self.data;
251
252     if let Err(slurs) = slur_check(&data.name) {
253       return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
254     }
255
256     if let Err(slurs) = slur_check(&data.title) {
257       return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
258     }
259
260     if let Some(description) = &data.description {
261       if let Err(slurs) = slur_check(description) {
262         return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
263       }
264     }
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     // Check for a site ban
274     if UserView::read(&conn, user_id)?.banned {
275       return Err(APIError::err("site_ban").into());
276     }
277
278     // Verify its a mod
279     let mut editors: Vec<i32> = Vec::new();
280     editors.append(
281       &mut CommunityModeratorView::for_community(&conn, data.edit_id)?
282         .into_iter()
283         .map(|m| m.user_id)
284         .collect(),
285     );
286     editors.append(&mut UserView::admins(&conn)?.into_iter().map(|a| a.id).collect());
287     if !editors.contains(&user_id) {
288       return Err(APIError::err("no_community_edit_allowed").into());
289     }
290
291     let community_form = CommunityForm {
292       name: data.name.to_owned(),
293       title: data.title.to_owned(),
294       description: data.description.to_owned(),
295       category_id: data.category_id.to_owned(),
296       creator_id: user_id,
297       removed: data.removed.to_owned(),
298       deleted: data.deleted.to_owned(),
299       nsfw: data.nsfw,
300       updated: Some(naive_now()),
301     };
302
303     let _updated_community = match Community::update(&conn, data.edit_id, &community_form) {
304       Ok(community) => community,
305       Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
306     };
307
308     // Mod tables
309     if let Some(removed) = data.removed.to_owned() {
310       let expires = match data.expires {
311         Some(time) => Some(naive_from_unix(time)),
312         None => None,
313       };
314       let form = ModRemoveCommunityForm {
315         mod_user_id: user_id,
316         community_id: data.edit_id,
317         removed: Some(removed),
318         reason: data.reason.to_owned(),
319         expires,
320       };
321       ModRemoveCommunity::create(&conn, &form)?;
322     }
323
324     let community_view = CommunityView::read(&conn, data.edit_id, Some(user_id))?;
325
326     Ok(CommunityResponse {
327       community: community_view,
328     })
329   }
330 }
331
332 impl Perform<ListCommunitiesResponse> for Oper<ListCommunities> {
333   fn perform(&self, conn: &PgConnection) -> Result<ListCommunitiesResponse, Error> {
334     let data: &ListCommunities = &self.data;
335
336     let user_claims: Option<Claims> = match &data.auth {
337       Some(auth) => match Claims::decode(&auth) {
338         Ok(claims) => Some(claims.claims),
339         Err(_e) => None,
340       },
341       None => None,
342     };
343
344     let user_id = match &user_claims {
345       Some(claims) => Some(claims.id),
346       None => None,
347     };
348
349     let show_nsfw = match &user_claims {
350       Some(claims) => claims.show_nsfw,
351       None => false,
352     };
353
354     let sort = SortType::from_str(&data.sort)?;
355
356     let communities = CommunityQueryBuilder::create(&conn)
357       .sort(&sort)
358       .for_user(user_id)
359       .show_nsfw(show_nsfw)
360       .page(data.page)
361       .limit(data.limit)
362       .list()?;
363
364     // Return the jwt
365     Ok(ListCommunitiesResponse { communities })
366   }
367 }
368
369 impl Perform<CommunityResponse> for Oper<FollowCommunity> {
370   fn perform(&self, conn: &PgConnection) -> Result<CommunityResponse, Error> {
371     let data: &FollowCommunity = &self.data;
372
373     let claims = match Claims::decode(&data.auth) {
374       Ok(claims) => claims.claims,
375       Err(_e) => return Err(APIError::err("not_logged_in").into()),
376     };
377
378     let user_id = claims.id;
379
380     let community_follower_form = CommunityFollowerForm {
381       community_id: data.community_id,
382       user_id,
383     };
384
385     if data.follow {
386       match CommunityFollower::follow(&conn, &community_follower_form) {
387         Ok(user) => user,
388         Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
389       };
390     } else {
391       match CommunityFollower::ignore(&conn, &community_follower_form) {
392         Ok(user) => user,
393         Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
394       };
395     }
396
397     let community_view = CommunityView::read(&conn, data.community_id, Some(user_id))?;
398
399     Ok(CommunityResponse {
400       community: community_view,
401     })
402   }
403 }
404
405 impl Perform<GetFollowedCommunitiesResponse> for Oper<GetFollowedCommunities> {
406   fn perform(&self, conn: &PgConnection) -> Result<GetFollowedCommunitiesResponse, Error> {
407     let data: &GetFollowedCommunities = &self.data;
408
409     let claims = match Claims::decode(&data.auth) {
410       Ok(claims) => claims.claims,
411       Err(_e) => return Err(APIError::err("not_logged_in").into()),
412     };
413
414     let user_id = claims.id;
415
416     let communities: Vec<CommunityFollowerView> =
417       match CommunityFollowerView::for_user(&conn, user_id) {
418         Ok(communities) => communities,
419         Err(_e) => return Err(APIError::err("system_err_login").into()),
420       };
421
422     // Return the jwt
423     Ok(GetFollowedCommunitiesResponse { communities })
424   }
425 }
426
427 impl Perform<BanFromCommunityResponse> for Oper<BanFromCommunity> {
428   fn perform(&self, conn: &PgConnection) -> Result<BanFromCommunityResponse, Error> {
429     let data: &BanFromCommunity = &self.data;
430
431     let claims = match Claims::decode(&data.auth) {
432       Ok(claims) => claims.claims,
433       Err(_e) => return Err(APIError::err("not_logged_in").into()),
434     };
435
436     let user_id = claims.id;
437
438     let community_user_ban_form = CommunityUserBanForm {
439       community_id: data.community_id,
440       user_id: data.user_id,
441     };
442
443     if data.ban {
444       match CommunityUserBan::ban(&conn, &community_user_ban_form) {
445         Ok(user) => user,
446         Err(_e) => return Err(APIError::err("community_user_already_banned").into()),
447       };
448     } else {
449       match CommunityUserBan::unban(&conn, &community_user_ban_form) {
450         Ok(user) => user,
451         Err(_e) => return Err(APIError::err("community_user_already_banned").into()),
452       };
453     }
454
455     // Mod tables
456     let expires = match data.expires {
457       Some(time) => Some(naive_from_unix(time)),
458       None => None,
459     };
460
461     let form = ModBanFromCommunityForm {
462       mod_user_id: user_id,
463       other_user_id: data.user_id,
464       community_id: data.community_id,
465       reason: data.reason.to_owned(),
466       banned: Some(data.ban),
467       expires,
468     };
469     ModBanFromCommunity::create(&conn, &form)?;
470
471     let user_view = UserView::read(&conn, data.user_id)?;
472
473     Ok(BanFromCommunityResponse {
474       user: user_view,
475       banned: data.ban,
476     })
477   }
478 }
479
480 impl Perform<AddModToCommunityResponse> for Oper<AddModToCommunity> {
481   fn perform(&self, conn: &PgConnection) -> Result<AddModToCommunityResponse, Error> {
482     let data: &AddModToCommunity = &self.data;
483
484     let claims = match Claims::decode(&data.auth) {
485       Ok(claims) => claims.claims,
486       Err(_e) => return Err(APIError::err("not_logged_in").into()),
487     };
488
489     let user_id = claims.id;
490
491     let community_moderator_form = CommunityModeratorForm {
492       community_id: data.community_id,
493       user_id: data.user_id,
494     };
495
496     if data.added {
497       match CommunityModerator::join(&conn, &community_moderator_form) {
498         Ok(user) => user,
499         Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()),
500       };
501     } else {
502       match CommunityModerator::leave(&conn, &community_moderator_form) {
503         Ok(user) => user,
504         Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()),
505       };
506     }
507
508     // Mod tables
509     let form = ModAddCommunityForm {
510       mod_user_id: user_id,
511       other_user_id: data.user_id,
512       community_id: data.community_id,
513       removed: Some(!data.added),
514     };
515     ModAddCommunity::create(&conn, &form)?;
516
517     let moderators = CommunityModeratorView::for_community(&conn, data.community_id)?;
518
519     Ok(AddModToCommunityResponse { moderators })
520   }
521 }
522
523 impl Perform<GetCommunityResponse> for Oper<TransferCommunity> {
524   fn perform(&self, conn: &PgConnection) -> Result<GetCommunityResponse, Error> {
525     let data: &TransferCommunity = &self.data;
526
527     let claims = match Claims::decode(&data.auth) {
528       Ok(claims) => claims.claims,
529       Err(_e) => return Err(APIError::err("not_logged_in").into()),
530     };
531
532     let user_id = claims.id;
533
534     let read_community = Community::read(&conn, data.community_id)?;
535
536     let site_creator_id = Site::read(&conn, 1)?.creator_id;
537     let mut admins = UserView::admins(&conn)?;
538     let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
539     let creator_user = admins.remove(creator_index);
540     admins.insert(0, creator_user);
541
542     // Make sure user is the creator, or an admin
543     if user_id != read_community.creator_id && !admins.iter().map(|a| a.id).any(|x| x == user_id) {
544       return Err(APIError::err("not_an_admin").into());
545     }
546
547     let community_form = CommunityForm {
548       name: read_community.name,
549       title: read_community.title,
550       description: read_community.description,
551       category_id: read_community.category_id,
552       creator_id: data.user_id,
553       removed: None,
554       deleted: None,
555       nsfw: read_community.nsfw,
556       updated: Some(naive_now()),
557     };
558
559     let _updated_community = match Community::update(&conn, data.community_id, &community_form) {
560       Ok(community) => community,
561       Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
562     };
563
564     // You also have to re-do the community_moderator table, reordering it.
565     let mut community_mods = CommunityModeratorView::for_community(&conn, data.community_id)?;
566     let creator_index = community_mods
567       .iter()
568       .position(|r| r.user_id == data.user_id)
569       .unwrap();
570     let creator_user = community_mods.remove(creator_index);
571     community_mods.insert(0, creator_user);
572
573     CommunityModerator::delete_for_community(&conn, data.community_id)?;
574
575     for cmod in &community_mods {
576       let community_moderator_form = CommunityModeratorForm {
577         community_id: cmod.community_id,
578         user_id: cmod.user_id,
579       };
580
581       let _inserted_community_moderator =
582         match CommunityModerator::join(&conn, &community_moderator_form) {
583           Ok(user) => user,
584           Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()),
585         };
586     }
587
588     // Mod tables
589     let form = ModAddCommunityForm {
590       mod_user_id: user_id,
591       other_user_id: data.user_id,
592       community_id: data.community_id,
593       removed: Some(false),
594     };
595     ModAddCommunity::create(&conn, &form)?;
596
597     let community_view = match CommunityView::read(&conn, data.community_id, Some(user_id)) {
598       Ok(community) => community,
599       Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
600     };
601
602     let moderators = match CommunityModeratorView::for_community(&conn, data.community_id) {
603       Ok(moderators) => moderators,
604       Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
605     };
606
607     // Return the jwt
608     Ok(GetCommunityResponse {
609       community: community_view,
610       moderators,
611       admins,
612       online: 0,
613     })
614   }
615 }