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