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