]> Untitled Git - lemmy.git/commitdiff
Hide community v2 (#2055)
authordayinjing <94152841+dayinjing@users.noreply.github.com>
Fri, 18 Feb 2022 02:30:47 +0000 (20:30 -0600)
committerGitHub <noreply@github.com>
Fri, 18 Feb 2022 02:30:47 +0000 (02:30 +0000)
* Initial working of hiding communities and adding a db entry for mod log

* Return mod log for hidden communities

* Clean up hidding communities PR

* use lower case like other migration files

* Formatting fix

* pass in admin id to list, make match logic the same in post_view as community_view. Dont force non null for reason

* Clean PR review stuff

* Change person_id to mod_person_id on hide community table

* Make bools optional, add a space for formating

Co-authored-by: Thor Odinson <odinson@asgard.com>
Co-authored-by: Dessalines <dessalines@users.noreply.github.com>
21 files changed:
crates/api/src/site.rs
crates/api_common/src/community.rs
crates/api_common/src/site.rs
crates/api_crud/src/community/update.rs
crates/apub/src/protocol/objects/group.rs
crates/db_schema/src/impls/community.rs
crates/db_schema/src/impls/moderator.rs
crates/db_schema/src/schema.rs
crates/db_schema/src/source/community.rs
crates/db_schema/src/source/moderator.rs
crates/db_views/src/comment_report_view.rs
crates/db_views/src/comment_view.rs
crates/db_views/src/post_report_view.rs
crates/db_views/src/post_view.rs
crates/db_views_actor/src/community_view.rs
crates/db_views_moderator/src/lib.rs
crates/db_views_moderator/src/mod_hide_community_view.rs [new file with mode: 0644]
migrations/2022-01-04-034553_add_hidden_column/down.sql [new file with mode: 0644]
migrations/2022-01-04-034553_add_hidden_column/up.sql [new file with mode: 0644]
src/api_routes.rs
src/code_migrations.rs

index 92dbdd64df6d7cd7a5fafdb3237d2869a0af8ee4..7baeae78d2536ddffab3ed82d4e983742fd536be 100644 (file)
@@ -50,6 +50,7 @@ use lemmy_db_views_moderator::{
   mod_add_view::ModAddView,
   mod_ban_from_community_view::ModBanFromCommunityView,
   mod_ban_view::ModBanView,
+  mod_hide_community_view::ModHideCommunityView,
   mod_lock_post_view::ModLockPostView,
   mod_remove_comment_view::ModRemoveCommentView,
   mod_remove_community_view::ModRemoveCommunityView,
@@ -117,6 +118,11 @@ impl Perform for GetModlog {
     })
     .await??;
 
+    let hidden_communities = blocking(context.pool(), move |conn| {
+      ModHideCommunityView::list(conn, community_id, mod_person_id, page, limit)
+    })
+    .await??;
+
     // These arrays are only for the full modlog, when a community isn't given
     let (removed_communities, banned, added) = if data.community_id.is_none() {
       blocking(context.pool(), move |conn| {
@@ -143,6 +149,7 @@ impl Perform for GetModlog {
       added_to_community,
       added,
       transferred_to_community,
+      hidden_communities,
     })
   }
 }
index 9686172ac8464dbe5394e4457533d52c3beff126..fbfe3c6607d9c507c3293fda15cd2cd878d99c46 100644 (file)
@@ -93,6 +93,14 @@ pub struct EditCommunity {
   pub auth: Sensitive<String>,
 }
 
+#[derive(Debug, Serialize, Deserialize)]
+pub struct HideCommunity {
+  pub community_id: CommunityId,
+  pub hidden: bool,
+  pub reason: Option<String>,
+  pub auth: Sensitive<String>,
+}
+
 #[derive(Debug, Serialize, Deserialize)]
 pub struct DeleteCommunity {
   pub community_id: CommunityId,
index e3bc52a2d01c5df83a82d3c3fa9c64e38f5f6683..a707dd656ec6817a992ce5b5daf4e822abde822c 100644 (file)
@@ -19,6 +19,7 @@ use lemmy_db_views_moderator::{
   mod_add_view::ModAddView,
   mod_ban_from_community_view::ModBanFromCommunityView,
   mod_ban_view::ModBanView,
+  mod_hide_community_view::ModHideCommunityView,
   mod_lock_post_view::ModLockPostView,
   mod_remove_comment_view::ModRemoveCommentView,
   mod_remove_community_view::ModRemoveCommunityView,
@@ -87,6 +88,7 @@ pub struct GetModlogResponse {
   pub added_to_community: Vec<ModAddCommunityView>,
   pub transferred_to_community: Vec<ModTransferCommunityView>,
   pub added: Vec<ModAddView>,
+  pub hidden_communities: Vec<ModHideCommunityView>,
 }
 
 #[derive(Debug, Serialize, Deserialize)]
index 324b90eba8ea022c2c7e1751fafc4c143298782b..a0b1dd3b4dd6faa712ba8e4aaff43c03f773be7b 100644 (file)
@@ -2,15 +2,19 @@ use crate::PerformCrud;
 use actix_web::web::Data;
 use lemmy_api_common::{
   blocking,
-  community::{CommunityResponse, EditCommunity},
+  community::{CommunityResponse, EditCommunity, HideCommunity},
   get_local_user_view_from_jwt,
+  is_admin,
 };
 use lemmy_apub::protocol::activities::community::update::UpdateCommunity;
 use lemmy_db_schema::{
   diesel_option_overwrite_to_url,
   naive_now,
   newtypes::PersonId,
-  source::community::{Community, CommunityForm},
+  source::{
+    community::{Community, CommunityForm},
+    moderator::{ModHideCommunity, ModHideCommunityForm},
+  },
   traits::Crud,
 };
 use lemmy_db_views_actor::community_moderator_view::CommunityModeratorView;
@@ -62,6 +66,7 @@ impl PerformCrud for EditCommunity {
       icon,
       banner,
       nsfw: data.nsfw,
+      hidden: Some(read_community.hidden),
       updated: Some(naive_now()),
       ..CommunityForm::default()
     };
@@ -85,3 +90,71 @@ impl PerformCrud for EditCommunity {
     send_community_ws_message(data.community_id, op, websocket_id, None, context).await
   }
 }
+
+#[async_trait::async_trait(?Send)]
+impl PerformCrud for HideCommunity {
+  type Response = CommunityResponse;
+
+  #[tracing::instrument(skip(context, websocket_id))]
+  async fn perform(
+    &self,
+    context: &Data<LemmyContext>,
+    websocket_id: Option<ConnectionId>,
+  ) -> Result<CommunityResponse, LemmyError> {
+    let data: &HideCommunity = self;
+
+    // Verify its a admin (only admin can hide or unhide it)
+    let local_user_view =
+      get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
+    is_admin(&local_user_view)?;
+
+    let community_id = data.community_id;
+    let read_community = blocking(context.pool(), move |conn| {
+      Community::read(conn, community_id)
+    })
+    .await??;
+
+    let community_form = CommunityForm {
+      name: read_community.name,
+      title: read_community.title,
+      description: read_community.description.to_owned(),
+      public_key: read_community.public_key,
+      icon: Some(read_community.icon),
+      banner: Some(read_community.banner),
+      nsfw: Some(read_community.nsfw),
+      updated: Some(naive_now()),
+      hidden: Some(data.hidden),
+      ..CommunityForm::default()
+    };
+
+    let mod_hide_community_form = ModHideCommunityForm {
+      community_id: data.community_id,
+      mod_person_id: local_user_view.person.id,
+      reason: data.reason.clone(),
+      hidden: Some(data.hidden),
+    };
+
+    let community_id = data.community_id;
+    let updated_community = blocking(context.pool(), move |conn| {
+      Community::update(conn, community_id, &community_form)
+    })
+    .await?
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_update_community_hidden_status"))?;
+
+    blocking(context.pool(), move |conn| {
+      ModHideCommunity::create(conn, &mod_hide_community_form)
+    })
+    .await??;
+
+    UpdateCommunity::send(
+      updated_community.into(),
+      &local_user_view.person.into(),
+      context,
+    )
+    .await?;
+
+    let op = UserOperationCrud::EditCommunity;
+    send_community_ws_message(data.community_id, op, websocket_id, None, context).await
+  }
+}
index 51e3a00ac137a769875dab8ac29b9296089d190f..5b564ff88a6fd571acac639d29309a9b69a41e85 100644 (file)
@@ -80,6 +80,7 @@ impl Group {
       actor_id: Some(self.id.into()),
       local: Some(false),
       private_key: None,
+      hidden: Some(false),
       public_key: self.public_key.public_key_pem,
       last_refreshed_at: Some(naive_now()),
       icon: Some(self.icon.map(|i| i.url.into())),
index b2b3a6d8188d85c67d36d6ad8590c8d6c8f61245..43b7db3e29621a17a601a12e115c7a3d1d55ad0b 100644 (file)
@@ -43,6 +43,7 @@ mod safe_type {
     local,
     icon,
     banner,
+    hidden,
   );
 
   impl ToSafe for Community {
@@ -62,6 +63,7 @@ mod safe_type {
         local,
         icon,
         banner,
+        hidden,
       )
     }
   }
@@ -372,6 +374,7 @@ mod tests {
       followers_url: inserted_community.followers_url.to_owned(),
       inbox_url: inserted_community.inbox_url.to_owned(),
       shared_inbox_url: None,
+      hidden: false,
     };
 
     let community_follower_form = CommunityFollowerForm {
index 952bab40e767cba01327aad8557514d79a8b446b..696dbe0a44d7a051cb0ccd2d33163a8b0048e6b0 100644 (file)
@@ -168,6 +168,30 @@ impl Crud for ModBan {
   }
 }
 
+impl Crud for ModHideCommunity {
+  type Form = ModHideCommunityForm;
+  type IdType = i32;
+
+  fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
+    use crate::schema::mod_hide_community::dsl::*;
+    mod_hide_community.find(from_id).first::<Self>(conn)
+  }
+
+  fn create(conn: &PgConnection, form: &ModHideCommunityForm) -> Result<Self, Error> {
+    use crate::schema::mod_hide_community::dsl::*;
+    insert_into(mod_hide_community)
+      .values(form)
+      .get_result::<Self>(conn)
+  }
+
+  fn update(conn: &PgConnection, from_id: i32, form: &ModHideCommunityForm) -> Result<Self, Error> {
+    use crate::schema::mod_hide_community::dsl::*;
+    diesel::update(mod_hide_community.find(from_id))
+      .set(form)
+      .get_result::<Self>(conn)
+  }
+}
+
 impl Crud for ModAddCommunity {
   type Form = ModAddCommunityForm;
   type IdType = i32;
index b93cd4e728e91a045abec1065dbd0212f9998ec8..811e8b09a984fe4f9480a47dd477872121f4134c 100644 (file)
@@ -93,6 +93,7 @@ table! {
         followers_url -> Varchar,
         inbox_url -> Varchar,
         shared_inbox_url -> Nullable<Varchar>,
+        hidden -> Bool,
     }
 }
 
@@ -593,6 +594,17 @@ table! {
     }
 }
 
