]> Untitled Git - lemmy.git/blobdiff - crates/api/src/community.rs
Implement instance actor (#1798)
[lemmy.git] / crates / api / src / community.rs
index bb341294b2624965445c31aa0767d3c125faef4a..f4f4332345ca21c3a8bae7ae023267d2475cd52e 100644 (file)
-use crate::{
+use crate::Perform;
+use actix_web::web::Data;
+use anyhow::Context;
+use lemmy_api_common::{
+  blocking,
   check_community_ban,
-  get_user_from_jwt,
-  get_user_from_jwt_opt,
-  is_admin,
+  check_community_deleted_or_removed,
+  community::*,
+  get_local_user_view_from_jwt,
   is_mod_or_admin,
-  Perform,
+  remove_user_data_in_community,
 };
-use actix_web::web::Data;
-use anyhow::Context;
-use lemmy_api_structs::{blocking, community::*};
 use lemmy_apub::{
-  activities::send::community::{send_add_mod, send_remove_mod},
-  generate_apub_endpoint,
-  generate_followers_url,
-  generate_inbox_url,
-  generate_shared_inbox_url,
-  ActorType,
-  EndpointType,
-};
-use lemmy_db_queries::{
-  diesel_option_overwrite_to_url,
-  source::{
-    comment::Comment_,
-    community::{CommunityModerator_, Community_},
-    post::Post_,
+  activities::block::SiteOrCommunity,
+  objects::{community::ApubCommunity, person::ApubPerson},
+  protocol::activities::{
+    block::{block_user::BlockUser, undo_block_user::UndoBlockUser},
+    community::{add_mod::AddMod, remove_mod::RemoveMod},
+    following::{follow::FollowCommunity as FollowCommunityApub, undo_follow::UndoFollowCommunity},
   },
-  ApubObject,
-  Bannable,
-  Crud,
-  Followable,
-  Joinable,
-  ListingType,
-  SortType,
 };
 use lemmy_db_schema::{
-  naive_now,
-  source::{comment::Comment, community::*, moderator::*, post::Post, site::*, user::User_},
+  source::{
+    community::{
+      Community,
+      CommunityFollower,
+      CommunityFollowerForm,
+      CommunityModerator,
+      CommunityModeratorForm,
+      CommunityPersonBan,
+      CommunityPersonBanForm,
+    },
+    community_block::{CommunityBlock, CommunityBlockForm},
+    moderator::{
+      ModAddCommunity,
+      ModAddCommunityForm,
+      ModBanFromCommunity,
+      ModBanFromCommunityForm,
+      ModTransferCommunity,
+      ModTransferCommunityForm,
+    },
+    person::Person,
+  },
+  traits::{Bannable, Blockable, Crud, Followable, Joinable},
 };
-use lemmy_db_views::comment_view::CommentQueryBuilder;
 use lemmy_db_views_actor::{
-  community_follower_view::CommunityFollowerView,
   community_moderator_view::CommunityModeratorView,
-  community_view::{CommunityQueryBuilder, CommunityView},
-  user_view::UserViewSafe,
-};
-use lemmy_utils::{
-  apub::generate_actor_keypair,
-  location_info,
-  utils::{check_slurs, check_slurs_opt, is_valid_community_name, naive_from_unix},
-  ApiError,
-  ConnectionId,
-  LemmyError,
-};
-use lemmy_websocket::{
-  messages::{GetCommunityUsersOnline, SendCommunityRoomMessage},
-  LemmyContext,
-  UserOperation,
+  community_view::CommunityView,
+  person_view::PersonViewSafe,
 };
-use std::str::FromStr;
-
-#[async_trait::async_trait(?Send)]
-impl Perform for GetCommunity {
-  type Response = GetCommunityResponse;
-
-  async fn perform(
-    &self,
-    context: &Data<LemmyContext>,
-    _websocket_id: Option<ConnectionId>,
-  ) -> Result<GetCommunityResponse, LemmyError> {
-    let data: &GetCommunity = &self;
-    let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
-    let user_id = user.map(|u| u.id);
-
-    let community_id = match data.id {
-      Some(id) => id,
-      None => {
-        let name = data.name.to_owned().unwrap_or_else(|| "main".to_string());
-        match blocking(context.pool(), move |conn| {
-          Community::read_from_name(conn, &name)
-        })
-        .await?
-        {
-          Ok(community) => community,
-          Err(_e) => return Err(ApiError::err("couldnt_find_community").into()),
-        }
-        .id
-      }
-    };
-
-    let community_view = match blocking(context.pool(), move |conn| {
-      CommunityView::read(conn, community_id, user_id)
-    })
-    .await?
-    {
-      Ok(community) => community,
-      Err(_e) => return Err(ApiError::err("couldnt_find_community").into()),
-    };
-
-    let moderators: Vec<CommunityModeratorView> = match blocking(context.pool(), move |conn| {
-      CommunityModeratorView::for_community(conn, community_id)
-    })
-    .await?
-    {
-      Ok(moderators) => moderators,
-      Err(_e) => return Err(ApiError::err("couldnt_find_community").into()),
-    };
-
-    let online = context
-      .chat_server()
-      .send(GetCommunityUsersOnline { community_id })
-      .await
-      .unwrap_or(1);
-
-    let res = GetCommunityResponse {
-      community_view,
-      moderators,
-      online,
-    };
-
-    // Return the jwt
-    Ok(res)
-  }
-}
-
-#[async_trait::async_trait(?Send)]
-impl Perform for CreateCommunity {
-  type Response = CommunityResponse;
-
-  async fn perform(
-    &self,
-    context: &Data<LemmyContext>,
-    _websocket_id: Option<ConnectionId>,
-  ) -> Result<CommunityResponse, LemmyError> {
-    let data: &CreateCommunity = &self;
-    let user = get_user_from_jwt(&data.auth, context.pool()).await?;
-
-    check_slurs(&data.name)?;
-    check_slurs(&data.title)?;
-    check_slurs_opt(&data.description)?;
-
-    if !is_valid_community_name(&data.name) {
-      return Err(ApiError::err("invalid_community_name").into());
-    }
-
-    // Double check for duplicate community actor_ids
-    let community_actor_id = generate_apub_endpoint(EndpointType::Community, &data.name)?;
-    let actor_id_cloned = community_actor_id.to_owned();
-    let community_dupe = blocking(context.pool(), move |conn| {
-      Community::read_from_apub_id(conn, &actor_id_cloned)
-    })
-    .await?;
-    if community_dupe.is_ok() {
-      return Err(ApiError::err("community_already_exists").into());
-    }
-
-    // Check to make sure the icon and banners are urls
-    let icon = diesel_option_overwrite_to_url(&data.icon)?;
-    let banner = diesel_option_overwrite_to_url(&data.banner)?;
-
-    // When you create a community, make sure the user becomes a moderator and a follower
-    let keypair = generate_actor_keypair()?;
-
-    let community_form = CommunityForm {
-      name: data.name.to_owned(),
-      title: data.title.to_owned(),
-      description: data.description.to_owned(),
-      icon,
-      banner,
-      creator_id: user.id,
-      removed: None,
-      deleted: None,
-      nsfw: data.nsfw,
-      updated: None,
-      actor_id: Some(community_actor_id.to_owned()),
-      local: true,
-      private_key: Some(keypair.private_key),
-      public_key: Some(keypair.public_key),
-      last_refreshed_at: None,
-      published: None,
-      followers_url: Some(generate_followers_url(&community_actor_id)?),
-      inbox_url: Some(generate_inbox_url(&community_actor_id)?),
-      shared_inbox_url: Some(Some(generate_shared_inbox_url(&community_actor_id)?)),
-    };
-
-    let inserted_community = match blocking(context.pool(), move |conn| {
-      Community::create(conn, &community_form)
-    })
-    .await?
-    {
-      Ok(community) => community,
-      Err(_e) => return Err(ApiError::err("community_already_exists").into()),
-    };
-
-    // The community creator becomes a moderator
-    let community_moderator_form = CommunityModeratorForm {
-      community_id: inserted_community.id,
-      user_id: user.id,
-    };
-
-    let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
-    if blocking(context.pool(), join).await?.is_err() {
-      return Err(ApiError::err("community_moderator_already_exists").into());
-    }
-
-    // Follow your own community
-    let community_follower_form = CommunityFollowerForm {
-      community_id: inserted_community.id,
-      user_id: user.id,
-      pending: false,
-    };
-
-    let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
-    if blocking(context.pool(), follow).await?.is_err() {
-      return Err(ApiError::err("community_follower_already_exists").into());
-    }
-
-    let user_id = user.id;
-    let community_view = blocking(context.pool(), move |conn| {
-      CommunityView::read(conn, inserted_community.id, Some(user_id))
-    })
-    .await??;
-
-    Ok(CommunityResponse { community_view })
-  }
-}
-
-#[async_trait::async_trait(?Send)]
-impl Perform for EditCommunity {
-  type Response = CommunityResponse;
-
-  async fn perform(
-    &self,
-    context: &Data<LemmyContext>,
-    websocket_id: Option<ConnectionId>,
-  ) -> Result<CommunityResponse, LemmyError> {
-    let data: &EditCommunity = &self;
-    let user = get_user_from_jwt(&data.auth, context.pool()).await?;
-
-    check_slurs(&data.title)?;
-    check_slurs_opt(&data.description)?;
-
-    // Verify its a mod (only mods can edit it)
-    let community_id = data.community_id;
-    let mods: Vec<i32> = blocking(context.pool(), move |conn| {
-      CommunityModeratorView::for_community(conn, community_id)
-        .map(|v| v.into_iter().map(|m| m.moderator.id).collect())
-    })
-    .await??;
-    if !mods.contains(&user.id) {
-      return Err(ApiError::err("not_a_moderator").into());
-    }
-
-    let community_id = data.community_id;
-    let read_community = blocking(context.pool(), move |conn| {
-      Community::read(conn, community_id)
-    })
-    .await??;
-
-    let icon = diesel_option_overwrite_to_url(&data.icon)?;
-    let banner = diesel_option_overwrite_to_url(&data.banner)?;
-
-    let community_form = CommunityForm {
-      name: read_community.name,
-      title: data.title.to_owned(),
-      description: data.description.to_owned(),
-      icon,
-      banner,
-      creator_id: read_community.creator_id,
-      removed: Some(read_community.removed),
-      deleted: Some(read_community.deleted),
-      nsfw: data.nsfw,
-      updated: Some(naive_now()),
-      actor_id: Some(read_community.actor_id),
-      local: read_community.local,
-      private_key: read_community.private_key,
-      public_key: read_community.public_key,
-      last_refreshed_at: None,
-      published: None,
-      followers_url: None,
-      inbox_url: None,
-      shared_inbox_url: None,
-    };
-
-    let community_id = data.community_id;
-    match blocking(context.pool(), move |conn| {
-      Community::update(conn, community_id, &community_form)
-    })
-    .await?
-    {
-      Ok(community) => community,
-      Err(_e) => return Err(ApiError::err("couldnt_update_community").into()),
-    };
-
-    // TODO there needs to be some kind of an apub update
-    // process for communities and users
-
-    let community_id = data.community_id;
-    let user_id = user.id;
-    let community_view = blocking(context.pool(), move |conn| {
-      CommunityView::read(conn, community_id, Some(user_id))
-    })
-    .await??;
-
-    let res = CommunityResponse { community_view };
-
-    send_community_websocket(&res, context, websocket_id, UserOperation::EditCommunity);
-
-    Ok(res)
-  }
-}
-
-#[async_trait::async_trait(?Send)]
-impl Perform for DeleteCommunity {
-  type Response = CommunityResponse;
-
-  async fn perform(
-    &self,
-    context: &Data<LemmyContext>,
-    websocket_id: Option<ConnectionId>,
-  ) -> Result<CommunityResponse, LemmyError> {
-    let data: &DeleteCommunity = &self;
-    let user = get_user_from_jwt(&data.auth, context.pool()).await?;
-
-    // Verify its the creator (only a creator can delete the community)
-    let community_id = data.community_id;
-    let read_community = blocking(context.pool(), move |conn| {
-      Community::read(conn, community_id)
-    })
-    .await??;
-    if read_community.creator_id != user.id {
-      return Err(ApiError::err("no_community_edit_allowed").into());
-    }
-
-    // Do the delete
-    let community_id = data.community_id;
-    let deleted = data.deleted;
-    let updated_community = match blocking(context.pool(), move |conn| {
-      Community::update_deleted(conn, community_id, deleted)
-    })
-    .await?
-    {
-      Ok(community) => community,
-      Err(_e) => return Err(ApiError::err("couldnt_update_community").into()),
-    };
-
-    // Send apub messages
-    if deleted {
-      updated_community.send_delete(context).await?;
-    } else {
-      updated_community.send_undo_delete(context).await?;
-    }
-
-    let community_id = data.community_id;
-    let user_id = user.id;
-    let community_view = blocking(context.pool(), move |conn| {
-      CommunityView::read(conn, community_id, Some(user_id))
-    })
-    .await??;
-
-    let res = CommunityResponse { community_view };
-
-    send_community_websocket(&res, context, websocket_id, UserOperation::DeleteCommunity);
-
-    Ok(res)
-  }
-}
-
-#[async_trait::async_trait(?Send)]
-impl Perform for RemoveCommunity {
-  type Response = CommunityResponse;
-
-  async fn perform(
-    &self,
-    context: &Data<LemmyContext>,
-    websocket_id: Option<ConnectionId>,
-  ) -> Result<CommunityResponse, LemmyError> {
-    let data: &RemoveCommunity = &self;
-    let user = get_user_from_jwt(&data.auth, context.pool()).await?;
-
-    // Verify its an admin (only an admin can remove a community)
-    is_admin(context.pool(), user.id).await?;
-
-    // Do the remove
-    let community_id = data.community_id;
-    let removed = data.removed;
-    let updated_community = match blocking(context.pool(), move |conn| {
-      Community::update_removed(conn, community_id, removed)
-    })
-    .await?
-    {
-      Ok(community) => community,
-      Err(_e) => return Err(ApiError::err("couldnt_update_community").into()),
-    };
-
-    // Mod tables
-    let expires = match data.expires {
-      Some(time) => Some(naive_from_unix(time)),
-      None => None,
-    };
-    let form = ModRemoveCommunityForm {
-      mod_user_id: user.id,
-      community_id: data.community_id,
-      removed: Some(removed),
-      reason: data.reason.to_owned(),
-      expires,
-    };
-    blocking(context.pool(), move |conn| {
-      ModRemoveCommunity::create(conn, &form)
-    })
-    .await??;
-
-    // Apub messages
-    if removed {
-      updated_community.send_remove(context).await?;
-    } else {
-      updated_community.send_undo_remove(context).await?;
-    }
-
-    let community_id = data.community_id;
-    let user_id = user.id;
-    let community_view = blocking(context.pool(), move |conn| {
-      CommunityView::read(conn, community_id, Some(user_id))
-    })
-    .await??;
-
-    let res = CommunityResponse { community_view };
-
-    send_community_websocket(&res, context, websocket_id, UserOperation::RemoveCommunity);
-
-    Ok(res)
-  }
-}
-
-#[async_trait::async_trait(?Send)]
-impl Perform for ListCommunities {
-  type Response = ListCommunitiesResponse;
-
-  async fn perform(
-    &self,
-    context: &Data<LemmyContext>,
-    _websocket_id: Option<ConnectionId>,
-  ) -> Result<ListCommunitiesResponse, LemmyError> {
-    let data: &ListCommunities = &self;
-    let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
-
-    let user_id = match &user {
-      Some(user) => Some(user.id),
-      None => None,
-    };
-
-    let show_nsfw = match &user {
-      Some(user) => user.show_nsfw,
-      None => false,
-    };
-
-    let type_ = ListingType::from_str(&data.type_)?;
-    let sort = SortType::from_str(&data.sort)?;
-
-    let page = data.page;
-    let limit = data.limit;
-    let communities = blocking(context.pool(), move |conn| {
-      CommunityQueryBuilder::create(conn)
-        .listing_type(&type_)
-        .sort(&sort)
-        .show_nsfw(show_nsfw)
-        .my_user_id(user_id)
-        .page(page)
-        .limit(limit)
-        .list()
-    })
-    .await??;
-
-    // Return the jwt
-    Ok(ListCommunitiesResponse { communities })
-  }
-}
+use lemmy_utils::{location_info, utils::naive_from_unix, ConnectionId, LemmyError};
+use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperation};
 
 #[async_trait::async_trait(?Send)]
 impl Perform for FollowCommunity {
   type Response = CommunityResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
     _websocket_id: Option<ConnectionId>,
   ) -> Result<CommunityResponse, LemmyError> {
-    let data: &FollowCommunity = &self;
-    let user = get_user_from_jwt(&data.auth, context.pool()).await?;
+    let data: &FollowCommunity = self;
+    let local_user_view =
+      get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
 
     let community_id = data.community_id;
-    let community = blocking(context.pool(), move |conn| {
+    let community: ApubCommunity = blocking(context.pool(), move |conn| {
       Community::read(conn, community_id)
     })
-    .await??;
+    .await??
+    .into();
     let community_follower_form = CommunityFollowerForm {
       community_id: data.community_id,
-      user_id: user.id,
+      person_id: local_user_view.person.id,
       pending: false,
     };
 
     if community.local {
       if data.follow {
-        check_community_ban(user.id, community_id, context.pool()).await?;
+        check_community_ban(local_user_view.person.id, community_id, context.pool()).await?;
+        check_community_deleted_or_removed(community_id, context.pool()).await?;
 
         let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
-        if blocking(context.pool(), follow).await?.is_err() {
-          return Err(ApiError::err("community_follower_already_exists").into());
-        }
+        blocking(context.pool(), follow)
+          .await?
+          .map_err(LemmyError::from)
+          .map_err(|e| e.with_message("community_follower_already_exists"))?;
       } else {
         let unfollow =
           move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form);
-        if blocking(context.pool(), unfollow).await?.is_err() {
-          return Err(ApiError::err("community_follower_already_exists").into());
-        }
+        blocking(context.pool(), unfollow)
+          .await?
+          .map_err(LemmyError::from)
+          .map_err(|e| e.with_message("community_follower_already_exists"))?;
       }
     } else if data.follow {
       // Dont actually add to the community followers here, because you need
       // to wait for the accept
-      user.send_follow(&community.actor_id(), context).await?;
+      FollowCommunityApub::send(&local_user_view.person.clone().into(), &community, context)
+        .await?;
     } else {
-      user.send_unfollow(&community.actor_id(), context).await?;
+      UndoFollowCommunity::send(&local_user_view.person.clone().into(), &community, context)
+        .await?;
       let unfollow = move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form);
-      if blocking(context.pool(), unfollow).await?.is_err() {
-        return Err(ApiError::err("community_follower_already_exists").into());
-      }
+      blocking(context.pool(), unfollow)
+        .await?
+        .map_err(LemmyError::from)
+        .map_err(|e| e.with_message("community_follower_already_exists"))?;
     }
 
     let community_id = data.community_id;
-    let user_id = user.id;
+    let person_id = local_user_view.person.id;
     let mut community_view = blocking(context.pool(), move |conn| {
-      CommunityView::read(conn, community_id, Some(user_id))
+      CommunityView::read(conn, community_id, Some(person_id))
     })
     .await??;
 
@@ -545,29 +129,66 @@ impl Perform for FollowCommunity {
 }
 
 #[async_trait::async_trait(?Send)]
-impl Perform for GetFollowedCommunities {
-  type Response = GetFollowedCommunitiesResponse;
+impl Perform for BlockCommunity {
+  type Response = BlockCommunityResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
     _websocket_id: Option<ConnectionId>,
-  ) -> Result<GetFollowedCommunitiesResponse, LemmyError> {
-    let data: &GetFollowedCommunities = &self;
-    let user = get_user_from_jwt(&data.auth, context.pool()).await?;
+  ) -> Result<BlockCommunityResponse, LemmyError> {
+    let data: &BlockCommunity = self;
+    let local_user_view =
+      get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
 
-    let user_id = user.id;
-    let communities = match blocking(context.pool(), move |conn| {
-      CommunityFollowerView::for_user(conn, user_id)
-    })
-    .await?
-    {
-      Ok(communities) => communities,
-      _ => return Err(ApiError::err("system_err_login").into()),
+    let community_id = data.community_id;
+    let person_id = local_user_view.person.id;
+    let community_block_form = CommunityBlockForm {
+      person_id,
+      community_id,
     };
 
-    // Return the jwt
-    Ok(GetFollowedCommunitiesResponse { communities })
+    if data.block {
+      let block = move |conn: &'_ _| CommunityBlock::block(conn, &community_block_form);
+      blocking(context.pool(), block)
+        .await?
+        .map_err(LemmyError::from)
+        .map_err(|e| e.with_message("community_block_already_exists"))?;
+
+      // Also, unfollow the community, and send a federated unfollow
+      let community_follower_form = CommunityFollowerForm {
+        community_id: data.community_id,
+        person_id,
+        pending: false,
+      };
+      blocking(context.pool(), move |conn: &'_ _| {
+        CommunityFollower::unfollow(conn, &community_follower_form)
+      })
+      .await?
+      .ok();
+      let community = blocking(context.pool(), move |conn| {
+        Community::read(conn, community_id)
+      })
+      .await??;
+      UndoFollowCommunity::send(&local_user_view.person.into(), &community.into(), context).await?;
+    } else {
+      let unblock = move |conn: &'_ _| CommunityBlock::unblock(conn, &community_block_form);
+      blocking(context.pool(), unblock)
+        .await?
+        .map_err(LemmyError::from)
+        .map_err(|e| e.with_message("community_block_already_exists"))?;
+    }
+
+    let community_view = blocking(context.pool(), move |conn| {
+      CommunityView::read(conn, community_id, Some(person_id))
+    })
+    .await??;
+
+    Ok(BlockCommunityResponse {
+      blocked: data.block,
+      community_view,
+    })
   }
 }
 
@@ -575,35 +196,52 @@ impl Perform for GetFollowedCommunities {
 impl Perform for BanFromCommunity {
   type Response = BanFromCommunityResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
     websocket_id: Option<ConnectionId>,
   ) -> Result<BanFromCommunityResponse, LemmyError> {
-    let data: &BanFromCommunity = &self;
-    let user = get_user_from_jwt(&data.auth, context.pool()).await?;
+    let data: &BanFromCommunity = self;
+    let local_user_view =
+      get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
 
     let community_id = data.community_id;
-    let banned_user_id = data.user_id;
+    let banned_person_id = data.person_id;
+    let remove_data = data.remove_data.unwrap_or(false);
+    let expires = data.expires.map(naive_from_unix);
 
     // Verify that only mods or admins can ban
-    is_mod_or_admin(context.pool(), user.id, community_id).await?;
+    is_mod_or_admin(context.pool(), local_user_view.person.id, community_id).await?;
 
-    let community_user_ban_form = CommunityUserBanForm {
+    let community_user_ban_form = CommunityPersonBanForm {
       community_id: data.community_id,
-      user_id: data.user_id,
+      person_id: data.person_id,
+      expires: Some(expires),
     };
 
+    let community: ApubCommunity = blocking(context.pool(), move |conn: &'_ _| {
+      Community::read(conn, community_id)
+    })
+    .await??
+    .into();
+    let banned_person: ApubPerson = blocking(context.pool(), move |conn: &'_ _| {
+      Person::read(conn, banned_person_id)
+    })
+    .await??
+    .into();
+
     if data.ban {
-      let ban = move |conn: &'_ _| CommunityUserBan::ban(conn, &community_user_ban_form);
-      if blocking(context.pool(), ban).await?.is_err() {
-        return Err(ApiError::err("community_user_already_banned").into());
-      }
+      let ban = move |conn: &'_ _| CommunityPersonBan::ban(conn, &community_user_ban_form);
+      blocking(context.pool(), ban)
+        .await?
+        .map_err(LemmyError::from)
+        .map_err(|e| e.with_message("community_user_already_banned"))?;
 
       // Also unsubscribe them from the community, if they are subscribed
       let community_follower_form = CommunityFollowerForm {
         community_id: data.community_id,
-        user_id: banned_user_id,
+        person_id: banned_person_id,
         pending: false,
       };
       blocking(context.pool(), move |conn: &'_ _| {
@@ -611,51 +249,42 @@ impl Perform for BanFromCommunity {
       })
       .await?
       .ok();
+
+      BlockUser::send(
+        &SiteOrCommunity::Community(community),
+        &banned_person,
+        &local_user_view.person.clone().into(),
+        remove_data,
+        data.reason.clone(),
+        expires,
+        context,
+      )
+      .await?;
     } else {
-      let unban = move |conn: &'_ _| CommunityUserBan::unban(conn, &community_user_ban_form);
-      if blocking(context.pool(), unban).await?.is_err() {
-        return Err(ApiError::err("community_user_already_banned").into());
-      }
+      let unban = move |conn: &'_ _| CommunityPersonBan::unban(conn, &community_user_ban_form);
+      blocking(context.pool(), unban)
+        .await?
+        .map_err(LemmyError::from)
+        .map_err(|e| e.with_message("community_user_already_banned"))?;
+      UndoBlockUser::send(
+        &SiteOrCommunity::Community(community),
+        &banned_person,
+        &local_user_view.person.clone().into(),
+        data.reason.clone(),
+        context,
+      )
+      .await?;
     }
 
     // Remove/Restore their data if that's desired
-    if data.remove_data {
-      // Posts
-      blocking(context.pool(), move |conn: &'_ _| {
-        Post::update_removed_for_creator(conn, banned_user_id, Some(community_id), true)
-      })
-      .await??;
-
-      // Comments
-      // TODO Diesel doesn't allow updates with joins, so this has to be a loop
-      let comments = blocking(context.pool(), move |conn| {
-        CommentQueryBuilder::create(conn)
-          .creator_id(banned_user_id)
-          .community_id(community_id)
-          .limit(std::i64::MAX)
-          .list()
-      })
-      .await??;
-
-      for comment_view in &comments {
-        let comment_id = comment_view.comment.id;
-        blocking(context.pool(), move |conn: &'_ _| {
-          Comment::update_removed(conn, comment_id, true)
-        })
-        .await??;
-      }
+    if remove_data {
+      remove_user_data_in_community(community_id, banned_person_id, context.pool()).await?;
     }
 
     // Mod tables
-    // TODO eventually do correct expires
-    let expires = match data.expires {
-      Some(time) => Some(naive_from_unix(time)),
-      None => None,
-    };
-
     let form = ModBanFromCommunityForm {
-      mod_user_id: user.id,
-      other_user_id: data.user_id,
+      mod_person_id: local_user_view.person.id,
+      other_person_id: data.person_id,
       community_id: data.community_id,
       reason: data.reason.to_owned(),
       banned: Some(data.ban),
@@ -666,14 +295,14 @@ impl Perform for BanFromCommunity {
     })
     .await??;
 
-    let user_id = data.user_id;
-    let user_view = blocking(context.pool(), move |conn| {
-      UserViewSafe::read(conn, user_id)
+    let person_id = data.person_id;
+    let person_view = blocking(context.pool(), move |conn| {
+      PersonViewSafe::read(conn, person_id)
     })
     .await??;
 
     let res = BanFromCommunityResponse {
-      user_view,
+      person_view,
       banned: data.ban,
     };
 
@@ -692,40 +321,44 @@ impl Perform for BanFromCommunity {
 impl Perform for AddModToCommunity {
   type Response = AddModToCommunityResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
     websocket_id: Option<ConnectionId>,
   ) -> Result<AddModToCommunityResponse, LemmyError> {
-    let data: &AddModToCommunity = &self;
-    let user = get_user_from_jwt(&data.auth, context.pool()).await?;
+    let data: &AddModToCommunity = self;
+    let local_user_view =
+      get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
 
     let community_id = data.community_id;
 
     // Verify that only mods or admins can add mod
-    is_mod_or_admin(context.pool(), user.id, community_id).await?;
+    is_mod_or_admin(context.pool(), local_user_view.person.id, community_id).await?;
 
     // Update in local database
     let community_moderator_form = CommunityModeratorForm {
       community_id: data.community_id,
-      user_id: data.user_id,
+      person_id: data.person_id,
     };
     if data.added {
       let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
-      if blocking(context.pool(), join).await?.is_err() {
-        return Err(ApiError::err("community_moderator_already_exists").into());
-      }
+      blocking(context.pool(), join)
+        .await?
+        .map_err(LemmyError::from)
+        .map_err(|e| e.with_message("community_moderator_already_exists"))?;
     } else {
       let leave = move |conn: &'_ _| CommunityModerator::leave(conn, &community_moderator_form);
-      if blocking(context.pool(), leave).await?.is_err() {
-        return Err(ApiError::err("community_moderator_already_exists").into());
-      }
+      blocking(context.pool(), leave)
+        .await?
+        .map_err(LemmyError::from)
+        .map_err(|e| e.with_message("community_moderator_already_exists"))?;
     }
 
     // Mod tables
     let form = ModAddCommunityForm {
-      mod_user_id: user.id,
-      other_user_id: data.user_id,
+      mod_person_id: local_user_view.person.id,
+      other_person_id: data.person_id,
       community_id: data.community_id,
       removed: Some(!data.added),
     };
@@ -735,19 +368,33 @@ impl Perform for AddModToCommunity {
     .await??;
 
     // Send to federated instances
-    let updated_mod_id = data.user_id;
-    let updated_mod = blocking(context.pool(), move |conn| {
-      User_::read(conn, updated_mod_id)
+    let updated_mod_id = data.person_id;
+    let updated_mod: ApubPerson = blocking(context.pool(), move |conn| {
+      Person::read(conn, updated_mod_id)
     })
-    .await??;
-    let community = blocking(context.pool(), move |conn| {
+    .await??
+    .into();
+    let community: ApubCommunity = blocking(context.pool(), move |conn| {
       Community::read(conn, community_id)
     })
-    .await??;
+    .await??
+    .into();
     if data.added {
-      send_add_mod(user, updated_mod, community, context).await?;
+      AddMod::send(
+        &community,
+        &updated_mod,
+        &local_user_view.person.into(),
+        context,
+      )
+      .await?;
     } else {
-      send_remove_mod(user, updated_mod, community, context).await?;
+      RemoveMod::send(
+        &community,
+        &updated_mod,
+        &local_user_view.person.into(),
+        context,
+      )
+      .await?;
     }
 
     // Note: in case a remote mod is added, this returns the old moderators list, it will only get
@@ -775,62 +422,45 @@ impl Perform for AddModToCommunity {
 impl Perform for TransferCommunity {
   type Response = GetCommunityResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
     _websocket_id: Option<ConnectionId>,
   ) -> Result<GetCommunityResponse, LemmyError> {
-    let data: &TransferCommunity = &self;
-    let user = get_user_from_jwt(&data.auth, context.pool()).await?;
+    let data: &TransferCommunity = self;
+    let local_user_view =
+      get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
 
-    let community_id = data.community_id;
-    let read_community = blocking(context.pool(), move |conn| {
-      Community::read(conn, community_id)
-    })
-    .await??;
+    let admins = blocking(context.pool(), PersonViewSafe::admins).await??;
 
-    let site_creator_id = blocking(context.pool(), move |conn| {
-      Site::read(conn, 1).map(|s| s.creator_id)
+    // Fetch the community mods
+    let community_id = data.community_id;
+    let mut community_mods = blocking(context.pool(), move |conn| {
+      CommunityModeratorView::for_community(conn, community_id)
     })
     .await??;
 
-    let mut admins = blocking(context.pool(), move |conn| UserViewSafe::admins(conn)).await??;
-
-    // Making sure the creator, if an admin, is at the top
-    let creator_index = admins
-      .iter()
-      .position(|r| r.user.id == site_creator_id)
-      .context(location_info!())?;
-    let creator_user = admins.remove(creator_index);
-    admins.insert(0, creator_user);
-
-    // Make sure user is the creator, or an admin
-    if user.id != read_community.creator_id
-      && !admins.iter().map(|a| a.user.id).any(|x| x == user.id)
+    // Make sure transferrer is either the top community mod, or an admin
+    if local_user_view.person.id != community_mods[0].moderator.id
+      && !admins
+        .iter()
+        .map(|a| a.person.id)
+        .any(|x| x == local_user_view.person.id)
     {
-      return Err(ApiError::err("not_an_admin").into());
+      return Err(LemmyError::from_message("not_an_admin"));
     }
 
-    let community_id = data.community_id;
-    let new_creator = data.user_id;
-    let update = move |conn: &'_ _| Community::update_creator(conn, community_id, new_creator);
-    if blocking(context.pool(), update).await?.is_err() {
-      return Err(ApiError::err("couldnt_update_community").into());
-    };
-
-    // You also have to re-do the community_moderator table, reordering it.
-    let community_id = data.community_id;
-    let mut community_mods = blocking(context.pool(), move |conn| {
-      CommunityModeratorView::for_community(conn, community_id)
-    })
-    .await??;
+    // You have to re-do the community_moderator table, reordering it.
+    // Add the transferee to the top
     let creator_index = community_mods
       .iter()
-      .position(|r| r.moderator.id == data.user_id)
+      .position(|r| r.moderator.id == data.person_id)
       .context(location_info!())?;
-    let creator_user = community_mods.remove(creator_index);
-    community_mods.insert(0, creator_user);
+    let creator_person = community_mods.remove(creator_index);
+    community_mods.insert(0, creator_person);
 
+    // Delete all the mods
     let community_id = data.community_id;
     blocking(context.pool(), move |conn| {
       CommunityModerator::delete_for_community(conn, community_id)
@@ -838,50 +468,48 @@ impl Perform for TransferCommunity {
     .await??;
 
     // TODO: this should probably be a bulk operation
+    // Re-add the mods, in the new order
     for cmod in &community_mods {
       let community_moderator_form = CommunityModeratorForm {
         community_id: cmod.community.id,
-        user_id: cmod.moderator.id,
+        person_id: cmod.moderator.id,
       };
 
       let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
-      if blocking(context.pool(), join).await?.is_err() {
-        return Err(ApiError::err("community_moderator_already_exists").into());
-      }
+      blocking(context.pool(), join)
+        .await?
+        .map_err(LemmyError::from)
+        .map_err(|e| e.with_message("community_moderator_already_exists"))?;
     }
 
     // Mod tables
-    let form = ModAddCommunityForm {
-      mod_user_id: user.id,
-      other_user_id: data.user_id,
+    let form = ModTransferCommunityForm {
+      mod_person_id: local_user_view.person.id,
+      other_person_id: data.person_id,
       community_id: data.community_id,
       removed: Some(false),
     };
     blocking(context.pool(), move |conn| {
-      ModAddCommunity::create(conn, &form)
+      ModTransferCommunity::create(conn, &form)
     })
     .await??;
 
     let community_id = data.community_id;
-    let user_id = user.id;
-    let community_view = match blocking(context.pool(), move |conn| {
-      CommunityView::read(conn, community_id, Some(user_id))
+    let person_id = local_user_view.person.id;
+    let community_view = blocking(context.pool(), move |conn| {
+      CommunityView::read(conn, community_id, Some(person_id))
     })
     .await?
-    {
-      Ok(community) => community,
-      Err(_e) => return Err(ApiError::err("couldnt_find_community").into()),
-    };
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_find_community"))?;
 
     let community_id = data.community_id;
-    let moderators = match blocking(context.pool(), move |conn| {
+    let moderators = blocking(context.pool(), move |conn| {
       CommunityModeratorView::for_community(conn, community_id)
     })
     .await?
-    {
-      Ok(moderators) => moderators,
-      Err(_e) => return Err(ApiError::err("couldnt_find_community").into()),
-    };
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_find_community"))?;
 
     // Return the jwt
     Ok(GetCommunityResponse {
@@ -891,21 +519,3 @@ impl Perform for TransferCommunity {
     })
   }
 }
-
-fn send_community_websocket(
-  res: &CommunityResponse,
-  context: &Data<LemmyContext>,
-  websocket_id: Option<ConnectionId>,
-  op: UserOperation,
-) {
-  // Strip out the user id and subscribed when sending to others
-  let mut res_sent = res.clone();
-  res_sent.community_view.subscribed = false;
-
-  context.chat_server().do_send(SendCommunityRoomMessage {
-    op,
-    response: res_sent,
-    community_id: res.community_view.community.id,
-    websocket_id,
-  });
-}