Skip to content

Commit

Permalink
delete_account (#221)
Browse files Browse the repository at this point in the history
* delete_account

* option instead of string

* delete account

* mailer_account_removal

* fixed

* test fixed

* test fixed

* leftover removed
  • Loading branch information
dzlk17 authored Nov 20, 2024
1 parent 31aa71a commit 738e7a4
Show file tree
Hide file tree
Showing 45 changed files with 1,065 additions and 217 deletions.
3 changes: 2 additions & 1 deletion database/migrations/0003_users.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ CREATE TABLE users(
email TEXT NOT NULL UNIQUE,
password_hash TEXT,
passkeys TEXT,
creation_timestamp TIMESTAMPTZ NOT NULL
creation_timestamp TIMESTAMPTZ NOT NULL,
deactivated_at TIMESTAMPTZ
);

CREATE INDEX users_name_idx ON users(user_id);
Expand Down
28 changes: 27 additions & 1 deletion database/src/tables/registered_app/update.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
use super::table_struct::{DbRegisteredApp, REGISTERED_APPS_KEYS, REGISTERED_APPS_TABLE_NAME};
use crate::{db::Db, structs::db_error::DbError, tables::utils::get_current_datetime};

use crate::{
db::Db,
structs::db_error::DbError,
tables::{team::table_struct::TEAM_TABLE_NAME, utils::get_current_datetime},
};
use sqlx::{query, Transaction};

impl Db {
Expand Down Expand Up @@ -114,4 +119,25 @@ impl Db {
Err(e) => Err(e).map_err(|e| e.into()),
}
}

pub async fn deactivate_user_apps(
&self,
tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
user_id: &str,
) -> Result<(), DbError> {
let query_body = format!(
"UPDATE {REGISTERED_APPS_TABLE_NAME} SET deactivated_at = $1 WHERE team_id IN (SELECT team_id FROM {TEAM_TABLE_NAME} WHERE team_admin_id = $2 AND deactivated_at IS NULL) AND deactivated_at IS NULL",
);

let query_result = query(&query_body)
.bind(&get_current_datetime())
.bind(user_id)
.execute(&mut **tx)
.await;

match query_result {
Ok(_) => Ok(()),
Err(e) => Err(e).map_err(|e| e.into()),
}
}
}
12 changes: 0 additions & 12 deletions database/src/tables/team/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,18 +101,6 @@ impl Db {
.map_err(|e| e.into());
}

