]> Untitled Git - lemmy.git/blob - crates/api/src/community.rs
~80% done
[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.person.send_follow(&community.actor_id(), context).await?;
521     } else {
522       local_user_view.person.send_unfollow(&community.actor_id(), context).await?;
523       let unfollow = move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form);
524       if blocking(context.pool(), unfollow).await?.is_err() {
525         return Err(ApiError::err("community_follower_already_exists").into());
526       }
527     }
528
529     let community_id = data.community_id;
530     let person_id = local_user_view.person.id;
531     let mut community_view = blocking(context.pool(), move |conn| {
532       CommunityView::read(conn, community_id, Some(person_id))
533     })
534     .await??;
535
536     // TODO: this needs to return a "pending" state, until Accept is received from the remote server
537     // For now, just assume that remote follows are accepted.
538     // Otherwise, the subscribed will be null
539     if !community.local {
540       community_view.subscribed = data.follow;
541     }
542
543     Ok(CommunityResponse { community_view })
544   }
545 }
546
547 #[async_trait::async_trait(?Send)]
548 impl Perform for GetFollowedCommunities {
549   type Response = GetFollowedCommunitiesResponse;
550
551   async fn perform(
552     &self,
553     context: &Data<LemmyContext>,
554     _websocket_id: Option<ConnectionId>,
555   ) -> Result<GetFollowedCommunitiesResponse, LemmyError> {
556     let data: &GetFollowedCommunities = &self;
557     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
558
559     let person_id = local_user_view.person.id;
560     let communities = match blocking(context.pool(), move |conn| {
561       CommunityFollowerView::for_person(conn, person_id)
562     })
563     .await?
564     {
565       Ok(communities) => communities,
566       _ => return Err(ApiError::err("system_err_login").into()),
567     };
568
569     // Return the jwt
570     Ok(GetFollowedCommunitiesResponse { communities })
571   }
572 }
573
574 #[async_trait::async_trait(?Send)]
575 impl Perform for BanFromCommunity {
576   type Response = BanFromCommunityResponse;
577
578   async fn perform(
579     &self,
580     context: &Data<LemmyContext>,
581     websocket_id: Option<ConnectionId>,
582   ) -> Result<BanFromCommunityResponse, LemmyError> {
583     let data: &BanFromCommunity = &self;
584     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
585
586     let community_id = data.community_id;
587     let banned_person_id = data.person_id;
588
589     // Verify that only mods or admins can ban
590     is_mod_or_admin(context.pool(), local_user_view.person.id, community_id).await?;
591
592     let community_user_ban_form = CommunityPersonBanForm {
593       community_id: data.community_id,
594       person_id: data.person_id,
595     };
596
597     if data.ban {
598       let ban = move |conn: &'_ _| CommunityPersonBan::ban(conn, &community_user_ban_form);
599       if blocking(context.pool(), ban).await?.is_err() {
600         return Err(ApiError::err("community_user_already_banned").into());
601       }
602
603       // Also unsubscribe them from the community, if they are subscribed
604       let community_follower_form = CommunityFollowerForm {
605         community_id: data.community_id,
606         person_id: banned_person_id,
607         pending: false,
608       };
609       blocking(context.pool(), move |conn: &'_ _| {
610         CommunityFollower::unfollow(conn, &community_follower_form)
611       })
612       .await?
613       .ok();
614     } else {
615       let unban = move |conn: &'_ _| CommunityPersonBan::unban(conn, &community_user_ban_form);
616       if blocking(context.pool(), unban).await?.is_err() {
617         return Err(ApiError::err("community_user_already_banned").into());
618       }
619     }
620
621     // Remove/Restore their data if that's desired
622     if data.remove_data {
623       // Posts
624       blocking(context.pool(), move |conn: &'_ _| {
625         Post::update_removed_for_creator(conn, banned_person_id, Some(community_id), true)
626       })
627       .await??;
628
629       // Comments
630       // TODO Diesel doesn't allow updates with joins, so this has to be a loop
631       let comments = blocking(context.pool(), move |conn| {
632         CommentQueryBuilder::create(conn)
633           .creator_id(banned_person_id)
634           .community_id(community_id)
635           .limit(std::i64::MAX)
636           .list()
637       })
638       .await??;
639
640       for comment_view in &comments {
641         let comment_id = comment_view.comment.id;
642         blocking(context.pool(), move |conn: &'_ _| {
643           Comment::update_removed(conn, comment_id, true)
644         })
645         .await??;
646       }
647     }
648
649     // Mod tables
650     // TODO eventually do correct expires
651     let expires = match data.expires {
652       Some(time) => Some(naive_from_unix(time)),
653       None => None,
654     };
655
656     let form = ModBanFromCommunityForm {
657       mod_person_id: local_user_view.person.id,
658       other_person_id: data.person_id,
659       community_id: data.community_id,
660       reason: data.reason.to_owned(),
661       banned: Some(data.ban),
662       expires,
663     };
664     blocking(context.pool(), move |conn| {
665       ModBanFromCommunity::create(conn, &form)
666     })
667     .await??;
668
669     let person_id = data.person_id;
670     let person_view = blocking(context.pool(), move |conn| {
671       PersonViewSafe::read(conn, person_id)
672     })
673     .await??;
674
675     let res = BanFromCommunityResponse {
676       person_view,
677       banned: data.ban,
678     };
679
680     context.chat_server().do_send(SendCommunityRoomMessage {
681       op: UserOperation::BanFromCommunity,
682       response: res.clone(),
683       community_id,
684       websocket_id,
685     });
686
687     Ok(res)
688   }
689 }
690
691 #[async_trait::async_trait(?Send)]
692 impl Perform for AddModToCommunity {
693   type Response = AddModToCommunityResponse;
694
695   async fn perform(
696     &self,
697     context: &Data<LemmyContext>,
698     websocket_id: Option<ConnectionId>,
699   ) -> Result<AddModToCommunityResponse, LemmyError> {
700     let data: &AddModToCommunity = &self;
701     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
702
703     let community_moderator_form = CommunityModeratorForm {
704       community_id: data.community_id,
705       person_id: data.person_id,
706     };
707
708     let community_id = data.community_id;
709
710     // Verify that only mods or admins can add mod
711     is_mod_or_admin(context.pool(), local_user_view.person.id, community_id).await?;
712
713     if data.added {
714       let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
715       if blocking(context.pool(), join).await?.is_err() {
716         return Err(ApiError::err("community_moderator_already_exists").into());
717       }
718     } else {
719       let leave = move |conn: &'_ _| CommunityModerator::leave(conn, &community_moderator_form);
720       if blocking(context.pool(), leave).await?.is_err() {
721         return Err(ApiError::err("community_moderator_already_exists").into());
722       }
723     }
724
725     // Mod tables
726     let form = ModAddCommunityForm {
727       mod_person_id: local_user_view.person.id,
728       other_person_id: data.person_id,
729       community_id: data.community_id,
730       removed: Some(!data.added),
731     };
732     blocking(context.pool(), move |conn| {
733       ModAddCommunity::create(conn, &form)
734     })
735     .await??;
736
737     let community_id = data.community_id;
738     let moderators = blocking(context.pool(), move |conn| {
739       CommunityModeratorView::for_community(conn, community_id)
740     })
741     .await??;
742
743     let res = AddModToCommunityResponse { moderators };
744
745     context.chat_server().do_send(SendCommunityRoomMessage {
746       op: UserOperation::AddModToCommunity,
747       response: res.clone(),
748       community_id,
749       websocket_id,
750     });
751
752     Ok(res)
753   }
754 }
755
756 #[async_trait::async_trait(?Send)]
757 impl Perform for TransferCommunity {
758   type Response = GetCommunityResponse;
759
760   async fn perform(
761     &self,
762     context: &Data<LemmyContext>,
763     _websocket_id: Option<ConnectionId>,
764   ) -> Result<GetCommunityResponse, LemmyError> {
765     let data: &TransferCommunity = &self;
766     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
767
768     let community_id = data.community_id;
769     let read_community = blocking(context.pool(), move |conn| {
770       Community::read(conn, community_id)
771     })
772     .await??;
773
774     let site_creator_id = blocking(context.pool(), move |conn| {
775       Site::read(conn, 1).map(|s| s.creator_id)
776     })
777     .await??;
778
779     let mut admins = blocking(context.pool(), move |conn| PersonViewSafe::admins(conn)).await??;
780
781     // Making sure the creator, if an admin, is at the top
782     let creator_index = admins
783       .iter()
784       .position(|r| r.person.id == site_creator_id)
785       .context(location_info!())?;
786     let creator_person = admins.remove(creator_index);
787     admins.insert(0, creator_person);
788
789     // Make sure user is the creator, or an admin
790     if local_user_view.person.id != read_community.creator_id
791       && !admins.iter().map(|a| a.person.id).any(|x| x == local_user_view.person.id)
792     {
793       return Err(ApiError::err("not_an_admin").into());
794     }
795
796     let community_id = data.community_id;
797     let new_creator = data.person_id;
798     let update = move |conn: &'_ _| Community::update_creator(conn, community_id, new_creator);
799     if blocking(context.pool(), update).await?.is_err() {
800       return Err(ApiError::err("couldnt_update_community").into());
801     };
802
803     // You also have to re-do the community_moderator table, reordering it.
804     let community_id = data.community_id;
805     let mut community_mods = blocking(context.pool(), move |conn| {
806       CommunityModeratorView::for_community(conn, community_id)
807     })
808     .await??;
809     let creator_index = community_mods
810       .iter()
811       .position(|r| r.moderator.id == data.person_id)
812       .context(location_info!())?;
813     let creator_person = community_mods.remove(creator_index);
814     community_mods.insert(0, creator_person);
815
816     let community_id = data.community_id;
817     blocking(context.pool(), move |conn| {
818       CommunityModerator::delete_for_community(conn, community_id)
819     })
820     .await??;
821
822     // TODO: this should probably be a bulk operation
823     for cmod in &community_mods {
824       let community_moderator_form = CommunityModeratorForm {
825         community_id: cmod.community.id,
826         person_id: cmod.moderator.id,
827       };
828
829       let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
830       if blocking(context.pool(), join).await?.is_err() {
831         return Err(ApiError::err("community_moderator_already_exists").into());
832       }
833     }
834
835     // Mod tables
836     let form = ModAddCommunityForm {
837       mod_person_id: local_user_view.person.id,
838       other_person_id: data.person_id,
839       community_id: data.community_id,
840       removed: Some(false),
841     };
842     blocking(context.pool(), move |conn| {
843       ModAddCommunity::create(conn, &form)
844     })
845     .await??;
846
847     let community_id = data.community_id;
848     let person_id = local_user_view.person.id;
849     let community_view = match blocking(context.pool(), move |conn| {
850       CommunityView::read(conn, community_id, Some(person_id))
851     })
852     .await?
853     {
854       Ok(community) => community,
855       Err(_e) => return Err(ApiError::err("couldnt_find_community").into()),
856     };
857
858     let community_id = data.community_id;
859     let moderators = match blocking(context.pool(), move |conn| {
860       CommunityModeratorView::for_community(conn, community_id)
861     })
862     .await?
863     {
864       Ok(moderators) => moderators,
865       Err(_e) => return Err(ApiError::err("couldnt_find_community").into()),
866     };
867
868     // Return the jwt
869     Ok(GetCommunityResponse {
870       community_view,
871       moderators,
872       online: 0,
873     })
874   }
875 }
876
877 fn send_community_websocket(
878   res: &CommunityResponse,
879   context: &Data<LemmyContext>,
880   websocket_id: Option<ConnectionId>,
881   op: UserOperation,
882 ) {
883   // Strip out the person id and subscribed when sending to others
884   let mut res_sent = res.clone();
885   res_sent.community_view.subscribed = false;
886
887   context.chat_server().do_send(SendCommunityRoomMessage {
888     op,
889     response: res_sent,
890     community_id: res.community_view.community.id,
891     websocket_id,
892   });
893 }