From 01fc1228d510470d5fd1bc90414d5250fc356658 Mon Sep 17 00:00:00 2001
From: Felix Ableitner <me@nutomic.com>
Date: Thu, 25 Mar 2021 20:30:15 +0100
Subject: [PATCH] Fix API and clippy warnings

---
 crates/api/src/community.rs                   |   5 +-
 crates/api/src/lib.rs                         | 121 +---------------
 crates/api/src/local_user.rs                  |   7 +-
 crates/api_crud/src/comment/create.rs         |   4 +-
 crates/api_crud/src/comment/delete.rs         |   6 +-
 crates/api_crud/src/comment/update.rs         |   4 +-
 crates/api_crud/src/community/delete.rs       |  21 ++-
 crates/api_crud/src/community/mod.rs          |   4 +-
 crates/api_crud/src/community/read.rs         |   5 +-
 crates/api_crud/src/community/update.rs       |   9 +-
 crates/api_crud/src/lib.rs                    | 116 ++++++++++++++-
 crates/api_crud/src/post/create.rs            |   4 +-
 crates/api_crud/src/post/delete.rs            |   6 +-
 crates/api_crud/src/post/read.rs              |   5 +-
 crates/api_crud/src/post/update.rs            |   4 +-
 crates/api_crud/src/private_message/create.rs |   4 +-
 crates/api_crud/src/private_message/delete.rs |   4 +-
 crates/api_crud/src/private_message/update.rs |   4 +-
 crates/api_crud/src/routes.rs                 | 133 ------------------
 crates/api_crud/src/site/update.rs            |   4 +-
 crates/apub/src/activities/receive/comment.rs |  10 +-
 .../src/activities/receive/comment_undo.rs    |   6 +-
 .../apub/src/activities/receive/community.rs  |  10 +-
 crates/apub/src/activities/receive/post.rs    |  10 +-
 .../apub/src/activities/receive/post_undo.rs  |   6 +-
 .../src/activities/receive/private_message.rs |  10 +-
 crates/db_views/src/local_user_view.rs        |   8 +-
 crates/websocket/src/chat_server.rs           |  82 +++++++----
 crates/websocket/src/handlers.rs              |  30 ++--
 crates/websocket/src/lib.rs                   |  75 ++++++----
 crates/websocket/src/messages.rs              |  20 +--
 crates/websocket/src/routes.rs                |   6 +-
 docker/federation/docker-compose.yml          |   2 +-
 crates/api/src/routes.rs => src/api_routes.rs | 102 +++++++++++++-
 src/lib.rs                                    |   1 +
 src/main.rs                                   |   8 +-
 36 files changed, 440 insertions(+), 416 deletions(-)
 delete mode 100644 crates/api_crud/src/routes.rs
 rename crates/api/src/routes.rs => src/api_routes.rs (60%)

