]> Untitled Git - lemmy.git/blob - crates/api/src/community.rs
Allow adding remote users as community mods (ref #1061)
[lemmy.git] / crates / api / src / community.rs
1 use crate::{
2   check_community_ban,
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_api_structs::{blocking, community::*};
12 use lemmy_apub::{
13   activities::send::community::{send_add_mod, send_remove_mod},
14   generate_apub_endpoint,
15   generate_followers_url,
16   generate_inbox_url,
17   generate_shared_inbox_url,
18   ActorType,
19   EndpointType,
20 };
21 use lemmy_db_queries::{
22   diesel_option_overwrite_to_url,
23   source::{
24     comment::Comment_,
25     community::{CommunityModerator_, Community_},
26     post::Post_,
27   },
28   ApubObject,
29   Bannable,
30   Crud,
31   Followable,
32   Joinable,
33   ListingType,
34   SortType,
35 };
36 use lemmy_db_schema::{
37   naive_now,
38   source::{comment::Comment, community::*, moderator::*, post::Post, site::*, user::User_},
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   user_view::UserViewSafe,
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 user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
73     let user_id = user.map(|u| u.id);
74
75     let community_id = match data.id {
76       Some(id) => id,
77       None => {
78         let name = data.name.to_owned().unwrap_or_else(|| "main".to_string());
79         match blocking(context.pool(), move |conn| {
80           Community::read_from_name(conn, &name)
81         })
82         .await?
83         {
84           Ok(community) => community,
85           Err(_e) => return Err(ApiError::err("couldnt_find_community").into()),
86         }
87         .id
88       }
89     };
90
91     let community_view = match blocking(context.pool(), move |conn| {
92       CommunityView::read(conn, community_id, user_id)
93     })
94     .await?
95     {
96       Ok(community) => community,
97       Err(_e) => return Err(ApiError::err("couldnt_find_community").into()),
98     };
99
100     let moderators: Vec<CommunityModeratorView> = match blocking(context.pool(), move |conn| {
101       CommunityModeratorView::for_community(conn, community_id)
102     })
103     .await?
104     {
105       Ok(moderators) => moderators,
106       Err(_e) => return Err(ApiError::err("couldnt_find_community").into()),
107     };
108
109     let online = context
110       .chat_server()
111       .send(GetCommunityUsersOnline { community_id })
112       .await
113       .unwrap_or(1);
114
115     let res = GetCommunityResponse {
116       community_view,
117       moderators,
118       online,
119     };
120
121     // Return the jwt
122     Ok(res)
123   }
124 }
125
126 #[async_trait::async_trait(?Send)]
127 impl Perform for CreateCommunity {
128   type Response = CommunityResponse;
129
130   async fn perform(
131     &self,
132     context: &Data<LemmyContext>,
133     _websocket_id: Option<ConnectionId>,
134   ) -> Result<CommunityResponse, LemmyError> {
135     let data: &CreateCommunity = &self;
136     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
137
138     check_slurs(&data.name)?;
139     check_slurs(&data.title)?;
140     check_slurs_opt(&data.description)?;
141
142     if !is_valid_community_name(&data.name) {
143       return Err(ApiError::err("invalid_community_name").into());
144     }
145
146     // Double check for duplicate community actor_ids
147     let community_actor_id = generate_apub_endpoint(EndpointType::Community, &data.name)?;
148     let actor_id_cloned = community_actor_id.to_owned();
149     let community_dupe = blocking(context.pool(), move |conn| {
150       Community::read_from_apub_id(conn, &actor_id_cloned)
151     })
152     .await?;
153     if community_dupe.is_ok() {
154       return Err(ApiError::err("community_already_exists").into());
155     }
156
157     // Check to make sure the icon and banners are urls
158     let icon = diesel_option_overwrite_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: user.id,
171       removed: None,
172       deleted: None,
173       nsfw: data.nsfw,
174       updated: None,
175       actor_id: Some(community_actor_id.to_owned()),
176       local: true,
177       private_key: Some(keypair.private_key),
178       public_key: Some(keypair.public_key),
179       last_refreshed_at: None,
180       published: None,
181       followers_url: Some(generate_followers_url(&community_actor_id)?),
182       inbox_url: Some(generate_inbox_url(&community_actor_id)?),
183       shared_inbox_url: Some(Some(generate_shared_inbox_url(&community_actor_id)?)),
184     };
185
186     let inserted_community = match blocking(context.pool(), move |conn| {
187       Community::create(conn, &community_form)
188     })
189     .await?
190     {
191       Ok(community) => community,
192       Err(_e) => return Err(ApiError::err("community_already_exists").into()),
193     };
194
195     // The community creator becomes a moderator
196     let community_moderator_form = CommunityModeratorForm {
197       community_id: inserted_community.id,
198       user_id: user.id,
199     };
200
201     let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
202     if blocking(context.pool(), join).await?.is_err() {
203       return Err(ApiError::err("community_moderator_already_exists").into());
204     }
205
206     // Follow your own community
207     let community_follower_form = CommunityFollowerForm {
208       community_id: inserted_community.id,
209       user_id: user.id,
210       pending: false,
211     };
212
213     let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
214     if blocking(context.pool(), follow).await?.is_err() {
215       return Err(ApiError::err("community_follower_already_exists").into());
216     }
217
218     let user_id = user.id;
219     let community_view = blocking(context.pool(), move |conn| {
220       CommunityView::read(conn, inserted_community.id, Some(user_id))
221     })
222     .await??;
223
224     Ok(CommunityResponse { community_view })
225   }
226 }
227
228 #[async_trait::async_trait(?Send)]
229 impl Perform for EditCommunity {
230   type Response = CommunityResponse;
231
232   async fn perform(
233     &self,
234     context: &Data<LemmyContext>,
235     websocket_id: Option<ConnectionId>,
236   ) -> Result<CommunityResponse, LemmyError> {
237     let data: &EditCommunity = &self;
238     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
239
240     check_slurs(&data.title)?;
241     check_slurs_opt(&data.description)?;
242
243     // Verify its a mod (only mods can edit it)
244     let community_id = data.community_id;
245     let mods: Vec<i32> = blocking(context.pool(), move |conn| {
246       CommunityModeratorView::for_community(conn, community_id)
247         .map(|v| v.into_iter().map(|m| m.moderator.id).collect())
248     })
249     .await??;
250     if !mods.contains(&user.id) {
251       return Err(ApiError::err("not_a_moderator").into());
252     }
253
254     let community_id = data.community_id;
255     let read_community = blocking(context.pool(), move |conn| {
256       Community::read(conn, community_id)
257     })
258     .await??;
259
260     let icon = diesel_option_overwrite_to_url(&data.icon)?;
261     let banner = diesel_option_overwrite_to_url(&data.banner)?;
262
263     let community_form = CommunityForm {
264       name: read_community.name,
265       title: data.title.to_owned(),
266       description: data.description.to_owned(),
267       icon,
268       banner,
269       creator_id: read_community.creator_id,
270       removed: Some(read_community.removed),
271       deleted: Some(read_community.deleted),
272       nsfw: data.nsfw,
273       updated: Some(naive_now()),
274       actor_id: Some(read_community.actor_id),
275       local: read_community.local,
276       private_key: read_community.private_key,
277       public_key: read_community.public_key,
278       last_refreshed_at: None,
279       published: None,
280       followers_url: None,
281       inbox_url: None,
282       shared_inbox_url: None,
283     };
284
285     let community_id = data.community_id;
286     match blocking(context.pool(), move |conn| {
287       Community::update(conn, community_id, &community_form)
288     })
289     .await?
290     {
291       Ok(community) => community,
292       Err(_e) => return Err(ApiError::err("couldnt_update_community").into()),
293     };
294
295     // TODO there needs to be some kind of an apub update
296     // process for communities and users
297
298     let community_id = data.community_id;
299     let user_id = user.id;
300     let community_view = blocking(context.pool(), move |conn| {
301       CommunityView::read(conn, community_id, Some(user_id))
302     })
303     .await??;
304
305     let res = CommunityResponse { community_view };
306
307     send_community_websocket(&res, context, websocket_id, UserOperation::EditCommunity);
308
309     Ok(res)
310   }
311 }
312
313 #[async_trait::async_trait(?Send)]
314 impl Perform for DeleteCommunity {
315   type Response = CommunityResponse;
316
317   async fn perform(
318     &self,
319     context: &Data<LemmyContext>,
320     websocket_id: Option<ConnectionId>,
321   ) -> Result<CommunityResponse, LemmyError> {
322     let data: &DeleteCommunity = &self;
323     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
324
325     // Verify its the creator (only a creator can delete the community)
326     let community_id = data.community_id;
327     let read_community = blocking(context.pool(), move |conn| {
328       Community::read(conn, community_id)
329     })
330     .await??;
331     if read_community.creator_id != user.id {
332       return Err(ApiError::err("no_community_edit_allowed").into());
333     }
334
335     // Do the delete
336     let community_id = data.community_id;
337     let deleted = data.deleted;
338     let updated_community = match blocking(context.pool(), move |conn| {
339       Community::update_deleted(conn, community_id, deleted)
340     })
341     .await?
342     {
343       Ok(community) => community,
344       Err(_e) => return Err(ApiError::err("couldnt_update_community").into()),
345     };
346
347     // Send apub messages
348     if deleted {
349       updated_community.send_delete(context).await?;
350     } else {
351       updated_community.send_undo_delete(context).await?;
352     }
353
354     let community_id = data.community_id;
355     let user_id = user.id;
356     let community_view = blocking(context.pool(), move |conn| {
357       CommunityView::read(conn, community_id, Some(user_id))
358     })
359     .await??;
360
361     let res = CommunityResponse { community_view };
362
363     send_community_websocket(&res, context, websocket_id, UserOperation::DeleteCommunity);
364
365     Ok(res)
366   }
367 }
368
369 #[async_trait::async_trait(?Send)]
370 impl Perform for RemoveCommunity {
371   type Response = CommunityResponse;
372
373   async fn perform(
374     &self,
375     context: &Data<LemmyContext>,
376     websocket_id: Option<ConnectionId>,
377   ) -> Result<CommunityResponse, LemmyError> {
378     let data: &RemoveCommunity = &self;
379     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
380
381     // Verify its an admin (only an admin can remove a community)
382     is_admin(context.pool(), user.id).await?;
383
384     // Do the remove
385     let community_id = data.community_id;
386     let removed = data.removed;
387     let updated_community = match blocking(context.pool(), move |conn| {
388       Community::update_removed(conn, community_id, removed)
389     })
390     .await?
391     {
392       Ok(community) => community,
393       Err(_e) => return Err(ApiError::err("couldnt_update_community").into()),
394     };
395
396     // Mod tables
397     let expires = match data.expires {
398       Some(time) => Some(naive_from_unix(time)),
399       None => None,
400     };
401     let form = ModRemoveCommunityForm {
402       mod_user_id: user.id,
403       community_id: data.community_id,
404       removed: Some(removed),
405       reason: data.reason.to_owned(),
406       expires,
407     };
408     blocking(context.pool(), move |conn| {
409       ModRemoveCommunity::create(conn, &form)
410     })
411     .await??;
412
413     // Apub messages
414     if removed {
415       updated_community.send_remove(context).await?;
416     } else {
417       updated_community.send_undo_remove(context).await?;
418     }
419
420     let community_id = data.community_id;
421     let user_id = user.id;
422     let community_view = blocking(context.pool(), move |conn| {
423       CommunityView::read(conn, community_id, Some(user_id))
424     })
425     .await??;
426
427     let res = CommunityResponse { community_view };
428
429     send_community_websocket(&res, context, websocket_id, UserOperation::RemoveCommunity);
430
431     Ok(res)
432   }
433 }
434
435 #[async_trait::async_trait(?Send)]
436 impl Perform for ListCommunities {
437   type Response = ListCommunitiesResponse;
438
439   async fn perform(
440     &self,
441     context: &Data<LemmyContext>,
442     _websocket_id: Option<ConnectionId>,
443   ) -> Result<ListCommunitiesResponse, LemmyError> {
444     let data: &ListCommunities = &self;
445     let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
446
447     let user_id = match &user {
448       Some(user) => Some(user.id),
449       None => None,
450     };
451
452     let show_nsfw = match &user {
453       Some(user) => 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_user_id(user_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 user = get_user_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       user_id: user.id,
499       pending: false,
500     };
501
502     if community.local {
503       if data.follow {
504         check_community_ban(user.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       user.send_follow(&community.actor_id(), context).await?;
521     } else {
522       user.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 user_id = user.id;
531     let mut community_view = blocking(context.pool(), move |conn| {
532       CommunityView::read(conn, community_id, Some(user_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 user = get_user_from_jwt(&data.auth, context.pool()).await?;
558
559     let user_id = user.id;
560     let communities = match blocking(context.pool(), move |conn| {
561       CommunityFollowerView::for_user(conn, user_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 user = get_user_from_jwt(&data.auth, context.pool()).await?;
585
586     let community_id = data.community_id;
587     let banned_user_id = data.user_id;
588
589     // Verify that only mods or admins can ban
590     is_mod_or_admin(context.pool(), user.id, community_id).await?;
591
592     let community_user_ban_form = CommunityUserBanForm {
593       community_id: data.community_id,
594       user_id: data.user_id,
595     };
596
597     if data.ban {
598       let ban = move |conn: &'_ _| CommunityUserBan::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         user_id: banned_user_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: &'_ _| CommunityUserBan::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_user_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_user_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_user_id: user.id,
658       other_user_id: data.user_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 user_id = data.user_id;
670     let user_view = blocking(context.pool(), move |conn| {
671       UserViewSafe::read(conn, user_id)
672     })
673     .await??;
674
675     let res = BanFromCommunityResponse {
676       user_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 user = get_user_from_jwt(&data.auth, context.pool()).await?;
702
703     let community_id = data.community_id;
704
705     // Verify that only mods or admins can add mod
706     is_mod_or_admin(context.pool(), user.id, community_id).await?;
707
708     // Update in local database
709     let community_moderator_form = CommunityModeratorForm {
710       community_id: data.community_id,
711       user_id: data.user_id,
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_user_id: user.id,
728       other_user_id: data.user_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     // Send to federated instances
738     let updated_mod_id = data.user_id;
739     let updated_mod = blocking(context.pool(), move |conn| {
740       User_::read(conn, updated_mod_id)
741     })
742     .await??;
743     let community = blocking(context.pool(), move |conn| {
744       Community::read(conn, community_id)
745     })
746     .await??;
747     dbg!(data.added);
748     if data.added {
749       send_add_mod(user, updated_mod, community, context).await?;
750     } else {
751       send_remove_mod(user, updated_mod, community, context).await?;
752     }
753
754     // Note: in case a remote mod is added, this returns the old moderators list, it will only get
755     //       updated once we receive an activity from the community (like `Announce/Add/Moderator`)
756     let community_id = data.community_id;
757     let moderators = blocking(context.pool(), move |conn| {
758       CommunityModeratorView::for_community(conn, community_id)
759     })
760     .await??;
761
762     let res = AddModToCommunityResponse { moderators };
763     context.chat_server().do_send(SendCommunityRoomMessage {
764       op: UserOperation::AddModToCommunity,
765       response: res.clone(),
766       community_id,
767       websocket_id,
768     });
769     Ok(res)
770   }
771 }
772
773 // TODO: we dont do anything for federation here, it should be updated the next time the community
774 //       gets fetched. i hope we can get rid of the community creator role soon.
775 #[async_trait::async_trait(?Send)]
776 impl Perform for TransferCommunity {
777   type Response = GetCommunityResponse;
778
779   async fn perform(
780     &self,
781     context: &Data<LemmyContext>,
782     _websocket_id: Option<ConnectionId>,
783   ) -> Result<GetCommunityResponse, LemmyError> {
784     let data: &TransferCommunity = &self;
785     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
786
787     let community_id = data.community_id;
788     let read_community = blocking(context.pool(), move |conn| {
789       Community::read(conn, community_id)
790     })
791     .await??;
792
793     let site_creator_id = blocking(context.pool(), move |conn| {
794       Site::read(conn, 1).map(|s| s.creator_id)
795     })
796     .await??;
797
798     let mut admins = blocking(context.pool(), move |conn| UserViewSafe::admins(conn)).await??;
799
800     // Making sure the creator, if an admin, is at the top
801     let creator_index = admins
802       .iter()
803       .position(|r| r.user.id == site_creator_id)
804       .context(location_info!())?;
805     let creator_user = admins.remove(creator_index);
806     admins.insert(0, creator_user);
807
808     // Make sure user is the creator, or an admin
809     if user.id != read_community.creator_id
810       && !admins.iter().map(|a| a.user.id).any(|x| x == user.id)
811     {
812       return Err(ApiError::err("not_an_admin").into());
813     }
814
815     let community_id = data.community_id;
816     let new_creator = data.user_id;
817     let update = move |conn: &'_ _| Community::update_creator(conn, community_id, new_creator);
818     if blocking(context.pool(), update).await?.is_err() {
819       return Err(ApiError::err("couldnt_update_community").into());
820     };
821
822     // You also have to re-do the community_moderator table, reordering it.
823     let community_id = data.community_id;
824     let mut community_mods = blocking(context.pool(), move |conn| {
825       CommunityModeratorView::for_community(conn, community_id)
826     })
827     .await??;
828     let creator_index = community_mods
829       .iter()
830       .position(|r| r.moderator.id == data.user_id)
831       .context(location_info!())?;
832     let creator_user = community_mods.remove(creator_index);
833     community_mods.insert(0, creator_user);
834
835     let community_id = data.community_id;
836     blocking(context.pool(), move |conn| {
837       CommunityModerator::delete_for_community(conn, community_id)
838     })
839     .await??;
840
841     // TODO: this should probably be a bulk operation
842     for cmod in &community_mods {
843       let community_moderator_form = CommunityModeratorForm {
844         community_id: cmod.community.id,
845         user_id: cmod.moderator.id,
846       };
847
848       let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
849       if blocking(context.pool(), join).await?.is_err() {
850         return Err(ApiError::err("community_moderator_already_exists").into());
851       }
852     }
853
854     // Mod tables
855     let form = ModAddCommunityForm {
856       mod_user_id: user.id,
857       other_user_id: data.user_id,
858       community_id: data.community_id,
859       removed: Some(false),
860     };
861     blocking(context.pool(), move |conn| {
862       ModAddCommunity::create(conn, &form)
863     })
864     .await??;
865
866     let community_id = data.community_id;
867     let user_id = user.id;
868     let community_view = match blocking(context.pool(), move |conn| {
869       CommunityView::read(conn, community_id, Some(user_id))
870     })
871     .await?
872     {
873       Ok(community) => community,
874       Err(_e) => return Err(ApiError::err("couldnt_find_community").into()),
875     };
876
877     let community_id = data.community_id;
878     let moderators = match blocking(context.pool(), move |conn| {
879       CommunityModeratorView::for_community(conn, community_id)
880     })
881     .await?
882     {
883       Ok(moderators) => moderators,
884       Err(_e) => return Err(ApiError::err("couldnt_find_community").into()),
885     };
886
887     // Return the jwt
888     Ok(GetCommunityResponse {
889       community_view,
890       moderators,
891       online: 0,
892     })
893   }
894 }
895
896 fn send_community_websocket(
897   res: &CommunityResponse,
898   context: &Data<LemmyContext>,
899   websocket_id: Option<ConnectionId>,
900   op: UserOperation,
901 ) {
902   // Strip out the user id and subscribed when sending to others
903   let mut res_sent = res.clone();
904   res_sent.community_view.subscribed = false;
905
906   context.chat_server().do_send(SendCommunityRoomMessage {
907     op,
908     response: res_sent,
909     community_id: res.community_view.community.id,
910     websocket_id,
911   });
912 }