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