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