pub async fn get_team_by_admin_id(&self, admin_id: &String) -> Result<Option<Team>, DbError> {
let query = format!(
"SELECT * FROM {TEAM_TABLE_NAME} WHERE team_admin_id = $1 AND deactivated_at IS NULL"
);
let typed_query = query_as::<_, Team>(&query);

return typed_query
.bind(&admin_id)
.fetch_optional(&self.connection_pool)
.await
.map_err(|e| e.into());
}
pub async fn get_all_teams(&self) -> Result<Vec<Team>, DbError> {
let query = format!("SELECT * FROM {TEAM_TABLE_NAME} WHERE deactivated_at IS NULL");
let typed_query = query_as::<_, Team>(&query);
Expand Down
6 changes: 2 additions & 4 deletions database/src/tables/team/table_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub const TEAM_KEYS: &str =
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Team {
pub team_id: String,
pub grafana_id: String,
pub grafana_id: Option<String>,
pub personal: bool,
pub team_name: String,
// Subscription is required to get access to the statistics
Expand All @@ -26,9 +26,7 @@ impl FromRow<'_, PgRow> for Team {
fn from_row(row: &sqlx::postgres::PgRow) -> std::result::Result<Self, sqlx::Error> {
Ok(Team {
team_id: row.get("team_id"),
grafana_id: row
.try_get::<Option<String>, _>("grafana_id")?
.unwrap_or_default(),
grafana_id: row.get("grafana_id"),
team_name: row.get("team_name"),
personal: row.get("personal"),
subscription: row.get("subscription"),
Expand Down
21 changes: 20 additions & 1 deletion database/src/tables/team/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,25 @@ impl Db {
Err(e) => Err(e).map_err(|e| e.into()),
}
}

pub async fn delete_all_user_teams(&self,
tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
user_id: &str) -> Result<(), DbError> {
let query_body = format!(
"UPDATE {TEAM_TABLE_NAME} SET deactivated_at = $1 WHERE team_admin_id = $2 AND deactivated_at IS NULL",
);

let query_result = query(&query_body)
.bind(&get_current_datetime())
.bind(user_id)
.execute(&mut **tx)
.await;

match query_result {
Ok(_) => Ok(()),
Err(e) => Err(e).map_err(|e| e.into()),
}
}
}

#[cfg(feature = "cloud_integration_tests")]
Expand All @@ -151,7 +170,7 @@ mod tests {
// Create team and register app
let team = Team {
team_id: "test_team_id".to_string(),
grafana_id: "test_grafana_id".to_string(),
grafana_id: Some("test_grafana_id".to_string()),
team_name: "test_team_name".to_string(),
personal: false,
subscription: None,
Expand Down
25 changes: 24 additions & 1 deletion database/src/tables/team_invites/update.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::table_struct::{TEAM_INVITES_KEYS, TEAM_INVITES_TABLE_NAME};
use crate::db::Db;
use crate::structs::db_error::DbError;
use crate::tables::utils::get_current_datetime;
use crate::tables::{team::table_struct::TEAM_TABLE_NAME, utils::get_current_datetime};
use sqlx::{query, Transaction};

impl Db {
Expand Down Expand Up @@ -92,4 +92,27 @@ impl Db {
Err(e) => Err(e).map_err(|e| e.into()),
}
}

pub async fn cancel_all_team_invites_containing_email(
&self,
tx: &mut Transaction<'_, sqlx::Postgres>,
user_email: &String,
user_id: &String,
) -> Result<(), DbError> {
let query_body = format!(
"UPDATE {TEAM_INVITES_TABLE_NAME} SET cancelled_at = $1 WHERE (user_email = $2 OR team_id IN (SELECT team_id FROM {TEAM_TABLE_NAME} WHERE team_admin_id = $3 AND deactivated_at IS NULL) ) AND accepted_at IS NULL AND cancelled_at IS NULL"
);

let query_result = query(&query_body)
.bind(&get_current_datetime())
.bind(&user_email)
.bind(&user_id)
.execute(&mut **tx)
.await;

match query_result {
Ok(_) => Ok(()),
Err(e) => Err(e).map_err(|e| e.into()),
}
}
}
2 changes: 1 addition & 1 deletion database/src/tables/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ pub mod test_utils {

let team = Team {
team_id: team_id.clone(),
grafana_id: "test_grafana_id".to_string(),
grafana_id: None,
team_name: "test_team_name".to_string(),
personal: false,
subscription: None,
Expand Down
36 changes: 35 additions & 1 deletion database/src/tables/user_app_privileges/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,40 @@ impl Db {
Err(e) => Err(e).map_err(|e| e.into()),
}
}

pub async fn remove_inactive_user_from_teams(
&self,
tx: &mut Transaction<'_, sqlx::Postgres>,
user_id: &String,
) -> Result<(), DbError> {
let query_body = format!("DELETE FROM {USER_APP_PRIVILEGES_TABLE_NAME} WHERE user_id = $1");
let query_result = query(&query_body).bind(user_id).execute(&mut **tx).await;
match query_result {
Ok(_) => Ok(()),
Err(e) => Err(e).map_err(|e| e.into()),
}
}

pub async fn remove_privileges_for_inactive_teams(
&self,
tx: &mut Transaction<'_, sqlx::Postgres>,
user_id: &String,
) -> Result<(), DbError> {
let query_body = format!(
"DELETE FROM {USER_APP_PRIVILEGES_TABLE_NAME}
WHERE app_id IN (
SELECT app_id
FROM {REGISTERED_APPS_TABLE_NAME} r
INNER JOIN team t ON r.team_id = t.team_id
WHERE t.team_admin_id = $1
)"
);
let query_result = query(&query_body).bind(user_id).execute(&mut **tx).await;
match query_result {
Ok(_) => Ok(()),
Err(e) => Err(e).map_err(|e| e.into()),
}
}
}

#[cfg(feature = "cloud_integration_tests")]
Expand Down Expand Up @@ -371,7 +405,7 @@ mod tests {

let team = Team {
team_id: team_id.clone(),
grafana_id: "test_grafana_id_2".to_string(),
grafana_id: None,
team_name: "test_team_name".to_string(),
personal: false,
subscription: None,
Expand Down
11 changes: 7 additions & 4 deletions database/src/tables/users/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ use sqlx::query_as;

impl Db {
pub async fn get_user_by_user_id(&self, user_id: &String) -> Result<Option<User>, DbError> {
let query = format!("SELECT * FROM {USERS_TABLE_NAME} WHERE user_id = $1");
let query = format!(
"SELECT * FROM {USERS_TABLE_NAME} WHERE user_id = $1 AND deactivated_at IS NULL"
);
let typed_query = query_as::<_, User>(&query);

return typed_query
Expand All @@ -19,7 +21,8 @@ impl Db {
}

pub async fn get_user_by_email(&self, email: &String) -> Result<Option<User>, DbError> {
let query = format!("SELECT * FROM {USERS_TABLE_NAME} WHERE email = $1");
let query =
format!("SELECT * FROM {USERS_TABLE_NAME} WHERE email = $1 AND deactivated_at IS NULL");
let typed_query = query_as::<_, User>(&query);

return typed_query
Expand All @@ -34,7 +37,7 @@ impl Db {
emails: &Vec<String>,
) -> Result<HashMap<String, String>, DbError> {
// User email to user id
let query = format!("SELECT user_id, email FROM {USERS_TABLE_NAME} WHERE email = ANY($1)");
let query = format!("SELECT user_id, email FROM {USERS_TABLE_NAME} WHERE email = ANY($1) AND deactivated_at IS NULL");
let typed_query = query_as::<_, UserIdEmail>(&query);

let data_vec = typed_query
Expand All @@ -52,7 +55,7 @@ impl Db {
) -> Result<HashMap<String, String>, DbError> {
// User id to user email
let query =
format!("SELECT user_id, email FROM {USERS_TABLE_NAME} WHERE user_id = ANY($1)");
format!("SELECT user_id, email FROM {USERS_TABLE_NAME} WHERE user_id = ANY($1) AND deactivated_at IS NULL");
let typed_query = query_as::<_, UserIdEmail>(&query);

let data_vec = typed_query
Expand Down
5 changes: 4 additions & 1 deletion database/src/tables/users/table_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ use sqlx::{
use webauthn_rs::prelude::Passkey;

pub const USERS_TABLE_NAME: &str = "users";
pub const USERS_KEYS: &str = "user_id, email, password_hash, passkeys, creation_timestamp";
pub const USERS_KEYS: &str =
"user_id, email, password_hash, passkeys, creation_timestamp, deactivated_at";

#[derive(Clone, Debug, PartialEq)]
pub struct User {
Expand All @@ -15,6 +16,7 @@ pub struct User {
pub password_hash: Option<String>,
pub passkeys: Option<Vec<Passkey>>,
pub creation_timestamp: DateTime<Utc>,
pub deactivated_at: Option<DateTime<Utc>>,
}

impl FromRow<'_, PgRow> for User {
Expand All @@ -31,6 +33,7 @@ impl FromRow<'_, PgRow> for User {
},
user_id: row.get("user_id"),
creation_timestamp: row.get("creation_timestamp"),
deactivated_at: row.get("deactivated_at"),
})
}
}
Expand Down
33 changes: 28 additions & 5 deletions database/src/tables/users/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::table_struct::{USERS_KEYS, USERS_TABLE_NAME};
use crate::db::Db;
use crate::structs::db_error::DbError;
use crate::tables::utils::get_current_datetime;
use sqlx::query;
use sqlx::{query, Transaction};
use webauthn_rs::prelude::Passkey;

impl Db {
Expand All @@ -13,8 +13,9 @@ impl Db {
password_hash: Option<&String>,
passkey: Option<&Passkey>,
) -> Result<(), DbError> {
let query_body =
format!("INSERT INTO {USERS_TABLE_NAME} ({USERS_KEYS}) VALUES ($1, $2, $3, $4, $5)");
let query_body = format!(
"INSERT INTO {USERS_TABLE_NAME} ({USERS_KEYS}) VALUES ($1, $2, $3, $4, $5, NULL)"
);

let passkey = match passkey {
Some(passkey) => {
Expand Down Expand Up @@ -51,7 +52,7 @@ impl Db {
new_password: &String,
) -> Result<(), DbError> {
let query_body =
format!("UPDATE {USERS_TABLE_NAME} SET password_hash = $1 WHERE email = $2");
format!("UPDATE {USERS_TABLE_NAME} SET password_hash = $1 WHERE email = $2 AND deactivated_at IS NULL");

let query_result = query(&query_body)
.bind(new_password)
Expand All @@ -74,7 +75,7 @@ impl Db {
DbError::DatabaseError(format!("Failed to serialize passkey: {}", e.to_string()))
})?;

let query_body = format!("UPDATE {USERS_TABLE_NAME} SET passkeys = $1 WHERE email = $2");
let query_body = format!("UPDATE {USERS_TABLE_NAME} SET passkeys = $1 WHERE email = $2 AND deactivated_at IS NULL");

let query_result = query(&query_body)
.bind(&serialized_passkey)
Expand All @@ -87,6 +88,27 @@ impl Db {
Err(e) => Err(e).map_err(|e| e.into()),
}
}

pub async fn deactivate_user(
&self,
user_id: &String,
tx: &mut Transaction<'_, sqlx::Postgres>,
) -> Result<(), DbError> {
let query_body = format!(
"UPDATE {USERS_TABLE_NAME} SET deactivated_at = $1 WHERE user_id = $2 AND deactivated_at IS NULL"
);

let query_result = query(&query_body)
.bind(&get_current_datetime())
.bind(user_id)
.execute(&mut **tx)
.await;

match query_result {
Ok(_) => Ok(()),
Err(e) => Err(e).map_err(|e| e.into()),
}
}
}

#[cfg(feature = "cloud_integration_tests")]
Expand Down Expand Up @@ -115,6 +137,7 @@ mod tests {
user_id: "test_user_id".to_string(),
passkeys: None,
creation_timestamp: to_microsecond_precision(&Utc::now()),
deactivated_at: None,
};

db.add_new_user(&user.user_id, &user.email, Some(&password), None)
Expand Down
Loading

0 comments on commit 738e7a4

Please sign in to comment.