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