]> Untitled Git - lemmy.git/commitdiff
User / community blocking. Fixes #426 (#1604)
authorDessalines <dessalines@users.noreply.github.com>
Thu, 19 Aug 2021 20:54:15 +0000 (16:54 -0400)
committerGitHub <noreply@github.com>
Thu, 19 Aug 2021 20:54:15 +0000 (20:54 +0000)
* A first pass at user / community blocking. #426

* Adding unit tests for person and community block.

* Moving migration

* Fixing creator_blocked for comment queries, added tests.

* Don't let a person block themselves

* Fix post creator_blocked

* Adding creator_blocked to PersonMentionView

* Moving blocked and follows to MyUserInfo

* Rename to local_user_view

* Add moderates to MyUserInfo

* Adding BlockCommunityResponse

* Fixing name, and check_person_block

* Fixing tests.

* Using type in Blockable trait.

* Changing recipient to target, adding unfollow to block action.

41 files changed:
api_tests/package.json
api_tests/src/comment.spec.ts
api_tests/src/follow.spec.ts
api_tests/src/shared.ts
api_tests/src/user.spec.ts
api_tests/yarn.lock
crates/api/src/comment.rs
crates/api/src/community.rs
crates/api/src/lib.rs
crates/api/src/local_user.rs
crates/api/src/post.rs
crates/api/src/site.rs
crates/api_common/src/community.rs
crates/api_common/src/lib.rs
crates/api_common/src/person.rs
crates/api_common/src/site.rs
crates/api_crud/src/comment/create.rs
crates/api_crud/src/comment/update.rs
crates/api_crud/src/private_message/create.rs
crates/api_crud/src/site/read.rs
crates/api_crud/src/user/read.rs
crates/db_queries/src/lib.rs
crates/db_queries/src/source/community_block.rs [new file with mode: 0644]
crates/db_queries/src/source/mod.rs
crates/db_queries/src/source/person_block.rs [new file with mode: 0644]
crates/db_schema/src/lib.rs
crates/db_schema/src/schema.rs
crates/db_schema/src/source/community_block.rs [new file with mode: 0644]
crates/db_schema/src/source/mod.rs
crates/db_schema/src/source/person_block.rs [new file with mode: 0644]
crates/db_views/src/comment_view.rs
crates/db_views/src/post_view.rs
crates/db_views_actor/src/community_block_view.rs [new file with mode: 0644]
crates/db_views_actor/src/community_view.rs
crates/db_views_actor/src/lib.rs
crates/db_views_actor/src/person_block_view.rs [new file with mode: 0644]
crates/db_views_actor/src/person_mention_view.rs
crates/websocket/src/lib.rs
migrations/2021-08-04-223559_create_user_community_block/down.sql [new file with mode: 0644]
migrations/2021-08-04-223559_create_user_community_block/up.sql [new file with mode: 0644]
src/api_routes.rs

index b34737daa800fa43b3c42a2544c030b58afe3de5..e8e4cc09184212806884d1da585feb8f713222d2 100644 (file)
@@ -16,7 +16,7 @@
     "eslint": "^7.30.0",
     "eslint-plugin-jane": "^9.0.3",
     "jest": "^27.0.6",
-    "lemmy-js-client": "0.11.0-rc.3",
+    "lemmy-js-client": "0.11.4-rc.9",
     "node-fetch": "^2.6.1",
     "prettier": "^2.3.2",
     "ts-jest": "^27.0.3",
