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