+table! {
+    mod_hide_community (id) {
+        id -> Int4,
+        community_id -> Int4,
+        mod_person_id -> Int4,
+        reason -> Nullable<Text>,
+        hidden -> Nullable<Bool>,
+        when_ -> Timestamp,
+    }
+}
+
 joinable!(comment_alias_1 -> person_alias_1 (creator_id));
 joinable!(comment -> comment_alias_1 (parent_id));
 joinable!(person_mention -> person_alias_1 (recipient_id));
@@ -656,6 +668,8 @@ joinable!(site_aggregates -> site (site_id));
 joinable!(email_verification -> local_user (local_user_id));
 joinable!(registration_application -> local_user (local_user_id));
 joinable!(registration_application -> person (admin_id));
+joinable!(mod_hide_community -> person (mod_person_id));
+joinable!(mod_hide_community -> community (community_id));
 
 allow_tables_to_appear_in_same_query!(
   activity,
@@ -681,6 +695,7 @@ allow_tables_to_appear_in_same_query!(
   mod_remove_community,
   mod_remove_post,
   mod_sticky_post,
+  mod_hide_community,
   password_reset_request,
   person,
   person_aggregates,
index 986015e7d36d727733b2bafbebab55d2b9055c44..35b695db0448b0453a43e11b034d3738eb9447c8 100644 (file)
@@ -26,6 +26,7 @@ pub struct Community {
   pub followers_url: DbUrl,
   pub inbox_url: DbUrl,
   pub shared_inbox_url: Option<DbUrl>,
+  pub hidden: bool,
 }
 
 /// A safe representation of community, without the sensitive info
@@ -45,6 +46,7 @@ pub struct CommunitySafe {
   pub local: bool,
   pub icon: Option<DbUrl>,
   pub banner: Option<DbUrl>,
+  pub hidden: bool,
 }
 
 #[derive(Insertable, AsChangeset, Debug, Default)]
@@ -68,6 +70,7 @@ pub struct CommunityForm {
   pub followers_url: Option<DbUrl>,
   pub inbox_url: Option<DbUrl>,
   pub shared_inbox_url: Option<Option<DbUrl>>,
+  pub hidden: Option<bool>,
 }
 
 #[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
index ee8bc693fe7ab860bedea13448050a5a15e9e882..84121f9a457e64e8aea430a9fa1e55f6dc18ce21 100644 (file)
@@ -5,6 +5,7 @@ use crate::{
     mod_add_community,
     mod_ban,
     mod_ban_from_community,
+    mod_hide_community,
     mod_lock_post,
     mod_remove_comment,
     mod_remove_community,
@@ -149,6 +150,25 @@ pub struct ModBan {
   pub when_: chrono::NaiveDateTime,
 }
 
+#[derive(Insertable, AsChangeset)]
+#[table_name = "mod_hide_community"]
+pub struct ModHideCommunityForm {
+  pub community_id: CommunityId,
+  pub mod_person_id: PersonId,
+  pub hidden: Option<bool>,
+  pub reason: Option<String>,
+}
+#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
+#[table_name = "mod_hide_community"]
+pub struct ModHideCommunity {
+  pub id: i32,
+  pub community_id: CommunityId,
+  pub mod_person_id: PersonId,
+  pub reason: Option<String>,
+  pub hidden: Option<bool>,
+  pub when_: chrono::NaiveDateTime,
+}
+
 #[derive(Insertable, AsChangeset)]
 #[table_name = "mod_ban"]
 pub struct ModBanForm {
index 52089ec57d7289f4c738939b4194a6da5ab45827..68c000271e72dda103f12c7a230207ab164fb119 100644 (file)
@@ -434,6 +434,7 @@ mod tests {
         description: None,
         updated: None,
         banner: None,
+        hidden: false,
         published: inserted_community.published,
       },
       creator: PersonSafe {
index 6dcda899bea19f550b1c6f77cd9bdcb205062c75..4a7f96b6acece8cfe782dd869bc007d695654bf2 100644 (file)
@@ -448,10 +448,25 @@ impl<'a> CommentQueryBuilder<'a> {
     };
 
     if let Some(listing_type) = self.listing_type {
-      query = match listing_type {
-        ListingType::Subscribed => query.filter(community_follower::person_id.is_not_null()), // TODO could be this: and(community_follower::person_id.eq(person_id_join)),
-        ListingType::Local => query.filter(community::local.eq(true)),
-        _ => query,
+      match listing_type {
+        ListingType::Subscribed => {
+          query = query.filter(community_follower::person_id.is_not_null())
+        } // TODO could be this: and(community_follower::person_id.eq(person_id_join)),
+        ListingType::Local => {
+          query = query.filter(community::local.eq(true)).filter(
+            community::hidden
+              .eq(false)
+              .or(community_follower::person_id.eq(person_id_join)),
+          )
+        }
+        ListingType::All => {
+          query = query.filter(
+            community::hidden
+              .eq(false)
+              .or(community_follower::person_id.eq(person_id_join)),
+          )
+        }
+        ListingType::Community => {}
       };
     }
 
@@ -693,6 +708,7 @@ mod tests {
         description: None,
         updated: None,
         banner: None,
+        hidden: false,
         published: inserted_community.published,
       },
       counts: CommentAggregates {
index e7ba0308a2ad1583ed9e5cbdf9f54cb9fe51823f..e8532a916c5d5506d3a4722e5403744afbc05ee7 100644 (file)
@@ -412,6 +412,7 @@ mod tests {
         description: None,
         updated: None,
         banner: None,
+        hidden: false,
         published: inserted_community.published,
       },
       creator: PersonSafe {
index 14138374a146f837f623eb5ed11214c4df54c086..b9350f8fc0e159a37edf1c5f3e43da6f98158a78 100644 (file)
@@ -355,17 +355,32 @@ impl<'a> PostQueryBuilder<'a> {
       .into_boxed();
 
     if let Some(listing_type) = self.listing_type {
-      query = match listing_type {
-        ListingType::Subscribed => query.filter(community_follower::person_id.is_not_null()),
-        ListingType::Local => query.filter(community::local.eq(true)),
-        _ => query,
-      };
-    }
-
-    if let Some(community_id) = self.community_id {
-      query = query
-        .filter(post::community_id.eq(community_id))
-        .then_order_by(post_aggregates::stickied.desc());
+      match listing_type {
+        ListingType::Subscribed => {
+          query = query.filter(community_follower::person_id.is_not_null())
+        }
+        ListingType::Local => {
+          query = query.filter(community::local.eq(true)).filter(
+            community::hidden
+              .eq(false)
+              .or(community_follower::person_id.eq(person_id_join)),
+          );
+        }
+        ListingType::All => {
+          query = query.filter(
+            community::hidden
+              .eq(false)
+              .or(community_follower::person_id.eq(person_id_join)),
+          )
+        }
+        ListingType::Community => {
+          if let Some(community_id) = self.community_id {
+            query = query
+              .filter(post::community_id.eq(community_id))
+              .then_order_by(post_aggregates::stickied.desc());
+          }
+        }
+      }
     }
 
     if let Some(community_actor_id) = self.community_actor_id {
@@ -679,6 +694,7 @@ mod tests {
         description: None,
         updated: None,
         banner: None,
+        hidden: false,
         published: inserted_community.published,
       },
       counts: PostAggregates {
index 48270c25c2de3a28d2a4bac081eebbd69fd4d80a..50ca6f4dd0ef234593408ec41b67505b2ccd2a8d 100644 (file)
@@ -201,7 +201,24 @@ impl<'a> CommunityQueryBuilder<'a> {
       SortType::New => query = query.order_by(community::published.desc()),
       SortType::TopAll => query = query.order_by(community_aggregates::subscribers.desc()),
       SortType::TopMonth => query = query.order_by(community_aggregates::users_active_month.desc()),
-      // Covers all other sorts, including hot
+      SortType::Hot => {
+        query = query
+          .order_by(
+            hot_rank(
+              community_aggregates::subscribers,
+              community_aggregates::published,
+            )
+            .desc(),
+          )
+          .then_order_by(community_aggregates::published.desc());
+        // Don't show hidden communities in Hot (trending)
+        query = query.filter(
+          community::hidden
+            .eq(false)
+            .or(community_follower::person_id.eq(person_id_join)),
+        );
+      }
+      // Covers all other sorts
       _ => {
         query = query
           .order_by(
index 354504c4ac85b96bac74edc712bf69f80073a7c4..ce51617ceba5c3db7e098fdb77967330c93e496d 100644 (file)
@@ -2,6 +2,7 @@ pub mod mod_add_community_view;
 pub mod mod_add_view;
 pub mod mod_ban_from_community_view;
 pub mod mod_ban_view;
+pub mod mod_hide_community_view;
 pub mod mod_lock_post_view;
 pub mod mod_remove_comment_view;
 pub mod mod_remove_community_view;
diff --git a/crates/db_views_moderator/src/mod_hide_community_view.rs b/crates/db_views_moderator/src/mod_hide_community_view.rs
new file mode 100644 (file)
index 0000000..c7aba77
--- /dev/null
@@ -0,0 +1,75 @@
+use diesel::{result::Error, *};
+use lemmy_db_schema::{
+  limit_and_offset,
+  newtypes::{CommunityId, PersonId},
+  schema::{community, mod_hide_community, person},
+  source::{
+    community::{Community, CommunitySafe},
+    moderator::ModHideCommunity,
+    person::{Person, PersonSafe},
+  },
+  traits::{ToSafe, ViewToVec},
+};
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct ModHideCommunityView {
+  pub mod_hide_community: ModHideCommunity,
+  pub admin: PersonSafe,
+  pub community: CommunitySafe,
+}
+
+type ModHideCommunityViewTuple = (ModHideCommunity, PersonSafe, CommunitySafe);
+
+impl ModHideCommunityView {
+  // Pass in mod_id as admin_id because only admins can do this action
+  pub fn list(
+    conn: &PgConnection,
+    community_id: Option<CommunityId>,
+    admin_id: Option<PersonId>,
+    page: Option<i64>,
+    limit: Option<i64>,
+  ) -> Result<Vec<Self>, Error> {
+    let mut query = mod_hide_community::table
+      .inner_join(person::table)
+      .inner_join(community::table.on(mod_hide_community::community_id.eq(community::id)))
+      .select((
+        mod_hide_community::all_columns,
+        Person::safe_columns_tuple(),
+        Community::safe_columns_tuple(),
+      ))
+      .into_boxed();
+
+    if let Some(community_id) = community_id {
+      query = query.filter(mod_hide_community::community_id.eq(community_id));
+    };
+
+    if let Some(admin_id) = admin_id {
+      query = query.filter(mod_hide_community::mod_person_id.eq(admin_id));
+    };
+
+    let (limit, offset) = limit_and_offset(page, limit);
+
+    let res = query
+      .limit(limit)
+      .offset(offset)
+      .order_by(mod_hide_community::when_.desc())
+      .load::<ModHideCommunityViewTuple>(conn)?;
+
+    Ok(Self::from_tuple_to_vec(res))
+  }
+}
+
+impl ViewToVec for ModHideCommunityView {
+  type DbTuple = ModHideCommunityViewTuple;
+  fn from_tuple_to_vec(items: Vec<Self::DbTuple>) -> Vec<Self> {
+    items
+      .iter()
+      .map(|a| Self {
+        mod_hide_community: a.0.to_owned(),
+        admin: a.1.to_owned(),
+        community: a.2.to_owned(),
+      })
+      .collect::<Vec<Self>>()
+  }
+}
diff --git a/migrations/2022-01-04-034553_add_hidden_column/down.sql b/migrations/2022-01-04-034553_add_hidden_column/down.sql
new file mode 100644 (file)
index 0000000..55a054a
--- /dev/null
@@ -0,0 +1,3 @@
+alter table community drop column hidden;
+
+drop table mod_hide_community;
diff --git a/migrations/2022-01-04-034553_add_hidden_column/up.sql b/migrations/2022-01-04-034553_add_hidden_column/up.sql
new file mode 100644 (file)
index 0000000..b743661
--- /dev/null
@@ -0,0 +1,13 @@
+alter table community add column hidden boolean default false;
+
+
+create table mod_hide_community
+(
+    id serial primary key,
+    community_id int references community on update cascade on delete cascade not null,
+    mod_person_id int references person on update cascade on delete cascade not null,
+    when_ timestamp not null default now(),
+    reason text,
+    hidden boolean default false
+);
+
index 69bfe50bf76886a51841480403c3c1e78405dc23..1af9f028f19766d01d170e9b173f3c8d35e02a0f 100644 (file)
@@ -49,6 +49,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
           .wrap(rate_limit.message())
           .route("", web::get().to(route_get_crud::<GetCommunity>))
           .route("", web::put().to(route_post_crud::<EditCommunity>))
+          .route("/hide", web::put().to(route_post_crud::<HideCommunity>))
           .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>))
index 161dd3b607f83e89108311fa63017e41a61eec85..4c0becfe951202dc7b55ed3fe48bfb4947dd7380 100644 (file)
@@ -111,6 +111,7 @@ fn community_updates_2020_04_02(
       deleted: None,
       nsfw: None,
       updated: None,
+      hidden: Some(false),
       actor_id: Some(community_actor_id.to_owned()),
       local: Some(ccommunity.local),
       private_key: Some(Some(keypair.private_key)),