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