3 api::{is_admin, is_mod_or_admin, APIError, Oper, Perform},
7 server::{JoinCommunityRoom, SendCommunityRoomMessage},
14 diesel_option_overwrite,
23 generate_actor_keypair,
24 is_valid_community_name,
29 use serde::{Deserialize, Serialize};
30 use std::str::FromStr;
32 #[derive(Serialize, Deserialize)]
33 pub struct GetCommunity {
35 pub name: Option<String>,
39 #[derive(Serialize, Deserialize)]
40 pub struct GetCommunityResponse {
41 pub community: CommunityView,
42 pub moderators: Vec<CommunityModeratorView>,
46 #[derive(Serialize, Deserialize)]
47 pub struct CreateCommunity {
50 description: Option<String>,
52 banner: Option<String>,
58 #[derive(Serialize, Deserialize, Clone)]
59 pub struct CommunityResponse {
60 pub community: CommunityView,
63 #[derive(Serialize, Deserialize, Debug)]
64 pub struct ListCommunities {
66 pub page: Option<i64>,
67 pub limit: Option<i64>,
68 pub auth: Option<String>,
71 #[derive(Serialize, Deserialize, Debug)]
72 pub struct ListCommunitiesResponse {
73 pub communities: Vec<CommunityView>,
76 #[derive(Serialize, Deserialize, Clone)]
77 pub struct BanFromCommunity {
78 pub community_id: i32,
81 reason: Option<String>,
86 #[derive(Serialize, Deserialize, Clone)]
87 pub struct BanFromCommunityResponse {
92 #[derive(Serialize, Deserialize)]
93 pub struct AddModToCommunity {
94 pub community_id: i32,
100 #[derive(Serialize, Deserialize, Clone)]
101 pub struct AddModToCommunityResponse {
102 moderators: Vec<CommunityModeratorView>,
105 #[derive(Serialize, Deserialize)]
106 pub struct EditCommunity {
109 description: Option<String>,
110 icon: Option<String>,
111 banner: Option<String>,
117 #[derive(Serialize, Deserialize)]
118 pub struct DeleteCommunity {
124 #[derive(Serialize, Deserialize)]
125 pub struct RemoveCommunity {
128 reason: Option<String>,
129 expires: Option<i64>,
133 #[derive(Serialize, Deserialize)]
134 pub struct FollowCommunity {
140 #[derive(Serialize, Deserialize)]
141 pub struct GetFollowedCommunities {
145 #[derive(Serialize, Deserialize)]
146 pub struct GetFollowedCommunitiesResponse {
147 communities: Vec<CommunityFollowerView>,
150 #[derive(Serialize, Deserialize)]
151 pub struct TransferCommunity {
157 #[async_trait::async_trait(?Send)]
158 impl Perform for Oper<GetCommunity> {
159 type Response = GetCommunityResponse;
164 websocket_info: Option<WebsocketInfo>,
165 ) -> Result<GetCommunityResponse, LemmyError> {
166 let data: &GetCommunity = &self.data;
167 let user = get_user_from_jwt_opt(&data.auth, pool).await?;
168 let user_id = user.map(|u| u.id);
170 let name = data.name.to_owned().unwrap_or_else(|| "main".to_string());
171 let community = match data.id {
172 Some(id) => blocking(pool, move |conn| Community::read(conn, id)).await??,
173 None => match blocking(pool, move |conn| Community::read_from_name(conn, &name)).await? {
174 Ok(community) => community,
175 Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
179 let community_id = community.id;
180 let community_view = match blocking(pool, move |conn| {
181 CommunityView::read(conn, community_id, user_id)
185 Ok(community) => community,
186 Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
189 let community_id = community.id;
190 let moderators: Vec<CommunityModeratorView> = match blocking(pool, move |conn| {
191 CommunityModeratorView::for_community(conn, community_id)
195 Ok(moderators) => moderators,
196 Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
199 let online = if let Some(ws) = websocket_info {
200 if let Some(id) = ws.id {
201 ws.chatserver.do_send(JoinCommunityRoom {
202 community_id: community.id,
210 // ws.chatserver.send(GetCommunityUsersOnline {community_id}).await.unwrap()
212 // Runtime::new().unwrap().block_on(fut)
217 let res = GetCommunityResponse {
218 community: community_view,
228 #[async_trait::async_trait(?Send)]
229 impl Perform for Oper<CreateCommunity> {
230 type Response = CommunityResponse;
235 _websocket_info: Option<WebsocketInfo>,
236 ) -> Result<CommunityResponse, LemmyError> {
237 let data: &CreateCommunity = &self.data;
238 let user = get_user_from_jwt(&data.auth, pool).await?;
240 check_slurs(&data.name)?;
241 check_slurs(&data.title)?;
242 check_slurs_opt(&data.description)?;
244 if !is_valid_community_name(&data.name) {
245 return Err(APIError::err("invalid_community_name").into());
248 // Double check for duplicate community actor_ids
249 let actor_id = make_apub_endpoint(EndpointType::Community, &data.name).to_string();
250 let actor_id_cloned = actor_id.to_owned();
251 let community_dupe = blocking(pool, move |conn| {
252 Community::read_from_actor_id(conn, &actor_id_cloned)
255 if community_dupe.is_ok() {
256 return Err(APIError::err("community_already_exists").into());
259 // When you create a community, make sure the user becomes a moderator and a follower
260 let keypair = generate_actor_keypair()?;
262 let community_form = CommunityForm {
263 name: data.name.to_owned(),
264 title: data.title.to_owned(),
265 description: data.description.to_owned(),
266 icon: Some(data.icon.to_owned()),
267 banner: Some(data.banner.to_owned()),
268 category_id: data.category_id,
276 private_key: Some(keypair.private_key),
277 public_key: Some(keypair.public_key),
278 last_refreshed_at: None,
282 let inserted_community =
283 match blocking(pool, move |conn| Community::create(conn, &community_form)).await? {
284 Ok(community) => community,
285 Err(_e) => return Err(APIError::err("community_already_exists").into()),
288 let community_moderator_form = CommunityModeratorForm {
289 community_id: inserted_community.id,
293 let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
294 if blocking(pool, join).await?.is_err() {
295 return Err(APIError::err("community_moderator_already_exists").into());
298 let community_follower_form = CommunityFollowerForm {
299 community_id: inserted_community.id,
303 let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
304 if blocking(pool, follow).await?.is_err() {
305 return Err(APIError::err("community_follower_already_exists").into());
308 let user_id = user.id;
309 let community_view = blocking(pool, move |conn| {
310 CommunityView::read(conn, inserted_community.id, Some(user_id))
314 Ok(CommunityResponse {
315 community: community_view,
320 #[async_trait::async_trait(?Send)]
321 impl Perform for Oper<EditCommunity> {
322 type Response = CommunityResponse;
327 websocket_info: Option<WebsocketInfo>,
328 ) -> Result<CommunityResponse, LemmyError> {
329 let data: &EditCommunity = &self.data;
330 let user = get_user_from_jwt(&data.auth, pool).await?;
332 check_slurs(&data.title)?;
333 check_slurs_opt(&data.description)?;
335 // Verify its a mod (only mods can edit it)
336 let edit_id = data.edit_id;
337 let mods: Vec<i32> = blocking(pool, move |conn| {
338 CommunityModeratorView::for_community(conn, edit_id)
339 .map(|v| v.into_iter().map(|m| m.user_id).collect())
342 if !mods.contains(&user.id) {
343 return Err(APIError::err("not_a_moderator").into());
346 let edit_id = data.edit_id;
347 let read_community = blocking(pool, move |conn| Community::read(conn, edit_id)).await??;
349 let icon = diesel_option_overwrite(&data.icon);
350 let banner = diesel_option_overwrite(&data.banner);
352 let community_form = CommunityForm {
353 name: read_community.name,
354 title: data.title.to_owned(),
355 description: data.description.to_owned(),
358 category_id: data.category_id.to_owned(),
359 creator_id: read_community.creator_id,
360 removed: Some(read_community.removed),
361 deleted: Some(read_community.deleted),
363 updated: Some(naive_now()),
364 actor_id: read_community.actor_id,
365 local: read_community.local,
366 private_key: read_community.private_key,
367 public_key: read_community.public_key,
368 last_refreshed_at: None,
372 let edit_id = data.edit_id;
373 match blocking(pool, move |conn| {
374 Community::update(conn, edit_id, &community_form)
378 Ok(community) => community,
379 Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
382 // TODO there needs to be some kind of an apub update
383 // process for communities and users
385 let edit_id = data.edit_id;
386 let user_id = user.id;
387 let community_view = blocking(pool, move |conn| {
388 CommunityView::read(conn, edit_id, Some(user_id))
392 let res = CommunityResponse {
393 community: community_view,
396 send_community_websocket(&res, websocket_info, UserOperation::EditCommunity);
402 #[async_trait::async_trait(?Send)]
403 impl Perform for Oper<DeleteCommunity> {
404 type Response = CommunityResponse;
409 websocket_info: Option<WebsocketInfo>,
410 ) -> Result<CommunityResponse, LemmyError> {
411 let data: &DeleteCommunity = &self.data;
412 let user = get_user_from_jwt(&data.auth, pool).await?;
414 // Verify its the creator (only a creator can delete the community)
415 let edit_id = data.edit_id;
416 let read_community = blocking(pool, move |conn| Community::read(conn, edit_id)).await??;
417 if read_community.creator_id != user.id {
418 return Err(APIError::err("no_community_edit_allowed").into());
422 let edit_id = data.edit_id;
423 let deleted = data.deleted;
424 let updated_community = match blocking(pool, move |conn| {
425 Community::update_deleted(conn, edit_id, deleted)
429 Ok(community) => community,
430 Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
433 // Send apub messages
436 .send_delete(&user, &self.client, pool)
440 .send_undo_delete(&user, &self.client, pool)
444 let edit_id = data.edit_id;
445 let user_id = user.id;
446 let community_view = blocking(pool, move |conn| {
447 CommunityView::read(conn, edit_id, Some(user_id))
451 let res = CommunityResponse {
452 community: community_view,
455 send_community_websocket(&res, websocket_info, UserOperation::DeleteCommunity);
461 #[async_trait::async_trait(?Send)]
462 impl Perform for Oper<RemoveCommunity> {
463 type Response = CommunityResponse;
468 websocket_info: Option<WebsocketInfo>,
469 ) -> Result<CommunityResponse, LemmyError> {
470 let data: &RemoveCommunity = &self.data;
471 let user = get_user_from_jwt(&data.auth, pool).await?;
473 // Verify its an admin (only an admin can remove a community)
474 is_admin(pool, user.id).await?;
477 let edit_id = data.edit_id;
478 let removed = data.removed;
479 let updated_community = match blocking(pool, move |conn| {
480 Community::update_removed(conn, edit_id, removed)
484 Ok(community) => community,
485 Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
489 let expires = match data.expires {
490 Some(time) => Some(naive_from_unix(time)),
493 let form = ModRemoveCommunityForm {
494 mod_user_id: user.id,
495 community_id: data.edit_id,
496 removed: Some(removed),
497 reason: data.reason.to_owned(),
500 blocking(pool, move |conn| ModRemoveCommunity::create(conn, &form)).await??;
505 .send_remove(&user, &self.client, pool)
509 .send_undo_remove(&user, &self.client, pool)
513 let edit_id = data.edit_id;
514 let user_id = user.id;
515 let community_view = blocking(pool, move |conn| {
516 CommunityView::read(conn, edit_id, Some(user_id))
520 let res = CommunityResponse {
521 community: community_view,
524 send_community_websocket(&res, websocket_info, UserOperation::RemoveCommunity);
530 #[async_trait::async_trait(?Send)]
531 impl Perform for Oper<ListCommunities> {
532 type Response = ListCommunitiesResponse;
537 _websocket_info: Option<WebsocketInfo>,
538 ) -> Result<ListCommunitiesResponse, LemmyError> {
539 let data: &ListCommunities = &self.data;
540 let user = get_user_from_jwt_opt(&data.auth, pool).await?;
542 let user_id = match &user {
543 Some(user) => Some(user.id),
547 let show_nsfw = match &user {
548 Some(user) => user.show_nsfw,
552 let sort = SortType::from_str(&data.sort)?;
554 let page = data.page;
555 let limit = data.limit;
556 let communities = blocking(pool, move |conn| {
557 CommunityQueryBuilder::create(conn)
560 .show_nsfw(show_nsfw)
568 Ok(ListCommunitiesResponse { communities })
572 #[async_trait::async_trait(?Send)]
573 impl Perform for Oper<FollowCommunity> {
574 type Response = CommunityResponse;
579 _websocket_info: Option<WebsocketInfo>,
580 ) -> Result<CommunityResponse, LemmyError> {
581 let data: &FollowCommunity = &self.data;
582 let user = get_user_from_jwt(&data.auth, pool).await?;
584 let community_id = data.community_id;
585 let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
586 let community_follower_form = CommunityFollowerForm {
587 community_id: data.community_id,
593 let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
594 if blocking(pool, follow).await?.is_err() {
595 return Err(APIError::err("community_follower_already_exists").into());
599 move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form);
600 if blocking(pool, unfollow).await?.is_err() {
601 return Err(APIError::err("community_follower_already_exists").into());
604 } else if data.follow {
605 // Dont actually add to the community followers here, because you need
606 // to wait for the accept
608 .send_follow(&community.actor_id, &self.client, pool)
612 .send_unfollow(&community.actor_id, &self.client, pool)
614 let unfollow = move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form);
615 if blocking(pool, unfollow).await?.is_err() {
616 return Err(APIError::err("community_follower_already_exists").into());
619 // TODO: this needs to return a "pending" state, until Accept is received from the remote server
621 let community_id = data.community_id;
622 let user_id = user.id;
623 let community_view = blocking(pool, move |conn| {
624 CommunityView::read(conn, community_id, Some(user_id))
628 Ok(CommunityResponse {
629 community: community_view,
634 #[async_trait::async_trait(?Send)]
635 impl Perform for Oper<GetFollowedCommunities> {
636 type Response = GetFollowedCommunitiesResponse;
641 _websocket_info: Option<WebsocketInfo>,
642 ) -> Result<GetFollowedCommunitiesResponse, LemmyError> {
643 let data: &GetFollowedCommunities = &self.data;
644 let user = get_user_from_jwt(&data.auth, pool).await?;
646 let user_id = user.id;
647 let communities = match blocking(pool, move |conn| {
648 CommunityFollowerView::for_user(conn, user_id)
652 Ok(communities) => communities,
653 _ => return Err(APIError::err("system_err_login").into()),
657 Ok(GetFollowedCommunitiesResponse { communities })
661 #[async_trait::async_trait(?Send)]
662 impl Perform for Oper<BanFromCommunity> {
663 type Response = BanFromCommunityResponse;
668 websocket_info: Option<WebsocketInfo>,
669 ) -> Result<BanFromCommunityResponse, LemmyError> {
670 let data: &BanFromCommunity = &self.data;
671 let user = get_user_from_jwt(&data.auth, pool).await?;
673 let community_id = data.community_id;
675 // Verify that only mods or admins can ban
676 is_mod_or_admin(pool, user.id, community_id).await?;
678 let community_user_ban_form = CommunityUserBanForm {
679 community_id: data.community_id,
680 user_id: data.user_id,
684 let ban = move |conn: &'_ _| CommunityUserBan::ban(conn, &community_user_ban_form);
685 if blocking(pool, ban).await?.is_err() {
686 return Err(APIError::err("community_user_already_banned").into());
689 let unban = move |conn: &'_ _| CommunityUserBan::unban(conn, &community_user_ban_form);
690 if blocking(pool, unban).await?.is_err() {
691 return Err(APIError::err("community_user_already_banned").into());
696 // TODO eventually do correct expires
697 let expires = match data.expires {
698 Some(time) => Some(naive_from_unix(time)),
702 let form = ModBanFromCommunityForm {
703 mod_user_id: user.id,
704 other_user_id: data.user_id,
705 community_id: data.community_id,
706 reason: data.reason.to_owned(),
707 banned: Some(data.ban),
710 blocking(pool, move |conn| ModBanFromCommunity::create(conn, &form)).await??;
712 let user_id = data.user_id;
713 let user_view = blocking(pool, move |conn| UserView::read(conn, user_id)).await??;
715 let res = BanFromCommunityResponse {
720 if let Some(ws) = websocket_info {
721 ws.chatserver.do_send(SendCommunityRoomMessage {
722 op: UserOperation::BanFromCommunity,
723 response: res.clone(),
724 community_id: data.community_id,
733 #[async_trait::async_trait(?Send)]
734 impl Perform for Oper<AddModToCommunity> {
735 type Response = AddModToCommunityResponse;
740 websocket_info: Option<WebsocketInfo>,
741 ) -> Result<AddModToCommunityResponse, LemmyError> {
742 let data: &AddModToCommunity = &self.data;
743 let user = get_user_from_jwt(&data.auth, pool).await?;
745 let community_moderator_form = CommunityModeratorForm {
746 community_id: data.community_id,
747 user_id: data.user_id,
750 let community_id = data.community_id;
752 // Verify that only mods or admins can add mod
753 is_mod_or_admin(pool, user.id, community_id).await?;
756 let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
757 if blocking(pool, join).await?.is_err() {
758 return Err(APIError::err("community_moderator_already_exists").into());
761 let leave = move |conn: &'_ _| CommunityModerator::leave(conn, &community_moderator_form);
762 if blocking(pool, leave).await?.is_err() {
763 return Err(APIError::err("community_moderator_already_exists").into());
768 let form = ModAddCommunityForm {
769 mod_user_id: user.id,
770 other_user_id: data.user_id,
771 community_id: data.community_id,
772 removed: Some(!data.added),
774 blocking(pool, move |conn| ModAddCommunity::create(conn, &form)).await??;
776 let community_id = data.community_id;
777 let moderators = blocking(pool, move |conn| {
778 CommunityModeratorView::for_community(conn, community_id)
782 let res = AddModToCommunityResponse { moderators };
784 if let Some(ws) = websocket_info {
785 ws.chatserver.do_send(SendCommunityRoomMessage {
786 op: UserOperation::AddModToCommunity,
787 response: res.clone(),
788 community_id: data.community_id,
797 #[async_trait::async_trait(?Send)]
798 impl Perform for Oper<TransferCommunity> {
799 type Response = GetCommunityResponse;
804 _websocket_info: Option<WebsocketInfo>,
805 ) -> Result<GetCommunityResponse, LemmyError> {
806 let data: &TransferCommunity = &self.data;
807 let user = get_user_from_jwt(&data.auth, pool).await?;
809 let community_id = data.community_id;
810 let read_community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
812 let site_creator_id =
813 blocking(pool, move |conn| Site::read(conn, 1).map(|s| s.creator_id)).await??;
815 let mut admins = blocking(pool, move |conn| UserView::admins(conn)).await??;
817 let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
818 let creator_user = admins.remove(creator_index);
819 admins.insert(0, creator_user);
821 // Make sure user is the creator, or an admin
822 if user.id != read_community.creator_id && !admins.iter().map(|a| a.id).any(|x| x == user.id) {
823 return Err(APIError::err("not_an_admin").into());
826 let community_id = data.community_id;
827 let new_creator = data.user_id;
828 let update = move |conn: &'_ _| Community::update_creator(conn, community_id, new_creator);
829 if blocking(pool, update).await?.is_err() {
830 return Err(APIError::err("couldnt_update_community").into());
833 // You also have to re-do the community_moderator table, reordering it.
834 let community_id = data.community_id;
835 let mut community_mods = blocking(pool, move |conn| {
836 CommunityModeratorView::for_community(conn, community_id)
839 let creator_index = community_mods
841 .position(|r| r.user_id == data.user_id)
843 let creator_user = community_mods.remove(creator_index);
844 community_mods.insert(0, creator_user);
846 let community_id = data.community_id;
847 blocking(pool, move |conn| {
848 CommunityModerator::delete_for_community(conn, community_id)
852 // TODO: this should probably be a bulk operation
853 for cmod in &community_mods {
854 let community_moderator_form = CommunityModeratorForm {
855 community_id: cmod.community_id,
856 user_id: cmod.user_id,
859 let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
860 if blocking(pool, join).await?.is_err() {
861 return Err(APIError::err("community_moderator_already_exists").into());
866 let form = ModAddCommunityForm {
867 mod_user_id: user.id,
868 other_user_id: data.user_id,
869 community_id: data.community_id,
870 removed: Some(false),
872 blocking(pool, move |conn| ModAddCommunity::create(conn, &form)).await??;
874 let community_id = data.community_id;
875 let user_id = user.id;
876 let community_view = match blocking(pool, move |conn| {
877 CommunityView::read(conn, community_id, Some(user_id))
881 Ok(community) => community,
882 Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
885 let community_id = data.community_id;
886 let moderators = match blocking(pool, move |conn| {
887 CommunityModeratorView::for_community(conn, community_id)
891 Ok(moderators) => moderators,
892 Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
896 Ok(GetCommunityResponse {
897 community: community_view,
904 pub fn send_community_websocket(
905 res: &CommunityResponse,
906 websocket_info: Option<WebsocketInfo>,
909 if let Some(ws) = websocket_info {
910 // Strip out the user id and subscribed when sending to others
911 let mut res_sent = res.clone();
912 res_sent.community.user_id = None;
913 res_sent.community.subscribed = None;
915 ws.chatserver.do_send(SendCommunityRoomMessage {
918 community_id: res.community.id,