]> Untitled Git - lemmy.git/commitdiff
Removing the site creator, adding leave_admin. Fixes #1808 (#2052)
authorDessalines <dessalines@users.noreply.github.com>
Wed, 26 Jan 2022 17:57:16 +0000 (12:57 -0500)
committerGitHub <noreply@github.com>
Wed, 26 Jan 2022 17:57:16 +0000 (17:57 +0000)
* Removing the site creator, adding leave_admin. Fixes #1808

* Making sure there's at least one admin. Fixing unit tests

18 files changed:
crates/api/src/community.rs
crates/api/src/lib.rs
crates/api/src/local_user.rs
crates/api/src/site.rs
crates/api_common/src/site.rs
crates/api_crud/src/site/create.rs
crates/api_crud/src/site/read.rs
crates/api_crud/src/site/update.rs
crates/db_schema/src/aggregates/site_aggregates.rs
crates/db_schema/src/impls/person.rs
crates/db_schema/src/impls/site.rs
crates/db_schema/src/schema.rs
crates/db_schema/src/source/site.rs
crates/db_views/src/site_view.rs
crates/websocket/src/lib.rs
migrations/2022-01-20-160328_remove_site_creator/down.sql [new file with mode: 0644]
migrations/2022-01-20-160328_remove_site_creator/up.sql [new file with mode: 0644]
src/api_routes.rs

index c8b44aceb0cba749b6297407aa812b5a80641b46..a1a44f0941462ee9256de042494db40d1b943023 100644 (file)
@@ -44,7 +44,6 @@ use lemmy_db_schema::{
     },
     person::Person,
     post::Post,
-    site::Site,
   },
   traits::{Bannable, Blockable, Crud, Followable, Joinable},
 };
