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