3 api::{is_admin, is_mod_or_admin, APIError, Oper, Perform},
7 server::{JoinCommunityRoom, SendCommunityRoomMessage},
13 use lemmy_db::{naive_now, Bannable, Crud, Followable, Joinable, SortType};
15 generate_actor_keypair,
16 is_valid_community_name,
21 use serde::{Deserialize, Serialize};
22 use std::str::FromStr;
24 #[derive(Serialize, Deserialize)]
25 pub struct GetCommunity {
27 pub name: Option<String>,
31 #[derive(Serialize, Deserialize)]
32 pub struct GetCommunityResponse {
33 pub community: CommunityView,
34 pub moderators: Vec<CommunityModeratorView>,
38 #[derive(Serialize, Deserialize)]
39 pub struct CreateCommunity {
42 description: Option<String>,
48 #[derive(Serialize, Deserialize, Clone)]
49 pub struct CommunityResponse {
50 pub community: CommunityView,
53 #[derive(Serialize, Deserialize, Debug)]
54 pub struct ListCommunities {
56 pub page: Option<i64>,
57 pub limit: Option<i64>,
58 pub auth: Option<String>,
61 #[derive(Serialize, Deserialize, Debug)]
62 pub struct ListCommunitiesResponse {
63 pub communities: Vec<CommunityView>,
66 #[derive(Serialize, Deserialize, Clone)]
67 pub struct BanFromCommunity {
68 pub community_id: i32,
71 reason: Option<String>,
76 #[derive(Serialize, Deserialize, Clone)]
77 pub struct BanFromCommunityResponse {
82 #[derive(Serialize, Deserialize)]
83 pub struct AddModToCommunity {
84 pub community_id: i32,
90 #[derive(Serialize, Deserialize, Clone)]
91 pub struct AddModToCommunityResponse {
92 moderators: Vec<CommunityModeratorView>,
95 #[derive(Serialize, Deserialize)]
96 pub struct EditCommunity {
99 description: Option<String>,
105 #[derive(Serialize, Deserialize)]
106 pub struct DeleteCommunity {
112 #[derive(Serialize, Deserialize)]
113 pub struct RemoveCommunity {
116 reason: Option<String>,
117 expires: Option<i64>,
121 #[derive(Serialize, Deserialize)]
122 pub struct FollowCommunity {
128 #[derive(Serialize, Deserialize)]
129 pub struct GetFollowedCommunities {
133 #[derive(Serialize, Deserialize)]
134 pub struct GetFollowedCommunitiesResponse {
135 communities: Vec<CommunityFollowerView>,
138 #[derive(Serialize, Deserialize)]
139 pub struct TransferCommunity {
145 #[async_trait::async_trait(?Send)]
146 impl Perform for Oper<GetCommunity> {
147 type Response = GetCommunityResponse;
152 websocket_info: Option<WebsocketInfo>,
153 ) -> Result<GetCommunityResponse, LemmyError> {
154 let data: &GetCommunity = &self.data;
155 let user = get_user_from_jwt_opt(&data.auth, pool).await?;
156 let user_id = user.map(|u| u.id);
158 let name = data.name.to_owned().unwrap_or_else(|| "main".to_string());
159 let community = match data.id {
160 Some(id) => blocking(pool, move |conn| Community::read(conn, id)).await??,
161 None => match blocking(pool, move |conn| Community::read_from_name(conn, &name)).await? {
162 Ok(community) => community,
163 Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
167 let community_id = community.id;
168 let community_view = match blocking(pool, move |conn| {
169 CommunityView::read(conn, community_id, user_id)
173 Ok(community) => community,
174 Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
177 let community_id = community.id;
178 let moderators: Vec<CommunityModeratorView> = match blocking(pool, move |conn| {
179 CommunityModeratorView::for_community(conn, community_id)
183 Ok(moderators) => moderators,
184 Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
187 let online = if let Some(ws) = websocket_info {
188 if let Some(id) = ws.id {
189 ws.chatserver.do_send(JoinCommunityRoom {
190 community_id: community.id,
198 // ws.chatserver.send(GetCommunityUsersOnline {community_id}).await.unwrap()
200 // Runtime::new().unwrap().block_on(fut)
205 let res = GetCommunityResponse {
206 community: community_view,
216 #[async_trait::async_trait(?Send)]
217 impl Perform for Oper<CreateCommunity> {
218 type Response = CommunityResponse;
223 _websocket_info: Option<WebsocketInfo>,
224 ) -> Result<CommunityResponse, LemmyError> {
225 let data: &CreateCommunity = &self.data;
226 let user = get_user_from_jwt(&data.auth, pool).await?;
228 check_slurs(&data.name)?;
229 check_slurs(&data.title)?;
230 check_slurs_opt(&data.description)?;
232 if !is_valid_community_name(&data.name) {
233 return Err(APIError::err("invalid_community_name").into());
236 // Double check for duplicate community actor_ids
237 let actor_id = make_apub_endpoint(EndpointType::Community, &data.name).to_string();
238 let actor_id_cloned = actor_id.to_owned();
239 let community_dupe = blocking(pool, move |conn| {
240 Community::read_from_actor_id(conn, &actor_id_cloned)
243 if community_dupe.is_ok() {
244 return Err(APIError::err("community_already_exists").into());
247 // When you create a community, make sure the user becomes a moderator and a follower
248 let keypair = generate_actor_keypair()?;
250 let community_form = CommunityForm {
251 name: data.name.to_owned(),
252 title: data.title.to_owned(),
253 description: data.description.to_owned(),
254 category_id: data.category_id,
262 private_key: Some(keypair.private_key),
263 public_key: Some(keypair.public_key),
264 last_refreshed_at: None,
268 let inserted_community =
269 match blocking(pool, move |conn| Community::create(conn, &community_form)).await? {
270 Ok(community) => community,
271 Err(_e) => return Err(APIError::err("community_already_exists").into()),
274 let community_moderator_form = CommunityModeratorForm {
275 community_id: inserted_community.id,
279 let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
280 if blocking(pool, join).await?.is_err() {
281 return Err(APIError::err("community_moderator_already_exists").into());
284 let community_follower_form = CommunityFollowerForm {
285 community_id: inserted_community.id,
289 let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
290 if blocking(pool, follow).await?.is_err() {
291 return Err(APIError::err("community_follower_already_exists").into());
294 let user_id = user.id;
295 let community_view = blocking(pool, move |conn| {
296 CommunityView::read(conn, inserted_community.id, Some(user_id))
300 Ok(CommunityResponse {
301 community: community_view,
306 #[async_trait::async_trait(?Send)]
307 impl Perform for Oper<EditCommunity> {
308 type Response = CommunityResponse;
313 websocket_info: Option<WebsocketInfo>,
314 ) -> Result<CommunityResponse, LemmyError> {
315 let data: &EditCommunity = &self.data;
316 let user = get_user_from_jwt(&data.auth, pool).await?;
318 check_slurs(&data.title)?;
319 check_slurs_opt(&data.description)?;
321 // Verify its a mod (only mods can edit it)
322 let edit_id = data.edit_id;
323 let mods: Vec<i32> = blocking(pool, move |conn| {
324 CommunityModeratorView::for_community(conn, edit_id)
325 .map(|v| v.into_iter().map(|m| m.user_id).collect())
328 if !mods.contains(&user.id) {
329 return Err(APIError::err("not_a_moderator").into());
332 let edit_id = data.edit_id;
333 let read_community = blocking(pool, move |conn| Community::read(conn, edit_id)).await??;
335 let community_form = CommunityForm {
336 name: read_community.name,
337 title: data.title.to_owned(),
338 description: data.description.to_owned(),
339 category_id: data.category_id.to_owned(),
340 creator_id: read_community.creator_id,
341 removed: Some(read_community.removed),
342 deleted: Some(read_community.deleted),
344 updated: Some(naive_now()),
345 actor_id: read_community.actor_id,
346 local: read_community.local,
347 private_key: read_community.private_key,
348 public_key: read_community.public_key,
349 last_refreshed_at: None,
353 let edit_id = data.edit_id;
354 match blocking(pool, move |conn| {
355 Community::update(conn, edit_id, &community_form)
359 Ok(community) => community,
360 Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
363 // TODO there needs to be some kind of an apub update
364 // process for communities and users
366 let edit_id = data.edit_id;
367 let user_id = user.id;
368 let community_view = blocking(pool, move |conn| {
369 CommunityView::read(conn, edit_id, Some(user_id))
373 let res = CommunityResponse {
374 community: community_view,
377 send_community_websocket(&res, websocket_info, UserOperation::EditCommunity);
383 #[async_trait::async_trait(?Send)]
384 impl Perform for Oper<DeleteCommunity> {
385 type Response = CommunityResponse;
390 websocket_info: Option<WebsocketInfo>,
391 ) -> Result<CommunityResponse, LemmyError> {
392 let data: &DeleteCommunity = &self.data;
393 let user = get_user_from_jwt(&data.auth, pool).await?;
395 // Verify its the creator (only a creator can delete the community)
396 let edit_id = data.edit_id;
397 let read_community = blocking(pool, move |conn| Community::read(conn, edit_id)).await??;
398 if read_community.creator_id != user.id {
399 return Err(APIError::err("no_community_edit_allowed").into());
403 let edit_id = data.edit_id;
404 let deleted = data.deleted;
405 let updated_community = match blocking(pool, move |conn| {
406 Community::update_deleted(conn, edit_id, deleted)
410 Ok(community) => community,
411 Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
414 // Send apub messages
417 .send_delete(&user, &self.client, pool)
421 .send_undo_delete(&user, &self.client, pool)
425 let edit_id = data.edit_id;
426 let user_id = user.id;
427 let community_view = blocking(pool, move |conn| {
428 CommunityView::read(conn, edit_id, Some(user_id))
432 let res = CommunityResponse {
433 community: community_view,
436 send_community_websocket(&res, websocket_info, UserOperation::DeleteCommunity);
442 #[async_trait::async_trait(?Send)]
443 impl Perform for Oper<RemoveCommunity> {
444 type Response = CommunityResponse;
449 websocket_info: Option<WebsocketInfo>,
450 ) -> Result<CommunityResponse, LemmyError> {
451 let data: &RemoveCommunity = &self.data;
452 let user = get_user_from_jwt(&data.auth, pool).await?;
454 // Verify its an admin (only an admin can remove a community)
455 is_admin(pool, user.id).await?;
458 let edit_id = data.edit_id;
459 let removed = data.removed;
460 let updated_community = match blocking(pool, move |conn| {
461 Community::update_removed(conn, edit_id, removed)
465 Ok(community) => community,
466 Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
470 let expires = match data.expires {
471 Some(time) => Some(naive_from_unix(time)),
474 let form = ModRemoveCommunityForm {
475 mod_user_id: user.id,
476 community_id: data.edit_id,
477 removed: Some(removed),
478 reason: data.reason.to_owned(),
481 blocking(pool, move |conn| ModRemoveCommunity::create(conn, &form)).await??;
486 .send_remove(&user, &self.client, pool)
490 .send_undo_remove(&user, &self.client, pool)
494 let edit_id = data.edit_id;
495 let user_id = user.id;
496 let community_view = blocking(pool, move |conn| {
497 CommunityView::read(conn, edit_id, Some(user_id))
501 let res = CommunityResponse {
502 community: community_view,
505 send_community_websocket(&res, websocket_info, UserOperation::RemoveCommunity);
511 #[async_trait::async_trait(?Send)]
512 impl Perform for Oper<ListCommunities> {
513 type Response = ListCommunitiesResponse;
518 _websocket_info: Option<WebsocketInfo>,
519 ) -> Result<ListCommunitiesResponse, LemmyError> {
520 let data: &ListCommunities = &self.data;
521 let user = get_user_from_jwt_opt(&data.auth, pool).await?;
523 let user_id = match &user {
524 Some(user) => Some(user.id),
528 let show_nsfw = match &user {
529 Some(user) => user.show_nsfw,
533 let sort = SortType::from_str(&data.sort)?;
535 let page = data.page;
536 let limit = data.limit;
537 let communities = blocking(pool, move |conn| {
538 CommunityQueryBuilder::create(conn)
541 .show_nsfw(show_nsfw)
549 Ok(ListCommunitiesResponse { communities })
553 #[async_trait::async_trait(?Send)]
554 impl Perform for Oper<FollowCommunity> {
555 type Response = CommunityResponse;
560 _websocket_info: Option<WebsocketInfo>,
561 ) -> Result<CommunityResponse, LemmyError> {
562 let data: &FollowCommunity = &self.data;
563 let user = get_user_from_jwt(&data.auth, pool).await?;
565 let community_id = data.community_id;
566 let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
567 let community_follower_form = CommunityFollowerForm {
568 community_id: data.community_id,
574 let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
575 if blocking(pool, follow).await?.is_err() {
576 return Err(APIError::err("community_follower_already_exists").into());
580 move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form);
581 if blocking(pool, unfollow).await?.is_err() {
582 return Err(APIError::err("community_follower_already_exists").into());
585 } else if data.follow {
586 // Dont actually add to the community followers here, because you need
587 // to wait for the accept
589 .send_follow(&community.actor_id, &self.client, pool)
593 .send_unfollow(&community.actor_id, &self.client, pool)
595 let unfollow = move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form);
596 if blocking(pool, unfollow).await?.is_err() {
597 return Err(APIError::err("community_follower_already_exists").into());
600 // TODO: this needs to return a "pending" state, until Accept is received from the remote server
602 let community_id = data.community_id;
603 let user_id = user.id;
604 let community_view = blocking(pool, move |conn| {
605 CommunityView::read(conn, community_id, Some(user_id))
609 Ok(CommunityResponse {
610 community: community_view,
615 #[async_trait::async_trait(?Send)]
616 impl Perform for Oper<GetFollowedCommunities> {
617 type Response = GetFollowedCommunitiesResponse;
622 _websocket_info: Option<WebsocketInfo>,
623 ) -> Result<GetFollowedCommunitiesResponse, LemmyError> {
624 let data: &GetFollowedCommunities = &self.data;
625 let user = get_user_from_jwt(&data.auth, pool).await?;
627 let user_id = user.id;
628 let communities = match blocking(pool, move |conn| {
629 CommunityFollowerView::for_user(conn, user_id)
633 Ok(communities) => communities,
634 _ => return Err(APIError::err("system_err_login").into()),
638 Ok(GetFollowedCommunitiesResponse { communities })
642 #[async_trait::async_trait(?Send)]
643 impl Perform for Oper<BanFromCommunity> {
644 type Response = BanFromCommunityResponse;
649 websocket_info: Option<WebsocketInfo>,
650 ) -> Result<BanFromCommunityResponse, LemmyError> {
651 let data: &BanFromCommunity = &self.data;
652 let user = get_user_from_jwt(&data.auth, pool).await?;
654 let community_id = data.community_id;
656 // Verify that only mods or admins can ban
657 is_mod_or_admin(pool, user.id, community_id).await?;
659 let community_user_ban_form = CommunityUserBanForm {
660 community_id: data.community_id,
661 user_id: data.user_id,
665 let ban = move |conn: &'_ _| CommunityUserBan::ban(conn, &community_user_ban_form);
666 if blocking(pool, ban).await?.is_err() {
667 return Err(APIError::err("community_user_already_banned").into());
670 let unban = move |conn: &'_ _| CommunityUserBan::unban(conn, &community_user_ban_form);
671 if blocking(pool, unban).await?.is_err() {
672 return Err(APIError::err("community_user_already_banned").into());
677 // TODO eventually do correct expires
678 let expires = match data.expires {
679 Some(time) => Some(naive_from_unix(time)),
683 let form = ModBanFromCommunityForm {
684 mod_user_id: user.id,
685 other_user_id: data.user_id,
686 community_id: data.community_id,
687 reason: data.reason.to_owned(),
688 banned: Some(data.ban),
691 blocking(pool, move |conn| ModBanFromCommunity::create(conn, &form)).await??;
693 let user_id = data.user_id;
694 let user_view = blocking(pool, move |conn| UserView::read(conn, user_id)).await??;
696 let res = BanFromCommunityResponse {
701 if let Some(ws) = websocket_info {
702 ws.chatserver.do_send(SendCommunityRoomMessage {
703 op: UserOperation::BanFromCommunity,
704 response: res.clone(),
705 community_id: data.community_id,
714 #[async_trait::async_trait(?Send)]
715 impl Perform for Oper<AddModToCommunity> {
716 type Response = AddModToCommunityResponse;
721 websocket_info: Option<WebsocketInfo>,
722 ) -> Result<AddModToCommunityResponse, LemmyError> {
723 let data: &AddModToCommunity = &self.data;
724 let user = get_user_from_jwt(&data.auth, pool).await?;
726 let community_moderator_form = CommunityModeratorForm {
727 community_id: data.community_id,
728 user_id: data.user_id,
731 let community_id = data.community_id;
733 // Verify that only mods or admins can add mod
734 is_mod_or_admin(pool, user.id, community_id).await?;
737 let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
738 if blocking(pool, join).await?.is_err() {
739 return Err(APIError::err("community_moderator_already_exists").into());
742 let leave = move |conn: &'_ _| CommunityModerator::leave(conn, &community_moderator_form);
743 if blocking(pool, leave).await?.is_err() {
744 return Err(APIError::err("community_moderator_already_exists").into());
749 let form = ModAddCommunityForm {
750 mod_user_id: user.id,
751 other_user_id: data.user_id,
752 community_id: data.community_id,
753 removed: Some(!data.added),
755 blocking(pool, move |conn| ModAddCommunity::create(conn, &form)).await??;
757 let community_id = data.community_id;
758 let moderators = blocking(pool, move |conn| {
759 CommunityModeratorView::for_community(conn, community_id)
763 let res = AddModToCommunityResponse { moderators };
765 if let Some(ws) = websocket_info {
766 ws.chatserver.do_send(SendCommunityRoomMessage {
767 op: UserOperation::AddModToCommunity,
768 response: res.clone(),
769 community_id: data.community_id,
778 #[async_trait::async_trait(?Send)]
779 impl Perform for Oper<TransferCommunity> {
780 type Response = GetCommunityResponse;
785 _websocket_info: Option<WebsocketInfo>,
786 ) -> Result<GetCommunityResponse, LemmyError> {
787 let data: &TransferCommunity = &self.data;
788 let user = get_user_from_jwt(&data.auth, pool).await?;
790 let community_id = data.community_id;
791 let read_community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
793 let site_creator_id =
794 blocking(pool, move |conn| Site::read(conn, 1).map(|s| s.creator_id)).await??;
796 let mut admins = blocking(pool, move |conn| UserView::admins(conn)).await??;
798 let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
799 let creator_user = admins.remove(creator_index);
800 admins.insert(0, creator_user);
802 // Make sure user is the creator, or an admin
803 if user.id != read_community.creator_id && !admins.iter().map(|a| a.id).any(|x| x == user.id) {
804 return Err(APIError::err("not_an_admin").into());
807 let community_id = data.community_id;
808 let new_creator = data.user_id;
809 let update = move |conn: &'_ _| Community::update_creator(conn, community_id, new_creator);
810 if blocking(pool, update).await?.is_err() {
811 return Err(APIError::err("couldnt_update_community").into());
814 // You also have to re-do the community_moderator table, reordering it.
815 let community_id = data.community_id;
816 let mut community_mods = blocking(pool, move |conn| {
817 CommunityModeratorView::for_community(conn, community_id)
820 let creator_index = community_mods
822 .position(|r| r.user_id == data.user_id)
824 let creator_user = community_mods.remove(creator_index);
825 community_mods.insert(0, creator_user);
827 let community_id = data.community_id;
828 blocking(pool, move |conn| {
829 CommunityModerator::delete_for_community(conn, community_id)
833 // TODO: this should probably be a bulk operation
834 for cmod in &community_mods {
835 let community_moderator_form = CommunityModeratorForm {
836 community_id: cmod.community_id,
837 user_id: cmod.user_id,
840 let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
841 if blocking(pool, join).await?.is_err() {
842 return Err(APIError::err("community_moderator_already_exists").into());
847 let form = ModAddCommunityForm {
848 mod_user_id: user.id,
849 other_user_id: data.user_id,
850 community_id: data.community_id,
851 removed: Some(false),
853 blocking(pool, move |conn| ModAddCommunity::create(conn, &form)).await??;
855 let community_id = data.community_id;
856 let user_id = user.id;
857 let community_view = match blocking(pool, move |conn| {
858 CommunityView::read(conn, community_id, Some(user_id))
862 Ok(community) => community,
863 Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
866 let community_id = data.community_id;
867 let moderators = match blocking(pool, move |conn| {
868 CommunityModeratorView::for_community(conn, community_id)
872 Ok(moderators) => moderators,
873 Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
877 Ok(GetCommunityResponse {
878 community: community_view,
885 pub fn send_community_websocket(
886 res: &CommunityResponse,
887 websocket_info: Option<WebsocketInfo>,
890 if let Some(ws) = websocket_info {
891 // Strip out the user id and subscribed when sending to others
892 let mut res_sent = res.clone();
893 res_sent.community.user_id = None;
894 res_sent.community.subscribed = None;
896 ws.chatserver.do_send(SendCommunityRoomMessage {
899 community_id: res.community.id,