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