@@ -457,20 +456,7 @@ impl Perform for TransferCommunity {
     let local_user_view =
       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
 
-    let site_creator_id = blocking(context.pool(), move |conn| {
-      Site::read(conn, 1).map(|s| s.creator_id)
-    })
-    .await??;
-
-    let mut admins = blocking(context.pool(), PersonViewSafe::admins).await??;
-
-    // Making sure the site creator, if an admin, is at the top
-    let creator_index = admins
-      .iter()
-      .position(|r| r.person.id == site_creator_id)
-      .context(location_info!())?;
-    let creator_person = admins.remove(creator_index);
-    admins.insert(0, creator_person);
+    let admins = blocking(context.pool(), PersonViewSafe::admins).await??;
 
     // Fetch the community mods
     let community_id = data.community_id;
index 3cffe016642f80730e07ee4bb251007b0be0d7f3..c7c24062db547a0c85e0d660d8baa269f210df50 100644 (file)
@@ -111,9 +111,7 @@ pub async fn match_websocket_operation(
     UserOperation::TransferCommunity => {
       do_websocket_operation::<TransferCommunity>(context, id, op, data).await
     }
-    UserOperation::TransferSite => {
-      do_websocket_operation::<TransferSite>(context, id, op, data).await
-    }
+    UserOperation::LeaveAdmin => do_websocket_operation::<LeaveAdmin>(context, id, op, data).await,
 
     // Community ops
     UserOperation::FollowCommunity => {
index 9452e7540dbfb2b5735abc1f734eef39009093c2..974beb6eab44371459f8b590ed1836185d0998d0 100644 (file)
@@ -1,6 +1,5 @@
 use crate::{captcha_as_wav_base64, Perform};
 use actix_web::web::Data;
-use anyhow::Context;
 use bcrypt::verify;
 use captcha::{gen, Difficulty};
 use chrono::Duration;
@@ -51,7 +50,6 @@ use lemmy_db_views_actor::{
 };
 use lemmy_utils::{
   claims::Claims,
-  location_info,
   utils::{is_valid_display_name, is_valid_matrix_id, naive_from_unix},
   ConnectionId,
   LemmyError,
@@ -409,18 +407,7 @@ impl Perform for AddAdmin {
 
     blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
 
-    let site_creator_id = blocking(context.pool(), move |conn| {
-      Site::read(conn, 1).map(|s| s.creator_id)
-    })
-    .await??;
-
-    let mut admins = blocking(context.pool(), PersonViewSafe::admins).await??;
-    let creator_index = admins
-      .iter()
-      .position(|r| r.person.id == site_creator_id)
-      .context(location_info!())?;
-    let creator_person = admins.remove(creator_index);
-    admins.insert(0, creator_person);
+    let admins = blocking(context.pool(), PersonViewSafe::admins).await??;
 
     let res = AddAdminResponse { admins };
 
index 04cd099ecff72478cfdcbc92c4455068373193f6..c77b177fab31d8a2a90d3ce5e3d052a2e2aed3dd 100644 (file)
@@ -1,6 +1,5 @@
 use crate::Perform;
 use actix_web::web::Data;
-use anyhow::Context;
 use diesel::NotFound;
 use lemmy_api_common::{
   blocking,
@@ -27,6 +26,7 @@ use lemmy_db_schema::{
   source::{
     local_user::{LocalUser, LocalUserForm},
     moderator::*,
+    person::Person,
     registration_application::{RegistrationApplication, RegistrationApplicationForm},
     site::Site,
   },
@@ -62,7 +62,7 @@ use lemmy_db_views_moderator::{
   mod_sticky_post_view::ModStickyPostView,
   mod_transfer_community_view::ModTransferCommunityView,
 };
-use lemmy_utils::{location_info, settings::structs::Settings, version, ConnectionId, LemmyError};
+use lemmy_utils::{settings::structs::Settings, version, ConnectionId, LemmyError};
 use lemmy_websocket::LemmyContext;
 
 #[async_trait::async_trait(?Send)]
@@ -462,7 +462,7 @@ async fn convert_response(
 }
 
 #[async_trait::async_trait(?Send)]
-impl Perform for TransferSite {
+impl Perform for LeaveAdmin {
   type Response = GetSiteResponse;
 
   #[tracing::instrument(skip(context, _websocket_id))]
@@ -471,44 +471,36 @@ impl Perform for TransferSite {
     context: &Data<LemmyContext>,
     _websocket_id: Option<ConnectionId>,
   ) -> Result<GetSiteResponse, LemmyError> {
-    let data: &TransferSite = self;
+    let data: &LeaveAdmin = self;
     let local_user_view =
       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
 
     is_admin(&local_user_view)?;
 
-    let read_site = blocking(context.pool(), Site::read_simple).await??;
-
-    // Make sure user is the creator
-    if read_site.creator_id != local_user_view.person.id {
-      return Err(LemmyError::from_message("not_an_admin"));
+    // Make sure there isn't just one admin (so if one leaves, there will still be one left)
+    let admins = blocking(context.pool(), PersonViewSafe::admins).await??;
+    if admins.len() == 1 {
+      return Err(LemmyError::from_message("cannot_leave_admin"));
     }
 
-    let new_creator_id = data.person_id;
-    let transfer_site = move |conn: &'_ _| Site::transfer(conn, new_creator_id);
-    blocking(context.pool(), transfer_site)
-      .await?
-      .map_err(LemmyError::from)
-      .map_err(|e| e.with_message("couldnt_update_site"))?;
+    let person_id = local_user_view.person.id;
+    blocking(context.pool(), move |conn| {
+      Person::leave_admin(conn, person_id)
+    })
+    .await??;
 
     // Mod tables
     let form = ModAddForm {
-      mod_person_id: local_user_view.person.id,
-      other_person_id: data.person_id,
-      removed: Some(false),
+      mod_person_id: person_id,
+      other_person_id: person_id,
+      removed: Some(true),
     };
 
     blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
 
+    // Reread site and admins
     let site_view = blocking(context.pool(), SiteView::read).await??;
-
-    let mut admins = blocking(context.pool(), PersonViewSafe::admins).await??;
-    let creator_index = admins
-      .iter()
-      .position(|r| r.person.id == site_view.creator.id)
-      .context(location_info!())?;
-    let creator_person = admins.remove(creator_index);
-    admins.insert(0, creator_person);
+    let admins = blocking(context.pool(), PersonViewSafe::admins).await??;
 
     let federated_instances = build_federated_instances(
       context.pool(),
index 07f7e8539537e38ecd01a8eeb2abb1c4b3bd8591..e3bc52a2d01c5df83a82d3c3fa9c64e38f5f6683 100644 (file)
@@ -155,8 +155,7 @@ pub struct MyUserInfo {
 }
 
 #[derive(Debug, Serialize, Deserialize)]
-pub struct TransferSite {
-  pub person_id: PersonId,
+pub struct LeaveAdmin {
   pub auth: Sensitive<String>,
 }
 
index 043a31c2b2254b5ffb428e7dc80386b4d027abea..3de2c3ab84418e10ff88ec39f682ad25a5ad5859 100644 (file)
@@ -62,7 +62,6 @@ impl PerformCrud for CreateSite {
       description,
       icon,
       banner,
-      creator_id: local_user_view.person.id,
       enable_downvotes: data.enable_downvotes,
       open_registration: data.open_registration,
       enable_nsfw: data.enable_nsfw,
index 229d5939c5a35a993043ff82c0601acbd4781fae..ee777d2d7edc4b76f3dce098c1a94dd0bab5e9b4 100644 (file)
@@ -79,18 +79,7 @@ impl PerformCrud for GetSite {
       }
     };
 
-    let mut admins = blocking(context.pool(), PersonViewSafe::admins).await??;
-
-    // Make sure the site creator is the top admin
-    if let Some(site_view) = site_view.to_owned() {
-      let site_creator_id = site_view.creator.id;
-      // TODO investigate why this is sometimes coming back null
-      // Maybe user_.admin isn't being set to true?
-      if let Some(creator_index) = admins.iter().position(|r| r.person.id == site_creator_id) {
-        let creator_person = admins.remove(creator_index);
-        admins.insert(0, creator_person);
-      }
-    }
+    let admins = blocking(context.pool(), PersonViewSafe::admins).await??;
 
     let online = context
       .chat_server()
index bcd6a1a32f24c0261493591eae8ef0014bc6202c..0a94062a7eb07314c7cd2c541f796ab1b02cb2bd 100644 (file)
@@ -54,7 +54,6 @@ impl PerformCrud for EditSite {
     }
 
     let site_form = SiteForm {
-      creator_id: found_site.creator_id,
       name: data.name.to_owned().unwrap_or(found_site.name),
       sidebar,
       description,
index 08d4dd01ef6d2592c70bd3b9471388ac231af860..b948992470a380a2fb4d39e4d00d98fb24531d13 100644 (file)
@@ -55,7 +55,6 @@ mod tests {
 
     let site_form = SiteForm {
       name: "test_site".into(),
-      creator_id: inserted_person.id,
       sidebar: None,
       description: None,
       icon: None,
@@ -133,7 +132,12 @@ mod tests {
     let community_num_deleted = Community::delete(&conn, inserted_community.id).unwrap();
     assert_eq!(1, community_num_deleted);
 
-    let after_delete = SiteAggregates::read(&conn);
-    assert!(after_delete.is_err());
+    // Site should still exist, it can without a site creator.
+    let after_delete_creator = SiteAggregates::read(&conn);
+    assert!(after_delete_creator.is_ok());
+
+    Site::delete(&conn, 1).unwrap();
+    let after_delete_site = SiteAggregates::read(&conn);
+    assert!(after_delete_site.is_err());
   }
 }
index 7e756543b3a6658bf031a47fbe3b7dfdaad9e3e5..17833253bcc0083b7f4967b1f7623b7c1aa3049b 100644 (file)
@@ -274,6 +274,12 @@ impl Person {
   pub fn is_banned(&self) -> bool {
     is_banned(self.banned, self.ban_expires)
   }
+
+  pub fn leave_admin(conn: &PgConnection, person_id: PersonId) -> Result<Self, Error> {
+    diesel::update(person.find(person_id))
+      .set(admin.eq(false))
+      .get_result::<Self>(conn)
+  }
 }
 
 impl PersonSafe {
index af0e8153d08ec148277e23cf0feb4720cfa9c013..8a84bdfbbbf21a49c4dd2cbe2438840b72ceb6ab 100644 (file)
@@ -1,4 +1,4 @@
-use crate::{naive_now, newtypes::PersonId, source::site::*, traits::Crud};
+use crate::{source::site::*, traits::Crud};
 use diesel::{dsl::*, result::Error, *};
 
 impl Crud for Site {
@@ -27,13 +27,6 @@ impl Crud for Site {
 }
 
 impl Site {
-  pub fn transfer(conn: &PgConnection, new_creator_id: PersonId) -> Result<Site, Error> {
-    use crate::schema::site::dsl::*;
-    diesel::update(site.find(1))
-      .set((creator_id.eq(new_creator_id), updated.eq(naive_now())))
-      .get_result::<Self>(conn)
-  }
-
   pub fn read_simple(conn: &PgConnection) -> Result<Self, Error> {
     use crate::schema::site::dsl::*;
     site.first::<Self>(conn)
index c01114d9eb6e19a549fb19e22057dbfcc559c641..681452d9f290c8ddbf50ebd7a0bd2db65bfec968 100644 (file)
@@ -441,7 +441,6 @@ table! {
         id -> Int4,
         name -> Varchar,
         sidebar -> Nullable<Text>,
-        creator_id -> Int4,
         published -> Timestamp,
         updated -> Nullable<Timestamp>,
         enable_downvotes -> Bool,
@@ -648,7 +647,6 @@ joinable!(post_read -> post (post_id));
 joinable!(post_report -> post (post_id));
 joinable!(post_saved -> person (person_id));
 joinable!(post_saved -> post (post_id));
-joinable!(site -> person (creator_id));
 joinable!(site_aggregates -> site (site_id));
 joinable!(email_verification -> local_user (local_user_id));
 joinable!(registration_application -> local_user (local_user_id));
index f99ffd8873d04280298b008056ee07c42064e0fb..01c5bc16ec7195cc383280c3cca611215d08c467 100644 (file)
@@ -1,7 +1,4 @@
-use crate::{
-  newtypes::{DbUrl, PersonId},
-  schema::site,
-};
+use crate::{newtypes::DbUrl, schema::site};
 use serde::{Deserialize, Serialize};
 
 #[derive(Queryable, Identifiable, PartialEq, Debug, Clone, Serialize, Deserialize)]
@@ -10,7 +7,6 @@ pub struct Site {
   pub id: i32,
   pub name: String,
   pub sidebar: Option<String>,
-  pub creator_id: PersonId,
   pub published: chrono::NaiveDateTime,
   pub updated: Option<chrono::NaiveDateTime>,
   pub enable_downvotes: bool,
@@ -30,7 +26,6 @@ pub struct Site {
 #[table_name = "site"]
 pub struct SiteForm {
   pub name: String,
-  pub creator_id: PersonId,
   pub sidebar: Option<Option<String>>,
   pub updated: Option<chrono::NaiveDateTime>,
   pub enable_downvotes: Option<bool>,
index 3c94975aa2034e170ebec22a43a7f2db83955250..e77d7c6ce770298a51bb84b6b3026199d54499e4 100644 (file)
@@ -1,38 +1,24 @@
 use diesel::{result::Error, *};
 use lemmy_db_schema::{
   aggregates::site_aggregates::SiteAggregates,
-  schema::{person, site, site_aggregates},
-  source::{
-    person::{Person, PersonSafe},
-    site::Site,
-  },
-  traits::ToSafe,
+  schema::{site, site_aggregates},
+  source::site::Site,
 };
 use serde::{Deserialize, Serialize};
 
 #[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct SiteView {
   pub site: Site,
-  pub creator: PersonSafe,
   pub counts: SiteAggregates,
 }
 
 impl SiteView {
   pub fn read(conn: &PgConnection) -> Result<Self, Error> {
-    let (site, creator, counts) = site::table
-      .inner_join(person::table)
+    let (site, counts) = site::table
       .inner_join(site_aggregates::table)
-      .select((
-        site::all_columns,
-        Person::safe_columns_tuple(),
-        site_aggregates::all_columns,
-      ))
-      .first::<(Site, PersonSafe, SiteAggregates)>(conn)?;
+      .select((site::all_columns, site_aggregates::all_columns))
+      .first::<(Site, SiteAggregates)>(conn)?;
 
-    Ok(SiteView {
-      site,
-      creator,
-      counts,
-    })
+    Ok(SiteView { site, counts })
   }
 }
index e5c2730faa7334c72027fe27ba4251e9b5b4e71e..324562180b0854d9ff897a5ce5ece6f8de0e1219 100644 (file)
@@ -136,7 +136,7 @@ pub enum UserOperation {
   MarkAllAsRead,
   SaveUserSettings,
   TransferCommunity,
-  TransferSite,
+  LeaveAdmin,
   PasswordReset,
   PasswordChange,
   MarkPrivateMessageAsRead,
diff --git a/migrations/2022-01-20-160328_remove_site_creator/down.sql b/migrations/2022-01-20-160328_remove_site_creator/down.sql
new file mode 100644 (file)
index 0000000..8195f71
--- /dev/null
@@ -0,0 +1,14 @@
+--  Add the column back
+alter table site add column creator_id int references person on update cascade on delete cascade;
+
+-- Add the data, selecting the highest admin
+update site
+set creator_id = sub.id
+from (
+  select id from person
+  where admin = true
+  limit 1
+) as sub;
+
+-- Set to not null
+alter table site alter column creator_id set not null;
diff --git a/migrations/2022-01-20-160328_remove_site_creator/up.sql b/migrations/2022-01-20-160328_remove_site_creator/up.sql
new file mode 100644 (file)
index 0000000..7877444
--- /dev/null
@@ -0,0 +1,2 @@
+-- Drop the column
+alter table site drop column creator_id;
index 5d5fe876c30ef38b0a0b361e8a2842375efbf21a..69bfe50bf76886a51841480403c3c1e78405dc23 100644 (file)
@@ -19,7 +19,6 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
           // 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>)),
       )
@@ -212,7 +211,8 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
           )
           .route("/report_count", web::get().to(route_get::<GetReportCount>))
           .route("/unread_count", web::get().to(route_get::<GetUnreadCount>))
-          .route("/verify_email", web::post().to(route_post::<VerifyEmail>)),
+          .route("/verify_email", web::post().to(route_post::<VerifyEmail>))
+          .route("/leave_admin", web::post().to(route_post::<LeaveAdmin>)),
       )
       // Admin Actions
       .service(