diff --git a/crates/api/src/community.rs b/crates/api/src/community.rs
index 1ee2a9a7..f1cf570c 100644
--- a/crates/api/src/community.rs
+++ b/crates/api/src/community.rs
@@ -188,10 +188,7 @@ impl Perform for BanFromCommunity {
 
     // Mod tables
     // TODO eventually do correct expires
-    let expires = match data.expires {
-      Some(time) => Some(naive_from_unix(time)),
-      None => None,
-    };
+    let expires = data.expires.map(naive_from_unix);
 
     let form = ModBanFromCommunityForm {
       mod_person_id: local_user_view.person.id,
diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs
index 277498f1..17aaf407 100644
--- a/crates/api/src/lib.rs
+++ b/crates/api/src/lib.rs
@@ -12,7 +12,6 @@ mod local_user;
 mod post;
 mod post_report;
 mod private_message;
-pub mod routes;
 mod site;
 mod websocket;
 
@@ -33,29 +32,15 @@ pub async fn match_websocket_operation(
   op: UserOperation,
   data: &str,
 ) -> Result<String, LemmyError> {
-  //TODO: handle commented out actions in crud crate
-
   match op {
     // User ops
-    UserOperation::Login => {
-      //do_websocket_operation::<Login>(context, id, op, data).await
-      todo!()
-    }
-    UserOperation::Register => {
-      //do_websocket_operation::<Register>(context, id, op, data).await
-      todo!()
-    }
+    UserOperation::Login => do_websocket_operation::<Login>(context, id, op, data).await,
     UserOperation::GetCaptcha => do_websocket_operation::<GetCaptcha>(context, id, op, data).await,
-    UserOperation::GetPersonDetails => {
-      //do_websocket_operation::<GetPersonDetails>(context, id, op, data).await
-      todo!()
-    }
     UserOperation::GetReplies => do_websocket_operation::<GetReplies>(context, id, op, data).await,
     UserOperation::AddAdmin => do_websocket_operation::<AddAdmin>(context, id, op, data).await,
     UserOperation::BanPerson => do_websocket_operation::<BanPerson>(context, id, op, data).await,
     UserOperation::GetPersonMentions => {
-      //do_websocket_operation::<GetPersonMentions>(context, id, op, data).await
-      todo!()
+      do_websocket_operation::<GetPersonMentions>(context, id, op, data).await
     }
     UserOperation::MarkPersonMentionAsRead => {
       do_websocket_operation::<MarkPersonMentionAsRead>(context, id, op, data).await
@@ -63,10 +48,6 @@ pub async fn match_websocket_operation(
     UserOperation::MarkAllAsRead => {
       do_websocket_operation::<MarkAllAsRead>(context, id, op, data).await
     }
-    UserOperation::DeleteAccount => {
-      //do_websocket_operation::<DeleteAccount>(context, id, op, data).await
-      todo!()
-    }
     UserOperation::PasswordReset => {
       do_websocket_operation::<PasswordReset>(context, id, op, data).await
     }
@@ -87,40 +68,12 @@ pub async fn match_websocket_operation(
     }
 
     // Private Message ops
-    UserOperation::CreatePrivateMessage => {
-      //do_websocket_operation::<CreatePrivateMessage>(context, id, op, data).await
-      todo!()
-    }
-    UserOperation::EditPrivateMessage => {
-      //do_websocket_operation::<EditPrivateMessage>(context, id, op, data).await
-      todo!()
-    }
-    UserOperation::DeletePrivateMessage => {
-      //do_websocket_operation::<DeletePrivateMessage>(context, id, op, data).await
-      todo!()
-    }
     UserOperation::MarkPrivateMessageAsRead => {
       do_websocket_operation::<MarkPrivateMessageAsRead>(context, id, op, data).await
     }
-    UserOperation::GetPrivateMessages => {
-      //do_websocket_operation::<GetPrivateMessages>(context, id, op, data).await
-      todo!()
-    }
 
     // Site ops
     UserOperation::GetModlog => do_websocket_operation::<GetModlog>(context, id, op, data).await,
-    UserOperation::CreateSite => {
-      //do_websocket_operation::<CreateSite>(context, id, op, data).await
-      todo!()
-    }
-    UserOperation::EditSite => {
-      //do_websocket_operation::<EditSite>(context, id, op, data).await
-      todo!()
-    }
-    UserOperation::GetSite => {
-      //do_websocket_operation::<GetSite>(context, id, op, data).await
-      todo!()
-    }
     UserOperation::GetSiteConfig => {
       do_websocket_operation::<GetSiteConfig>(context, id, op, data).await
     }
@@ -136,30 +89,6 @@ pub async fn match_websocket_operation(
     }
 
     // Community ops
-    UserOperation::GetCommunity => {
-      //do_websocket_operation::<GetCommunity>(context, id, op, data).await
-      todo!()
-    }
-    UserOperation::ListCommunities => {
-      //do_websocket_operation::<ListCommunities>(context, id, op, data).await
-      todo!()
-    }
-    UserOperation::CreateCommunity => {
-      //do_websocket_operation::<CreateCommunity>(context, id, op, data).await
-      todo!()
-    }
-    UserOperation::EditCommunity => {
-      //do_websocket_operation::<EditCommunity>(context, id, op, data).await
-      todo!()
-    }
-    UserOperation::DeleteCommunity => {
-      //do_websocket_operation::<DeleteCommunity>(context, id, op, data).await
-      todo!()
-    }
-    UserOperation::RemoveCommunity => {
-      //do_websocket_operation::<RemoveCommunity>(context, id, op, data).await
-      todo!()
-    }
     UserOperation::FollowCommunity => {
       do_websocket_operation::<FollowCommunity>(context, id, op, data).await
     }
@@ -174,30 +103,6 @@ pub async fn match_websocket_operation(
     }
 
     // Post ops
-    UserOperation::CreatePost => {
-      //do_websocket_operation::<CreatePost>(context, id, op, data).await
-      todo!()
-    }
-    UserOperation::GetPost => {
-      //do_websocket_operation::<GetPost>(context, id, op, data).await
-      todo!()
-    }
-    UserOperation::GetPosts => {
-      //do_websocket_operation::<GetPosts>(context, id, op, data).await
-      todo!()
-    }
-    UserOperation::EditPost => {
-      //do_websocket_operation::<EditPost>(context, id, op, data).await
-      todo!()
-    }
-    UserOperation::DeletePost => {
-      //do_websocket_operation::<DeletePost>(context, id, op, data).await
-      todo!()
-    }
-    UserOperation::RemovePost => {
-      //do_websocket_operation::<RemovePost>(context, id, op, data).await
-      todo!()
-    }
     UserOperation::LockPost => do_websocket_operation::<LockPost>(context, id, op, data).await,
     UserOperation::StickyPost => do_websocket_operation::<StickyPost>(context, id, op, data).await,
     UserOperation::CreatePostLike => {
@@ -215,32 +120,12 @@ pub async fn match_websocket_operation(
     }
 
     // Comment ops
-    UserOperation::CreateComment => {
-      //do_websocket_operation::<CreateComment>(context, id, op, data).await
-      todo!()
-    }
-    UserOperation::EditComment => {
-      //do_websocket_operation::<EditComment>(context, id, op, data).await
-      todo!()
-    }
-    UserOperation::DeleteComment => {
-      //do_websocket_operation::<DeleteComment>(context, id, op, data).await
-      todo!()
-    }
-    UserOperation::RemoveComment => {
-      //do_websocket_operation::<RemoveComment>(context, id, op, data).await
-      todo!()
-    }
     UserOperation::MarkCommentAsRead => {
       do_websocket_operation::<MarkCommentAsRead>(context, id, op, data).await
     }
     UserOperation::SaveComment => {
       do_websocket_operation::<SaveComment>(context, id, op, data).await
     }
-    UserOperation::GetComments => {
-      //do_websocket_operation::<GetComments>(context, id, op, data).await
-      todo!()
-    }
     UserOperation::CreateCommentLike => {
       do_websocket_operation::<CreateCommentLike>(context, id, op, data).await
     }
@@ -326,7 +211,7 @@ pub(crate) fn espeak_wav_base64(text: &str) -> Result<String, LemmyError> {
 
 #[cfg(test)]
 mod tests {
-  use crate::{captcha_espeak_wav_base64, check_validator_time};
+  use crate::captcha_espeak_wav_base64;
   use lemmy_api_common::check_validator_time;
   use lemmy_db_queries::{establish_unpooled_connection, source::local_user::LocalUser_, Crud};
   use lemmy_db_schema::source::{
diff --git a/crates/api/src/local_user.rs b/crates/api/src/local_user.rs
index aacb7d0b..ff28341b 100644
--- a/crates/api/src/local_user.rs
+++ b/crates/api/src/local_user.rs
@@ -153,7 +153,7 @@ impl Perform for GetCaptcha {
     context.chat_server().do_send(captcha_item);
 
     Ok(GetCaptchaResponse {
-      ok: Some(CaptchaResponse { png, uuid, wav }),
+      ok: Some(CaptchaResponse { png, wav, uuid }),
     })
   }
 }
@@ -407,10 +407,7 @@ impl Perform for BanPerson {
     }
 
     // Mod tables
-    let expires = match data.expires {
-      Some(time) => Some(naive_from_unix(time)),
-      None => None,
-    };
+    let expires = data.expires.map(naive_from_unix);
 
     let form = ModBanForm {
       mod_person_id: local_user_view.person.id,
diff --git a/crates/api_crud/src/comment/create.rs b/crates/api_crud/src/comment/create.rs
index 74ef27f1..57c85013 100644
--- a/crates/api_crud/src/comment/create.rs
+++ b/crates/api_crud/src/comment/create.rs
@@ -18,7 +18,7 @@ use lemmy_utils::{
   ConnectionId,
   LemmyError,
 };
-use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation};
+use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperationCrud};
 
 #[async_trait::async_trait(?Send)]
 impl PerformCrud for CreateComment {
@@ -158,7 +158,7 @@ impl PerformCrud for CreateComment {
     };
 
     context.chat_server().do_send(SendComment {
-      op: UserOperation::CreateComment,
+      op: UserOperationCrud::CreateComment,
       comment: res.clone(),
       websocket_id,
     });
diff --git a/crates/api_crud/src/comment/delete.rs b/crates/api_crud/src/comment/delete.rs
index 1980106b..bbac9c84 100644
--- a/crates/api_crud/src/comment/delete.rs
+++ b/crates/api_crud/src/comment/delete.rs
@@ -13,7 +13,7 @@ use lemmy_db_queries::{source::comment::Comment_, Crud};
 use lemmy_db_schema::source::{comment::*, moderator::*};
 use lemmy_db_views::comment_view::CommentView;
 use lemmy_utils::{ApiError, ConnectionId, LemmyError};
-use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation};
+use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperationCrud};
 
 #[async_trait::async_trait(?Send)]
 impl PerformCrud for DeleteComment {
@@ -95,7 +95,7 @@ impl PerformCrud for DeleteComment {
     };
 
     context.chat_server().do_send(SendComment {
-      op: UserOperation::DeleteComment,
+      op: UserOperationCrud::DeleteComment,
       comment: res.clone(),
       websocket_id,
     });
@@ -200,7 +200,7 @@ impl PerformCrud for RemoveComment {
     };
 
     context.chat_server().do_send(SendComment {
-      op: UserOperation::RemoveComment,
+      op: UserOperationCrud::RemoveComment,
       comment: res.clone(),
       websocket_id,
     });
diff --git a/crates/api_crud/src/comment/update.rs b/crates/api_crud/src/comment/update.rs
index 46d99e93..fb6bdaf1 100644
--- a/crates/api_crud/src/comment/update.rs
+++ b/crates/api_crud/src/comment/update.rs
@@ -17,7 +17,7 @@ use lemmy_utils::{
   ConnectionId,
   LemmyError,
 };
-use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation};
+use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperationCrud};
 
 #[async_trait::async_trait(?Send)]
 impl PerformCrud for EditComment {
@@ -93,7 +93,7 @@ impl PerformCrud for EditComment {
     };
 
     context.chat_server().do_send(SendComment {
-      op: UserOperation::EditComment,
+      op: UserOperationCrud::EditComment,
       comment: res.clone(),
       websocket_id,
     });
diff --git a/crates/api_crud/src/community/delete.rs b/crates/api_crud/src/community/delete.rs
index e59ccd6b..9b52660f 100644
--- a/crates/api_crud/src/community/delete.rs
+++ b/crates/api_crud/src/community/delete.rs
@@ -9,7 +9,7 @@ use lemmy_db_schema::source::{
 };
 use lemmy_db_views_actor::community_view::CommunityView;
 use lemmy_utils::{utils::naive_from_unix, ApiError, ConnectionId, LemmyError};
-use lemmy_websocket::{LemmyContext, UserOperation};
+use lemmy_websocket::{LemmyContext, UserOperationCrud};
 
 #[async_trait::async_trait(?Send)]
 impl PerformCrud for DeleteCommunity {
@@ -61,7 +61,12 @@ impl PerformCrud for DeleteCommunity {
 
     let res = CommunityResponse { community_view };
 
-    send_community_websocket(&res, context, websocket_id, UserOperation::DeleteCommunity);
+    send_community_websocket(
+      &res,
+      context,
+      websocket_id,
+      UserOperationCrud::DeleteCommunity,
+    );
 
     Ok(res)
   }
@@ -95,10 +100,7 @@ impl PerformCrud for RemoveCommunity {
     };
 
     // Mod tables
-    let expires = match data.expires {
-      Some(time) => Some(naive_from_unix(time)),
-      None => None,
-    };
+    let expires = data.expires.map(naive_from_unix);
     let form = ModRemoveCommunityForm {
       mod_person_id: local_user_view.person.id,
       community_id: data.community_id,
@@ -127,7 +129,12 @@ impl PerformCrud for RemoveCommunity {
 
     let res = CommunityResponse { community_view };
 
-    send_community_websocket(&res, context, websocket_id, UserOperation::RemoveCommunity);
+    send_community_websocket(
+      &res,
+      context,
+      websocket_id,
+      UserOperationCrud::RemoveCommunity,
+    );
 
     Ok(res)
   }
diff --git a/crates/api_crud/src/community/mod.rs b/crates/api_crud/src/community/mod.rs
index 874aba9a..9098cb54 100644
--- a/crates/api_crud/src/community/mod.rs
+++ b/crates/api_crud/src/community/mod.rs
@@ -1,7 +1,7 @@
 use actix_web::web::Data;
 use lemmy_api_common::community::CommunityResponse;
 use lemmy_utils::ConnectionId;
-use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperation};
+use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperationCrud};
 
 mod create;
 mod delete;
@@ -12,7 +12,7 @@ pub(in crate::community) fn send_community_websocket(
   res: &CommunityResponse,
   context: &Data<LemmyContext>,
   websocket_id: Option<ConnectionId>,
-  op: UserOperation,
+  op: UserOperationCrud,
 ) {
   // Strip out the person id and subscribed when sending to others
   let mut res_sent = res.clone();
diff --git a/crates/api_crud/src/community/read.rs b/crates/api_crud/src/community/read.rs
index af83a774..08982aa1 100644
--- a/crates/api_crud/src/community/read.rs
+++ b/crates/api_crud/src/community/read.rs
@@ -87,10 +87,7 @@ impl PerformCrud for ListCommunities {
     let data: &ListCommunities = &self;
     let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?;
 
-    let person_id = match &local_user_view {
-      Some(uv) => Some(uv.person.id),
-      None => None,
-    };
+    let person_id = local_user_view.to_owned().map(|l| l.person.id);
 
     // Don't show NSFW by default
     let show_nsfw = match &local_user_view {
diff --git a/crates/api_crud/src/community/update.rs b/crates/api_crud/src/community/update.rs
index d7fa3061..1e5b9d0c 100644
--- a/crates/api_crud/src/community/update.rs
+++ b/crates/api_crud/src/community/update.rs
@@ -21,7 +21,7 @@ use lemmy_utils::{
   ConnectionId,
   LemmyError,
 };
-use lemmy_websocket::{LemmyContext, UserOperation};
+use lemmy_websocket::{LemmyContext, UserOperationCrud};
 
 #[async_trait::async_trait(?Send)]
 impl PerformCrud for EditCommunity {
@@ -102,7 +102,12 @@ impl PerformCrud for EditCommunity {
 
     let res = CommunityResponse { community_view };
 
-    send_community_websocket(&res, context, websocket_id, UserOperation::EditCommunity);
+    send_community_websocket(
+      &res,
+      context,
+      websocket_id,
+      UserOperationCrud::EditCommunity,
+    );
 
     Ok(res)
   }
diff --git a/crates/api_crud/src/lib.rs b/crates/api_crud/src/lib.rs
index 77a900dd..c6385d05 100644
--- a/crates/api_crud/src/lib.rs
+++ b/crates/api_crud/src/lib.rs
@@ -1,12 +1,13 @@
-use actix_web::web::Data;
+use actix_web::{web, web::Data};
+use lemmy_api_common::{comment::*, community::*, person::*, post::*, site::*};
 use lemmy_utils::{ConnectionId, LemmyError};
-use lemmy_websocket::LemmyContext;
+use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperationCrud};
+use serde::Deserialize;
 
 mod comment;
 mod community;
 mod post;
 mod private_message;
-pub mod routes;
 mod site;
 mod user;
 
@@ -20,3 +21,112 @@ pub trait PerformCrud {
     websocket_id: Option<ConnectionId>,
   ) -> Result<Self::Response, LemmyError>;
 }
+
+pub async fn match_websocket_operation_crud(
+  context: LemmyContext,
+  id: ConnectionId,
+  op: UserOperationCrud,
+  data: &str,
+) -> Result<String, LemmyError> {
+  //TODO: handle commented out actions in crud crate
+
+  match op {
+    // User ops
+    UserOperationCrud::Register => do_websocket_operation::<Register>(context, id, op, data).await,
+    UserOperationCrud::GetPersonDetails => {
+      do_websocket_operation::<GetPersonDetails>(context, id, op, data).await
+    }
+    UserOperationCrud::DeleteAccount => {
+      do_websocket_operation::<DeleteAccount>(context, id, op, data).await
+    }
+
+    // Private Message ops
+    UserOperationCrud::CreatePrivateMessage => {
+      do_websocket_operation::<CreatePrivateMessage>(context, id, op, data).await
+    }
+    UserOperationCrud::EditPrivateMessage => {
+      do_websocket_operation::<EditPrivateMessage>(context, id, op, data).await
+    }
+    UserOperationCrud::DeletePrivateMessage => {
+      do_websocket_operation::<DeletePrivateMessage>(context, id, op, data).await
+    }
+    UserOperationCrud::GetPrivateMessages => {
+      do_websocket_operation::<GetPrivateMessages>(context, id, op, data).await
+    }
+
+    // Site ops
+    UserOperationCrud::CreateSite => {
+      do_websocket_operation::<CreateSite>(context, id, op, data).await
+    }
+    UserOperationCrud::EditSite => do_websocket_operation::<EditSite>(context, id, op, data).await,
+    UserOperationCrud::GetSite => do_websocket_operation::<GetSite>(context, id, op, data).await,
+
+    // Community ops
+    UserOperationCrud::GetCommunity => {
+      do_websocket_operation::<GetCommunity>(context, id, op, data).await
+    }
+    UserOperationCrud::ListCommunities => {
+      do_websocket_operation::<ListCommunities>(context, id, op, data).await
+    }
+    UserOperationCrud::CreateCommunity => {
+      do_websocket_operation::<CreateCommunity>(context, id, op, data).await
+    }
+    UserOperationCrud::EditCommunity => {
+      do_websocket_operation::<EditCommunity>(context, id, op, data).await
+    }
+    UserOperationCrud::DeleteCommunity => {
+      do_websocket_operation::<DeleteCommunity>(context, id, op, data).await
+    }
+    UserOperationCrud::RemoveCommunity => {
+      do_websocket_operation::<RemoveCommunity>(context, id, op, data).await
+    }
+
+    // Post ops
+    UserOperationCrud::CreatePost => {
+      do_websocket_operation::<CreatePost>(context, id, op, data).await
+    }
+    UserOperationCrud::GetPost => do_websocket_operation::<GetPost>(context, id, op, data).await,
+    UserOperationCrud::GetPosts => do_websocket_operation::<GetPosts>(context, id, op, data).await,
+    UserOperationCrud::EditPost => do_websocket_operation::<EditPost>(context, id, op, data).await,
+    UserOperationCrud::DeletePost => {
+      do_websocket_operation::<DeletePost>(context, id, op, data).await
+    }
+    UserOperationCrud::RemovePost => {
+      do_websocket_operation::<RemovePost>(context, id, op, data).await
+    }
+
+    // Comment ops
+    UserOperationCrud::CreateComment => {
+      do_websocket_operation::<CreateComment>(context, id, op, data).await
+    }
+    UserOperationCrud::EditComment => {
+      do_websocket_operation::<EditComment>(context, id, op, data).await
+    }
+    UserOperationCrud::DeleteComment => {
+      do_websocket_operation::<DeleteComment>(context, id, op, data).await
+    }
+    UserOperationCrud::RemoveComment => {
+      do_websocket_operation::<RemoveComment>(context, id, op, data).await
+    }
+    UserOperationCrud::GetComments => {
+      do_websocket_operation::<GetComments>(context, id, op, data).await
+    }
+  }
+}
+
+async fn do_websocket_operation<'a, 'b, Data>(
+  context: LemmyContext,
+  id: ConnectionId,
+  op: UserOperationCrud,
+  data: &str,
+) -> Result<String, LemmyError>
+where
+  for<'de> Data: Deserialize<'de> + 'a,
+  Data: PerformCrud,
+{
+  let parsed_data: Data = serde_json::from_str(&data)?;
+  let res = parsed_data
+    .perform(&web::Data::new(context), Some(id))
+    .await?;
+  serialize_websocket_message(&op, &res)
+}
diff --git a/crates/api_crud/src/post/create.rs b/crates/api_crud/src/post/create.rs
index 231a891d..f8bce061 100644
--- a/crates/api_crud/src/post/create.rs
+++ b/crates/api_crud/src/post/create.rs
@@ -12,7 +12,7 @@ use lemmy_utils::{
   ConnectionId,
   LemmyError,
 };
-use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperation};
+use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperationCrud};
 
 #[async_trait::async_trait(?Send)]
 impl PerformCrud for CreatePost {
@@ -120,7 +120,7 @@ impl PerformCrud for CreatePost {
     let res = PostResponse { post_view };
 
     context.chat_server().do_send(SendPost {
-      op: UserOperation::CreatePost,
+      op: UserOperationCrud::CreatePost,
       post: res.clone(),
       websocket_id,
     });
diff --git a/crates/api_crud/src/post/delete.rs b/crates/api_crud/src/post/delete.rs
index ca25f3b1..32b4e9ff 100644
--- a/crates/api_crud/src/post/delete.rs
+++ b/crates/api_crud/src/post/delete.rs
@@ -12,7 +12,7 @@ use lemmy_db_queries::{source::post::Post_, Crud};
 use lemmy_db_schema::source::{moderator::*, post::*};
 use lemmy_db_views::post_view::PostView;
 use lemmy_utils::{ApiError, ConnectionId, LemmyError};
-use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperation};
+use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperationCrud};
 
 #[async_trait::async_trait(?Send)]
 impl PerformCrud for DeletePost {
@@ -70,7 +70,7 @@ impl PerformCrud for DeletePost {
     let res = PostResponse { post_view };
 
     context.chat_server().do_send(SendPost {
-      op: UserOperation::DeletePost,
+      op: UserOperationCrud::DeletePost,
       post: res.clone(),
       websocket_id,
     });
@@ -151,7 +151,7 @@ impl PerformCrud for RemovePost {
     let res = PostResponse { post_view };
 
     context.chat_server().do_send(SendPost {
-      op: UserOperation::RemovePost,
+      op: UserOperationCrud::RemovePost,
       post: res.clone(),
       websocket_id,
     });
diff --git a/crates/api_crud/src/post/read.rs b/crates/api_crud/src/post/read.rs
index 1b173418..d8897317 100644
--- a/crates/api_crud/src/post/read.rs
+++ b/crates/api_crud/src/post/read.rs
@@ -92,10 +92,7 @@ impl PerformCrud for GetPosts {
     let data: &GetPosts = &self;
     let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?;
 
-    let person_id = match &local_user_view {
-      Some(uv) => Some(uv.person.id),
-      None => None,
-    };
+    let person_id = local_user_view.to_owned().map(|l| l.person.id);
 
     let show_nsfw = match &local_user_view {
       Some(uv) => uv.local_user.show_nsfw,
diff --git a/crates/api_crud/src/post/update.rs b/crates/api_crud/src/post/update.rs
index c03bddf8..e1efc790 100644
--- a/crates/api_crud/src/post/update.rs
+++ b/crates/api_crud/src/post/update.rs
@@ -12,7 +12,7 @@ use lemmy_utils::{
   ConnectionId,
   LemmyError,
 };
-use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperation};
+use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperationCrud};
 
 #[async_trait::async_trait(?Send)]
 impl PerformCrud for EditPost {
@@ -106,7 +106,7 @@ impl PerformCrud for EditPost {
     let res = PostResponse { post_view };
 
     context.chat_server().do_send(SendPost {
-      op: UserOperation::EditPost,
+      op: UserOperationCrud::EditPost,
       post: res.clone(),
       websocket_id,
     });
diff --git a/crates/api_crud/src/private_message/create.rs b/crates/api_crud/src/private_message/create.rs
index 9654d22f..02561263 100644
--- a/crates/api_crud/src/private_message/create.rs
+++ b/crates/api_crud/src/private_message/create.rs
@@ -11,7 +11,7 @@ use lemmy_db_queries::{source::private_message::PrivateMessage_, Crud};
 use lemmy_db_schema::source::private_message::{PrivateMessage, PrivateMessageForm};
 use lemmy_db_views::{local_user_view::LocalUserView, private_message_view::PrivateMessageView};
 use lemmy_utils::{utils::remove_slurs, ApiError, ConnectionId, LemmyError};
-use lemmy_websocket::{messages::SendUserRoomMessage, LemmyContext, UserOperation};
+use lemmy_websocket::{messages::SendUserRoomMessage, LemmyContext, UserOperationCrud};
 
 #[async_trait::async_trait(?Send)]
 impl PerformCrud for CreatePrivateMessage {
@@ -100,7 +100,7 @@ impl PerformCrud for CreatePrivateMessage {
 
       let local_recipient_id = local_recipient.local_user.id;
       context.chat_server().do_send(SendUserRoomMessage {
-        op: UserOperation::CreatePrivateMessage,
+        op: UserOperationCrud::CreatePrivateMessage,
         response: res.clone(),
         local_recipient_id,
         websocket_id,
diff --git a/crates/api_crud/src/private_message/delete.rs b/crates/api_crud/src/private_message/delete.rs
index 120f57aa..506b0490 100644
--- a/crates/api_crud/src/private_message/delete.rs
+++ b/crates/api_crud/src/private_message/delete.rs
@@ -10,7 +10,7 @@ use lemmy_db_queries::{source::private_message::PrivateMessage_, Crud};
 use lemmy_db_schema::source::private_message::PrivateMessage;
 use lemmy_db_views::{local_user_view::LocalUserView, private_message_view::PrivateMessageView};
 use lemmy_utils::{ApiError, ConnectionId, LemmyError};
-use lemmy_websocket::{messages::SendUserRoomMessage, LemmyContext, UserOperation};
+use lemmy_websocket::{messages::SendUserRoomMessage, LemmyContext, UserOperationCrud};
 
 #[async_trait::async_trait(?Send)]
 impl PerformCrud for DeletePrivateMessage {
@@ -76,7 +76,7 @@ impl PerformCrud for DeletePrivateMessage {
     {
       let local_recipient_id = local_recipient.local_user.id;
       context.chat_server().do_send(SendUserRoomMessage {
-        op: UserOperation::DeletePrivateMessage,
+        op: UserOperationCrud::DeletePrivateMessage,
         response: res.clone(),
         local_recipient_id,
         websocket_id,
diff --git a/crates/api_crud/src/private_message/update.rs b/crates/api_crud/src/private_message/update.rs
index b6baa036..4bfb1bb9 100644
--- a/crates/api_crud/src/private_message/update.rs
+++ b/crates/api_crud/src/private_message/update.rs
@@ -10,7 +10,7 @@ use lemmy_db_queries::{source::private_message::PrivateMessage_, Crud};
 use lemmy_db_schema::source::private_message::PrivateMessage;
 use lemmy_db_views::{local_user_view::LocalUserView, private_message_view::PrivateMessageView};
 use lemmy_utils::{utils::remove_slurs, ApiError, ConnectionId, LemmyError};
-use lemmy_websocket::{messages::SendUserRoomMessage, LemmyContext, UserOperation};
+use lemmy_websocket::{messages::SendUserRoomMessage, LemmyContext, UserOperationCrud};
 
 #[async_trait::async_trait(?Send)]
 impl PerformCrud for EditPrivateMessage {
@@ -70,7 +70,7 @@ impl PerformCrud for EditPrivateMessage {
     {
       let local_recipient_id = local_recipient.local_user.id;
       context.chat_server().do_send(SendUserRoomMessage {
-        op: UserOperation::EditPrivateMessage,
+        op: UserOperationCrud::EditPrivateMessage,
         response: res.clone(),
         local_recipient_id,
         websocket_id,
diff --git a/crates/api_crud/src/routes.rs b/crates/api_crud/src/routes.rs
deleted file mode 100644
index 774268b6..00000000
--- a/crates/api_crud/src/routes.rs
+++ /dev/null
@@ -1,133 +0,0 @@
-use crate::PerformCrud;
-use actix_web::{error::ErrorBadRequest, *};
-use lemmy_api_common::{comment::*, community::*, person::*, post::*, site::*};
-use lemmy_utils::rate_limit::RateLimit;
-use lemmy_websocket::LemmyContext;
-use serde::Deserialize;
-
-pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
-  cfg
-    .service(
-      web::scope("/api/v2")
-        // Site
-        .service(
-          web::scope("/site")
-            .wrap(rate_limit.message())
-            .route("", web::get().to(route_get::<GetSite>))
-            // Admin Actions
-            .route("", web::post().to(route_post::<CreateSite>))
-            .route("", web::put().to(route_post::<EditSite>)),
-        )
-        // Community
-        .service(
-          web::resource("/community")
-            .guard(guard::Post())
-            .wrap(rate_limit.register())
-            .route(web::post().to(route_post::<CreateCommunity>)),
-        )
-        .service(
-          web::scope("/community")
-            .wrap(rate_limit.message())
-            .route("", web::get().to(route_get::<GetCommunity>))
-            .route("", web::put().to(route_post::<EditCommunity>))
-            .route("/list", web::get().to(route_get::<ListCommunities>))
-            .route("/delete", web::post().to(route_post::<DeleteCommunity>))
-            // Mod Actions
-            .route("/remove", web::post().to(route_post::<RemoveCommunity>)),
-        )
-        // Post
-        .service(
-          // Handle POST to /post separately to add the post() rate limitter
-          web::resource("/post")
-            .guard(guard::Post())
-            .wrap(rate_limit.post())
-            .route(web::post().to(route_post::<CreatePost>)),
-        )
-        .service(
-          web::scope("/post")
-            .wrap(rate_limit.message())
-            .route("", web::get().to(route_get::<GetPost>))
-            .route("", web::put().to(route_post::<EditPost>))
-            .route("/delete", web::post().to(route_post::<DeletePost>))
-            .route("/remove", web::post().to(route_post::<RemovePost>))
-            .route("/list", web::get().to(route_get::<GetPosts>)),
-        )
-        // Comment
-        .service(
-          web::scope("/comment")
-            .wrap(rate_limit.message())
-            .route("", web::post().to(route_post::<CreateComment>))
-            .route("", web::put().to(route_post::<EditComment>))
-            .route("/delete", web::post().to(route_post::<DeleteComment>))
-            .route("/remove", web::post().to(route_post::<RemoveComment>))
-            .route("/list", web::get().to(route_get::<GetComments>)),
-        ),
-    )
-    // Private Message
-    .service(
-      web::scope("/private_message")
-        .wrap(rate_limit.message())
-        .route("/list", web::get().to(route_get::<GetPrivateMessages>))
-        .route("", web::post().to(route_post::<CreatePrivateMessage>))
-        .route("", web::put().to(route_post::<EditPrivateMessage>))
-        .route(
-          "/delete",
-          web::post().to(route_post::<DeletePrivateMessage>),
-        ),
-    )
-    // User
-    .service(
-      // Account action, I don't like that it's in /user maybe /accounts
-      // Handle /user/register separately to add the register() rate limitter
-      web::resource("/user/register")
-        .guard(guard::Post())
-        .wrap(rate_limit.register())
-        .route(web::post().to(route_post::<Register>)),
-    )
-    // User actions
-    .service(
-      web::scope("/user")
-        .wrap(rate_limit.message())
-        .route("", web::get().to(route_get::<GetPersonDetails>))
-        .route(
-          "/delete_account",
-          web::post().to(route_post::<DeleteAccount>),
-        ),
-    );
-}
-
-async fn perform<Request>(
-  data: Request,
-  context: web::Data<LemmyContext>,
-) -> Result<HttpResponse, Error>
-where
-  Request: PerformCrud,
-  Request: Send + 'static,
-{
-  let res = data
-    .perform(&context, None)
-    .await
-    .map(|json| HttpResponse::Ok().json(json))
-    .map_err(ErrorBadRequest)?;
-  Ok(res)
-}
-
-async fn route_get<'a, Data>(
-  data: web::Query<Data>,
-  context: web::Data<LemmyContext>,
-) -> Result<HttpResponse, Error>
-where
-  Data: Deserialize<'a> + Send + 'static + PerformCrud,
-{
-  perform::<Data>(data.0, context).await
-}
-
-async fn route_post<'a, Data>(
-  data: web::Json<Data>,
-  context: web::Data<LemmyContext>,
-) -> Result<HttpResponse, Error>
-where
-  Data: Deserialize<'a> + Send + 'static + PerformCrud,
-{
-  perform::<Data>(data.0, context).await
-}
diff --git a/crates/api_crud/src/site/update.rs b/crates/api_crud/src/site/update.rs
index 3a4f5072..06940a75 100644
--- a/crates/api_crud/src/site/update.rs
+++ b/crates/api_crud/src/site/update.rs
@@ -18,7 +18,7 @@ use lemmy_utils::{
   ConnectionId,
   LemmyError,
 };
-use lemmy_websocket::{messages::SendAllMessage, LemmyContext, UserOperation};
+use lemmy_websocket::{messages::SendAllMessage, LemmyContext, UserOperationCrud};
 
 #[async_trait::async_trait(?Send)]
 impl PerformCrud for EditSite {
@@ -64,7 +64,7 @@ impl PerformCrud for EditSite {
     let res = SiteResponse { site_view };
 
     context.chat_server().do_send(SendAllMessage {
-      op: UserOperation::EditSite,
+      op: UserOperationCrud::EditSite,
       response: res.clone(),
       websocket_id,
     });
diff --git a/crates/apub/src/activities/receive/comment.rs b/crates/apub/src/activities/receive/comment.rs
index 2575035b..d79ef84a 100644
--- a/crates/apub/src/activities/receive/comment.rs
+++ b/crates/apub/src/activities/receive/comment.rs
@@ -12,7 +12,7 @@ use lemmy_db_schema::source::{
 };
 use lemmy_db_views::comment_view::CommentView;
 use lemmy_utils::{location_info, utils::scrape_text_for_mentions, LemmyError};
-use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation};
+use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation, UserOperationCrud};
 
 pub(crate) async fn receive_create_comment(
   create: Create,
@@ -57,7 +57,7 @@ pub(crate) async fn receive_create_comment(
   };
 
   context.chat_server().do_send(SendComment {
-    op: UserOperation::CreateComment,
+    op: UserOperationCrud::CreateComment,
     comment: res,
     websocket_id: None,
   });
@@ -98,7 +98,7 @@ pub(crate) async fn receive_update_comment(
   };
 
   context.chat_server().do_send(SendComment {
-    op: UserOperation::EditComment,
+    op: UserOperationCrud::EditComment,
     comment: res,
     websocket_id: None,
   });
@@ -220,7 +220,7 @@ pub(crate) async fn receive_delete_comment(
     form_id: None,
   };
   context.chat_server().do_send(SendComment {
-    op: UserOperation::EditComment,
+    op: UserOperationCrud::EditComment,
     comment: res,
     websocket_id: None,
   });
@@ -252,7 +252,7 @@ pub(crate) async fn receive_remove_comment(
     form_id: None,
   };
   context.chat_server().do_send(SendComment {
-    op: UserOperation::EditComment,
+    op: UserOperationCrud::EditComment,
     comment: res,
     websocket_id: None,
   });
diff --git a/crates/apub/src/activities/receive/comment_undo.rs b/crates/apub/src/activities/receive/comment_undo.rs
index 12a49ee3..7214c8f0 100644
--- a/crates/apub/src/activities/receive/comment_undo.rs
+++ b/crates/apub/src/activities/receive/comment_undo.rs
@@ -5,7 +5,7 @@ use lemmy_db_queries::{source::comment::Comment_, Likeable};
 use lemmy_db_schema::source::comment::{Comment, CommentLike};
 use lemmy_db_views::comment_view::CommentView;
 use lemmy_utils::LemmyError;
-use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation};
+use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation, UserOperationCrud};
 
 pub(crate) async fn receive_undo_like_comment(
   like: &Like,
@@ -108,7 +108,7 @@ pub(crate) async fn receive_undo_delete_comment(
   };
 
   context.chat_server().do_send(SendComment {
-    op: UserOperation::EditComment,
+    op: UserOperationCrud::EditComment,
     comment: res,
     websocket_id: None,
   });
@@ -141,7 +141,7 @@ pub(crate) async fn receive_undo_remove_comment(
   };
 
   context.chat_server().do_send(SendComment {
-    op: UserOperation::EditComment,
+    op: UserOperationCrud::EditComment,
     comment: res,
     websocket_id: None,
   });
diff --git a/crates/apub/src/activities/receive/community.rs b/crates/apub/src/activities/receive/community.rs
index d6dba673..a40bcdfa 100644
--- a/crates/apub/src/activities/receive/community.rs
+++ b/crates/apub/src/activities/receive/community.rs
@@ -3,7 +3,7 @@ use lemmy_db_queries::source::community::Community_;
 use lemmy_db_schema::source::community::Community;
 use lemmy_db_views_actor::community_view::CommunityView;
 use lemmy_utils::LemmyError;
-use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperation};
+use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperationCrud};
 
 pub(crate) async fn receive_delete_community(
   context: &LemmyContext,
@@ -24,7 +24,7 @@ pub(crate) async fn receive_delete_community(
 
   let community_id = res.community_view.community.id;
   context.chat_server().do_send(SendCommunityRoomMessage {
-    op: UserOperation::EditCommunity,
+    op: UserOperationCrud::EditCommunity,
     response: res,
     community_id,
     websocket_id: None,
@@ -52,7 +52,7 @@ pub(crate) async fn receive_remove_community(
 
   let community_id = res.community_view.community.id;
   context.chat_server().do_send(SendCommunityRoomMessage {
-    op: UserOperation::EditCommunity,
+    op: UserOperationCrud::EditCommunity,
     response: res,
     community_id,
     websocket_id: None,
@@ -80,7 +80,7 @@ pub(crate) async fn receive_undo_delete_community(
 
   let community_id = res.community_view.community.id;
   context.chat_server().do_send(SendCommunityRoomMessage {
-    op: UserOperation::EditCommunity,
+    op: UserOperationCrud::EditCommunity,
     response: res,
     community_id,
     websocket_id: None,
@@ -109,7 +109,7 @@ pub(crate) async fn receive_undo_remove_community(
   let community_id = res.community_view.community.id;
 
   context.chat_server().do_send(SendCommunityRoomMessage {
-    op: UserOperation::EditCommunity,
+    op: UserOperationCrud::EditCommunity,
     response: res,
     community_id,
     websocket_id: None,
diff --git a/crates/apub/src/activities/receive/post.rs b/crates/apub/src/activities/receive/post.rs
index e490964d..2f4290d0 100644
--- a/crates/apub/src/activities/receive/post.rs
+++ b/crates/apub/src/activities/receive/post.rs
@@ -21,7 +21,7 @@ use lemmy_db_schema::{
 };
 use lemmy_db_views::post_view::PostView;
 use lemmy_utils::{location_info, LemmyError};
-use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperation};
+use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperation, UserOperationCrud};
 
 pub(crate) async fn receive_create_post(
   create: Create,
@@ -44,7 +44,7 @@ pub(crate) async fn receive_create_post(
   let res = PostResponse { post_view };
 
   context.chat_server().do_send(SendPost {
-    op: UserOperation::CreatePost,
+    op: UserOperationCrud::CreatePost,
     post: res,
     websocket_id: None,
   });
@@ -107,7 +107,7 @@ pub(crate) async fn receive_update_post(
   let res = PostResponse { post_view };
 
   context.chat_server().do_send(SendPost {
-    op: UserOperation::EditPost,
+    op: UserOperationCrud::EditPost,
     post: res,
     websocket_id: None,
   });
@@ -209,7 +209,7 @@ pub(crate) async fn receive_delete_post(
 
   let res = PostResponse { post_view };
   context.chat_server().do_send(SendPost {
-    op: UserOperation::EditPost,
+    op: UserOperationCrud::EditPost,
     post: res,
     websocket_id: None,
   });
@@ -235,7 +235,7 @@ pub(crate) async fn receive_remove_post(
 
   let res = PostResponse { post_view };
   context.chat_server().do_send(SendPost {
-    op: UserOperation::EditPost,
+    op: UserOperationCrud::EditPost,
     post: res,
     websocket_id: None,
   });
diff --git a/crates/apub/src/activities/receive/post_undo.rs b/crates/apub/src/activities/receive/post_undo.rs
index 589b0d22..2ed6ecac 100644
--- a/crates/apub/src/activities/receive/post_undo.rs
+++ b/crates/apub/src/activities/receive/post_undo.rs
@@ -5,7 +5,7 @@ use lemmy_db_queries::{source::post::Post_, Likeable};
 use lemmy_db_schema::source::post::{Post, PostLike};
 use lemmy_db_views::post_view::PostView;
 use lemmy_utils::LemmyError;
-use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperation};
+use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperation, UserOperationCrud};
 
 pub(crate) async fn receive_undo_like_post(
   like: &Like,
@@ -89,7 +89,7 @@ pub(crate) async fn receive_undo_delete_post(
 
   let res = PostResponse { post_view };
   context.chat_server().do_send(SendPost {
-    op: UserOperation::EditPost,
+    op: UserOperationCrud::EditPost,
     post: res,
     websocket_id: None,
   });
@@ -116,7 +116,7 @@ pub(crate) async fn receive_undo_remove_post(
   let res = PostResponse { post_view };
 
   context.chat_server().do_send(SendPost {
-    op: UserOperation::EditPost,
+    op: UserOperationCrud::EditPost,
     post: res,
     websocket_id: None,
   });
diff --git a/crates/apub/src/activities/receive/private_message.rs b/crates/apub/src/activities/receive/private_message.rs
index 47067b7a..6d90e868 100644
--- a/crates/apub/src/activities/receive/private_message.rs
+++ b/crates/apub/src/activities/receive/private_message.rs
@@ -18,7 +18,7 @@ use lemmy_db_queries::source::private_message::PrivateMessage_;
 use lemmy_db_schema::source::private_message::PrivateMessage;
 use lemmy_db_views::{local_user_view::LocalUserView, private_message_view::PrivateMessageView};
 use lemmy_utils::{location_info, LemmyError};
-use lemmy_websocket::{messages::SendUserRoomMessage, LemmyContext, UserOperation};
+use lemmy_websocket::{messages::SendUserRoomMessage, LemmyContext, UserOperationCrud};
 use url::Url;
 
 pub(crate) async fn receive_create_private_message(
@@ -60,7 +60,7 @@ pub(crate) async fn receive_create_private_message(
   .id;
 
   context.chat_server().do_send(SendUserRoomMessage {
-    op: UserOperation::CreatePrivateMessage,
+    op: UserOperationCrud::CreatePrivateMessage,
     response: res,
     local_recipient_id,
     websocket_id: None,
@@ -106,7 +106,7 @@ pub(crate) async fn receive_update_private_message(
   .id;
 
   context.chat_server().do_send(SendUserRoomMessage {
-    op: UserOperation::EditPrivateMessage,
+    op: UserOperationCrud::EditPrivateMessage,
     response: res,
     local_recipient_id,
     websocket_id: None,
@@ -146,7 +146,7 @@ pub(crate) async fn receive_delete_private_message(
   .id;
 
   context.chat_server().do_send(SendUserRoomMessage {
-    op: UserOperation::EditPrivateMessage,
+    op: UserOperationCrud::EditPrivateMessage,
     response: res,
     local_recipient_id,
     websocket_id: None,
@@ -191,7 +191,7 @@ pub(crate) async fn receive_undo_delete_private_message(
   .id;
 
   context.chat_server().do_send(SendUserRoomMessage {
-    op: UserOperation::EditPrivateMessage,
+    op: UserOperationCrud::EditPrivateMessage,
     response: res,
     local_recipient_id,
     websocket_id: None,
diff --git a/crates/db_views/src/local_user_view.rs b/crates/db_views/src/local_user_view.rs
index 85a83e15..63dedb86 100644
--- a/crates/db_views/src/local_user_view.rs
+++ b/crates/db_views/src/local_user_view.rs
@@ -70,9 +70,9 @@ impl LocalUserView {
       ))
       .first::<LocalUserViewTuple>(conn)?;
     Ok(Self {
+      local_user,
       person,
       counts,
-      local_user,
     })
   }
 
@@ -92,9 +92,9 @@ impl LocalUserView {
       ))
       .first::<LocalUserViewTuple>(conn)?;
     Ok(Self {
+      local_user,
       person,
       counts,
-      local_user,
     })
   }
 
@@ -110,9 +110,9 @@ impl LocalUserView {
       ))
       .first::<LocalUserViewTuple>(conn)?;
     Ok(Self {
+      local_user,
       person,
       counts,
-      local_user,
     })
   }
 }
@@ -139,9 +139,9 @@ impl LocalUserSettingsView {
       ))
       .first::<LocalUserSettingsViewTuple>(conn)?;
     Ok(Self {
+      local_user,
       person,
       counts,
-      local_user,
     })
   }
 }
diff --git a/crates/websocket/src/chat_server.rs b/crates/websocket/src/chat_server.rs
index f1c936d6..e08aa94a 100644
--- a/crates/websocket/src/chat_server.rs
+++ b/crates/websocket/src/chat_server.rs
@@ -1,4 +1,11 @@
-use crate::{messages::*, serialize_websocket_message, LemmyContext, UserOperation};
+use crate::{
+  messages::*,
+  serialize_websocket_message,
+  LemmyContext,
+  OperationType,
+  UserOperation,
+  UserOperationCrud,
+};
 use actix::prelude::*;
 use anyhow::Context as acontext;
 use background_jobs::QueueHandle;
@@ -33,6 +40,13 @@ type MessageHandlerType = fn(
   data: &str,
 ) -> Pin<Box<dyn Future<Output = Result<String, LemmyError>> + '_>>;
 
+type MessageHandlerCrudType = fn(
+  context: LemmyContext,
+  id: ConnectionId,
+  op: UserOperationCrud,
+  data: &str,
+) -> Pin<Box<dyn Future<Output = Result<String, LemmyError>> + '_>>;
+
 /// `ChatServer` manages chat rooms and responsible for coordinating chat
 /// session.
 pub struct ChatServer {
@@ -63,6 +77,7 @@ pub struct ChatServer {
   pub(super) captchas: Vec<CaptchaItem>,
 
   message_handler: MessageHandlerType,
+  message_handler_crud: MessageHandlerCrudType,
 
   /// An HTTP Client
   client: Client,
@@ -83,6 +98,7 @@ impl ChatServer {
     pool: Pool<ConnectionManager<PgConnection>>,
     rate_limiter: RateLimit,
     message_handler: MessageHandlerType,
+    message_handler_crud: MessageHandlerCrudType,
     client: Client,
     activity_queue: QueueHandle,
   ) -> ChatServer {
@@ -97,6 +113,7 @@ impl ChatServer {
       rate_limiter,
       captchas: Vec::new(),
       message_handler,
+      message_handler_crud,
       client,
       activity_queue,
     }
@@ -207,14 +224,15 @@ impl ChatServer {
     Ok(())
   }
 
-  fn send_post_room_message<Response>(
+  fn send_post_room_message<OP, Response>(
     &self,
-    op: &UserOperation,
+    op: &OP,
     response: &Response,
     post_id: PostId,
     websocket_id: Option<ConnectionId>,
   ) -> Result<(), LemmyError>
   where
+    OP: OperationType + ToString,
     Response: Serialize,
   {
     let res_str = &serialize_websocket_message(op, response)?;
@@ -231,14 +249,15 @@ impl ChatServer {
     Ok(())
   }
 
-  pub fn send_community_room_message<Response>(
+  pub fn send_community_room_message<OP, Response>(
     &self,
-    op: &UserOperation,
+    op: &OP,
     response: &Response,
     community_id: CommunityId,
     websocket_id: Option<ConnectionId>,
   ) -> Result<(), LemmyError>
   where
+    OP: OperationType + ToString,
     Response: Serialize,
   {
     let res_str = &serialize_websocket_message(op, response)?;
@@ -255,14 +274,15 @@ impl ChatServer {
     Ok(())
   }
 
-  pub fn send_mod_room_message<Response>(
+  pub fn send_mod_room_message<OP, Response>(
     &self,
-    op: &UserOperation,
+    op: &OP,
     response: &Response,
     community_id: CommunityId,
     websocket_id: Option<ConnectionId>,
   ) -> Result<(), LemmyError>
   where
+    OP: OperationType + ToString,
     Response: Serialize,
   {
     let res_str = &serialize_websocket_message(op, response)?;
@@ -279,13 +299,14 @@ impl ChatServer {
     Ok(())
   }
 
-  pub fn send_all_message<Response>(
+  pub fn send_all_message<OP, Response>(
     &self,
-    op: &UserOperation,
+    op: &OP,
     response: &Response,
     websocket_id: Option<ConnectionId>,
   ) -> Result<(), LemmyError>
   where
+    OP: OperationType + ToString,
     Response: Serialize,
   {
     let res_str = &serialize_websocket_message(op, response)?;
@@ -300,14 +321,15 @@ impl ChatServer {
     Ok(())
   }
 
-  pub fn send_user_room_message<Response>(
+  pub fn send_user_room_message<OP, Response>(
     &self,
-    op: &UserOperation,
+    op: &OP,
     response: &Response,
     recipient_id: LocalUserId,
     websocket_id: Option<ConnectionId>,
   ) -> Result<(), LemmyError>
   where
+    OP: OperationType + ToString,
     Response: Serialize,
   {
     let res_str = &serialize_websocket_message(op, response)?;
@@ -324,12 +346,15 @@ impl ChatServer {
     Ok(())
   }
 
-  pub fn send_comment(
+  pub fn send_comment<OP>(
     &self,
-    user_operation: &UserOperation,
+    user_operation: &OP,
     comment: &CommentResponse,
     websocket_id: Option<ConnectionId>,
-  ) -> Result<(), LemmyError> {
+  ) -> Result<(), LemmyError>
+  where
+    OP: OperationType + ToString,
+  {
     let mut comment_reply_sent = comment.clone();
 
     // Strip out my specific user info
@@ -373,12 +398,15 @@ impl ChatServer {
     Ok(())
   }
 
-  pub fn send_post(
+  pub fn send_post<OP>(
     &self,
-    user_operation: &UserOperation,
+    user_operation: &OP,
     post_res: &PostResponse,
     websocket_id: Option<ConnectionId>,
-  ) -> Result<(), LemmyError> {
+  ) -> Result<(), LemmyError>
+  where
+    OP: OperationType + ToString,
+  {
     let community_id = post_res.post_view.community.id;
 
     // Don't send my data with it
@@ -424,6 +452,7 @@ impl ChatServer {
       client: self.client.to_owned(),
       activity_queue: self.activity_queue.to_owned(),
     };
+    let message_handler_crud = self.message_handler_crud;
     let message_handler = self.message_handler;
     async move {
       let json: Value = serde_json::from_str(&msg.msg)?;
@@ -432,13 +461,18 @@ impl ChatServer {
         message: "Unknown op type".to_string(),
       })?;
 
-      let user_operation = UserOperation::from_str(&op)?;
-      let fut = (message_handler)(context, msg.id, user_operation.clone(), data);
-      match user_operation {
-        UserOperation::Register => rate_limiter.register().wrap(ip, fut).await,
-        UserOperation::CreatePost => rate_limiter.post().wrap(ip, fut).await,
-        UserOperation::CreateCommunity => rate_limiter.register().wrap(ip, fut).await,
-        _ => rate_limiter.message().wrap(ip, fut).await,
+      if let Ok(user_operation_crud) = UserOperationCrud::from_str(&op) {
+        let fut = (message_handler_crud)(context, msg.id, user_operation_crud.clone(), data);
+        match user_operation_crud {
+          UserOperationCrud::Register => rate_limiter.register().wrap(ip, fut).await,
+          UserOperationCrud::CreatePost => rate_limiter.post().wrap(ip, fut).await,
+          UserOperationCrud::CreateCommunity => rate_limiter.register().wrap(ip, fut).await,
+          _ => rate_limiter.message().wrap(ip, fut).await,
+        }
+      } else {
+        let user_operation = UserOperation::from_str(&op)?;
+        let fut = (message_handler)(context, msg.id, user_operation.clone(), data);
+        rate_limiter.message().wrap(ip, fut).await
       }
     }
   }
diff --git a/crates/websocket/src/handlers.rs b/crates/websocket/src/handlers.rs
index 30cd9639..055129cd 100644
--- a/crates/websocket/src/handlers.rs
+++ b/crates/websocket/src/handlers.rs
@@ -1,6 +1,7 @@
 use crate::{
   chat_server::{ChatServer, SessionInfo},
   messages::*,
+  OperationType,
 };
 use actix::{Actor, Context, Handler, ResponseFuture};
 use lemmy_db_schema::naive_now;
@@ -82,26 +83,28 @@ impl Handler<StandardMessage> for ChatServer {
   }
 }
 
-impl<Response> Handler<SendAllMessage<Response>> for ChatServer
+impl<OP, Response> Handler<SendAllMessage<OP, Response>> for ChatServer
 where
+  OP: OperationType + ToString,
   Response: Serialize,
 {
   type Result = ();
 
-  fn handle(&mut self, msg: SendAllMessage<Response>, _: &mut Context<Self>) {
+  fn handle(&mut self, msg: SendAllMessage<OP, Response>, _: &mut Context<Self>) {
     self
       .send_all_message(&msg.op, &msg.response, msg.websocket_id)
       .ok();
   }
 }
 
-impl<Response> Handler<SendUserRoomMessage<Response>> for ChatServer
+impl<OP, Response> Handler<SendUserRoomMessage<OP, Response>> for ChatServer
 where
+  OP: OperationType + ToString,
   Response: Serialize,
 {
   type Result = ();
 
-  fn handle(&mut self, msg: SendUserRoomMessage<Response>, _: &mut Context<Self>) {
+  fn handle(&mut self, msg: SendUserRoomMessage<OP, Response>, _: &mut Context<Self>) {
     self
       .send_user_room_message(
         &msg.op,
@@ -113,13 +116,14 @@ where
   }
 }
 
-impl<Response> Handler<SendCommunityRoomMessage<Response>> for ChatServer
+impl<OP, Response> Handler<SendCommunityRoomMessage<OP, Response>> for ChatServer
 where
+  OP: OperationType + ToString,
   Response: Serialize,
 {
   type Result = ();
 
-  fn handle(&mut self, msg: SendCommunityRoomMessage<Response>, _: &mut Context<Self>) {
+  fn handle(&mut self, msg: SendCommunityRoomMessage<OP, Response>, _: &mut Context<Self>) {
     self
       .send_community_room_message(&msg.op, &msg.response, msg.community_id, msg.websocket_id)
       .ok();
@@ -139,18 +143,24 @@ where
   }
 }
 
-impl Handler<SendPost> for ChatServer {
+impl<OP> Handler<SendPost<OP>> for ChatServer
+where
+  OP: OperationType + ToString,
+{
   type Result = ();
 
-  fn handle(&mut self, msg: SendPost, _: &mut Context<Self>) {
+  fn handle(&mut self, msg: SendPost<OP>, _: &mut Context<Self>) {
     self.send_post(&msg.op, &msg.post, msg.websocket_id).ok();
   }
 }
 
-impl Handler<SendComment> for ChatServer {
+impl<OP> Handler<SendComment<OP>> for ChatServer
+where
+  OP: OperationType + ToString,
+{
   type Result = ();
 
-  fn handle(&mut self, msg: SendComment, _: &mut Context<Self>) {
+  fn handle(&mut self, msg: SendComment<OP>, _: &mut Context<Self>) {
     self
       .send_comment(&msg.op, &msg.comment, msg.websocket_id)
       .ok();
diff --git a/crates/websocket/src/lib.rs b/crates/websocket/src/lib.rs
index 56b18a17..0b2a9fb4 100644
--- a/crates/websocket/src/lib.rs
+++ b/crates/websocket/src/lib.rs
@@ -66,12 +66,13 @@ struct WebsocketResponse<T> {
   data: T,
 }
 
-pub fn serialize_websocket_message<Response>(
-  op: &UserOperation,
+pub fn serialize_websocket_message<OP, Response>(
+  op: &OP,
   data: &Response,
 ) -> Result<String, LemmyError>
 where
   Response: Serialize,
+  OP: ToString,
 {
   let response = WebsocketResponse {
     op: op.to_string(),
@@ -83,28 +84,14 @@ where
 #[derive(EnumString, ToString, Debug, Clone)]
 pub enum UserOperation {
   Login,
-  Register,
   GetCaptcha,
-  CreateCommunity,
-  CreatePost,
-  ListCommunities,
-  GetPost,
-  GetCommunity,
-  CreateComment,
-  EditComment,
-  DeleteComment,
-  RemoveComment,
   MarkCommentAsRead,
   SaveComment,
   CreateCommentLike,
   CreateCommentReport,
   ResolveCommentReport,
   ListCommentReports,
-  GetPosts,
   CreatePostLike,
-  EditPost,
-  DeletePost,
-  RemovePost,
   LockPost,
   StickyPost,
   SavePost,
@@ -112,21 +99,14 @@ pub enum UserOperation {
   ResolvePostReport,
   ListPostReports,
   GetReportCount,
-  EditCommunity,
-  DeleteCommunity,
-  RemoveCommunity,
   FollowCommunity,
   GetFollowedCommunities,
-  GetPersonDetails,
   GetReplies,
   GetPersonMentions,
   MarkPersonMentionAsRead,
   GetModlog,
   BanFromCommunity,
   AddModToCommunity,
-  CreateSite,
-  EditSite,
-  GetSite,
   AddAdmin,
   BanPerson,
   Search,
@@ -134,19 +114,56 @@ pub enum UserOperation {
   SaveUserSettings,
   TransferCommunity,
   TransferSite,
-  DeleteAccount,
   PasswordReset,
   PasswordChange,
-  CreatePrivateMessage,
-  EditPrivateMessage,
-  DeletePrivateMessage,
   MarkPrivateMessageAsRead,
-  GetPrivateMessages,
   UserJoin,
-  GetComments,
   GetSiteConfig,
   SaveSiteConfig,
   PostJoin,
   CommunityJoin,
   ModJoin,
 }
+
+#[derive(EnumString, ToString, Debug, Clone)]
+pub enum UserOperationCrud {
+  // Site
+  CreateSite,
+  GetSite,
+  EditSite,
+  // Community
+  CreateCommunity,
+  ListCommunities,
+  GetCommunity,
+  EditCommunity,
+  DeleteCommunity,
+  RemoveCommunity,
+  // Post
+  CreatePost,
+  GetPost,
+  GetPosts,
+  EditPost,
+  DeletePost,
+  RemovePost,
+  // Comment
+  CreateComment,
+  GetComments,
+  EditComment,
+  DeleteComment,
+  RemoveComment,
+  // User
+  Register,
+  GetPersonDetails,
+  DeleteAccount,
+  // Private Message
+  CreatePrivateMessage,
+  GetPrivateMessages,
+  EditPrivateMessage,
+  DeletePrivateMessage,
+}
+
+pub trait OperationType {}
+
+impl OperationType for UserOperationCrud {}
+
+impl OperationType for UserOperation {}
diff --git a/crates/websocket/src/messages.rs b/crates/websocket/src/messages.rs
index 31ca755f..1ef3da5b 100644
--- a/crates/websocket/src/messages.rs
+++ b/crates/websocket/src/messages.rs
@@ -40,16 +40,16 @@ pub struct StandardMessage {
 
 #[derive(Message)]
 #[rtype(result = "()")]
-pub struct SendAllMessage<Response> {
-  pub op: UserOperation,
+pub struct SendAllMessage<OP: ToString, Response> {
+  pub op: OP,
   pub response: Response,
   pub websocket_id: Option<ConnectionId>,
 }
 
 #[derive(Message)]
 #[rtype(result = "()")]
-pub struct SendUserRoomMessage<Response> {
-  pub op: UserOperation,
+pub struct SendUserRoomMessage<OP: ToString, Response> {
+  pub op: OP,
   pub response: Response,
   pub local_recipient_id: LocalUserId,
   pub websocket_id: Option<ConnectionId>,
@@ -57,8 +57,8 @@ pub struct SendUserRoomMessage<Response> {
 
 #[derive(Message)]
 #[rtype(result = "()")]
-pub struct SendCommunityRoomMessage<Response> {
-  pub op: UserOperation,
+pub struct SendCommunityRoomMessage<OP: ToString, Response> {
+  pub op: OP,
   pub response: Response,
   pub community_id: CommunityId,
   pub websocket_id: Option<ConnectionId>,
@@ -75,16 +75,16 @@ pub struct SendModRoomMessage<Response> {
 
 #[derive(Message)]
 #[rtype(result = "()")]
-pub struct SendPost {
-  pub op: UserOperation,
+pub struct SendPost<OP: ToString> {
+  pub op: OP,
   pub post: PostResponse,
   pub websocket_id: Option<ConnectionId>,
 }
 
 #[derive(Message)]
 #[rtype(result = "()")]
-pub struct SendComment {
-  pub op: UserOperation,
+pub struct SendComment<OP: ToString> {
+  pub op: OP,
   pub comment: CommentResponse,
   pub websocket_id: Option<ConnectionId>,
 }
diff --git a/crates/websocket/src/routes.rs b/crates/websocket/src/routes.rs
index 8a487813..a240646f 100644
--- a/crates/websocket/src/routes.rs
+++ b/crates/websocket/src/routes.rs
@@ -15,8 +15,12 @@ const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5);
 /// How long before lack of client response causes a timeout
 const CLIENT_TIMEOUT: Duration = Duration::from_secs(10);
 
+pub fn config(cfg: &mut web::ServiceConfig) {
+  cfg.service(web::resource("/ws").to(chat_route));
+}
+
 /// Entry point for our route
-pub async fn chat_route(
+async fn chat_route(
   req: HttpRequest,
   stream: web::Payload,
   context: web::Data<LemmyContext>,
diff --git a/docker/federation/docker-compose.yml b/docker/federation/docker-compose.yml
index 03355aa4..4925c1bd 100644
--- a/docker/federation/docker-compose.yml
+++ b/docker/federation/docker-compose.yml
@@ -29,7 +29,7 @@ services:
       - ./volumes/pictrs_alpha:/mnt
 
   lemmy-alpha-ui:
-    image: dessalines/lemmy-ui:0.9.9
+    image: dessalines/lemmy-ui:0.10.0-rc.12
     environment:
       - LEMMY_INTERNAL_HOST=lemmy-alpha:8541
       - LEMMY_EXTERNAL_HOST=localhost:8541
diff --git a/crates/api/src/routes.rs b/src/api_routes.rs
similarity index 60%
rename from crates/api/src/routes.rs
rename to src/api_routes.rs
index cdc9e736..34501519 100644
--- a/crates/api/src/routes.rs
+++ b/src/api_routes.rs
@@ -1,20 +1,22 @@
-use crate::Perform;
 use actix_web::{error::ErrorBadRequest, *};
+use lemmy_api::Perform;
 use lemmy_api_common::{comment::*, community::*, person::*, post::*, site::*, websocket::*};
+use lemmy_api_crud::PerformCrud;
 use lemmy_utils::rate_limit::RateLimit;
-use lemmy_websocket::{routes::chat_route, LemmyContext};
+use lemmy_websocket::LemmyContext;
 use serde::Deserialize;
 
 pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
   cfg.service(
     web::scope("/api/v2")
-      // Websockets
-      .service(web::resource("/ws").to(chat_route))
       // Site
       .service(
         web::scope("/site")
           .wrap(rate_limit.message())
+          .route("", web::get().to(route_get_crud::<GetSite>))
           // Admin Actions
+          .route("", web::post().to(route_post_crud::<CreateSite>))
+          .route("", web::put().to(route_post_crud::<EditSite>))
           .route("/transfer", web::post().to(route_post::<TransferSite>))
           .route("/config", web::get().to(route_get::<GetSiteConfig>))
           .route("/config", web::put().to(route_post::<SaveSiteConfig>)),
@@ -30,10 +32,28 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
           .route(web::get().to(route_get::<Search>)),
       )
       // Community
+      .service(
+        web::resource("/community")
+          .guard(guard::Post())
+          .wrap(rate_limit.register())
+          .route(web::post().to(route_post_crud::<CreateCommunity>)),
+      )
       .service(
         web::scope("/community")
           .wrap(rate_limit.message())
+          .route("", web::get().to(route_get_crud::<GetCommunity>))
+          .route("", web::put().to(route_post_crud::<EditCommunity>))
+          .route("/list", web::get().to(route_get_crud::<ListCommunities>))
           .route("/follow", web::post().to(route_post::<FollowCommunity>))
+          .route(
+            "/delete",
+            web::post().to(route_post_crud::<DeleteCommunity>),
+          )
+          // Mod Actions
+          .route(
+            "/remove",
+            web::post().to(route_post_crud::<RemoveCommunity>),
+          )
           .route("/transfer", web::post().to(route_post::<TransferCommunity>))
           .route("/ban_user", web::post().to(route_post::<BanFromCommunity>))
           .route("/mod", web::post().to(route_post::<AddModToCommunity>))
@@ -41,11 +61,23 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
           .route("/mod/join", web::post().to(route_post::<ModJoin>)),
       )
       // Post
+      .service(
+        // Handle POST to /post separately to add the post() rate limitter
+        web::resource("/post")
+          .guard(guard::Post())
+          .wrap(rate_limit.post())
+          .route(web::post().to(route_post_crud::<CreatePost>)),
+      )
       .service(
         web::scope("/post")
           .wrap(rate_limit.message())
+          .route("", web::get().to(route_get_crud::<GetPost>))
+          .route("", web::put().to(route_post_crud::<EditPost>))
+          .route("/delete", web::post().to(route_post_crud::<DeletePost>))
+          .route("/remove", web::post().to(route_post_crud::<RemovePost>))
           .route("/lock", web::post().to(route_post::<LockPost>))
           .route("/sticky", web::post().to(route_post::<StickyPost>))
+          .route("/list", web::get().to(route_get_crud::<GetPosts>))
           .route("/like", web::post().to(route_post::<CreatePostLike>))
           .route("/save", web::put().to(route_post::<SavePost>))
           .route("/join", web::post().to(route_post::<PostJoin>))
@@ -60,12 +92,17 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
       .service(
         web::scope("/comment")
           .wrap(rate_limit.message())
+          .route("", web::post().to(route_post_crud::<CreateComment>))
+          .route("", web::put().to(route_post_crud::<EditComment>))
+          .route("/delete", web::post().to(route_post_crud::<DeleteComment>))
+          .route("/remove", web::post().to(route_post_crud::<RemoveComment>))
           .route(
             "/mark_as_read",
             web::post().to(route_post::<MarkCommentAsRead>),
           )
           .route("/like", web::post().to(route_post::<CreateCommentLike>))
           .route("/save", web::put().to(route_post::<SaveComment>))
+          .route("/list", web::get().to(route_get_crud::<GetComments>))
           .route("/report", web::post().to(route_post::<CreateCommentReport>))
           .route(
             "/report/resolve",
@@ -80,15 +117,32 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
       .service(
         web::scope("/private_message")
           .wrap(rate_limit.message())
+          .route("/list", web::get().to(route_get_crud::<GetPrivateMessages>))
+          .route("", web::post().to(route_post_crud::<CreatePrivateMessage>))
+          .route("", web::put().to(route_post_crud::<EditPrivateMessage>))
+          .route(
+            "/delete",
+            web::post().to(route_post_crud::<DeletePrivateMessage>),
+          )
           .route(
             "/mark_as_read",
             web::post().to(route_post::<MarkPrivateMessageAsRead>),
           ),
       )
+      // User
+      .service(
+        // Account action, I don't like that it's in /user maybe /accounts
+        // Handle /user/register separately to add the register() rate limitter
+        web::resource("/user/register")
+          .guard(guard::Post())
+          .wrap(rate_limit.register())
+          .route(web::post().to(route_post_crud::<Register>)),
+      )
       // User actions
       .service(
         web::scope("/user")
           .wrap(rate_limit.message())
+          .route("", web::get().to(route_get_crud::<GetPersonDetails>))
           .route("/mention", web::get().to(route_get::<GetPersonMentions>))
           .route(
             "/mention/mark_as_read",
@@ -105,6 +159,10 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
           // Account actions. I don't like that they're in /user maybe /accounts
           .route("/login", web::post().to(route_post::<Login>))
           .route("/get_captcha", web::get().to(route_get::<GetCaptcha>))
+          .route(
+            "/delete_account",
+            web::post().to(route_post_crud::<DeleteAccount>),
+          )
           .route(
             "/password_reset",
             web::post().to(route_post::<PasswordReset>),
@@ -168,3 +226,39 @@ where
 {
   perform::<Data>(data.0, context).await
 }
+
+async fn perform_crud<Request>(
+  data: Request,
+  context: web::Data<LemmyContext>,
+) -> Result<HttpResponse, Error>
+where
+  Request: PerformCrud,
+  Request: Send + 'static,
+{
+  let res = data
+    .perform(&context, None)
+    .await
+    .map(|json| HttpResponse::Ok().json(json))
+    .map_err(ErrorBadRequest)?;
+  Ok(res)
+}
+
+async fn route_get_crud<'a, Data>(
+  data: web::Query<Data>,
+  context: web::Data<LemmyContext>,
+) -> Result<HttpResponse, Error>
+where
+  Data: Deserialize<'a> + Send + 'static + PerformCrud,
+{
+  perform_crud::<Data>(data.0, context).await
+}
+
+async fn route_post_crud<'a, Data>(
+  data: web::Json<Data>,
+  context: web::Data<LemmyContext>,
+) -> Result<HttpResponse, Error>
+where
+  Data: Deserialize<'a> + Send + 'static + PerformCrud,
+{
+  perform_crud::<Data>(data.0, context).await
+}
diff --git a/src/lib.rs b/src/lib.rs
index 16cddf63..ea79cbb0 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,3 +1,4 @@
 #![recursion_limit = "512"]
+pub mod api_routes;
 pub mod code_migrations;
 pub mod scheduled_tasks;
diff --git a/src/main.rs b/src/main.rs
index 3cdab3ec..7f241733 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -9,10 +9,11 @@ use diesel::{
 };
 use lemmy_api::match_websocket_operation;
 use lemmy_api_common::blocking;
+use lemmy_api_crud::match_websocket_operation_crud;
 use lemmy_apub::activity_queue::create_activity_queue;
 use lemmy_db_queries::get_database_url_from_env;
 use lemmy_routes::{feeds, images, nodeinfo, webfinger};
-use lemmy_server::{code_migrations::run_advanced_migrations, scheduled_tasks};
+use lemmy_server::{api_routes, code_migrations::run_advanced_migrations, scheduled_tasks};
 use lemmy_utils::{
   rate_limit::{rate_limiter::RateLimiter, RateLimit},
   settings::structs::Settings,
@@ -70,6 +71,7 @@ async fn main() -> Result<(), LemmyError> {
     pool.clone(),
     rate_limiter.clone(),
     |c, i, o, d| Box::pin(match_websocket_operation(c, i, o, d)),
+    |c, i, o, d| Box::pin(match_websocket_operation_crud(c, i, o, d)),
     Client::default(),
     activity_queue.clone(),
   )
@@ -88,8 +90,8 @@ async fn main() -> Result<(), LemmyError> {
       .wrap(middleware::Logger::default())
       .data(context)
       // The routes
-      .configure(|cfg| lemmy_api_crud::routes::config(cfg, &rate_limiter))
-      .configure(|cfg| lemmy_api::routes::config(cfg, &rate_limiter))
+      .configure(|cfg| api_routes::config(cfg, &rate_limiter))
+      .configure(lemmy_websocket::routes::config)
       .configure(lemmy_apub::routes::config)
       .configure(feeds::config)
       .configure(|cfg| images::config(cfg, &rate_limiter))
-- 
2.44.1