diff --git a/migrations/20230527011302_add-email-to-request-delete.down.sql b/migrations/20230527011302_add-email-to-request-delete.down.sql new file mode 100644 index 0000000000000000000000000000000000000000..b82b26e3a050590df52a19ecb96bac6e0cc6f78a --- /dev/null +++ b/migrations/20230527011302_add-email-to-request-delete.down.sql @@ -0,0 +1,2 @@ +-- Add down migration script here +ALTER TABLE request_delete DROP user_email; diff --git a/migrations/20230527011302_add-email-to-request-delete.up.sql b/migrations/20230527011302_add-email-to-request-delete.up.sql new file mode 100644 index 0000000000000000000000000000000000000000..cda6367c8bef20aa2839a3e9438130036eb0dc3b --- /dev/null +++ b/migrations/20230527011302_add-email-to-request-delete.up.sql @@ -0,0 +1,2 @@ +-- Add up migration script here +ALTER TABLE request_delete ADD user_email varchar(60) NOT NULL DEFAULT 'not recorded' AFTER `user_id`; \ No newline at end of file diff --git a/src/application/datakey.rs b/src/application/datakey.rs index 3e5cbbaa6e4a33a9fda747068961f1d2ec53b14f..b460a98afb158a2fbd8aa03361672f1546e5e41d 100644 --- a/src/application/datakey.rs +++ b/src/application/datakey.rs @@ -128,13 +128,14 @@ where async fn request_delete(&self, user: UserIdentity, id: i32) -> Result<()> { let user_id = user.id; + let user_email = user.email.clone(); let key = self.get_and_check_permission(Some(user), id).await?; if key.key_state == KeyState::Enabled { return Err(Error::ParameterError("enabled key does not support delete".to_string())); } match key.visibility { Visibility::Public => { - self.repository.request_delete_public_key(user_id, key.id).await + self.repository.request_delete_public_key(user_id, user_email, key.id).await } Visibility::Private => { self.repository.delete_private_key(key.id, user_id).await diff --git a/src/domain/datakey/entity.rs b/src/domain/datakey/entity.rs index 48b8b6d49dee7d62368464327c26a8b58da29abf..f9a9bf1f13502c45104f6a6c2b8b54a1620298bc 100644 --- a/src/domain/datakey/entity.rs +++ b/src/domain/datakey/entity.rs @@ -106,6 +106,7 @@ pub struct DataKey { pub expire_at: DateTime, pub key_state: KeyState, pub user_email: Option, + pub request_delete_users: Option, } impl ExtendableAttributes for DataKey { diff --git a/src/domain/datakey/repository.rs b/src/domain/datakey/repository.rs index b060a7c913fe6d6c9c60dbf0521761c4b09a931d..8584bdb83e8ffc853192928c9f5c8ab9dcbcb40a 100644 --- a/src/domain/datakey/repository.rs +++ b/src/domain/datakey/repository.rs @@ -30,6 +30,6 @@ pub trait Repository: Send + Sync { async fn update_state(&self, id: i32, state: KeyState) -> Result<()>; async fn get_enabled_key_by_type_and_name(&self, key_type: String, name: String) -> Result; async fn delete_private_key(&self, id: i32, user_id: i32) -> Result<()>; - async fn request_delete_public_key(&self, user_id: i32, id: i32) -> Result<()>; + async fn request_delete_public_key(&self, user_id: i32, user_email: String, id: i32) -> Result<()>; async fn cancel_delete_public_key(&self, user_id: i32, id: i32) -> Result<()>; } diff --git a/src/infra/database/model/datakey/dto.rs b/src/infra/database/model/datakey/dto.rs index a20bbdfbc3250625eb5d100ff50c9c26bbdf55ee..b6d55590bb7539f675d86771124d1b0d49ddd1ee 100644 --- a/src/infra/database/model/datakey/dto.rs +++ b/src/infra/database/model/datakey/dto.rs @@ -41,6 +41,7 @@ pub(super) struct DataKeyDTO { pub expire_at: DateTime, pub key_state: String, pub user_email: Option, + pub request_delete_users: Option, } @@ -64,6 +65,7 @@ impl TryFrom for DataKey { expire_at: dto.expire_at, key_state: KeyState::from_str(&dto.key_state)?, user_email: dto.user_email, + request_delete_users: dto.request_delete_users }) } } @@ -93,7 +95,8 @@ impl TryFrom for DataKeyDTO { create_at: data_key.create_at, expire_at: data_key.expire_at, key_state: data_key.key_state.to_string(), - user_email: None + user_email: None, + request_delete_users: None }) } } diff --git a/src/infra/database/model/datakey/repository.rs b/src/infra/database/model/datakey/repository.rs index 1f42db0b84cd7f8cdd6eefb6ee527cddc0e5763a..1bc46a26ee67e26c6e67f06158bcf91d8a155a2c 100644 --- a/src/infra/database/model/datakey/repository.rs +++ b/src/infra/database/model/datakey/repository.rs @@ -39,10 +39,11 @@ impl DataKeyRepository { } } - async fn create_request_delete(&self, user_id: i32, id: i32, tx: &mut Transaction<'_, MySql>) -> Result<()> { - let _ : Option = sqlx::query_as("INSERT IGNORE INTO request_delete(user_id, key_id, create_at) VALUES (?, ?, ?)") + async fn create_request_delete(&self, user_id: i32, user_email: String, id: i32, tx: &mut Transaction<'_, MySql>) -> Result<()> { + let _ : Option = sqlx::query_as("INSERT IGNORE INTO request_delete(user_id, key_id, user_email, create_at) VALUES (?, ?, ?, ?)") .bind(user_id) .bind(id) + .bind(user_email) .bind(Utc::now()) .fetch_optional(tx) .await?; @@ -84,9 +85,12 @@ impl Repository for DataKeyRepository { async fn get_public_keys(&self) -> Result> { let dtos: Vec = sqlx::query_as( - "SELECT D.*, U.email AS user_email \ - FROM data_key D INNER JOIN user U ON D.user = U.id \ - WHERE D.key_state != ? and D.visibility = ?") + "SELECT D.*, U.email AS user_email, GROUP_CONCAT(R.user_email) as request_delete_users \ + FROM data_key D \ + INNER JOIN user U ON D.user = U.id \ + LEFT JOIN request_delete R ON D.id = R.key_id \ + WHERE D.key_state != ? and D.visibility = ? \ + GROUP BY D.id") .bind(KeyState::Deleted.to_string()) .bind(Visibility::Public.to_string()) .fetch_all(&self.db_pool) @@ -131,9 +135,11 @@ impl Repository for DataKeyRepository { async fn get_by_id(&self, id: i32) -> Result { let dto: DataKeyDTO = sqlx::query_as( - "SELECT D.*, U.email AS user_email \ + "SELECT D.*, U.email AS user_email, GROUP_CONCAT(R.user_email) as request_delete_users \ FROM data_key D INNER JOIN user U ON D.user = U.id \ - WHERE D.id = ? AND D.key_state != ?") + LEFT JOIN request_delete R ON D.id = R.key_id \ + WHERE D.id = ? AND D.key_state != ? \ + GROUP BY D.id") .bind(id) .bind(KeyState::Deleted.to_string()) .fetch_one(&self.db_pool) @@ -143,9 +149,11 @@ impl Repository for DataKeyRepository { async fn get_by_name(&self, name: &String) -> Result { let dto: DataKeyDTO = sqlx::query_as( - "SELECT D.*, U.email AS user_email \ + "SELECT D.*, U.email AS user_email GROUP_CONCAT(R.user_email) as request_delete_users \ FROM data_key D INNER JOIN user U ON D.user = U.id \ - WHERE D.name = ? AND D.key_state != ?") + LEFT JOIN request_delete R ON D.id = R.key_id \ + WHERE D.name = ? AND D.key_state != ? \ + GROUP BY D.id") .bind(name) .bind(KeyState::Deleted.to_string()) .fetch_one(&self.db_pool) @@ -188,7 +196,7 @@ impl Repository for DataKeyRepository { Ok(()) } - async fn request_delete_public_key(&self, user_id: i32, id: i32) -> Result<()> { + async fn request_delete_public_key(&self, user_id: i32, user_email: String, id: i32) -> Result<()> { let mut tx = self.db_pool.begin().await?; //1. update key state to pending delete if needed. let _: Option = sqlx::query_as( @@ -201,7 +209,7 @@ impl Repository for DataKeyRepository { .fetch_optional(&mut tx) .await?; //2. add request delete record - self.create_request_delete(user_id, id, &mut tx).await?; + self.create_request_delete(user_id, user_email, id, &mut tx).await?; //3. delete datakey if pending delete count >= threshold let _: Option = sqlx::query_as( "UPDATE data_key SET key_state = ? \ diff --git a/src/infra/database/model/request_delete/dto.rs b/src/infra/database/model/request_delete/dto.rs index 8a0e2062bf49405f65f888c8c511036130783d47..1f0c8f2d65842018398d9669e3bdc1948075daca 100644 --- a/src/infra/database/model/request_delete/dto.rs +++ b/src/infra/database/model/request_delete/dto.rs @@ -21,15 +21,17 @@ pub struct RequestDeleteDTO { pub id: i32, pub user_id: i32, pub key_id: i32, + pub user_email: String, pub create_at: DateTime, } impl RequestDeleteDTO { - pub fn new(key_id: i32, user_id: i32) -> Self { + pub fn new(key_id: i32, user_id: i32, user_email: String) -> Self { Self { id: 0, user_id, key_id, + user_email, create_at: Utc::now() } } diff --git a/src/infra/sign_plugin/openpgp.rs b/src/infra/sign_plugin/openpgp.rs index 6ba0975112db25015d8adf6cfa0c2100ace1e3e0..eee3bc4f5e43dc2b5171e5ad144c63cc5578e46c 100644 --- a/src/infra/sign_plugin/openpgp.rs +++ b/src/infra/sign_plugin/openpgp.rs @@ -325,6 +325,8 @@ mod test { create_at: now, expire_at: now, key_state: KeyState::Enabled, + user_email: None, + request_delete_users: None, } } diff --git a/src/infra/sign_plugin/x509.rs b/src/infra/sign_plugin/x509.rs index d7748db7e96e9ea22d6ba7635d44aead5236c6b2..eaa0689addf8709f4ace5e915b76b689b5b62957 100644 --- a/src/infra/sign_plugin/x509.rs +++ b/src/infra/sign_plugin/x509.rs @@ -300,6 +300,8 @@ mod test { create_at: now, expire_at: now, key_state: KeyState::Enabled, + user_email: None, + request_delete_users: None, } } diff --git a/src/presentation/handler/control/model/datakey/dto.rs b/src/presentation/handler/control/model/datakey/dto.rs index 070f5f3059ed20c0122db4264ec3643bd7286c12..74be5af8fd9a2e7b01f8ae3637138ee5da58ea63 100644 --- a/src/presentation/handler/control/model/datakey/dto.rs +++ b/src/presentation/handler/control/model/datakey/dto.rs @@ -133,6 +133,8 @@ pub struct DataKeyDTO { pub key_state: String, /// User email pub user_email: Option, + /// Request user email list, only for public key + pub request_delete_users: Option, } fn validate_utc_time(expire: &str) -> std::result::Result<(), ValidationError> { @@ -198,6 +200,7 @@ impl DataKey { expire_at: now, key_state: KeyState::default(), user_email: None, + request_delete_users: None, }) } @@ -228,6 +231,7 @@ impl DataKey { expire_at: dto.expire_at.parse()?, key_state: KeyState::default(), user_email: None, + request_delete_users: None, }) } } @@ -251,6 +255,7 @@ impl TryFrom for DataKeyDTO { expire_at: dto.expire_at.to_string(), key_state: dto.key_state.to_string(), user_email: dto.user_email, + request_delete_users: dto.request_delete_users, }) } }