Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

delete_account #221

Merged
merged 9 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading