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