index 68dbcf7c1995b165f09de16ae26e482081c41642..309cfd133e324ab2a1d7cb462285ad75408a7fc1 100644 (file)
@@ -332,9 +332,9 @@ test('A and G subscribe to B (center) A posts, G mentions B, it gets announced t
 
 test('Fetch in_reply_tos: A is unsubbed from B, B makes a post, and some embedded comments, A subs to B, B updates the lowest level comment, A fetches both the post and all the inreplyto comments for that post.', async () => {
   // Unfollow all remote communities
-  let followed = await unfollowRemotes(alpha);
+  let site = await unfollowRemotes(alpha);
   expect(
-    followed.communities.filter(c => c.community.local == false).length
+    site.my_user.follows.filter(c => c.community.local == false).length
   ).toBe(0);
 
   // B creates a post, and two comments, should be invisible to A
index 0749439c08e99b89cd755a5570957c6de66d738f..369a772a1c951079d7fba2108038761e6947d199 100644 (file)
@@ -4,8 +4,8 @@ import {
   setupLogins,
   searchForBetaCommunity,
   followCommunity,
-  checkFollowedCommunities,
   unfollowRemotes,
+  getSite,
 } from './shared';
 
 beforeAll(async () => {
@@ -29,8 +29,8 @@ test('Follow federated community', async () => {
   expect(follow.community_view.community.name).toBe('main');
 
   // Check it from local
-  let followCheck = await checkFollowedCommunities(alpha);
-  let remoteCommunityId = followCheck.communities.find(
+  let site = await getSite(alpha);
+  let remoteCommunityId = site.my_user.follows.find(
     c => c.community.local == false
   ).community.id;
   expect(remoteCommunityId).toBeDefined();
@@ -40,6 +40,6 @@ test('Follow federated community', async () => {
   expect(unfollow.community_view.community.local).toBe(false);
 
   // Make sure you are unsubbed locally
-  let unfollowCheck = await checkFollowedCommunities(alpha);
-  expect(unfollowCheck.communities.length).toBeGreaterThanOrEqual(1);
+  let siteUnfollowCheck = await getSite(alpha);
+  expect(siteUnfollowCheck.my_user.follows.length).toBeGreaterThanOrEqual(1);
 });
index df1624b8f33d6782f457e4e4916fc49d4a5d2f51..2de66c05b88fee9d3c9516e86f42c0a75b35d01b 100644 (file)
@@ -12,7 +12,6 @@ import {
   SearchResponse,
   FollowCommunity,
   CommunityResponse,
-  GetFollowedCommunitiesResponse,
   GetPostResponse,
   Register,
   Comment,
@@ -30,7 +29,6 @@ import {
   CreatePostLike,
   EditPrivateMessage,
   DeletePrivateMessage,
-  GetFollowedCommunities,
   GetPrivateMessages,
   GetSite,
   GetPost,
@@ -336,15 +334,6 @@ export async function followCommunity(
   return api.client.followCommunity(form);
 }
 
-export async function checkFollowedCommunities(
-  api: API
-): Promise<GetFollowedCommunitiesResponse> {
-  let form: GetFollowedCommunities = {
-    auth: api.auth,
-  };
-  return api.client.getFollowedCommunities(form);
-}
-
 export async function likePost(
   api: API,
   score: number,
@@ -543,8 +532,7 @@ export async function registerUser(
 }
 
 export async function saveUserSettingsBio(
-  api: API,
-  auth: string
+  api: API
 ): Promise<LoginResponse> {
   let form: SaveUserSettings = {
     show_nsfw: true,
@@ -555,7 +543,7 @@ export async function saveUserSettingsBio(
     show_avatars: true,
     send_notifications_to_email: false,
     bio: 'a changed bio',
-    auth,
+    auth: api.auth,
   };
   return saveUserSettings(api, form);
 }
@@ -568,11 +556,10 @@ export async function saveUserSettings(
 }
 
 export async function getSite(
-  api: API,
-  auth: string
+  api: API
 ): Promise<GetSiteResponse> {
   let form: GetSite = {
-    auth,
+    auth: api.auth,
   };
   return api.client.getSite(form);
 }
@@ -590,17 +577,17 @@ export async function listPrivateMessages(
 
 export async function unfollowRemotes(
   api: API
-): Promise<GetFollowedCommunitiesResponse> {
+): Promise<GetSiteResponse> {
   // Unfollow all remote communities
-  let followed = await checkFollowedCommunities(api);
-  let remoteFollowed = followed.communities.filter(
+  let site = await getSite(api);
+  let remoteFollowed = site.my_user.follows.filter(
     c => c.community.local == false
   );
   for (let cu of remoteFollowed) {
     await followCommunity(api, false, cu.community.id);
   }
-  let followed2 = await checkFollowedCommunities(api);
-  return followed2;
+  let siteRes = await getSite(api);
+  return siteRes;
 }
 
 export async function followBeta(api: API): Promise<CommunityResponse> {
index a10876cf5e435ad346b2dd45390a14b931189244..acbe8fe15b94db0cc8947c7edb8b8c59dcaa0163 100644 (file)
@@ -14,12 +14,11 @@ import {
   ListingType,
 } from 'lemmy-js-client';
 
-let auth: string;
 let apShortname: string;
 
 function assertUserFederation(userOne: PersonViewSafe, userTwo: PersonViewSafe) {
   expect(userOne.person.name).toBe(userTwo.person.name);
-  expect(userOne.person.preferred_username).toBe(userTwo.person.preferred_username);
+  expect(userOne.person.display_name).toBe(userTwo.person.display_name);
   expect(userOne.person.bio).toBe(userTwo.person.bio);
   expect(userOne.person.actor_id).toBe(userTwo.person.actor_id);
   expect(userOne.person.avatar).toBe(userTwo.person.avatar);
@@ -30,11 +29,11 @@ function assertUserFederation(userOne: PersonViewSafe, userTwo: PersonViewSafe)
 test('Create user', async () => {
   let userRes = await registerUser(alpha);
   expect(userRes.jwt).toBeDefined();
-  auth = userRes.jwt;
-
-  let site = await getSite(alpha, auth);
+  alpha.auth = userRes.jwt;
+  
+  let site = await getSite(alpha);
   expect(site.my_user).toBeDefined();
-  apShortname = `@${site.my_user.person.name}@lemmy-alpha:8541`;
+  apShortname = `@${site.my_user.local_user_view.person.name}@lemmy-alpha:8541`;
 });
 
 test('Set some user settings, check that they are federated', async () => {
@@ -49,11 +48,11 @@ test('Set some user settings, check that they are federated', async () => {
     lang: '',
     avatar,
     banner,
-    preferred_username: 'user321',
+    display_name: 'user321',
     show_avatars: false,
     send_notifications_to_email: false,
     bio,
-    auth,
+    auth: alpha.auth,
   };
   await saveUserSettings(alpha, form);
 
index a5be0f695af71d8c2e8d77de064cef3bbd872764..370873b1e4ea5786469a16b9c8f0886d2926a24e 100644 (file)
@@ -3076,10 +3076,10 @@ language-tags@^1.0.5:
   dependencies:
     language-subtag-registry "~0.3.2"
 
-lemmy-js-client@0.11.0-rc.3:
-  version "0.11.0-rc.3"
-  resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.11.0-rc.3.tgz#dd4727ca4d16fe9593368725aacfd9e7a8d52548"
-  integrity sha512-16mgl+TS1+0UHiY+ZKPuqHfbrn93h8K8tJ+kKJ1pjT2WhG23o0B8dLahG1jDQPG+dkXpR6PZxYudMYjuO8WvjQ==
+lemmy-js-client@0.11.4-rc.9:
+  version "0.11.4-rc.9"
+  resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.11.4-rc.9.tgz#f7b3c73691e4c1600daf3840d22d9cfbddc5f363"
+  integrity sha512-zP8JxWzQU+yuyE8cMG0GzR8aR3lJ++G5zzbynsXwDevzAZXhOm0ObNNtJiA3Q5msStFVKVYa3GwZxBv4XiYshw==
 
 leven@^3.1.0:
   version "3.1.0"
index 06510251f61f2308de9b077064f5462f9943726b..00e136adba5d534ad625fa15b949deb4c026dc07 100644 (file)
@@ -4,6 +4,7 @@ use lemmy_api_common::{
   blocking,
   check_community_ban,
   check_downvotes_enabled,
+  check_person_block,
   comment::*,
   get_local_user_view_from_jwt,
 };
@@ -151,6 +152,13 @@ impl Perform for CreateCommentLike {
     )
     .await?;
 
+    check_person_block(
+      local_user_view.person.id,
+      orig_comment.get_recipient_id(),
+      context.pool(),
+    )
+    .await?;
+
     // Add parent user to recipients
     let recipient_id = orig_comment.get_recipient_id();
     if let Ok(local_recipient) = blocking(context.pool(), move |conn| {
index 18a4dc4613381a00100c9538cb6216ff89278335..5aefd1443a071f48c520f0419615a973f29a8c1f 100644 (file)
@@ -18,6 +18,7 @@ use lemmy_apub::{
 use lemmy_db_queries::{
   source::{comment::Comment_, community::CommunityModerator_, post::Post_},
   Bannable,
+  Blockable,
   Crud,
   Followable,
   Joinable,
@@ -25,6 +26,7 @@ use lemmy_db_queries::{
 use lemmy_db_schema::source::{
   comment::Comment,
   community::*,
+  community_block::{CommunityBlock, CommunityBlockForm},
   moderator::*,
   person::Person,
   post::Post,
@@ -107,6 +109,66 @@ impl Perform for FollowCommunity {
   }
 }
 
+#[async_trait::async_trait(?Send)]
+impl Perform for BlockCommunity {
+  type Response = BlockCommunityResponse;
+
+  async fn perform(
+    &self,
+    context: &Data<LemmyContext>,
+    _websocket_id: Option<ConnectionId>,
+  ) -> Result<BlockCommunityResponse, LemmyError> {
+    let data: &BlockCommunity = self;
+    let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
+
+    let community_id = data.community_id;
+    let person_id = local_user_view.person.id;
+    let community_block_form = CommunityBlockForm {
+      person_id,
+      community_id,
+    };
+
+    if data.block {
+      let block = move |conn: &'_ _| CommunityBlock::block(conn, &community_block_form);
+      if blocking(context.pool(), block).await?.is_err() {
+        return Err(ApiError::err("community_block_already_exists").into());
+      }
+
+      // 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, &community, context).await?;
+    } else {
+      let unblock = move |conn: &'_ _| CommunityBlock::unblock(conn, &community_block_form);
+      if blocking(context.pool(), unblock).await?.is_err() {
+        return Err(ApiError::err("community_block_already_exists").into());
+      }
+    }
+
+    let community_view = blocking(context.pool(), move |conn| {
+      CommunityView::read(conn, community_id, Some(person_id))
+    })
+    .await??;
+
+    Ok(BlockCommunityResponse {
+      blocked: data.block,
+      community_view,
+    })
+  }
+}
+
 #[async_trait::async_trait(?Send)]
 impl Perform for BanFromCommunity {
   type Response = BanFromCommunityResponse;
index 1c194b3112f73c813202338a3802896d500bbcb8..bf3a813b36d1c87d90a475d042a010ab3356ca59 100644 (file)
@@ -39,6 +39,9 @@ pub async fn match_websocket_operation(
     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::BlockPerson => {
+      do_websocket_operation::<BlockPerson>(context, id, op, data).await
+    }
     UserOperation::GetPersonMentions => {
       do_websocket_operation::<GetPersonMentions>(context, id, op, data).await
     }
@@ -95,8 +98,8 @@ pub async fn match_websocket_operation(
     UserOperation::FollowCommunity => {
       do_websocket_operation::<FollowCommunity>(context, id, op, data).await
     }
-    UserOperation::GetFollowedCommunities => {
-      do_websocket_operation::<GetFollowedCommunities>(context, id, op, data).await
+    UserOperation::BlockCommunity => {
+      do_websocket_operation::<BlockCommunity>(context, id, op, data).await
     }
     UserOperation::BanFromCommunity => {
       do_websocket_operation::<BanFromCommunity>(context, id, op, data).await
index 8eee3c1c4599b8cd52c2bb13ba82d891c4a99f9d..510ef5adb1af5df722797fbf79eb3fde5ba4014d 100644 (file)
@@ -7,7 +7,6 @@ use chrono::Duration;
 use lemmy_api_common::{
   blocking,
   collect_moderated_communities,
-  community::{GetFollowedCommunities, GetFollowedCommunitiesResponse},
   get_local_user_view_from_jwt,
   is_admin,
   password_length_check,
@@ -27,6 +26,7 @@ use lemmy_db_queries::{
     post::Post_,
     private_message::PrivateMessage_,
   },
+  Blockable,
   Crud,
   SortType,
 };
@@ -39,6 +39,7 @@ use lemmy_db_schema::{
     moderator::*,
     password_reset_request::*,
     person::*,
+    person_block::{PersonBlock, PersonBlockForm},
     person_mention::*,
     post::Post,
     private_message::PrivateMessage,
@@ -52,7 +53,6 @@ use lemmy_db_views::{
   post_report_view::PostReportView,
 };
 use lemmy_db_views_actor::{
-  community_follower_view::CommunityFollowerView,
   community_moderator_view::CommunityModeratorView,
   person_mention_view::{PersonMentionQueryBuilder, PersonMentionView},
   person_view::PersonViewSafe,
@@ -471,6 +471,59 @@ impl Perform for BanPerson {
   }
 }
 
+#[async_trait::async_trait(?Send)]
+impl Perform for BlockPerson {
+  type Response = BlockPersonResponse;
+
+  async fn perform(
+    &self,
+    context: &Data<LemmyContext>,
+    _websocket_id: Option<ConnectionId>,
+  ) -> Result<BlockPersonResponse, LemmyError> {
+    let data: &BlockPerson = self;
+    let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
+
+    let target_id = data.person_id;
+    let person_id = local_user_view.person.id;
+
+    // Don't let a person block themselves
+    if target_id == person_id {
+      return Err(ApiError::err("cant_block_yourself").into());
+    }
+
+    let person_block_form = PersonBlockForm {
+      person_id,
+      target_id,
+    };
+
+    if data.block {
+      let block = move |conn: &'_ _| PersonBlock::block(conn, &person_block_form);
+      if blocking(context.pool(), block).await?.is_err() {
+        return Err(ApiError::err("person_block_already_exists").into());
+      }
+    } else {
+      let unblock = move |conn: &'_ _| PersonBlock::unblock(conn, &person_block_form);
+      if blocking(context.pool(), unblock).await?.is_err() {
+        return Err(ApiError::err("person_block_already_exists").into());
+      }
+    }
+
+    // TODO does any federated stuff need to be done here?
+
+    let person_view = blocking(context.pool(), move |conn| {
+      PersonViewSafe::read(conn, target_id)
+    })
+    .await??;
+
+    let res = BlockPersonResponse {
+      person_view,
+      blocked: data.block,
+    };
+
+    Ok(res)
+  }
+}
+
 #[async_trait::async_trait(?Send)]
 impl Perform for GetReplies {
   type Response = GetRepliesResponse;
@@ -778,27 +831,3 @@ impl Perform for GetReportCount {
     Ok(res)
   }
 }
-
-#[async_trait::async_trait(?Send)]
-impl Perform for GetFollowedCommunities {
-  type Response = GetFollowedCommunitiesResponse;
-
-  async fn perform(
-    &self,
-    context: &Data<LemmyContext>,
-    _websocket_id: Option<ConnectionId>,
-  ) -> Result<GetFollowedCommunitiesResponse, LemmyError> {
-    let data: &GetFollowedCommunities = self;
-    let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
-
-    let person_id = local_user_view.person.id;
-    let communities = blocking(context.pool(), move |conn| {
-      CommunityFollowerView::for_person(conn, person_id)
-    })
-    .await?
-    .map_err(|_| ApiError::err("system_err_login"))?;
-
-    // Return the jwt
-    Ok(GetFollowedCommunitiesResponse { communities })
-  }
-}
index 1cb6c587e2b31e70c6314253c912a8513dfe8366..f0f025e26ea745dc65f0e10e0b3a549cddcc53b4 100644 (file)
@@ -4,6 +4,7 @@ use lemmy_api_common::{
   blocking,
   check_community_ban,
   check_downvotes_enabled,
+  check_person_block,
   get_local_user_view_from_jwt,
   is_mod_or_admin,
   mark_post_as_read,
@@ -48,6 +49,8 @@ impl Perform for CreatePostLike {
 
     check_community_ban(local_user_view.person.id, post.community_id, context.pool()).await?;
 
+    check_person_block(local_user_view.person.id, post.creator_id, context.pool()).await?;
+
     let like_form = PostLikeForm {
       post_id: data.post_id,
       person_id: local_user_view.person.id,
index 7b6783a70ec47b340c9a43d922c9470f1c7bbaa3..221b9870fae159d7bceb15487a79114265b1995b 100644 (file)
@@ -4,7 +4,6 @@ use anyhow::Context;
 use lemmy_api_common::{
   blocking,
   build_federated_instances,
-  get_local_user_settings_view_from_jwt,
   get_local_user_view_from_jwt,
   get_local_user_view_from_jwt_opt,
   is_admin,
@@ -422,15 +421,13 @@ impl Perform for TransferSite {
     let banned = blocking(context.pool(), move |conn| PersonViewSafe::banned(conn)).await??;
     let federated_instances = build_federated_instances(context.pool()).await?;
 
-    let my_user = Some(get_local_user_settings_view_from_jwt(&data.auth, context.pool()).await?);
-
     Ok(GetSiteResponse {
       site_view: Some(site_view),
       admins,
       banned,
       online: 0,
       version: version::VERSION.to_string(),
-      my_user,
+      my_user: None,
       federated_instances,
     })
   }
index ab4e9d2a1ce5954ab76ff7fb8e38cf9753ffd9b2..68ba2ff68a7309cff63e3b763f79c5d270a8b3e0 100644 (file)
@@ -1,6 +1,5 @@
 use lemmy_db_schema::{CommunityId, PersonId};
 use lemmy_db_views_actor::{
-  community_follower_view::CommunityFollowerView,
   community_moderator_view::CommunityModeratorView,
   community_view::CommunityView,
   person_view::PersonViewSafe,
@@ -117,13 +116,16 @@ pub struct FollowCommunity {
 }
 
 #[derive(Deserialize)]
-pub struct GetFollowedCommunities {
+pub struct BlockCommunity {
+  pub community_id: CommunityId,
+  pub block: bool,
   pub auth: String,
 }
 
-#[derive(Serialize)]
-pub struct GetFollowedCommunitiesResponse {
-  pub communities: Vec<CommunityFollowerView>,
+#[derive(Serialize, Clone)]
+pub struct BlockCommunityResponse {
+  pub community_view: CommunityView,
+  pub blocked: bool,
 }
 
 #[derive(Deserialize)]
index 1f62a8f3e10148aed8117198206025958faaab4e..e7d5181bcdbfc16fdc0e8cedbb449b91ec8b80fc 100644 (file)
@@ -10,6 +10,7 @@ use diesel::PgConnection;
 use lemmy_db_queries::{
   source::{
     community::{CommunityModerator_, Community_},
+    person_block::PersonBlock_,
     site::Site_,
   },
   Crud,
@@ -21,6 +22,7 @@ use lemmy_db_schema::{
     comment::Comment,
     community::{Community, CommunityModerator},
     person::Person,
+    person_block::PersonBlock,
     person_mention::{PersonMention, PersonMentionForm},
     post::{Post, PostRead, PostReadForm},
     site::Site,
@@ -353,6 +355,19 @@ pub async fn check_community_ban(
   }
 }
 
+pub async fn check_person_block(
+  my_id: PersonId,
+  potential_blocker_id: PersonId,
+  pool: &DbPool,
+) -> Result<(), LemmyError> {
+  let is_blocked = move |conn: &'_ _| PersonBlock::read(conn, potential_blocker_id, my_id).is_ok();
+  if blocking(pool, is_blocked).await? {
+    Err(ApiError::err("person_block").into())
+  } else {
+    Ok(())
+  }
+}
+
 pub async fn check_downvotes_enabled(score: i16, pool: &DbPool) -> Result<(), LemmyError> {
   if score == -1 {
     let site = blocking(pool, move |conn| Site::read_simple(conn)).await??;
index c6745c69dbf46ac057329a0cc13f8a469c0ea441..a63e55d7efa0e45fc007c5474ddf91fd1dd0b165 100644 (file)
@@ -4,7 +4,6 @@ use lemmy_db_views::{
   private_message_view::PrivateMessageView,
 };
 use lemmy_db_views_actor::{
-  community_follower_view::CommunityFollowerView,
   community_moderator_view::CommunityModeratorView,
   person_mention_view::PersonMentionView,
   person_view::PersonViewSafe,
@@ -96,10 +95,9 @@ pub struct GetPersonDetails {
 #[derive(Serialize)]
 pub struct GetPersonDetailsResponse {
   pub person_view: PersonViewSafe,
-  pub follows: Vec<CommunityFollowerView>,
-  pub moderates: Vec<CommunityModeratorView>,
   pub comments: Vec<CommentView>,
   pub posts: Vec<PostView>,
+  pub moderates: Vec<CommunityModeratorView>,
 }
 
 #[derive(Serialize)]
@@ -145,6 +143,19 @@ pub struct BanPersonResponse {
   pub banned: bool,
 }
 
+#[derive(Deserialize)]
+pub struct BlockPerson {
+  pub person_id: PersonId,
+  pub block: bool,
+  pub auth: String,
+}
+
+#[derive(Serialize, Clone)]
+pub struct BlockPersonResponse {
+  pub person_view: PersonViewSafe,
+  pub blocked: bool,
+}
+
 #[derive(Deserialize)]
 pub struct GetReplies {
   pub sort: Option<String>,
index 796549860ab74444fd7042c1f9bb8aa3f8a69c93..af63854d3965f540fba9de6a5eec3a996e456927 100644 (file)
@@ -5,7 +5,14 @@ use lemmy_db_views::{
   post_view::PostView,
   site_view::SiteView,
 };
-use lemmy_db_views_actor::{community_view::CommunityView, person_view::PersonViewSafe};
+use lemmy_db_views_actor::{
+  community_block_view::CommunityBlockView,
+  community_follower_view::CommunityFollowerView,
+  community_moderator_view::CommunityModeratorView,
+  community_view::CommunityView,
+  person_block_view::PersonBlockView,
+  person_view::PersonViewSafe,
+};
 use lemmy_db_views_moderator::{
   mod_add_community_view::ModAddCommunityView,
   mod_add_view::ModAddView,
@@ -110,10 +117,19 @@ pub struct GetSiteResponse {
   pub banned: Vec<PersonViewSafe>,
   pub online: usize,
   pub version: String,
-  pub my_user: Option<LocalUserSettingsView>,
+  pub my_user: Option<MyUserInfo>,
   pub federated_instances: Option<FederatedInstances>, // Federation may be disabled
 }
 
+#[derive(Serialize)]
+pub struct MyUserInfo {
+  pub local_user_view: LocalUserSettingsView,
+  pub follows: Vec<CommunityFollowerView>,
+  pub moderates: Vec<CommunityModeratorView>,
+  pub community_blocks: Vec<CommunityBlockView>,
+  pub person_blocks: Vec<PersonBlockView>,
+}
+
 #[derive(Deserialize)]
 pub struct TransferSite {
   pub person_id: PersonId,
index a479969a734a565ca193e8514c804bfc32af0c2c..c25966c7d0536072a32aa58b34e6218e8f15055c 100644 (file)
@@ -3,6 +3,7 @@ use actix_web::web::Data;
 use lemmy_api_common::{
   blocking,
   check_community_ban,
+  check_person_block,
   comment::*,
   get_local_user_view_from_jwt,
   get_post,
@@ -49,6 +50,8 @@ impl PerformCrud for CreateComment {
 
     check_community_ban(local_user_view.person.id, community_id, context.pool()).await?;
 
+    check_person_block(local_user_view.person.id, post.creator_id, context.pool()).await?;
+
     // Check if post is locked, no new comments
     if post.locked {
       return Err(ApiError::err("locked").into());
@@ -60,6 +63,10 @@ impl PerformCrud for CreateComment {
       let parent = blocking(context.pool(), move |conn| Comment::read(conn, parent_id))
         .await?
         .map_err(|_| ApiError::err("couldnt_create_comment"))?;
+
+      check_person_block(local_user_view.person.id, parent.creator_id, context.pool()).await?;
+
+      // Strange issue where sometimes the post ID is incorrect
       if parent.post_id != post_id {
         return Err(ApiError::err("couldnt_create_comment").into());
       }
index ee2e7e28e591fa3c71db4e3fe7251235703962d5..8a5e8f0ed93d0cbb8758a6439c7f74e642024b79 100644 (file)
@@ -40,6 +40,7 @@ impl PerformCrud for EditComment {
     })
     .await??;
 
+    // TODO is this necessary? It should really only need to check on create
     check_community_ban(
       local_user_view.person.id,
       orig_comment.community.id,
index aa0bc8f4c40051fa010dd660cde70667dd15ad59..18ef3835d35c868d96907cd64cb68f29a916579b 100644 (file)
@@ -2,6 +2,7 @@ use crate::PerformCrud;
 use actix_web::web::Data;
 use lemmy_api_common::{
   blocking,
+  check_person_block,
   get_local_user_view_from_jwt,
   person::{CreatePrivateMessage, PrivateMessageResponse},
   send_email_to_user,
@@ -34,6 +35,8 @@ impl PerformCrud for CreatePrivateMessage {
 
     let content_slurs_removed = remove_slurs(&data.content.to_owned());
 
+    check_person_block(local_user_view.person.id, data.recipient_id, context.pool()).await?;
+
     let private_message_form = PrivateMessageForm {
       content: content_slurs_removed.to_owned(),
       creator_id: local_user_view.person.id,
index 8b2df7117426d1bf3b6b55931b4cb8382df4da02..cfbaf07a5a9135a01467ab4fe719d59796f1fe6a 100644 (file)
@@ -8,8 +8,14 @@ use lemmy_api_common::{
   site::*,
 };
 use lemmy_db_views::site_view::SiteView;
-use lemmy_db_views_actor::person_view::PersonViewSafe;
-use lemmy_utils::{settings::structs::Settings, version, ConnectionId, LemmyError};
+use lemmy_db_views_actor::{
+  community_block_view::CommunityBlockView,
+  community_follower_view::CommunityFollowerView,
+  community_moderator_view::CommunityModeratorView,
+  person_block_view::PersonBlockView,
+  person_view::PersonViewSafe,
+};
+use lemmy_utils::{settings::structs::Settings, version, ApiError, ConnectionId, LemmyError};
 use lemmy_websocket::{messages::GetUsersOnline, LemmyContext};
 use log::info;
 
@@ -83,7 +89,48 @@ impl PerformCrud for GetSite {
       .await
       .unwrap_or(1);
 
-    let my_user = get_local_user_settings_view_from_jwt_opt(&data.auth, context.pool()).await?;
+    // Build the local user
+    let my_user = if let Some(local_user_view) =
+      get_local_user_settings_view_from_jwt_opt(&data.auth, context.pool()).await?
+    {
+      let person_id = local_user_view.person.id;
+      let follows = blocking(context.pool(), move |conn| {
+        CommunityFollowerView::for_person(conn, person_id)
+      })
+      .await?
+      .map_err(|_| ApiError::err("system_err_login"))?;
+
+      let person_id = local_user_view.person.id;
+      let community_blocks = blocking(context.pool(), move |conn| {
+        CommunityBlockView::for_person(conn, person_id)
+      })
+      .await?
+      .map_err(|_| ApiError::err("system_err_login"))?;
+
+      let person_id = local_user_view.person.id;
+      let person_blocks = blocking(context.pool(), move |conn| {
+        PersonBlockView::for_person(conn, person_id)
+      })
+      .await?
+      .map_err(|_| ApiError::err("system_err_login"))?;
+
+      let moderates = blocking(context.pool(), move |conn| {
+        CommunityModeratorView::for_person(conn, person_id)
+      })
+      .await?
+      .map_err(|_| ApiError::err("system_err_login"))?;
+
+      Some(MyUserInfo {
+        local_user_view,
+        follows,
+        moderates,
+        community_blocks,
+        person_blocks,
+      })
+    } else {
+      None
+    };
+
     let federated_instances = build_federated_instances(context.pool()).await?;
 
     Ok(GetSiteResponse {
index ff8db0a7e01a48d0a2f2ccb16c735f46f1831fed..4cfd90ab5ae7d12ecdc9f3cfd0ea82260efd9941 100644 (file)
@@ -6,7 +6,6 @@ use lemmy_db_queries::{from_opt_str_to_opt_enum, ApubObject, SortType};
 use lemmy_db_schema::source::person::*;
 use lemmy_db_views::{comment_view::CommentQueryBuilder, post_view::PostQueryBuilder};
 use lemmy_db_views_actor::{
-  community_follower_view::CommunityFollowerView,
   community_moderator_view::CommunityModeratorView,
   person_view::PersonViewSafe,
 };
@@ -103,15 +102,6 @@ impl PerformCrud for GetPersonDetails {
     })
     .await??;
 
-    let mut follows = vec![];
-    if let Some(pid) = person_id {
-      if pid == person_details_id {
-        follows = blocking(context.pool(), move |conn| {
-          CommunityFollowerView::for_person(conn, person_details_id)
-        })
-        .await??;
-      }
-    };
     let moderates = blocking(context.pool(), move |conn| {
       CommunityModeratorView::for_person(conn, person_details_id)
     })
@@ -120,7 +110,6 @@ impl PerformCrud for GetPersonDetails {
     // Return the jwt
     Ok(GetPersonDetailsResponse {
       person_view,
-      follows,
       moderates,
       comments,
       posts,
index fb5b8cd5d0365abcd8f2c901f5253bd228851c4c..e62124b1827b49b3cb2099f605a67bbaba8f9189 100644 (file)
@@ -108,6 +108,16 @@ pub trait Saveable {
     Self: Sized;
 }
 
+pub trait Blockable {
+  type Form;
+  fn block(conn: &PgConnection, form: &Self::Form) -> Result<Self, Error>
+  where
+    Self: Sized;
+  fn unblock(conn: &PgConnection, form: &Self::Form) -> Result<usize, Error>
+  where
+    Self: Sized;
+}
+
 pub trait Readable {
   type Form;
   fn mark_as_read(conn: &PgConnection, form: &Self::Form) -> Result<Self, Error>
diff --git a/crates/db_queries/src/source/community_block.rs b/crates/db_queries/src/source/community_block.rs
new file mode 100644 (file)
index 0000000..346e7f3
--- /dev/null
@@ -0,0 +1,25 @@
+use crate::Blockable;
+use diesel::{dsl::*, result::Error, *};
+use lemmy_db_schema::source::community_block::{CommunityBlock, CommunityBlockForm};
+
+impl Blockable for CommunityBlock {
+  type Form = CommunityBlockForm;
+  fn block(conn: &PgConnection, community_block_form: &Self::Form) -> Result<Self, Error> {
+    use lemmy_db_schema::schema::community_block::dsl::*;
+    insert_into(community_block)
+      .values(community_block_form)
+      .on_conflict((person_id, community_id))
+      .do_update()
+      .set(community_block_form)
+      .get_result::<Self>(conn)
+  }
+  fn unblock(conn: &PgConnection, community_block_form: &Self::Form) -> Result<usize, Error> {
+    use lemmy_db_schema::schema::community_block::dsl::*;
+    diesel::delete(
+      community_block
+        .filter(person_id.eq(community_block_form.person_id))
+        .filter(community_id.eq(community_block_form.community_id)),
+    )
+    .execute(conn)
+  }
+}
index db928bd4f6ac908f3a9571399e027132acfc4ea4..8b82d31a7597a50953bc5f211d4e1c7dfc0f724c 100644 (file)
@@ -2,10 +2,12 @@ pub mod activity;
 pub mod comment;
 pub mod comment_report;
 pub mod community;
+pub mod community_block;
 pub mod local_user;
 pub mod moderator;
 pub mod password_reset_request;
 pub mod person;
+pub mod person_block;
 pub mod person_mention;
 pub mod post;
 pub mod post_report;
diff --git a/crates/db_queries/src/source/person_block.rs b/crates/db_queries/src/source/person_block.rs
new file mode 100644 (file)
index 0000000..d450551
--- /dev/null
@@ -0,0 +1,50 @@
+use crate::Blockable;
+use diesel::{dsl::*, result::Error, *};
+use lemmy_db_schema::{
+  source::person_block::{PersonBlock, PersonBlockForm},
+  PersonId,
+};
+
+pub trait PersonBlock_ {
+  fn read(
+    conn: &PgConnection,
+    person_id: PersonId,
+    target_id: PersonId,
+  ) -> Result<PersonBlock, Error>;
+}
+
+impl PersonBlock_ for PersonBlock {
+  fn read(
+    conn: &PgConnection,
+    for_person_id: PersonId,
+    for_recipient_id: PersonId,
+  ) -> Result<Self, Error> {
+    use lemmy_db_schema::schema::person_block::dsl::*;
+    person_block
+      .filter(person_id.eq(for_person_id))
+      .filter(target_id.eq(for_recipient_id))
+      .first::<Self>(conn)
+  }
+}
+
+impl Blockable for PersonBlock {
+  type Form = PersonBlockForm;
+  fn block(conn: &PgConnection, person_block_form: &PersonBlockForm) -> Result<Self, Error> {
+    use lemmy_db_schema::schema::person_block::dsl::*;
+    insert_into(person_block)
+      .values(person_block_form)
+      .on_conflict((person_id, target_id))
+      .do_update()
+      .set(person_block_form)
+      .get_result::<Self>(conn)
+  }
+  fn unblock(conn: &PgConnection, person_block_form: &Self::Form) -> Result<usize, Error> {
+    use lemmy_db_schema::schema::person_block::dsl::*;
+    diesel::delete(
+      person_block
+        .filter(person_id.eq(person_block_form.person_id))
+        .filter(target_id.eq(person_block_form.target_id)),
+    )
+    .execute(conn)
+  }
+}
index 3c8abaf79ff9bcec369fbbad08e19be4defac197..ba49a4cd80fc996c8eb4c50012c8995963a7e346 100644 (file)
@@ -67,6 +67,12 @@ impl fmt::Display for PrivateMessageId {
 #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, DieselNewType)]
 pub struct PersonMentionId(i32);
 
+#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, DieselNewType)]
+pub struct PersonBlockId(i32);
+
+#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, DieselNewType)]
+pub struct CommunityBlockId(i32);
+
 #[repr(transparent)]
 #[derive(Clone, PartialEq, Serialize, Deserialize, Debug, AsExpression, FromSqlRow)]
 #[sql_type = "Text"]
index ad1ce2b35b2195a8908d14632fb626ee95b18532..4c92fffba8b5fbb725ac105d5f23d59c3e426e0b 100644 (file)
@@ -465,6 +465,24 @@ table! {
     }
 }
 
+table! {
+    person_block (id) {
+        id -> Int4,
+        person_id -> Int4,
+        target_id -> Int4,
+        published -> Timestamp,
+    }
+}
+
+table! {
+    community_block (id) {
+        id -> Int4,
+        person_id -> Int4,
+        community_id -> Int4,
+        published -> Timestamp,
+    }
+}
+
 // These are necessary since diesel doesn't have self joins / aliases
 table! {
     comment_alias_1 (id) {
@@ -542,6 +560,9 @@ joinable!(comment -> person_alias_1 (creator_id));
 joinable!(post_report -> person_alias_2 (resolver_id));
 joinable!(comment_report -> person_alias_2 (resolver_id));
 
+joinable!(person_block -> person (person_id));
+joinable!(person_block -> person_alias_1 (target_id));
+
 joinable!(comment -> person (creator_id));
 joinable!(comment -> post (post_id));
 joinable!(comment_aggregates -> comment (comment_id));
@@ -552,6 +573,8 @@ joinable!(comment_report -> comment (comment_id));
 joinable!(comment_saved -> comment (comment_id));
 joinable!(comment_saved -> person (person_id));
 joinable!(community_aggregates -> community (community_id));
+joinable!(community_block -> community (community_id));
+joinable!(community_block -> person (person_id));
 joinable!(community_follower -> community (community_id));
 joinable!(community_follower -> person (person_id));
 joinable!(community_moderator -> community (community_id));
@@ -594,6 +617,7 @@ allow_tables_to_appear_in_same_query!(
   activity,
   comment,
   comment_aggregates,
+  community_block,
   comment_like,
   comment_report,
   comment_saved,
@@ -617,6 +641,7 @@ allow_tables_to_appear_in_same_query!(
   person,
   person_aggregates,
   person_ban,
+  person_block,
   person_mention,
   post,
   post_aggregates,
diff --git a/crates/db_schema/src/source/community_block.rs b/crates/db_schema/src/source/community_block.rs
new file mode 100644 (file)
index 0000000..6aa630b
--- /dev/null
@@ -0,0 +1,25 @@
+use crate::{
+  schema::community_block,
+  source::community::Community,
+  CommunityBlockId,
+  CommunityId,
+  PersonId,
+};
+use serde::Serialize;
+
+#[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug, Serialize)]
+#[table_name = "community_block"]
+#[belongs_to(Community)]
+pub struct CommunityBlock {
+  pub id: CommunityBlockId,
+  pub person_id: PersonId,
+  pub community_id: CommunityId,
+  pub published: chrono::NaiveDateTime,
+}
+
+#[derive(Insertable, AsChangeset)]
+#[table_name = "community_block"]
+pub struct CommunityBlockForm {
+  pub person_id: PersonId,
+  pub community_id: CommunityId,
+}
index db928bd4f6ac908f3a9571399e027132acfc4ea4..8b82d31a7597a50953bc5f211d4e1c7dfc0f724c 100644 (file)
@@ -2,10 +2,12 @@ pub mod activity;
 pub mod comment;
 pub mod comment_report;
 pub mod community;
+pub mod community_block;
 pub mod local_user;
 pub mod moderator;
 pub mod password_reset_request;
 pub mod person;
+pub mod person_block;
 pub mod person_mention;
 pub mod post;
 pub mod post_report;
diff --git a/crates/db_schema/src/source/person_block.rs b/crates/db_schema/src/source/person_block.rs
new file mode 100644 (file)
index 0000000..cedf7de
--- /dev/null
@@ -0,0 +1,18 @@
+use crate::{schema::person_block, PersonBlockId, PersonId};
+use serde::Serialize;
+
+#[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug, Serialize)]
+#[table_name = "person_block"]
+pub struct PersonBlock {
+  pub id: PersonBlockId,
+  pub person_id: PersonId,
+  pub target_id: PersonId,
+  pub published: chrono::NaiveDateTime,
+}
+
+#[derive(Insertable, AsChangeset)]
+#[table_name = "person_block"]
+pub struct PersonBlockForm {
+  pub person_id: PersonId,
+  pub target_id: PersonId,
+}
index 3449141d0492443a65f54849e2ee0ecd6aaee425..2183fa31e53df692562b165d92a39453de65fa20 100644 (file)
@@ -18,16 +18,19 @@ use lemmy_db_schema::{
     comment_like,
     comment_saved,
     community,
+    community_block,
     community_follower,
     community_person_ban,
     person,
     person_alias_1,
+    person_block,
     post,
   },
   source::{
     comment::{Comment, CommentAlias1, CommentSaved},
     community::{Community, CommunityFollower, CommunityPersonBan, CommunitySafe},
     person::{Person, PersonAlias1, PersonSafe, PersonSafeAlias1},
+    person_block::PersonBlock,
     post::Post,
   },
   CommentId,
@@ -49,6 +52,7 @@ pub struct CommentView {
   pub creator_banned_from_community: bool, // Left Join to CommunityPersonBan
   pub subscribed: bool,                    // Left join to CommunityFollower
   pub saved: bool,                         // Left join to CommentSaved
+  pub creator_blocked: bool,               // Left join to PersonBlock
   pub my_vote: Option<i16>,                // Left join to CommentLike
 }
 
@@ -63,6 +67,7 @@ type CommentViewTuple = (
   Option<CommunityPersonBan>,
   Option<CommunityFollower>,
   Option<CommentSaved>,
+  Option<PersonBlock>,
   Option<i16>,
 );
 
@@ -86,6 +91,7 @@ impl CommentView {
       creator_banned_from_community,
       subscribed,
       saved,
+      creator_blocked,
       comment_like,
     ) = comment::table
       .find(comment_id)
@@ -117,6 +123,13 @@ impl CommentView {
             .and(comment_saved::person_id.eq(person_id_join)),
         ),
       )
+      .left_join(
+        person_block::table.on(
+          comment::creator_id
+            .eq(person_block::target_id)
+            .and(person_block::person_id.eq(person_id_join)),
+        ),
+      )
       .left_join(
         comment_like::table.on(
           comment::id
@@ -135,6 +148,7 @@ impl CommentView {
         community_person_ban::all_columns.nullable(),
         community_follower::all_columns.nullable(),
         comment_saved::all_columns.nullable(),
+        person_block::all_columns.nullable(),
         comment_like::score.nullable(),
       ))
       .first::<CommentViewTuple>(conn)?;
@@ -157,6 +171,7 @@ impl CommentView {
       creator_banned_from_community: creator_banned_from_community.is_some(),
       subscribed: subscribed.is_some(),
       saved: saved.is_some(),
+      creator_blocked: creator_blocked.is_some(),
       my_vote,
     })
   }
@@ -315,6 +330,20 @@ impl<'a> CommentQueryBuilder<'a> {
             .and(comment_saved::person_id.eq(person_id_join)),
         ),
       )
+      .left_join(
+        person_block::table.on(
+          comment::creator_id
+            .eq(person_block::target_id)
+            .and(person_block::person_id.eq(person_id_join)),
+        ),
+      )
+      .left_join(
+        community_block::table.on(
+          community::id
+            .eq(community_block::community_id)
+            .and(community_block::person_id.eq(person_id_join)),
+        ),
+      )
       .left_join(
         comment_like::table.on(
           comment::id
@@ -333,6 +362,7 @@ impl<'a> CommentQueryBuilder<'a> {
         community_person_ban::all_columns.nullable(),
         community_follower::all_columns.nullable(),
         comment_saved::all_columns.nullable(),
+        person_block::all_columns.nullable(),
         comment_like::score.nullable(),
       ))
       .into_boxed();
@@ -413,6 +443,12 @@ impl<'a> CommentQueryBuilder<'a> {
         .order_by(comment_aggregates::score.desc()),
     };
 
+    // Don't show blocked communities or persons
+    if self.my_person_id.is_some() {
+      query = query.filter(community_block::person_id.is_null());
+      query = query.filter(person_block::person_id.is_null());
+    }
+
     let (limit, offset) = limit_and_offset(self.page, self.limit);
 
     // Note: deleted and removed comments are done on the front side
@@ -440,7 +476,8 @@ impl ViewToVec for CommentView {
         creator_banned_from_community: a.7.is_some(),
         subscribed: a.8.is_some(),
         saved: a.9.is_some(),
-        my_vote: a.10,
+        creator_blocked: a.10.is_some(),
+        my_vote: a.11,
       })
       .collect::<Vec<Self>>()
   }
@@ -452,10 +489,17 @@ mod tests {
   use lemmy_db_queries::{
     aggregates::comment_aggregates::CommentAggregates,
     establish_unpooled_connection,
+    Blockable,
     Crud,
     Likeable,
   };
-  use lemmy_db_schema::source::{comment::*, community::*, person::*, post::*};
+  use lemmy_db_schema::source::{
+    comment::*,
+    community::*,
+    person::*,
+    person_block::PersonBlockForm,
+    post::*,
+  };
   use serial_test::serial;
 
   #[test]
@@ -470,6 +514,13 @@ mod tests {
 
     let inserted_person = Person::create(&conn, &new_person).unwrap();
 
+    let new_person_2 = PersonForm {
+      name: "sara".into(),
+      ..PersonForm::default()
+    };
+
+    let inserted_person_2 = Person::create(&conn, &new_person_2).unwrap();
+
     let new_community = CommunityForm {
       name: "test community 5".to_string(),
       title: "nada".to_owned(),
@@ -496,6 +547,32 @@ mod tests {
 
     let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
 
+    let comment_form_2 = CommentForm {
+      content: "A test blocked comment".into(),
+      creator_id: inserted_person_2.id,
+      post_id: inserted_post.id,
+      parent_id: Some(inserted_comment.id),
+      ..CommentForm::default()
+    };
+
+    let inserted_comment_2 = Comment::create(&conn, &comment_form_2).unwrap();
+
+    let timmy_blocks_sara_form = PersonBlockForm {
+      person_id: inserted_person.id,
+      target_id: inserted_person_2.id,
+    };
+
+    let inserted_block = PersonBlock::block(&conn, &timmy_blocks_sara_form).unwrap();
+
+    let expected_block = PersonBlock {
+      id: inserted_block.id,
+      person_id: inserted_person.id,
+      target_id: inserted_person_2.id,
+      published: inserted_block.published,
+    };
+
+    assert_eq!(expected_block, inserted_block);
+
     let comment_like_form = CommentLikeForm {
       comment_id: inserted_comment.id,
       post_id: inserted_post.id,
@@ -512,6 +589,7 @@ mod tests {
       my_vote: None,
       subscribed: false,
       saved: false,
+      creator_blocked: false,
       comment: Comment {
         id: inserted_comment.id,
         content: "A test comment 32".into(),
@@ -606,20 +684,32 @@ mod tests {
       .list()
       .unwrap();
 
+    let read_comment_from_blocked_person =
+      CommentView::read(&conn, inserted_comment_2.id, Some(inserted_person.id)).unwrap();
+
     let like_removed = CommentLike::remove(&conn, inserted_person.id, inserted_comment.id).unwrap();
     let num_deleted = Comment::delete(&conn, inserted_comment.id).unwrap();
+    Comment::delete(&conn, inserted_comment_2.id).unwrap();
     Post::delete(&conn, inserted_post.id).unwrap();
     Community::delete(&conn, inserted_community.id).unwrap();
     Person::delete(&conn, inserted_person.id).unwrap();
+    Person::delete(&conn, inserted_person_2.id).unwrap();
+
+    // Make sure its 1, not showing the blocked comment
+    assert_eq!(1, read_comment_views_with_person.len());
 
     assert_eq!(
       expected_comment_view_no_person,
-      read_comment_views_no_person[0]
+      read_comment_views_no_person[1]
     );
     assert_eq!(
       expected_comment_view_with_person,
       read_comment_views_with_person[0]
     );
+
+    // Make sure block set the creator blocked
+    assert_eq!(true, read_comment_from_blocked_person.creator_blocked);
+
     assert_eq!(1, num_deleted);
     assert_eq!(1, like_removed);
   }
index 34847dcde4d20a5cff8ab475faa189d0c3e90d88..ec537ad9471298b62637afcd8b875243bd5183f3 100644 (file)
@@ -13,9 +13,11 @@ use lemmy_db_queries::{
 use lemmy_db_schema::{
   schema::{
     community,
+    community_block,
     community_follower,
     community_person_ban,
     person,
+    person_block,
     post,
     post_aggregates,
     post_like,
@@ -25,6 +27,7 @@ use lemmy_db_schema::{
   source::{
     community::{Community, CommunityFollower, CommunityPersonBan, CommunitySafe},
     person::{Person, PersonSafe},
+    person_block::PersonBlock,
     post::{Post, PostRead, PostSaved},
   },
   CommunityId,
@@ -42,10 +45,11 @@ pub struct PostView {
   pub community: CommunitySafe,
   pub creator_banned_from_community: bool, // Left Join to CommunityPersonBan
   pub counts: PostAggregates,
-  pub subscribed: bool,     // Left join to CommunityFollower
-  pub saved: bool,          // Left join to PostSaved
-  pub read: bool,           // Left join to PostRead
-  pub my_vote: Option<i16>, // Left join to PostLike
+  pub subscribed: bool,      // Left join to CommunityFollower
+  pub saved: bool,           // Left join to PostSaved
+  pub read: bool,            // Left join to PostRead
+  pub creator_blocked: bool, // Left join to PersonBlock
+  pub my_vote: Option<i16>,  // Left join to PostLike
 }
 
 type PostViewTuple = (
@@ -57,6 +61,7 @@ type PostViewTuple = (
   Option<CommunityFollower>,
   Option<PostSaved>,
   Option<PostRead>,
+  Option<PersonBlock>,
   Option<i16>,
 );
 
@@ -78,6 +83,7 @@ impl PostView {
       follower,
       saved,
       read,
+      creator_blocked,
       post_like,
     ) = post::table
       .find(post_id)
@@ -112,6 +118,13 @@ impl PostView {
             .and(post_read::person_id.eq(person_id_join)),
         ),
       )
+      .left_join(
+        person_block::table.on(
+          post::creator_id
+            .eq(person_block::target_id)
+            .and(person_block::person_id.eq(person_id_join)),
+        ),
+      )
       .left_join(
         post_like::table.on(
           post::id
@@ -128,6 +141,7 @@ impl PostView {
         community_follower::all_columns.nullable(),
         post_saved::all_columns.nullable(),
         post_read::all_columns.nullable(),
+        person_block::all_columns.nullable(),
         post_like::score.nullable(),
       ))
       .first::<PostViewTuple>(conn)?;
@@ -149,6 +163,7 @@ impl PostView {
       subscribed: follower.is_some(),
       saved: saved.is_some(),
       read: read.is_some(),
+      creator_blocked: creator_blocked.is_some(),
       my_vote,
     })
   }
@@ -301,6 +316,20 @@ impl<'a> PostQueryBuilder<'a> {
             .and(post_read::person_id.eq(person_id_join)),
         ),
       )
+      .left_join(
+        person_block::table.on(
+          post::creator_id
+            .eq(person_block::target_id)
+            .and(person_block::person_id.eq(person_id_join)),
+        ),
+      )
+      .left_join(
+        community_block::table.on(
+          community::id
+            .eq(community_block::community_id)
+            .and(community_block::person_id.eq(person_id_join)),
+        ),
+      )
       .left_join(
         post_like::table.on(
           post::id
@@ -317,6 +346,7 @@ impl<'a> PostQueryBuilder<'a> {
         community_follower::all_columns.nullable(),
         post_saved::all_columns.nullable(),
         post_read::all_columns.nullable(),
+        person_block::all_columns.nullable(),
         post_like::score.nullable(),
       ))
       .into_boxed();
@@ -377,6 +407,12 @@ impl<'a> PostQueryBuilder<'a> {
       query = query.filter(post_saved::id.is_not_null());
     };
 
+    // Don't show blocked communities or persons
+    if self.my_person_id.is_some() {
+      query = query.filter(community_block::person_id.is_null());
+      query = query.filter(person_block::person_id.is_null());
+    }
+
     query = match self.sort.unwrap_or(SortType::Hot) {
       SortType::Active => query
         .then_order_by(
@@ -440,7 +476,8 @@ impl ViewToVec for PostView {
         subscribed: a.5.is_some(),
         saved: a.6.is_some(),
         read: a.7.is_some(),
-        my_vote: a.8,
+        creator_blocked: a.8.is_some(),
+        my_vote: a.9,
       })
       .collect::<Vec<Self>>()
   }
@@ -452,12 +489,19 @@ mod tests {
   use lemmy_db_queries::{
     aggregates::post_aggregates::PostAggregates,
     establish_unpooled_connection,
+    Blockable,
     Crud,
     Likeable,
     ListingType,
     SortType,
   };
-  use lemmy_db_schema::source::{community::*, person::*, post::*};
+  use lemmy_db_schema::source::{
+    community::*,
+    community_block::{CommunityBlock, CommunityBlockForm},
+    person::*,
+    person_block::{PersonBlock, PersonBlockForm},
+    post::*,
+  };
   use serial_test::serial;
 
   #[test]
@@ -493,6 +537,32 @@ mod tests {
 
     let inserted_community = Community::create(&conn, &new_community).unwrap();
 
+    // Test a person block, make sure the post query doesn't include their post
+    let blocked_person = PersonForm {
+      name: person_name.to_owned(),
+      ..PersonForm::default()
+    };
+
+    let inserted_blocked_person = Person::create(&conn, &blocked_person).unwrap();
+
+    let post_from_blocked_person = PostForm {
+      name: "blocked_person_post".to_string(),
+      creator_id: inserted_blocked_person.id,
+      community_id: inserted_community.id,
+      ..PostForm::default()
+    };
+
+    Post::create(&conn, &post_from_blocked_person).unwrap();
+
+    // block that person
+    let person_block = PersonBlockForm {
+      person_id: inserted_person.id,
+      target_id: inserted_blocked_person.id,
+    };
+
+    PersonBlock::block(&conn, &person_block).unwrap();
+
+    // A sample post
     let new_post = PostForm {
       name: post_name.to_owned(),
       creator_id: inserted_person.id,
@@ -623,7 +693,24 @@ mod tests {
       subscribed: false,
       read: false,
       saved: false,
+      creator_blocked: false,
+    };
+
+    // Test a community block
+    let community_block = CommunityBlockForm {
+      person_id: inserted_person.id,
+      community_id: inserted_community.id,
     };
+    CommunityBlock::block(&conn, &community_block).unwrap();
+
+    let read_post_listings_with_person_after_block = PostQueryBuilder::create(&conn)
+      .listing_type(ListingType::Community)
+      .sort(SortType::New)
+      .show_bot_accounts(false)
+      .community_id(inserted_community.id)
+      .my_person_id(inserted_person.id)
+      .list()
+      .unwrap();
 
     // TODO More needs to be added here
     let mut expected_post_listing_with_user = expected_post_listing_no_person.to_owned();
@@ -631,9 +718,12 @@ mod tests {
 
     let like_removed = PostLike::remove(&conn, inserted_person.id, inserted_post.id).unwrap();
     let num_deleted = Post::delete(&conn, inserted_post.id).unwrap();
+    PersonBlock::unblock(&conn, &person_block).unwrap();
+    CommunityBlock::unblock(&conn, &community_block).unwrap();
     Community::delete(&conn, inserted_community.id).unwrap();
     Person::delete(&conn, inserted_person.id).unwrap();
     Person::delete(&conn, inserted_bot.id).unwrap();
+    Person::delete(&conn, inserted_blocked_person.id).unwrap();
 
     // The with user
     assert_eq!(
@@ -645,7 +735,7 @@ mod tests {
       read_post_listing_with_person
     );
 
-    // Should be only one person, IE the bot post should be missing
+    // Should be only one person, IE the bot post, and blocked should be missing
     assert_eq!(1, read_post_listings_with_person.len());
 
     // Without the user
@@ -655,8 +745,11 @@ mod tests {
     );
     assert_eq!(expected_post_listing_no_person, read_post_listing_no_person);
 
-    // Should be 2 posts, with the bot post
-    assert_eq!(2, read_post_listings_no_person.len());
+    // Should be 2 posts, with the bot post, and the blocked
+    assert_eq!(3, read_post_listings_no_person.len());
+
+    // Should be 0 posts after the community block
+    assert_eq!(0, read_post_listings_with_person_after_block.len());
 
     assert_eq!(expected_post_like, inserted_post_like);
     assert_eq!(1, like_removed);
diff --git a/crates/db_views_actor/src/community_block_view.rs b/crates/db_views_actor/src/community_block_view.rs
new file mode 100644 (file)
index 0000000..5e24bca
--- /dev/null
@@ -0,0 +1,49 @@
+use diesel::{result::Error, *};
+use lemmy_db_queries::{ToSafe, ViewToVec};
+use lemmy_db_schema::{
+  schema::{community, community_block, person},
+  source::{
+    community::{Community, CommunitySafe},
+    person::{Person, PersonSafe},
+  },
+  PersonId,
+};
+use serde::Serialize;
+
+#[derive(Debug, Serialize, Clone)]
+pub struct CommunityBlockView {
+  pub person: PersonSafe,
+  pub community: CommunitySafe,
+}
+
+type CommunityBlockViewTuple = (PersonSafe, CommunitySafe);
+
+impl CommunityBlockView {
+  pub fn for_person(conn: &PgConnection, person_id: PersonId) -> Result<Vec<Self>, Error> {
+    let res = community_block::table
+      .inner_join(person::table)
+      .inner_join(community::table)
+      .select((
+        Person::safe_columns_tuple(),
+        Community::safe_columns_tuple(),
+      ))
+      .filter(community_block::person_id.eq(person_id))
+      .order_by(community_block::published)
+      .load::<CommunityBlockViewTuple>(conn)?;
+
+    Ok(Self::from_tuple_to_vec(res))
+  }
+}
+
+impl ViewToVec for CommunityBlockView {
+  type DbTuple = CommunityBlockViewTuple;
+  fn from_tuple_to_vec(items: Vec<Self::DbTuple>) -> Vec<Self> {
+    items
+      .iter()
+      .map(|a| Self {
+        person: a.0.to_owned(),
+        community: a.1.to_owned(),
+      })
+      .collect::<Vec<Self>>()
+  }
+}
index fc582857aa1d64dda707321c36c1a2c345528fb7..8e64a5f9e588da1edf1436826d93e1212d70bf04 100644 (file)
@@ -12,8 +12,11 @@ use lemmy_db_queries::{
   ViewToVec,
 };
 use lemmy_db_schema::{
-  schema::{community, community_aggregates, community_follower},
-  source::community::{Community, CommunityFollower, CommunitySafe},
+  schema::{community, community_aggregates, community_block, community_follower},
+  source::{
+    community::{Community, CommunityFollower, CommunitySafe},
+    community_block::CommunityBlock,
+  },
   CommunityId,
   PersonId,
 };
@@ -23,6 +26,7 @@ use serde::Serialize;
 pub struct CommunityView {
   pub community: CommunitySafe,
   pub subscribed: bool,
+  pub blocked: bool,
   pub counts: CommunityAggregates,
 }
 
@@ -30,6 +34,7 @@ type CommunityViewTuple = (
   CommunitySafe,
   CommunityAggregates,
   Option<CommunityFollower>,
+  Option<CommunityBlock>,
 );
 
 impl CommunityView {
@@ -41,7 +46,7 @@ impl CommunityView {
     // The left join below will return None in this case
     let person_id_join = my_person_id.unwrap_or(PersonId(-1));
 
-    let (community, counts, follower) = community::table
+    let (community, counts, follower, blocked) = community::table
       .find(community_id)
       .inner_join(community_aggregates::table)
       .left_join(
@@ -51,16 +56,25 @@ impl CommunityView {
             .and(community_follower::person_id.eq(person_id_join)),
         ),
       )
+      .left_join(
+        community_block::table.on(
+          community::id
+            .eq(community_block::community_id)
+            .and(community_block::person_id.eq(person_id_join)),
+        ),
+      )
       .select((
         Community::safe_columns_tuple(),
         community_aggregates::all_columns,
         community_follower::all_columns.nullable(),
+        community_block::all_columns.nullable(),
       ))
       .first::<CommunityViewTuple>(conn)?;
 
     Ok(CommunityView {
       community,
       subscribed: follower.is_some(),
+      blocked: blocked.is_some(),
       counts,
     })
   }
@@ -165,10 +179,18 @@ impl<'a> CommunityQueryBuilder<'a> {
             .and(community_follower::person_id.eq(person_id_join)),
         ),
       )
+      .left_join(
+        community_block::table.on(
+          community::id
+            .eq(community_block::community_id)
+            .and(community_block::person_id.eq(person_id_join)),
+        ),
+      )
       .select((
         Community::safe_columns_tuple(),
         community_aggregates::all_columns,
         community_follower::all_columns.nullable(),
+        community_block::all_columns.nullable(),
       ))
       .into_boxed();
 
@@ -210,6 +232,11 @@ impl<'a> CommunityQueryBuilder<'a> {
       };
     }
 
+    // Don't show blocked communities
+    if self.my_person_id.is_some() {
+      query = query.filter(community_block::person_id.is_null());
+    }
+
     let (limit, offset) = limit_and_offset(self.page, self.limit);
     let res = query
       .limit(limit)
@@ -231,6 +258,7 @@ impl ViewToVec for CommunityView {
         community: a.0.to_owned(),
         counts: a.1.to_owned(),
         subscribed: a.2.is_some(),
+        blocked: a.3.is_some(),
       })
       .collect::<Vec<Self>>()
   }
index 5d5203c56640bcd1471fa03e62d5df5e50ea8730..6411feeedd32ca1f52659f66cfaa3a400c6e665d 100644 (file)
@@ -1,6 +1,8 @@
+pub mod community_block_view;
 pub mod community_follower_view;
 pub mod community_moderator_view;
 pub mod community_person_ban_view;
 pub mod community_view;
+pub mod person_block_view;
 pub mod person_mention_view;
 pub mod person_view;
diff --git a/crates/db_views_actor/src/person_block_view.rs b/crates/db_views_actor/src/person_block_view.rs
new file mode 100644 (file)
index 0000000..5eb1858
--- /dev/null
@@ -0,0 +1,46 @@
+use diesel::{result::Error, *};
+use lemmy_db_queries::{ToSafe, ViewToVec};
+use lemmy_db_schema::{
+  schema::{person, person_alias_1, person_block},
+  source::person::{Person, PersonAlias1, PersonSafe, PersonSafeAlias1},
+  PersonId,
+};
+use serde::Serialize;
+
+#[derive(Debug, Serialize, Clone)]
+pub struct PersonBlockView {
+  pub person: PersonSafe,
+  pub target: PersonSafeAlias1,
+}
+
+type PersonBlockViewTuple = (PersonSafe, PersonSafeAlias1);
+
+impl PersonBlockView {
+  pub fn for_person(conn: &PgConnection, person_id: PersonId) -> Result<Vec<Self>, Error> {
+    let res = person_block::table
+      .inner_join(person::table)
+      .inner_join(person_alias_1::table) // TODO I dont know if this will be smart abt the column
+      .select((
+        Person::safe_columns_tuple(),
+        PersonAlias1::safe_columns_tuple(),
+      ))
+      .filter(person_block::person_id.eq(person_id))
+      .order_by(person_block::published)
+      .load::<PersonBlockViewTuple>(conn)?;
+
+    Ok(Self::from_tuple_to_vec(res))
+  }
+}
+
+impl ViewToVec for PersonBlockView {
+  type DbTuple = PersonBlockViewTuple;
+  fn from_tuple_to_vec(items: Vec<Self::DbTuple>) -> Vec<Self> {
+    items
+      .iter()
+      .map(|a| Self {
+        person: a.0.to_owned(),
+        target: a.1.to_owned(),
+      })
+      .collect::<Vec<Self>>()
+  }
+}
index b391345ffe6a7f4ce90bbd64f76b1c7e5d777102..421c60a401e796ca6f527e06ecaefb4e88e826d6 100644 (file)
@@ -19,6 +19,7 @@ use lemmy_db_schema::{
     community_person_ban,
     person,
     person_alias_1,
+    person_block,
     person_mention,
     post,
   },
@@ -26,6 +27,7 @@ use lemmy_db_schema::{
     comment::{Comment, CommentSaved},
     community::{Community, CommunityFollower, CommunityPersonBan, CommunitySafe},
     person::{Person, PersonAlias1, PersonSafe, PersonSafeAlias1},
+    person_block::PersonBlock,
     person_mention::PersonMention,
     post::Post,
   },
@@ -46,6 +48,7 @@ pub struct PersonMentionView {
   pub creator_banned_from_community: bool, // Left Join to CommunityPersonBan
   pub subscribed: bool,                    // Left join to CommunityFollower
   pub saved: bool,                         // Left join to CommentSaved
+  pub creator_blocked: bool,               // Left join to PersonBlock
   pub my_vote: Option<i16>,                // Left join to CommentLike
 }
 
@@ -60,6 +63,7 @@ type PersonMentionViewTuple = (
   Option<CommunityPersonBan>,
   Option<CommunityFollower>,
   Option<CommentSaved>,
+  Option<PersonBlock>,
   Option<i16>,
 );
 
@@ -83,6 +87,7 @@ impl PersonMentionView {
       creator_banned_from_community,
       subscribed,
       saved,
+      creator_blocked,
       my_vote,
     ) = person_mention::table
       .find(person_mention_id)
@@ -113,6 +118,13 @@ impl PersonMentionView {
             .and(comment_saved::person_id.eq(person_id_join)),
         ),
       )
+      .left_join(
+        person_block::table.on(
+          comment::creator_id
+            .eq(person_block::target_id)
+            .and(person_block::person_id.eq(person_id_join)),
+        ),
+      )
       .left_join(
         comment_like::table.on(
           comment::id
@@ -131,6 +143,7 @@ impl PersonMentionView {
         community_person_ban::all_columns.nullable(),
         community_follower::all_columns.nullable(),
         comment_saved::all_columns.nullable(),
+        person_block::all_columns.nullable(),
         comment_like::score.nullable(),
       ))
       .first::<PersonMentionViewTuple>(conn)?;
@@ -146,6 +159,7 @@ impl PersonMentionView {
       creator_banned_from_community: creator_banned_from_community.is_some(),
       subscribed: subscribed.is_some(),
       saved: saved.is_some(),
+      creator_blocked: creator_blocked.is_some(),
       my_vote,
     })
   }
@@ -238,6 +252,13 @@ impl<'a> PersonMentionQueryBuilder<'a> {
             .and(comment_saved::person_id.eq(person_id_join)),
         ),
       )
+      .left_join(
+        person_block::table.on(
+          comment::creator_id
+            .eq(person_block::target_id)
+            .and(person_block::person_id.eq(person_id_join)),
+        ),
+      )
       .left_join(
         comment_like::table.on(
           comment::id
@@ -256,6 +277,7 @@ impl<'a> PersonMentionQueryBuilder<'a> {
         community_person_ban::all_columns.nullable(),
         community_follower::all_columns.nullable(),
         comment_saved::all_columns.nullable(),
+        person_block::all_columns.nullable(),
         comment_like::score.nullable(),
       ))
       .into_boxed();
@@ -317,7 +339,8 @@ impl ViewToVec for PersonMentionView {
         creator_banned_from_community: a.7.is_some(),
         subscribed: a.8.is_some(),
         saved: a.9.is_some(),
-        my_vote: a.10,
+        creator_blocked: a.10.is_some(),
+        my_vote: a.11,
       })
       .collect::<Vec<Self>>()
   }
index e01514030fec8c1fb3d7abd35a19e48dec9e50c7..fbb2bb512d1e4751a8f1cabed712f9410ac64129 100644 (file)
@@ -101,7 +101,6 @@ pub enum UserOperation {
   ListPostReports,
   GetReportCount,
   FollowCommunity,
-  GetFollowedCommunities,
   GetReplies,
   GetPersonMentions,
   MarkPersonMentionAsRead,
@@ -126,6 +125,8 @@ pub enum UserOperation {
   ModJoin,
   ChangePassword,
   GetSiteMetadata,
+  BlockCommunity,
+  BlockPerson,
 }
 
 #[derive(EnumString, ToString, Debug, Clone)]
diff --git a/migrations/2021-08-04-223559_create_user_community_block/down.sql b/migrations/2021-08-04-223559_create_user_community_block/down.sql
new file mode 100644 (file)
index 0000000..eb5a35e
--- /dev/null
@@ -0,0 +1,2 @@
+drop table person_block;
+drop table community_block;
diff --git a/migrations/2021-08-04-223559_create_user_community_block/up.sql b/migrations/2021-08-04-223559_create_user_community_block/up.sql
new file mode 100644 (file)
index 0000000..cbcadc2
--- /dev/null
@@ -0,0 +1,15 @@
+create table person_block (
+  id serial primary key,
+  person_id int references person on update cascade on delete cascade not null,
+  target_id int references person on update cascade on delete cascade not null,
+  published timestamp not null default now(),
+  unique(person_id, target_id)
+);
+
+create table community_block (
+  id serial primary key,
+  person_id int references person on update cascade on delete cascade not null,
+  community_id int references community on update cascade on delete cascade not null,
+  published timestamp not null default now(),
+  unique(person_id, community_id)
+);
index a3bf39fe1acbbcc7fc12091c953c67b6075825eb..feaf0e235e618bfd061711170048189ad1caa20e 100644 (file)
@@ -47,6 +47,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
           .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("/block", web::post().to(route_post::<BlockCommunity>))
           .route(
             "/delete",
             web::post().to(route_post_crud::<DeleteCommunity>),
@@ -155,13 +156,10 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
             web::post().to(route_post::<MarkPersonMentionAsRead>),
           )
           .route("/replies", web::get().to(route_get::<GetReplies>))
-          .route(
-            "/followed_communities",
-            web::get().to(route_get::<GetFollowedCommunities>),
-          )
           .route("/join", web::post().to(route_post::<UserJoin>))
           // Admin action. I don't like that it's in /user
           .route("/ban", web::post().to(route_post::<BanPerson>))
+          .route("/block", web::post().to(route_post::<BlockPerson>))
           // 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>))