From a997bebef8850c08fe94fea267ae78bb5c1da145 Mon Sep 17 00:00:00 2001 From: Julia Seweryn Date: Fri, 11 Oct 2024 13:35:36 +0200 Subject: [PATCH 1/9] delete team and app --- database/migrations/0002_team.sql | 2 + database/migrations/0004_registered_apps.sql | 4 +- .../connections_stats.rs | 173 ----- .../aggregated_views_queries/events_stats.rs | 709 ------------------ database/src/aggregated_views_queries/mod.rs | 6 +- .../session_average_time.rs | 110 --- .../sessions_stats.rs | 496 ------------ .../src/tables/connection_events/select.rs | 81 -- .../src/tables/domain_verifications/update.rs | 25 +- database/src/tables/registered_app/select.rs | 4 +- .../src/tables/registered_app/table_struct.rs | 6 +- database/src/tables/registered_app/update.rs | 33 +- database/src/tables/team/select.rs | 12 +- database/src/tables/team/table_struct.rs | 6 +- database/src/tables/team/update.rs | 45 +- database/src/tables/team_invites/update.rs | 23 +- database/src/tables/test_utils.rs | 4 + .../src/tables/user_app_privileges/select.rs | 9 +- .../src/tables/user_app_privileges/update.rs | 28 +- .../src/http/cloud/cancel_team_user_invite.rs | 7 + .../src/http/cloud/change_user_privileges.rs | 6 + server/src/http/cloud/delete_app.rs | 214 ++++++ server/src/http/cloud/delete_team.rs | 239 ++++++ .../domains/cancel_pending_domain_request.rs | 10 +- .../domains/remove_whitelisted_domain.rs | 10 +- .../cloud/domains/verify_domain_finish.rs | 10 +- .../http/cloud/domains/verify_domain_start.rs | 10 +- .../src/http/cloud/get_team_user_invites.rs | 6 + .../http/cloud/get_team_users_privileges.rs | 7 + .../grafana_utils/delete_registered_app.rs | 40 + .../http/cloud/grafana_utils/delete_team.rs | 50 ++ server/src/http/cloud/grafana_utils/mod.rs | 2 + server/src/http/cloud/invite_user_to_team.rs | 7 + server/src/http/cloud/leave_team.rs | 6 + server/src/http/cloud/mod.rs | 2 + server/src/http/cloud/register_new_app.rs | 3 +- server/src/http/cloud/register_new_team.rs | 2 + server/src/routes/cloud_router.rs | 7 + .../src/structs/cloud/cloud_http_endpoints.rs | 6 + 39 files changed, 813 insertions(+), 1607 deletions(-) delete mode 100644 database/src/aggregated_views_queries/connections_stats.rs delete mode 100644 database/src/aggregated_views_queries/events_stats.rs delete mode 100644 database/src/aggregated_views_queries/session_average_time.rs delete mode 100644 database/src/aggregated_views_queries/sessions_stats.rs create mode 100644 server/src/http/cloud/delete_app.rs create mode 100644 server/src/http/cloud/delete_team.rs create mode 100644 server/src/http/cloud/grafana_utils/delete_registered_app.rs create mode 100644 server/src/http/cloud/grafana_utils/delete_team.rs diff --git a/database/migrations/0002_team.sql b/database/migrations/0002_team.sql index 17139d1b..153dec8e 100644 --- a/database/migrations/0002_team.sql +++ b/database/migrations/0002_team.sql @@ -5,5 +5,7 @@ CREATE TABLE team( subscription subscription, team_admin_id TEXT NOT NULL, registration_timestamp TIMESTAMPTZ NOT NULL, + active BOOLEAN NOT NULL, + deactivated_at TIMESTAMPTZ, PRIMARY KEY (team_name, team_admin_id) ); \ No newline at end of file diff --git a/database/migrations/0004_registered_apps.sql b/database/migrations/0004_registered_apps.sql index dd741ac7..a0c2ad34 100644 --- a/database/migrations/0004_registered_apps.sql +++ b/database/migrations/0004_registered_apps.sql @@ -4,7 +4,9 @@ CREATE TABLE registered_apps( app_name TEXT NOT NULL, whitelisted_domains TEXT [] NOT NULL, ack_public_keys TEXT [] NOT NULL, - registration_timestamp TIMESTAMPTZ NOT NULL + registration_timestamp TIMESTAMPTZ NOT NULL, + active BOOLEAN NOT NULL, + deactivated_at TIMESTAMPTZ ); CREATE UNIQUE INDEX app_id_idx ON registered_apps(app_id); \ No newline at end of file diff --git a/database/src/aggregated_views_queries/connections_stats.rs b/database/src/aggregated_views_queries/connections_stats.rs deleted file mode 100644 index 23f3eeac..00000000 --- a/database/src/aggregated_views_queries/connections_stats.rs +++ /dev/null @@ -1,173 +0,0 @@ -/////////////////////////////////////// For now this will be commented out, as it might be used in nearby future. - -// use crate::{ -// db::Db, -// structs::{db_error::DbError, filter_requests::ConnectionStats, time_filters::TimeFilter}, -// tables::utils::{format_view_keys, format_view_name}, -// }; - -// pub const CONNECTIONS_STATS_BASE_VIEW_NAME: &str = "connection_stats_per_app"; -// pub const CONNECTIONS_STATS_BASE_KEYS: [(&'static str, bool); 4] = [ -// ("app_id", false), -// ("bucket", true), -// ("app_connection_count", true), -// ("clients_connection_count", true), -// ]; - -// impl Db { -// pub async fn get_connections_stats_by_app_id( -// &self, -// app_id: &str, -// filter: TimeFilter, -// ) -> Result, DbError> { -// let start_date = filter.to_date(); -// let bucket_size = filter.bucket_size(); - -// // Correctly selecting the view based on the bucket_size -// let prefix = match bucket_size { -// "1 hour" => "hourly", -// "1 day" => "daily", -// "1 month" => "monthly", -// _ => return Err(DbError::DatabaseError("Invalid bucket size".to_string())), -// }; - -// let formatted_keys = format_view_keys(prefix, &CONNECTIONS_STATS_BASE_KEYS); -// let formatted_view_name = format_view_name(prefix, CONNECTIONS_STATS_BASE_VIEW_NAME); -// let date_filter_key = CONNECTIONS_STATS_BASE_KEYS[1].0; -// let filter = format!("{prefix}_{date_filter_key}"); - -// let query = format!( -// "SELECT {formatted_keys} -// FROM {formatted_view_name} -// WHERE app_id = $1 AND {filter} >= $2 -// ORDER BY {filter} DESC", -// ); - -// println!("Query: {}", query); - -// sqlx::query_as::<_, ConnectionStats>(&query) -// .bind(app_id) -// .bind(start_date) -// .fetch_all(&self.connection_pool) -// .await -// .map_err(|e| e.into()) -// } -// } - -// #[cfg(feature = "cloud_integration_tests")] -// #[cfg(test)] -// mod tests { - -// use crate::{ -// structs::{session_type::SessionType, time_filters::TimeFilter}, -// tables::{sessions::table_struct::DbNcSession, utils::get_current_datetime}, -// }; -// use sqlx::types::chrono::Utc; - -// #[tokio::test] -// async fn test_connections() { -// let db = super::Db::connect_to_the_pool().await; -// db.truncate_all_tables().await.unwrap(); - -// // Create test team instance -// let team_id = "test_team_id".to_string(); -// let app_id = "test_app_id".to_string(); - -// db.setup_test_team(&team_id, &app_id, Utc::now()) -// .await -// .unwrap(); - -// let networks = vec![ -// "test_network_1", -// "test_network_2", -// "test_network_3", -// "test_network_4", -// "test_network_5", -// ]; -// // Create persistent a session for each odd number of network, for each session connect via app 3 times and for client connect number of network times -// for (i, network) in networks.iter().enumerate() { -// let session_id = format!("session_{app_id}_{i}"); - -// let session_start = get_current_datetime(); -// let session = DbNcSession { -// session_id: session_id.clone(), -// app_id: app_id.clone(), -// app_metadata: "test_metadata".to_string(), -// persistent: true, -// network: network.to_string(), -// client_data: None, -// session_open_timestamp: session_start.clone(), -// session_close_timestamp: None, -// }; - -// db.handle_new_session(&session, None, &"127.0.0.1".to_string(), &session_start) -// .await -// .unwrap(); - -// // Each time a session is created, means that app has been connected, create 2 more connections -// let mut tx = db.connection_pool.begin().await.unwrap(); -// db.create_new_connection_event_by_app( -// &mut tx, -// &session_id, -// &app_id, -// &network.to_string(), -// &network.to_string(), -// None, -// &session_start, -// ) -// .await -// .unwrap(); - -// db.create_new_connection_event_by_app( -// &mut tx, -// &session_id, -// &app_id, -// &network.to_string(), -// &network.to_string(), -// None, -// &session_start, -// ) -// .await -// .unwrap(); - -// for _j in 0..i { -// db.create_new_connection_event_by_client( -// &mut tx, -// &app_id, -// &network.to_string(), -// &session_id, -// &SessionType::Relay, -// &network.to_string(), -// None, -// &get_current_datetime(), -// ) -// .await -// .unwrap(); -// } - -// tx.commit().await.unwrap(); -// } - -// // Manually refresh the continuous aggregates -// db.refresh_continuous_aggregates(vec![ -// "hourly_connection_stats_per_app".to_string(), -// "daily_connection_stats_per_app".to_string(), -// "monthly_connection_stats_per_app".to_string(), -// ]) -// .await -// .unwrap(); - -// // Get stats for each network -// let stats = db -// .get_connections_stats_by_app_id(&app_id, TimeFilter::LastMonth) -// .await -// .unwrap(); - -// println!("{:#?}", stats); - -// assert_eq!(stats.len(), 1); -// assert_eq!(stats[0].app_id, app_id); -// assert_eq!(stats[0].app_connection_count, 15); -// assert_eq!(stats[0].clients_connection_count, 10); -// } -// } diff --git a/database/src/aggregated_views_queries/events_stats.rs b/database/src/aggregated_views_queries/events_stats.rs deleted file mode 100644 index 098adeba..00000000 --- a/database/src/aggregated_views_queries/events_stats.rs +++ /dev/null @@ -1,709 +0,0 @@ -#[cfg(feature = "cloud_integration_tests")] -#[cfg(test)] -mod test { - - use crate::{ - db::Db, - structs::{ - consts::{DAY_IN_SECONDS, HOUR_IN_SECONDS}, - device_metadata::{Device, DeviceMetadata, MobileMetadata, WebMetadata}, - event_type::EventType, - request_status::RequestStatus, - session_type::SessionType, - }, - tables::utils::get_current_datetime, - }; - use futures::future::join_all; - use rand::Rng; - use sqlx::types::chrono::{DateTime, Utc}; - use std::{sync::Arc, time::Duration}; - use tokio::task; - - #[tokio::test] - async fn test_requests_success_rate() { - let db = Db::connect_to_the_pool().await; - // db.truncate_all_tables().await.unwrap(); - - // Create test team instance - // let team_id = "test_team_id".to_string(); - let app_id = "TEMPLATE_UID".to_string(); - - let db_arc = Arc::new(db); - let mut tasks: Vec> = Vec::new(); - let network = "Solana".to_string(); - - let db_arc_clone = db_arc.clone(); - for i in 0..33 { - let db_clone_clone = db_arc_clone.clone(); - let app_id = app_id.clone(); - let network = network.clone(); - tasks.push(task::spawn(async move { - let mut tx = db_clone_clone - .connection_pool - .begin() - .await - .expect("Failed to start transaction"); - - let current_timestamp = get_current_datetime(); - let total_events = 300 - i; // Total events per day - let events_first_quarter = total_events / 7; // Approx 1/7th of events for first quarter - let events_second_quarter = events_first_quarter * 2; // 2 times the first quarter - - for j in 0..total_events { - let base_time_offset = i * DAY_IN_SECONDS; - let additional_millis = (j + 1) * 100; - - let (quarter_offset, n_th_value) = if j < events_first_quarter { - (0, 3) - } else if j < events_first_quarter + events_second_quarter { - (HOUR_IN_SECONDS / 4, 10) - } else { - (2 * HOUR_IN_SECONDS / 4, 2) - }; - - let creation_time: DateTime = current_timestamp - - Duration::from_secs(base_time_offset + quarter_offset) - - Duration::from_millis(additional_millis); - - // let status = if j % n_th_value == 0 { - // RequestStatus::Completed - // } else { - // RequestStatus::Pending - // }; - - let status = if rand::thread_rng().gen_range(0..=10) % 3 == 0 { - RequestStatus::Completed - } else { - RequestStatus::Pending - }; - - // Create new event - let event_id = db_clone_clone - .create_new_event_entry( - &mut tx, - &app_id, - &network, - &EventType::SignMessage, - &creation_time, - ) - .await - .unwrap(); - - let request_id = format!("test_request_id_{}_{}", i, j); - db_clone_clone - .create_new_event_sign_message( - &mut tx, - event_id, - &"test_session_id".to_string(), - &request_id, - &"test_network".to_string(), - ) - .await - .unwrap(); - - db_clone_clone - .update_event_sign_message(&mut tx, &request_id, status.clone()) - .await - .unwrap(); - - // Create new event - let event_id = db_clone_clone - .create_new_event_entry( - &mut tx, - &app_id, - &network, - &EventType::SignTransaction, - &creation_time, - ) - .await - .unwrap(); - - let request_id = format!("test_request_id_{}_{}_2", i, j); - db_clone_clone - .create_new_event_sign_transaction( - &mut tx, - event_id, - &"test_session_id".to_string(), - &request_id, - &"test_network".to_string(), - ) - .await - .unwrap(); - - db_clone_clone - .update_event_sign_transaction(&mut tx, &request_id, status.clone(), &None) - .await - .unwrap(); - - // Create new event - let event_id = db_clone_clone - .create_new_event_entry( - &mut tx, - &app_id, - &network, - &EventType::SignAndSendTransaction, - &creation_time, - ) - .await - .unwrap(); - - let request_id = format!("test_request_id_{}_{}_3", i, j); - db_clone_clone - .create_new_event_sign_and_send_transaction( - &mut tx, - event_id, - &"test_session_id".to_string(), - &request_id, - &"test_network".to_string(), - ) - .await - .unwrap(); - - db_clone_clone - .update_event_sign_and_send_transaction(&mut tx, &request_id, status, &None) - .await - .unwrap(); - } - - tx.commit().await.expect("Failed to commit transaction"); - })); - } - - // Await all tasks to complete - join_all(tasks).await; - - // We need to refresh manually the views - db_arc - .refresh_continuous_aggregates(vec![ - "quarter_events_sign_message_stats_per_app".to_string(), - "hour_events_sign_message_stats_per_app".to_string(), - "daily_events_sign_message_stats_per_app".to_string(), - ]) - .await - .unwrap(); - } - - #[tokio::test] - async fn test_requests_change_success_rate() { - let db = Db::connect_to_the_pool().await; - // db.truncate_all_tables().await.unwrap(); - - // Create test team instance - // let team_id = "test_team_id".to_string(); - let app_id = "TEMPLATE_UID".to_string(); - - let db_arc = Arc::new(db); - let mut tasks: Vec> = Vec::new(); - let network = "Solana".to_string(); - - let db_arc_clone = db_arc.clone(); - for i in 0..33 { - let db_clone_clone = db_arc_clone.clone(); - let app_id = app_id.clone(); - let network = network.clone(); - - tasks.push(task::spawn(async move { - let mut tx = db_clone_clone - .connection_pool - .begin() - .await - .expect("Failed to start transaction"); - - let current_timestamp = get_current_datetime(); - let total_events = 300 - i; // Total events per day - let events_first_quarter = total_events / 7; // Approx 1/7th of events for first quarter - let events_second_quarter = events_first_quarter * 2; // 2 times the first quarter - - let wallet_name = match i % 3 { - 0 => "test_wallet_name_1", - 1 => "test_wallet_name_2", - _ => "test_wallet_name_3", - }; - - for j in 0..total_events { - let base_time_offset = i * DAY_IN_SECONDS; - let additional_millis = (j + 1) * 100; - - let (quarter_offset, n_th_value) = if j < events_first_quarter { - (0, 3) - } else if j < events_first_quarter + events_second_quarter { - (HOUR_IN_SECONDS / 4, 10) - } else { - (2 * HOUR_IN_SECONDS / 4, 2) - }; - - let creation_time: DateTime = current_timestamp - - Duration::from_secs(base_time_offset + quarter_offset) - - Duration::from_millis(additional_millis); - - // let status = if j % n_th_value == 0 { - // RequestStatus::Completed - // } else { - // RequestStatus::Pending - // }; - - let status = if rand::thread_rng().gen_range(0..=10) % 3 == 0 { - RequestStatus::Completed - } else { - RequestStatus::Pending - }; - - // Create new event - let event_id = db_clone_clone - .create_new_event_entry( - &mut tx, - &app_id, - &network, - &EventType::SignMessage, - &creation_time, - ) - .await - .unwrap(); - - let request_id = format!("test_request_id_{}_{}_4", i, j); - db_clone_clone - .create_new_event_change_wallet( - &mut tx, - event_id, - &"test_session_id".to_string(), - &request_id, - &"test_network".to_string(), - &wallet_name.to_string(), - &"test_wallet_type".to_string(), - &"test_old_wallet_address".to_string(), - ) - .await - .unwrap(); - - db_clone_clone - .update_event_change_wallet(&mut tx, &request_id, status.clone(), &None) - .await - .unwrap(); - } - - tx.commit().await.expect("Failed to commit transaction"); - })); - } - - // Await all tasks to complete - join_all(tasks).await; - - // We need to refresh manually the views - db_arc - .refresh_continuous_aggregates(vec![ - "quarter_events_change_wallet_stats_per_app".to_string(), - "hour_events_change_wallet_stats_per_app".to_string(), - "daily_events_change_wallet_stats_per_app".to_string(), - ]) - .await - .unwrap(); - } - - #[tokio::test] - async fn test_requests_change_network_success_rate() { - let db = Db::connect_to_the_pool().await; - // db.truncate_all_tables().await.unwrap(); - - // Create test team instance - // let team_id = "test_team_id".to_string(); - let app_id = "TEMPLATE_UID".to_string(); - - let db_arc = Arc::new(db); - let mut tasks: Vec> = Vec::new(); - let network = "Solana".to_string(); - - let db_arc_clone = db_arc.clone(); - for i in 0..33 { - let db_clone_clone = db_arc_clone.clone(); - let app_id = app_id.clone(); - let network = network.clone(); - - tasks.push(task::spawn(async move { - let mut tx = db_clone_clone - .connection_pool - .begin() - .await - .expect("Failed to start transaction"); - - let current_timestamp = get_current_datetime(); - let total_events = 300 - i; // Total events per day - let events_first_quarter = total_events / 7; // Approx 1/7th of events for first quarter - let events_second_quarter = events_first_quarter * 2; // 2 times the first quarter - - let old_network = match i % 3 { - 0 => "SOLANA", - 1 => "APTOS", - _ => "NEAR", - }; - - for j in 0..total_events { - let base_time_offset = i * DAY_IN_SECONDS; - let additional_millis = (j + 1) * 100; - - let (quarter_offset, n_th_value) = if j < events_first_quarter { - (0, 3) - } else if j < events_first_quarter + events_second_quarter { - (HOUR_IN_SECONDS / 4, 10) - } else { - (2 * HOUR_IN_SECONDS / 4, 2) - }; - - let creation_time: DateTime = current_timestamp - - Duration::from_secs(base_time_offset + quarter_offset) - - Duration::from_millis(additional_millis); - - // let status = if j % n_th_value == 0 { - // RequestStatus::Completed - // } else { - // RequestStatus::Pending - // }; - - let status = if rand::thread_rng().gen_range(0..=10) % 3 == 0 { - RequestStatus::Completed - } else { - RequestStatus::Pending - }; - - // Create new event - let event_id = db_clone_clone - .create_new_event_entry( - &mut tx, - &app_id, - &network, - &EventType::ChangeNetwork, - &creation_time, - ) - .await - .unwrap(); - - let request_id = format!("test_request_id_{}_{}_4", i, j); - db_clone_clone - .create_new_event_change_network( - &mut tx, - event_id, - &"test_session_id".to_string(), - &request_id, - &old_network.to_string(), - ) - .await - .unwrap(); - - db_clone_clone - .update_event_change_network( - &mut tx, - &request_id, - status.clone(), - &Some("MY NEW NETWORK".to_string()), - ) - .await - .unwrap(); - } - - tx.commit().await.expect("Failed to commit transaction"); - })); - } - - // Await all tasks to complete - join_all(tasks).await; - - // We need to refresh manually the views - db_arc - .refresh_continuous_aggregates(vec![ - "quarter_events_change_network_stats_per_app".to_string(), - "hour_events_change_network_stats_per_app".to_string(), - "daily_events_change_network_stats_per_app".to_string(), - ]) - .await - .unwrap(); - } - - #[tokio::test] - async fn test_requests_client_connect_success_rate() { - let db = Db::connect_to_the_pool().await; - // db.truncate_all_tables().await.unwrap(); - - // Create test team instance - // let team_id = "test_team_id".to_string(); - let app_id = "TEMPLATE_UID".to_string(); - - let db_arc = Arc::new(db); - let mut tasks: Vec> = Vec::new(); - let network = "Solana".to_string(); - - let db_arc_clone = db_arc.clone(); - for i in 0..33 { - let db_clone_clone = db_arc_clone.clone(); - let app_id = app_id.clone(); - let network = network.clone(); - - tasks.push(task::spawn(async move { - let mut tx = db_clone_clone - .connection_pool - .begin() - .await - .expect("Failed to start transaction"); - - let current_timestamp = get_current_datetime(); - let total_events = 100 - i; // Total events per day - let events_first_quarter = total_events / 7; // Approx 1/7th of events for first quarter - let events_second_quarter = events_first_quarter * 2; // 2 times the first quarter - - let wallet_name = match i % 3 { - 0 => "test_wallet_name_1", - 1 => "test_wallet_name_2", - _ => "test_wallet_name_3", - }; - - for j in 0..total_events { - let base_time_offset = i * DAY_IN_SECONDS; - let additional_millis = (j + 1) * 100; - - let (quarter_offset, _n_th_value) = if j < events_first_quarter { - (0, 3) - } else if j < events_first_quarter + events_second_quarter { - (HOUR_IN_SECONDS / 4, 10) - } else { - (2 * HOUR_IN_SECONDS / 4, 2) - }; - - let creation_time: DateTime = current_timestamp - - Duration::from_secs(base_time_offset + quarter_offset) - - Duration::from_millis(additional_millis); - - let session_type = match j % 3 { - 0 => SessionType::Extension, - _ => SessionType::Relay, - }; - - // Create new event - let event_id = db_clone_clone - .create_new_event_entry( - &mut tx, - &app_id, - &network, - &EventType::ChangeNetwork, - &creation_time, - ) - .await - .unwrap(); - - let session_id = format!( - "TEST_SESSION_ID_{}_{}_{}", - base_time_offset, additional_millis, j - ); - db_clone_clone - .create_new_event_client_connect( - &mut tx, - event_id, - &"TEST_CLIENT_ID".to_string(), - &session_id, - &wallet_name.to_string(), - &"test_wallet_type".to_string(), - &session_type, - ) - .await - .unwrap(); - - // let success = j % 4 == 0; - let success = rand::thread_rng().gen_range(0..=10) % 3 == 0; - - db_clone_clone - .update_event_client_connect( - &mut tx, - &"TEST_CLIENT_ID".to_string(), - &session_id, - success, - &vec![format!( - "NEW_ADDRESS_{}_{}_{}", - base_time_offset, additional_millis, j - ) - .to_string()], - ) - .await - .unwrap(); - } - - tx.commit().await.expect("Failed to commit transaction"); - })); - } - - // Await all tasks to complete - join_all(tasks).await; - - // We need to refresh manually the views - db_arc - .refresh_continuous_aggregates(vec![ - "quarter_events_client_connect_wallet_stats_per_app".to_string(), - "hour_events_client_connect_wallet_stats_per_app".to_string(), - "daily_events_client_connect_wallet_stats_per_app".to_string(), - ]) - .await - .unwrap(); - } - - #[tokio::test] - async fn test_requests_app_connect_success_rate() { - let db = Db::connect_to_the_pool().await; - // db.truncate_all_tables().await.unwrap(); - - // Create test team instance - // let team_id = "test_team_id".to_string(); - let app_id = "TEMPLATE_UID".to_string(); - - let db_arc = Arc::new(db); - let mut tasks: Vec> = Vec::new(); - let network = "Solana".to_string(); - - let db_arc_clone = db_arc.clone(); - for i in 0..33 { - let db_clone_clone = db_arc_clone.clone(); - let app_id = app_id.clone(); - let network = network.clone(); - - tasks.push(task::spawn(async move { - let mut tx = db_clone_clone - .connection_pool - .begin() - .await - .expect("Failed to start transaction"); - - let current_timestamp = get_current_datetime(); - let total_events = 100 - i; // Total events per day - let events_first_quarter = total_events / 7; // Approx 1/7th of events for first quarter - let events_second_quarter = events_first_quarter * 2; // 2 times the first quarter - - for j in 0..total_events { - let base_time_offset = i * DAY_IN_SECONDS; - let additional_millis = (j + 1) * 100; - - let (quarter_offset, _n_th_value) = if j < events_first_quarter { - (0, 3) - } else if j < events_first_quarter + events_second_quarter { - (HOUR_IN_SECONDS / 4, 10) - } else { - (2 * HOUR_IN_SECONDS / 4, 2) - }; - - let creation_time: DateTime = current_timestamp - - Duration::from_secs(base_time_offset + quarter_offset) - - Duration::from_millis(additional_millis); - - let language = match j % 6 { - 0 | 1 | 2 => "English".to_string(), - 3 | 4 => "French".to_string(), - _ => "German".to_string(), - }; - - let device_metadata = match j % 7 { - 0 => { - let browser = if j % 2 == 0 { - "Chrome".to_string() - } else { - "Firefox".to_string() - }; - - let os = if j % 3 == 0 { - "Windows".to_string() - } else { - if rand::thread_rng().gen_range(0..=10) % 2 == 0 { - "Linux".to_string() - } else { - "Mac".to_string() - } - }; - - DeviceMetadata::Web(WebMetadata { - browser: browser, - os: os, - os_version: "test_os_version".to_string(), - browser_version: "test_version".to_string(), - }) - } - 1 | 2 | 3 | 4 | 5 => { - let rng = rand::thread_rng().gen::(); - - let system = match rng % 3 { - 0 => Device::Android, - 1 => Device::Apple, - _ => Device::Unknown, - }; - - DeviceMetadata::Mobile(MobileMetadata { - system: system, - version: "test_version".to_string(), - }) - } - _ => DeviceMetadata::Unknown, - }; - - // Create new event - let event_id = db_clone_clone - .create_new_event_entry( - &mut tx, - &app_id, - &network, - &EventType::ChangeNetwork, - &creation_time, - ) - .await - .unwrap(); - - let session_id = format!( - "TEST_SESSION_ID_{}_{}_{}", - base_time_offset, additional_millis, j - ); - db_clone_clone - .create_new_event_app_connect( - &mut tx, - event_id, - &app_id, - &network, - &session_id, - &device_metadata, - &language, - &"test_timezone".to_string(), - true, - &creation_time, - ) - .await - .unwrap(); - } - - tx.commit().await.expect("Failed to commit transaction"); - })); - } - - // Await all tasks to complete - join_all(tasks).await; - - // We need to refresh manually the views - db_arc - .refresh_continuous_aggregates(vec![ - "quarter_events_app_connect_language_stats_per_app".to_string(), - "hour_events_app_connect_language_stats_per_app".to_string(), - "daily_events_app_connect_language_stats_per_app".to_string(), - //////////////////////////////////////////////////////////////// - "quarter_events_app_connect_browser_stats_per_app".to_string(), - "hour_events_app_connect_browser_stats_per_app".to_string(), - "daily_events_app_connect_browser_stats_per_app".to_string(), - //////////////////////////////////////////////////////////////// - "quarter_events_app_connect_os_stats_per_app".to_string(), - "hour_events_app_connect_os_stats_per_app".to_string(), - "daily_events_app_connect_os_stats_per_app".to_string(), - //////////////////////////////////////////////////////////////// - "quarter_events_app_connect_system_stats_per_app".to_string(), - "hour_events_app_connect_system_stats_per_app".to_string(), - "daily_events_app_connect_system_stats_per_app".to_string(), - //////////////////////////////////////////////////////////////// - "quarter_events_app_connect_system_stats_per_app".to_string(), - "hour_events_app_connect_system_stats_per_app".to_string(), - "daily_events_app_connect_system_stats_per_app".to_string(), - //////////////////////////////////////////////////////////////// - "quarter_events_app_connect_session_type_stats_per_app".to_string(), - "hour_events_app_connect_session_type_stats_per_app".to_string(), - "daily_events_app_connect_session_type_stats_per_app".to_string(), - ]) - .await - .unwrap(); - } -} diff --git a/database/src/aggregated_views_queries/mod.rs b/database/src/aggregated_views_queries/mod.rs index 0ebe1681..6d15df19 100644 --- a/database/src/aggregated_views_queries/mod.rs +++ b/database/src/aggregated_views_queries/mod.rs @@ -1,5 +1,7 @@ -pub mod connections_stats; -pub mod session_average_time; +// Files unnecessary for now. Check commit history + +// pub mod connections_stats; +// pub mod session_average_time; // Used for data generation in tests // pub mod events_stats; // pub mod sessions_stats; diff --git a/database/src/aggregated_views_queries/session_average_time.rs b/database/src/aggregated_views_queries/session_average_time.rs deleted file mode 100644 index defff4d9..00000000 --- a/database/src/aggregated_views_queries/session_average_time.rs +++ /dev/null @@ -1,110 +0,0 @@ -/////////////////////////////////////// For now this will be commented out, as it might be used in nearby future. - -// use crate::{db::Db, structs::filter_requests::SessionAvgTime}; -// use sqlx::Error; - -// pub const MONTHLY_SESSIONS_AVG_TIME_PER_APP_VIEW_NAME: &str = "monthly_sessions_avg_time_per_app"; -// pub const MONTHLY_SESSIONS_AVG_TIME_PER_APP_KEYS: &str = -// "app_id, monthly_bucket as bucket, monthly_avg_session_duration_seconds as average_duration_seconds"; - -// impl Db { -// pub async fn get_monthly_avg_session_duration_per_app( -// &self, -// app_id: &str, -// ) -> Result, Error> { -// let query = format!( -// "SELECT {MONTHLY_SESSIONS_AVG_TIME_PER_APP_KEYS} -// FROM {MONTHLY_SESSIONS_AVG_TIME_PER_APP_VIEW_NAME} -// WHERE app_id = $1" -// ); - -// sqlx::query_as::<_, SessionAvgTime>(&query) -// .bind(app_id) -// .fetch_all(&self.connection_pool) -// .await -// } -// } - -// #[cfg(feature = "cloud_integration_tests")] -// #[cfg(test)] -// mod test { - -// use crate::tables::sessions::table_struct::DbNcSession; -// use sqlx::types::chrono::Utc; -// use std::time::Duration; - -// #[tokio::test] -// async fn test_average_session_duration() { -// let db = super::Db::connect_to_the_pool().await; -// db.truncate_all_tables().await.unwrap(); - -// // Create test team instance -// let team_id = "test_team_id".to_string(); -// let app_id = "test_app_id".to_string(); - -// db.setup_test_team(&team_id, &app_id, Utc::now()) -// .await -// .unwrap(); - -// // Create sessions -// let now = Utc::now(); -// let sessions = vec![ -// ( -// now - Duration::from_secs(4 * 60 * 60), -// now - Duration::from_secs((4 * 60 - 85) * 60), -// ), // 1 hour 25 minutes session, 4 hours ago -// ( -// now - Duration::from_secs((2 * 60 + 35) * 60), -// now - Duration::from_secs((2 * 60 + 10) * 60), -// ), // 25 minutes session, 2 hours and 35 minutes ago -// ( -// now - Duration::from_secs((1 * 60 + 50) * 60), -// now - Duration::from_secs((1 * 60 + 40) * 60), -// ), // 10 minutes session, 1 hour and 50 minutes ago -// ]; - -// for (start, end) in sessions.iter() { -// let session = DbNcSession { -// session_id: format!("session_id_{}", start.timestamp()), -// app_id: "test_app_id".to_string(), -// app_metadata: "test_app_metadata".to_string(), -// persistent: false, -// network: "test_network".to_string(), -// client_data: None, -// session_open_timestamp: start.clone(), -// session_close_timestamp: None, -// }; - -// db.handle_new_session(&session, None, &"127.0.0.1".to_string(), &start) -// .await -// .unwrap(); -// db.close_session(&session.session_id, *end).await.unwrap(); -// } - -// // We need to refresh manually the views -// db.refresh_continuous_aggregates(vec![ -// "daily_sessions_avg_time_per_app".to_string(), -// "monthly_sessions_avg_time_per_app".to_string(), -// ]) -// .await -// .unwrap(); - -// let result = db -// .get_monthly_avg_session_duration_per_app(&app_id) -// .await -// .unwrap(); - -// assert_eq!(result.len(), 1); - -// let expected_avg_duration_seconds: f64 = sessions -// .iter() -// .map(|(start, end)| (end.timestamp() - start.timestamp()) as f64) -// .sum::() -// / sessions.len() as f64; - -// assert_eq!( -// result[0].average_duration_seconds, -// expected_avg_duration_seconds -// ); -// } -// } diff --git a/database/src/aggregated_views_queries/sessions_stats.rs b/database/src/aggregated_views_queries/sessions_stats.rs deleted file mode 100644 index 50783fcf..00000000 --- a/database/src/aggregated_views_queries/sessions_stats.rs +++ /dev/null @@ -1,496 +0,0 @@ -#[cfg(feature = "cloud_integration_tests")] -#[cfg(test)] -mod tests { - use crate::{ - db::Db, - structs::{ - client_data::ClientData, geo_location::Geolocation, privilege_level::PrivilegeLevel, - session_type::SessionType, - }, - tables::{ - registered_app::table_struct::DbRegisteredApp, - sessions::table_struct::{DbNcSession, SESSIONS_TABLE_NAME}, - team::table_struct::Team, - user_app_privileges::table_struct::UserAppPrivilege, - utils::{get_current_datetime, to_microsecond_precision}, - }, - }; - use futures::future::join_all; - use rand::{thread_rng, Rng}; - use sqlx::query; - use sqlx::{types::chrono::Utc, Transaction}; - use std::{sync::Arc, time::Duration}; - use tokio::sync::Semaphore; - - #[derive(Debug, Clone)] - struct BoundingBox { - min_lat: f64, - max_lat: f64, - min_lon: f64, - max_lon: f64, - } - - #[tokio::test] - async fn generate_session_stats_2() { - let db = Db::connect_to_the_pool().await; - - // let team_id = "test_team_id".to_string(); - - let now = get_current_datetime(); - let start_date = now - Duration::from_secs(60 * 60 * 24 * 45); // Start of second period, 45 days ago - let end_date = now; - let num_days = (end_date - start_date).num_days(); - - let db_arc = Arc::new(db); - - // // Add second app - let app_id_2 = "018fe846-3b7d-7af8-a14f-96d97820bccf".to_string(); - // let registered_app = DbRegisteredApp { - // team_id: team_id.clone(), - // app_id: app_id_2.clone(), - // app_name: format!("{app_id_2}_APP_NAME").to_string(), - // whitelisted_domains: vec!["localhost".to_string()], - // ack_public_keys: vec!["key".to_string()], - // registration_timestamp: get_current_datetime(), - // }; - - // let mut tx = db_arc.connection_pool.begin().await.unwrap(); - // db_arc - // .register_new_app_within_tx(&mut tx, ®istered_app) - // .await - // .unwrap(); - // tx.commit().await.unwrap(); - - let land_boxes = Arc::new(vec![ - BoundingBox { - min_lat: 49.0, - max_lat: 70.0, - min_lon: -126.0, - max_lon: -60.0, - }, // Canada - BoundingBox { - min_lat: 24.0, - max_lat: 50.0, - min_lon: -125.0, - max_lon: -66.0, - }, // USA - BoundingBox { - min_lat: -55.0, - max_lat: -21.0, - min_lon: -74.0, - max_lon: -34.0, - }, // South America - BoundingBox { - min_lat: 34.0, - max_lat: 60.0, - min_lon: -10.0, - max_lon: 35.0, - }, // Europe - BoundingBox { - min_lat: -35.0, - max_lat: 10.0, - min_lon: 112.0, - max_lon: 154.0, - }, // Australia - BoundingBox { - min_lat: 8.0, - max_lat: 37.0, - min_lon: 68.0, - max_lon: 97.0, - }, // India - BoundingBox { - min_lat: 30.0, - max_lat: 45.0, - min_lon: 120.0, - max_lon: 150.0, - }, // Japan - ]); - - let points_per_box = 10; - let mut land_points = Vec::new(); - for box_ in land_boxes.iter() { - let mut box_points = Vec::with_capacity(points_per_box); - for _ in 0..points_per_box { - let lat = thread_rng().gen_range(box_.min_lat..box_.max_lat); - let lon = thread_rng().gen_range(box_.min_lon..box_.max_lon); - box_points.push((lat, lon)); - } - land_points.push(box_points); - } - let land_points = Arc::new(land_points); - - let semaphore = Arc::new(Semaphore::new(16)); - let mut handles = vec![]; - - for day_offset in 0..num_days { - let app_id_clone = app_id_2.clone(); - let db_arc_clone = db_arc.clone(); - let sem_clone = semaphore.clone(); - let land_points_clone = land_points.clone(); - - let handle = tokio::task::spawn(async move { - let _permit = sem_clone - .acquire() - .await - .expect("Failed to acquire semaphore permit"); - - let day_start = start_date + Duration::from_secs(60 * 60 * 24 * day_offset as u64); - - let is_seventh_day = day_offset % 7 == 6; // day_offset is 0-based; 6 represents the 7th day - - // Loop through each hour of the day - for hour in 0..24 { - let hour_start = day_start + Duration::from_secs(60 * 60 * hour as u64); - let session_count = if is_seventh_day { - // For the 7th day, limit sessions to 3-6 per hour - rand::thread_rng().gen_range(3..=6) - } else if hour >= 6 && hour <= 19 { - // Regular daytime hours - thread_rng().gen_range(20..=40) - } else { - // Regular nighttime hours - rand::thread_rng().gen_range(10..=20) - }; - - // Generate sessions for this hour - for i in 0..session_count { - let session_start = - hour_start + Duration::from_secs(rand::thread_rng().gen_range(0..3600)); - let session_end = session_start + Duration::from_secs(600); // 10 minutes - - let session = DbNcSession { - session_id: uuid7::uuid7().to_string(), - app_id: app_id_clone.clone(), - app_metadata: "test_metadata".to_string(), - persistent: false, - network: "Solana".to_string(), - client_data: None, - session_open_timestamp: session_start.clone(), - session_close_timestamp: Some(session_end), - }; - - let ip_address = "127.0.0.1".to_string(); - - db_arc_clone - .handle_new_session(&session, None, &ip_address, &session_start) - .await - .unwrap(); - - if i % 3 != 0 { - let data = ClientData { - client_id: "test_client_id".to_string(), - wallet_name: "test_wallet_name".to_string(), - wallet_type: "test_wallet_type".to_string(), - client_profile_id: session_start.timestamp(), - connected_at: session_start, - }; - - // Update the session with the client data - let query_body = format!( - "UPDATE {SESSIONS_TABLE_NAME} SET client_data = $1 WHERE session_id = $2" - ); - - query(&query_body) - .bind(&data) - .bind(&session.session_id) - .execute(&db_arc_clone.connection_pool) - .await - .unwrap(); - } - - let amount = thread_rng().gen_range(0..=10); - // Generate random amount of additional connections from user - for _ in 0..amount { - let geolocation = match rand::thread_rng().gen_range(0..=10) > 3 { - true => None, - false => { - // Randomly select a bounding box - let box_index = - thread_rng().gen_range(0..land_points_clone.len()); - let point_index = thread_rng() - .gen_range(0..land_points_clone[box_index].len()); - let (lat, lon) = land_points_clone[box_index][point_index]; - - Some(Geolocation { - country: None, - city: None, - lat: Some(lat), - lon: Some(lon), - }) - } - }; - - let mut tx = db_arc_clone.connection_pool.begin().await.unwrap(); - db_arc_clone - .create_new_connection_event_by_client( - &mut tx, - &session.app_id, - &"Solana".to_string(), - &session.session_id, - &SessionType::Relay, - &ip_address, - geolocation, - &(session_start + Duration::from_secs(1)), - ) - .await - .unwrap(); - tx.commit().await.unwrap(); - } - - db_arc_clone - .close_session(&session.session_id, session_end) - .await - .unwrap(); - } - } - }); - - handles.push(handle); - } - - // Wait for all tasks to complete - join_all(handles).await; - - // Manually refresh the continuous aggregates - db_arc - .refresh_continuous_aggregates(vec![ - "quarter_sessions_stats_per_app".to_string(), - "hourly_sessions_stats_per_app".to_string(), - "daily_sessions_stats_per_app".to_string(), - // "monthly_sessions_stats_per_app".to_string(), - ]) - .await - .unwrap(); - - db_arc - .refresh_continuous_aggregates(vec![ - "quarter_connection_stats_per_app".to_string(), - "hourly_connection_stats_per_app".to_string(), - "daily_connection_stats_per_app".to_string(), - // "monthly_sessions_stats_per_app".to_string(), - ]) - .await - .unwrap(); - } - - #[tokio::test] - async fn tesadasdasdasd() { - let db = Db::connect_to_the_pool().await; - // db.truncate_all_tables().await.unwrap(); - - let privilege = UserAppPrivilege { - user_id: "test_admin".to_string().clone(), - app_id: "test_app_id_2".to_string().clone(), - privilege_level: PrivilegeLevel::Read, - creation_timestamp: to_microsecond_precision(&Utc::now()), - }; - db.add_new_privilege(&privilege).await.unwrap(); - } - - #[tokio::test] - async fn user_privs() { - let db = Db::connect_to_the_pool().await; - // db.truncate_all_tables().await.unwrap(); - - let user_id = "test@gmail.com".to_string(); - let email = "test@gmail.com".to_string(); - - db.add_new_user(&user_id, &email, None, None).await.unwrap(); - - let privilege = UserAppPrivilege { - user_id, - app_id: "test_app_id_2".to_string().clone(), - privilege_level: PrivilegeLevel::Read, - creation_timestamp: to_microsecond_precision(&Utc::now()), - }; - db.add_new_privilege(&privilege).await.unwrap(); - } - - #[tokio::test] - async fn new_team() { - let db = Db::connect_to_the_pool().await; - // db.truncate_all_tables().await.unwrap(); - - let team_id = "test_team_id_2".to_string(); - let app_id = "test_app_id_3".to_string(); - - let user_id = "test_admin_2".to_string(); - let email = "admin_2".to_string(); - let password_hash = "pass_hash".to_string(); - - let registration_timestamp = get_current_datetime(); - - db.add_new_user(&user_id, &email, Some(&password_hash), None) - .await - .unwrap(); - - let team = Team { - team_id: team_id.clone(), - team_name: "test_team_name".to_string(), - personal: false, - subscription: None, - team_admin_id: user_id.clone(), - registration_timestamp: registration_timestamp, - }; - - let registered_app = DbRegisteredApp { - team_id: team_id.clone(), - app_id: app_id.clone(), - app_name: format!("{app_id}_APP_NAME").to_string(), - whitelisted_domains: vec!["localhost".to_string()], - ack_public_keys: vec!["key".to_string()], - registration_timestamp: registration_timestamp, - }; - - let admin_privilege = UserAppPrivilege { - app_id: app_id.clone(), - creation_timestamp: registration_timestamp, - privilege_level: PrivilegeLevel::Admin, - user_id: user_id.clone(), - }; - - // Start a transaction - let mut tx: Transaction<'_, sqlx::Postgres> = db.connection_pool.begin().await.unwrap(); - - // Attempt to create the new team within the transaction - let _create_team_result = db.create_new_team_within_tx(&mut tx, &team).await.unwrap(); - - // Attempt to register the new app within the same transaction - let _register_app_result = db - .register_new_app_within_tx(&mut tx, ®istered_app) - .await - .unwrap(); - - // Attempt to add team admin within the same transaction - let _add_admin_result = db - .add_new_privilege_within_tx(&mut tx, &admin_privilege) - .await - .unwrap(); - - // If both actions succeeded, commit the transaction - tx.commit().await.unwrap(); - } - - #[tokio::test] - async fn generate_session_stats_3() { - let db = Db::connect_to_the_pool().await; - - let app_id_3 = "test_app_id_3".to_string(); - - let now = get_current_datetime(); - let start_date = now - Duration::from_secs(60 * 60 * 24 * 60); // Start of second period, 60 days ago - let end_date = now; - let num_days = (end_date - start_date).num_days(); - - let db_arc = Arc::new(db); - - let semaphore = Arc::new(Semaphore::new(8)); - let mut handles = vec![]; - - for day_offset in 0..num_days { - let app_id_clone = app_id_3.clone(); - let db_arc_clone = db_arc.clone(); - let sem_clone = semaphore.clone(); - - let handle = tokio::task::spawn(async move { - let _permit = sem_clone - .acquire() - .await - .expect("Failed to acquire semaphore permit"); - - let day_start = start_date + Duration::from_secs(60 * 60 * 24 * day_offset as u64); - - let is_seventh_day = day_offset % 7 == 6; // day_offset is 0-based; 6 represents the 7th day - - // Loop through each hour of the day - for hour in 0..24 { - let hour_start = day_start + Duration::from_secs(60 * 60 * hour as u64); - let session_count = if is_seventh_day { - // For the 7th day, limit sessions to 3-6 per hour - rand::thread_rng().gen_range(1..=5) - } else if hour >= 6 && hour <= 19 { - // Regular daytime hours - thread_rng().gen_range(40..=90) - } else { - // Regular nighttime hours - rand::thread_rng().gen_range(15..=45) - }; - - // Generate sessions for this hour - for i in 0..session_count { - let session_start = - hour_start + Duration::from_secs(rand::thread_rng().gen_range(0..3600)); - let session_end = session_start + Duration::from_secs(600); // 10 minutes - - let client_data = if i % 2 == 0 { - Some(ClientData { - client_id: "test_client_id".to_string(), - wallet_name: "test_wallet_name".to_string(), - wallet_type: "test_wallet_type".to_string(), - client_profile_id: 1, - connected_at: session_start, - }) - } else { - None - }; - - let session = DbNcSession { - session_id: uuid7::uuid7().to_string(), - app_id: app_id_clone.clone(), - app_metadata: "test_metadata".to_string(), - persistent: false, - network: "Solana".to_string(), - client_data, - session_open_timestamp: session_start.clone(), - session_close_timestamp: Some(session_end), - }; - - db_arc_clone - .handle_new_session( - &session, - None, - &"127.0.0.1".to_string(), - &session_start, - ) - .await - .unwrap(); - db_arc_clone - .close_session(&session.session_id, session_end) - .await - .unwrap(); - } - } - }); - - handles.push(handle); - } - - // Wait for all tasks to complete - join_all(handles).await; - - // Manually refresh the continuous aggregates - db_arc - .refresh_continuous_aggregates(vec![ - "quarter_sessions_stats_per_app".to_string(), - "hourly_sessions_stats_per_app".to_string(), - "daily_sessions_stats_per_app".to_string(), - // "monthly_sessions_stats_per_app".to_string(), - ]) - .await - .unwrap(); - } - - #[tokio::test] - async fn admin_privs_team_2() { - let db = Db::connect_to_the_pool().await; - // db.truncate_all_tables().await.unwrap(); - - let privilege = UserAppPrivilege { - user_id: "test_admin".to_string().clone(), - app_id: "test_app_id_3".to_string().clone(), - privilege_level: PrivilegeLevel::Read, - creation_timestamp: to_microsecond_precision(&Utc::now()), - }; - db.add_new_privilege(&privilege).await.unwrap(); - } -} diff --git a/database/src/tables/connection_events/select.rs b/database/src/tables/connection_events/select.rs index 9e5e1c1b..842245f9 100644 --- a/database/src/tables/connection_events/select.rs +++ b/database/src/tables/connection_events/select.rs @@ -2,91 +2,10 @@ use super::table_struct::ConnectionEvent; use crate::db::Db; use crate::structs::db_error::DbError; use crate::structs::entity_type::EntityType; -use crate::structs::filter_requests::DistinctConnectedClient; use crate::tables::connection_events::table_struct::CONNECTION_EVENTS_TABLE_NAME; use sqlx::query_as; impl Db { - pub async fn get_connection_events_by_session_id( - &self, - session_id: &String, - ) -> Result, DbError> { - let query = format!("SELECT * FROM {CONNECTION_EVENTS_TABLE_NAME} WHERE session_id = $1"); - let typed_query = query_as::<_, ConnectionEvent>(&query); - - return typed_query - .bind(&session_id) - .fetch_all(&self.connection_pool) - .await - .map_err(|e| e.into()); - } - - pub async fn get_connection_events_by_client_profile_id( - &self, - client_profile_id: &String, - ) -> Result, DbError> { - let query = format!( - "SELECT * FROM {CONNECTION_EVENTS_TABLE_NAME} WHERE entity_id = $1 AND entity_type = $2" - ); - let typed_query = query_as::<_, ConnectionEvent>(&query); - - return typed_query - .bind(&client_profile_id) - .bind(&EntityType::Client) - .fetch_all(&self.connection_pool) - .await - .map_err(|e| e.into()); - } - - pub async fn get_connection_events_by_app_id( - &self, - app_id: &String, - ) -> Result, DbError> { - let query = format!("SELECT * FROM {CONNECTION_EVENTS_TABLE_NAME} WHERE app_id = $1"); - let typed_query = query_as::<_, ConnectionEvent>(&query); - - return typed_query - .bind(&app_id) - .fetch_all(&self.connection_pool) - .await - .map_err(|e| e.into()); - } - - pub async fn get_connection_events_by_app( - &self, - app_id: &String, - ) -> Result, DbError> { - let query = format!("SELECT * FROM {CONNECTION_EVENTS_TABLE_NAME} WHERE entity_id = $1 AND entity_type = $2"); - let typed_query = query_as::<_, ConnectionEvent>(&query); - - return typed_query - .bind(&app_id) - .bind(&EntityType::App) - .fetch_all(&self.connection_pool) - .await - .map_err(|e| e.into()); - } - - pub async fn get_all_app_distinct_users( - &self, - app_id: &String, - ) -> Result, DbError> { - let query = format!( - "SELECT pk.public_key, MIN(ce.connected_at) AS first_connection, MAX(ce.connected_at) AS last_connection - FROM {CONNECTION_EVENTS_TABLE_NAME} ce - JOIN public_keys pk ON ce.entity_id = CAST(pk.client_profile_id AS TEXT) - WHERE ce.app_id = $1 AND ce.entity_type = $2 - GROUP BY pk.public_key" - ); - - return query_as::<_, DistinctConnectedClient>(&query) - .bind(app_id) - .bind(EntityType::Client) - .fetch_all(&self.connection_pool) - .await - .map_err(|e| e.into()); - } - pub async fn get_last_connection_attempt_by_client( &self, app_id: &String, diff --git a/database/src/tables/domain_verifications/update.rs b/database/src/tables/domain_verifications/update.rs index 1a7fc111..e8d26d05 100644 --- a/database/src/tables/domain_verifications/update.rs +++ b/database/src/tables/domain_verifications/update.rs @@ -81,8 +81,6 @@ impl Db { domain_name: &String, app_id: &String, ) -> Result<(), DbError> { - println!("domain_name: {}", domain_name); - println!("app_id: {}", app_id); let query_body = format!( "UPDATE {DOMAIN_VERIFICATIONS_TABLE_NAME} SET deleted_at = $1 WHERE domain_name = $2 AND app_id = $3 AND deleted_at IS NULL AND finished_at IS NOT NULL AND cancelled_at IS NULL" @@ -100,6 +98,29 @@ impl Db { Err(e) => Err(e).map_err(|e| e.into()), } } + + pub async fn delete_domain_verification_for_inactive_app( + &self, + tx: &mut sqlx::Transaction<'_, sqlx::Postgres>, + app_id: &String, + ) -> Result<(), DbError> { + + let query_body = format!( + "UPDATE {DOMAIN_VERIFICATIONS_TABLE_NAME} SET deleted_at = $1 WHERE app_id = $2 AND deleted_at IS NULL" + ); + + let query_result = query(&query_body) + .bind(&get_current_datetime()) + .bind(&app_id) + .execute(&mut **tx) + .await; + + match query_result { + Ok(_) => Ok(()), + Err(e) => Err(e).map_err(|e| e.into()), + } + } + } #[cfg(feature = "cloud_integration_tests")] diff --git a/database/src/tables/registered_app/select.rs b/database/src/tables/registered_app/select.rs index 6735b1f0..69a05d2e 100644 --- a/database/src/tables/registered_app/select.rs +++ b/database/src/tables/registered_app/select.rs @@ -7,7 +7,7 @@ impl Db { &self, app_id: &String, ) -> Result, DbError> { - let query = format!("SELECT * FROM {REGISTERED_APPS_TABLE_NAME} WHERE app_id = $1"); + let query = format!("SELECT * FROM {REGISTERED_APPS_TABLE_NAME} WHERE app_id = $1 AND active = true"); let typed_query = query_as::<_, DbRegisteredApp>(&query); return typed_query @@ -23,7 +23,7 @@ impl Db { team_id: &String, ) -> Result, DbError> { let query = format!( - "SELECT * FROM {REGISTERED_APPS_TABLE_NAME} WHERE app_name = $1 AND team_id = $2" + "SELECT * FROM {REGISTERED_APPS_TABLE_NAME} WHERE app_name = $1 AND team_id = $2 AND active = true" ); let typed_query = query_as::<_, DbRegisteredApp>(&query); diff --git a/database/src/tables/registered_app/table_struct.rs b/database/src/tables/registered_app/table_struct.rs index 528671c3..9023f32e 100644 --- a/database/src/tables/registered_app/table_struct.rs +++ b/database/src/tables/registered_app/table_struct.rs @@ -6,7 +6,7 @@ use sqlx::{ pub const REGISTERED_APPS_TABLE_NAME: &str = "registered_apps"; pub const REGISTERED_APPS_KEYS: &str = - "team_id, app_id, app_name, whitelisted_domains, ack_public_keys, registration_timestamp"; + "team_id, app_id, app_name, whitelisted_domains, ack_public_keys, registration_timestamp, active, deactivated_at"; #[derive(Clone, Debug, Eq, PartialEq)] pub struct DbRegisteredApp { @@ -16,6 +16,8 @@ pub struct DbRegisteredApp { pub whitelisted_domains: Vec, pub ack_public_keys: Vec, pub registration_timestamp: DateTime, + pub active: bool, + pub deactivated_at: Option>, } impl FromRow<'_, PgRow> for DbRegisteredApp { @@ -27,6 +29,8 @@ impl FromRow<'_, PgRow> for DbRegisteredApp { whitelisted_domains: row.get("whitelisted_domains"), ack_public_keys: row.get("ack_public_keys"), registration_timestamp: row.get("registration_timestamp"), + active: row.get("active"), + deactivated_at: row.get("deactivated_at"), }) } } diff --git a/database/src/tables/registered_app/update.rs b/database/src/tables/registered_app/update.rs index 4020acc1..31ad7c9e 100644 --- a/database/src/tables/registered_app/update.rs +++ b/database/src/tables/registered_app/update.rs @@ -1,11 +1,11 @@ use super::table_struct::{DbRegisteredApp, REGISTERED_APPS_KEYS, REGISTERED_APPS_TABLE_NAME}; -use crate::{db::Db, structs::db_error::DbError}; +use crate::{db::Db, structs::db_error::DbError, tables::utils::get_current_datetime}; use sqlx::{query, Transaction}; impl Db { pub async fn register_new_app(&self, app: &DbRegisteredApp) -> Result<(), DbError> { let query_body = format!( - "INSERT INTO {REGISTERED_APPS_TABLE_NAME} ({REGISTERED_APPS_KEYS}) VALUES ($1, $2, $3, $4, $5, $6)" + "INSERT INTO {REGISTERED_APPS_TABLE_NAME} ({REGISTERED_APPS_KEYS}) VALUES ($1, $2, $3, $4, $5, $6, $7, NULL)", ); let query_result = query(&query_body) @@ -15,6 +15,7 @@ impl Db { .bind(&app.whitelisted_domains) .bind(&app.ack_public_keys) .bind(&app.registration_timestamp) + .bind(&app.active) .execute(&self.connection_pool) .await; @@ -30,7 +31,7 @@ impl Db { app: &DbRegisteredApp, ) -> Result<(), DbError> { let query_body = format!( - "INSERT INTO {REGISTERED_APPS_TABLE_NAME} ({}) VALUES ($1, $2, $3, $4, $5, $6)", + "INSERT INTO {REGISTERED_APPS_TABLE_NAME} ({}) VALUES ($1, $2, $3, $4, $5, $6, $7, NULL)", REGISTERED_APPS_KEYS ); @@ -41,6 +42,7 @@ impl Db { .bind(&app.whitelisted_domains) .bind(&app.ack_public_keys) .bind(&app.registration_timestamp) + .bind(&app.active) .execute(&mut **tx) .await; @@ -57,7 +59,7 @@ impl Db { domain: &str, ) -> Result<(), DbError> { let query_body = format!( - "UPDATE {REGISTERED_APPS_TABLE_NAME} SET whitelisted_domains = array_append(whitelisted_domains, $1) WHERE app_id = $2", + "UPDATE {REGISTERED_APPS_TABLE_NAME} SET whitelisted_domains = array_append(whitelisted_domains, $1) WHERE app_id = $2 AND active = true", ); let query_result = query(&query_body) @@ -79,7 +81,7 @@ impl Db { domain: &str, ) -> Result<(), DbError> { let query_body = format!( - "UPDATE {REGISTERED_APPS_TABLE_NAME} SET whitelisted_domains = array_remove(whitelisted_domains, $1) WHERE app_id = $2", + "UPDATE {REGISTERED_APPS_TABLE_NAME} SET whitelisted_domains = array_remove(whitelisted_domains, $1) WHERE app_id = $2 AND active = true", ); let query_result = query(&query_body) @@ -93,4 +95,25 @@ impl Db { Err(e) => Err(e).map_err(|e| e.into()), } } + + pub async fn deactivate_app( + &self, + tx: &mut sqlx::Transaction<'_, sqlx::Postgres>, + app_id: &str, + ) -> Result<(), DbError> { + let query_body = format!( + "UPDATE {REGISTERED_APPS_TABLE_NAME} SET active = false, deactivated_at = $1 WHERE app_id = $2 AND active = true", + ); + + let query_result = query(&query_body) + .bind(&get_current_datetime()) + .bind(app_id) + .execute(&mut **tx) + .await; + + match query_result { + Ok(_) => Ok(()), + Err(e) => Err(e).map_err(|e| e.into()), + } + } } diff --git a/database/src/tables/team/select.rs b/database/src/tables/team/select.rs index 49cec382..75b6123b 100644 --- a/database/src/tables/team/select.rs +++ b/database/src/tables/team/select.rs @@ -11,7 +11,7 @@ impl Db { tx: Option<&mut Transaction<'_, sqlx::Postgres>>, team_id: &String, ) -> Result, DbError> { - let query = format!("SELECT * FROM {TEAM_TABLE_NAME} WHERE team_id = $1"); + let query = format!("SELECT * FROM {TEAM_TABLE_NAME} WHERE team_id = $1 AND active = true"); let typed_query = query_as::<_, Team>(&query); match tx { @@ -39,7 +39,7 @@ impl Db { let query = format!( "SELECT r.* FROM {REGISTERED_APPS_TABLE_NAME} r INNER JOIN team t ON r.team_id = t.team_id - WHERE t.team_id = $1 + WHERE t.team_id = $1 AND r.active = true AND t.active = true ORDER BY t.registration_timestamp DESC" ); let typed_query = query_as::<_, DbRegisteredApp>(&query); @@ -56,7 +56,7 @@ impl Db { admin_id: &String, ) -> Result, DbError> { let query = format!( - "SELECT * FROM {TEAM_TABLE_NAME} WHERE team_admin_id = $1 AND personal = false" + "SELECT * FROM {TEAM_TABLE_NAME} WHERE team_admin_id = $1 AND personal = false AND active = true" ); let typed_query = query_as::<_, Team>(&query); @@ -72,7 +72,7 @@ impl Db { admin_id: &String, ) -> Result, DbError> { let query = - format!("SELECT * FROM {TEAM_TABLE_NAME} WHERE team_admin_id = $1 AND personal = true"); + format!("SELECT * FROM {TEAM_TABLE_NAME} WHERE team_admin_id = $1 AND personal = true AND active = true"); let typed_query = query_as::<_, Team>(&query); return typed_query @@ -88,7 +88,7 @@ impl Db { admin_id: &String, ) -> Result, DbError> { let query = - format!("SELECT * FROM {TEAM_TABLE_NAME} WHERE team_name = $1 AND team_admin_id = $2"); + format!("SELECT * FROM {TEAM_TABLE_NAME} WHERE team_name = $1 AND active = true AND team_admin_id = $2"); let typed_query = query_as::<_, Team>(&query); return typed_query @@ -100,7 +100,7 @@ impl Db { } pub async fn get_team_by_admin_id(&self, admin_id: &String) -> Result, DbError> { - let query = format!("SELECT * FROM {TEAM_TABLE_NAME} WHERE team_admin_id = $1"); + let query = format!("SELECT * FROM {TEAM_TABLE_NAME} WHERE team_admin_id = $1 AND active = true"); let typed_query = query_as::<_, Team>(&query); return typed_query diff --git a/database/src/tables/team/table_struct.rs b/database/src/tables/team/table_struct.rs index 2e368fc2..b1ad1af7 100644 --- a/database/src/tables/team/table_struct.rs +++ b/database/src/tables/team/table_struct.rs @@ -7,7 +7,7 @@ use sqlx::{ pub const TEAM_TABLE_NAME: &str = "team"; pub const TEAM_KEYS: &str = - "team_id, team_name, personal, subscription, team_admin_id, registration_timestamp"; + "team_id, team_name, personal, subscription, team_admin_id, registration_timestamp, active, deactivated_at"; #[derive(Clone, Debug, Eq, PartialEq)] pub struct Team { @@ -18,6 +18,8 @@ pub struct Team { pub subscription: Option, pub team_admin_id: String, pub registration_timestamp: DateTime, + pub active: bool, + pub deactivated_at: Option>, } impl FromRow<'_, PgRow> for Team { @@ -29,6 +31,8 @@ impl FromRow<'_, PgRow> for Team { subscription: row.get("subscription"), registration_timestamp: row.get("registration_timestamp"), team_admin_id: row.get("team_admin_id"), + active: row.get("active"), + deactivated_at: row.get("deactivated_at"), }) } } diff --git a/database/src/tables/team/update.rs b/database/src/tables/team/update.rs index 4e61ad6e..cd30c3cf 100644 --- a/database/src/tables/team/update.rs +++ b/database/src/tables/team/update.rs @@ -2,7 +2,10 @@ use super::table_struct::TEAM_KEYS; use crate::{ db::Db, structs::{db_error::DbError, subscription::Subscription}, - tables::team::table_struct::{Team, TEAM_TABLE_NAME}, + tables::{ + team::table_struct::{Team, TEAM_TABLE_NAME}, + utils::get_current_datetime, + }, }; use sqlx::{query, Transaction}; @@ -12,8 +15,9 @@ impl Db { tx: &mut Transaction<'_, sqlx::Postgres>, team: &Team, ) -> Result<(), DbError> { - let query_body = - format!("INSERT INTO {TEAM_TABLE_NAME} ({TEAM_KEYS}) VALUES ($1, $2, $3, $4, $5, $6)"); + let query_body = format!( + "INSERT INTO {TEAM_TABLE_NAME} ({TEAM_KEYS}) VALUES ($1, $2, $3, $4, $5, $6, $7, NULL)" + ); let query_result = query(&query_body) .bind(&team.team_id) @@ -22,6 +26,7 @@ impl Db { .bind(&team.subscription) .bind(&team.team_admin_id) .bind(&team.registration_timestamp) + .bind(&team.active) .execute(&mut **tx) .await; @@ -32,8 +37,9 @@ impl Db { } pub async fn create_new_team(&self, team: &Team) -> Result<(), DbError> { - let query_body = - format!("INSERT INTO {TEAM_TABLE_NAME} ({TEAM_KEYS}) VALUES ($1, $2, $3, $4, $5, $6)"); + let query_body = format!( + "INSERT INTO {TEAM_TABLE_NAME} ({TEAM_KEYS}) VALUES ($1, $2, $3, $4, $5, $6, $7, NULL)" + ); let query_result = query(&query_body) .bind(&team.team_id) @@ -42,6 +48,7 @@ impl Db { .bind(&team.subscription) .bind(&team.team_admin_id) .bind(&team.registration_timestamp) + .bind(&team.active) .execute(&self.connection_pool) .await; @@ -56,8 +63,9 @@ impl Db { team_id: &String, subscription: &Subscription, ) -> Result<(), DbError> { - let query_body = - format!("UPDATE {TEAM_TABLE_NAME} SET subscription = $1 WHERE team_id = $2"); + let query_body = format!( + "UPDATE {TEAM_TABLE_NAME} SET subscription = $1 WHERE team_id = $2 AND active = true" + ); let query_result = query(&query_body) .bind(subscription) .bind(team_id) @@ -69,6 +77,27 @@ impl Db { Err(e) => Err(e).map_err(|e| e.into()), } } + + pub async fn deactivate_team( + &self, + tx: &mut sqlx::Transaction<'_, sqlx::Postgres>, + team_id: &str, + ) -> Result<(), DbError> { + let query_body = format!( + "UPDATE {TEAM_TABLE_NAME} SET active = false, deactivated_at = $1 WHERE team_id = $2 AND active = true", + ); + + let query_result = query(&query_body) + .bind(&get_current_datetime()) + .bind(team_id) + .execute(&mut **tx) + .await; + + match query_result { + Ok(_) => Ok(()), + Err(e) => Err(e).map_err(|e| e.into()), + } + } } #[cfg(feature = "cloud_integration_tests")] @@ -99,6 +128,8 @@ mod tests { subscription: None, team_admin_id: "test_team_admin_id".to_string(), registration_timestamp: to_microsecond_precision(&Utc::now()), + active: true, + deactivated_at: None, }; db.create_new_team(&team).await.unwrap(); diff --git a/database/src/tables/team_invites/update.rs b/database/src/tables/team_invites/update.rs index 2aab9ad3..22baf5a4 100644 --- a/database/src/tables/team_invites/update.rs +++ b/database/src/tables/team_invites/update.rs @@ -2,7 +2,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 sqlx::query; +use sqlx::{query, Transaction}; impl Db { pub async fn create_new_team_invite( @@ -71,4 +71,25 @@ impl Db { Err(e) => Err(e).map_err(|e| e.into()), } } + + pub async fn cancel_all_team_invites( + &self, + tx: &mut Transaction<'_, sqlx::Postgres>, + team_id: &String, + ) -> Result<(), DbError> { + let query_body = format!( + "UPDATE {TEAM_INVITES_TABLE_NAME} SET cancelled_at = $1 WHERE team_id = $2 AND accepted_at IS NULL AND cancelled_at IS NULL" + ); + + let query_result = query(&query_body) + .bind(&get_current_datetime()) + .bind(&team_id) + .execute(&mut **tx) + .await; + + match query_result { + Ok(_) => Ok(()), + Err(e) => Err(e).map_err(|e| e.into()), + } + } } diff --git a/database/src/tables/test_utils.rs b/database/src/tables/test_utils.rs index f8b8fbfd..7f84c30e 100644 --- a/database/src/tables/test_utils.rs +++ b/database/src/tables/test_utils.rs @@ -102,6 +102,8 @@ pub mod test_utils { subscription: None, team_admin_id: user_id.clone(), registration_timestamp: registration_timestamp, + active: true, + deactivated_at: None, }; let registered_app = DbRegisteredApp { @@ -111,6 +113,8 @@ pub mod test_utils { whitelisted_domains: vec!["localhost".to_string()], ack_public_keys: vec!["key".to_string()], registration_timestamp: registration_timestamp, + active: true, + deactivated_at: None, }; let admin_privilege = UserAppPrivilege { diff --git a/database/src/tables/user_app_privileges/select.rs b/database/src/tables/user_app_privileges/select.rs index 092bd2d6..44320216 100644 --- a/database/src/tables/user_app_privileges/select.rs +++ b/database/src/tables/user_app_privileges/select.rs @@ -13,6 +13,8 @@ use sqlx::{query_as, types::chrono::DateTime}; use sqlx::{types::chrono::Utc, Row}; use std::collections::HashMap; +// When app or team is not active, the privileges will be deleted, so we don't need to check it + impl Db { pub async fn get_privilege_by_user_id_and_app_id( &self, @@ -122,7 +124,7 @@ impl Db { LEFT JOIN {REGISTERED_APPS_TABLE_NAME} ra ON t.team_id = ra.team_id LEFT JOIN {USER_APP_PRIVILEGES_TABLE_NAME} uap ON ra.app_id = uap.app_id AND uap.user_id = $1 JOIN {USERS_TABLE_NAME} gu ON t.team_admin_id = gu.user_id - WHERE t.team_admin_id = $1 OR uap.user_id = $1 + WHERE (t.team_admin_id = $1 OR uap.user_id = $1) AND ra.active = true ) SELECT rt.team_id, rt.team_name, rt.personal, rt.subscription, rt.registration_timestamp, rt.team_admin_email, rt.team_admin_id, ra.app_id, ra.app_name, ra.whitelisted_domains, @@ -132,6 +134,7 @@ impl Db { FROM RelevantTeams rt LEFT JOIN {REGISTERED_APPS_TABLE_NAME} ra ON rt.team_id = ra.team_id LEFT JOIN {USER_APP_PRIVILEGES_TABLE_NAME} uap ON ra.app_id = uap.app_id AND uap.user_id = $1 + WHERE ra.active = true ORDER BY rt.team_id, ra.app_id" ); @@ -165,6 +168,8 @@ impl Db { subscription: row.get("subscription"), registration_timestamp: row.get("registration_timestamp"), team_admin_id: row.get("team_admin_id"), + active: row.get("active"), + deactivated_at: row.get("deactivated_at"), }; let admin_email = row.get("team_admin_email"); @@ -185,6 +190,8 @@ impl Db { whitelisted_domains: row.get("whitelisted_domains"), ack_public_keys: row.get("ack_public_keys"), registration_timestamp: row.get("app_registration_timestamp"), + active: row.get("active"), + deactivated_at: row.get("deactivated_at"), }; let privilege = UserAppPrivilege { diff --git a/database/src/tables/user_app_privileges/update.rs b/database/src/tables/user_app_privileges/update.rs index bd27f45a..db544358 100644 --- a/database/src/tables/user_app_privileges/update.rs +++ b/database/src/tables/user_app_privileges/update.rs @@ -62,8 +62,9 @@ impl Db { team_id: &String, ) -> Result<(), DbError> { // Retrieve all apps associated with the team - let apps_query = - format!("SELECT app_id FROM {REGISTERED_APPS_TABLE_NAME} WHERE team_id = $1"); + let apps_query = format!( + "SELECT app_id FROM {REGISTERED_APPS_TABLE_NAME} WHERE team_id = $1 AND active = true" + ); let apps: Vec = sqlx::query_as(&apps_query) .bind(team_id) .fetch_all(&self.connection_pool) @@ -110,8 +111,9 @@ impl Db { team_id: &String, ) -> Result<(), DbError> { // Retrieve all apps associated with the team - let apps_query = - format!("SELECT app_id FROM {REGISTERED_APPS_TABLE_NAME} WHERE team_id = $1"); + let apps_query = format!( + "SELECT app_id FROM {REGISTERED_APPS_TABLE_NAME} WHERE team_id = $1 AND active = true" + ); let apps: Vec = sqlx::query_as(&apps_query) .bind(team_id) .fetch_all(&self.connection_pool) @@ -245,6 +247,18 @@ impl Db { Err(e) => Err(e).map_err(|e| e.into()), } } + pub async fn remove_privileges_for_inactive_app_within_tx( + &self, + tx: &mut sqlx::Transaction<'_, sqlx::Postgres>, + app_id: &str, + ) -> Result<(), DbError> { + let query_body = format!("DELETE FROM {USER_APP_PRIVILEGES_TABLE_NAME} WHERE app_id = $1"); + let query_result = query(&query_body).bind(app_id).execute(&mut **tx).await; + match query_result { + Ok(_) => Ok(()), + Err(e) => Err(e).map_err(|e| e.into()), + } + } } #[cfg(feature = "cloud_integration_tests")] @@ -333,6 +347,8 @@ mod tests { app_name: format!("test_app_name_{}", i), team_id: team_id.clone(), registration_timestamp: to_microsecond_precision(&Utc::now()), + active: true, + deactivated_at: None, }; db.register_new_app(&app).await.unwrap(); @@ -357,6 +373,8 @@ mod tests { subscription: None, team_admin_id: "test_team_admin_id".to_string(), registration_timestamp: to_microsecond_precision(&Utc::now()), + active: true, + deactivated_at: None, }; db.create_new_team(&team).await.unwrap(); @@ -371,6 +389,8 @@ mod tests { app_name: format!("test_app_name_{}", i), team_id: team_id.clone(), registration_timestamp: to_microsecond_precision(&Utc::now()), + active: true, + deactivated_at: None, }; db.register_new_app(&app).await.unwrap(); diff --git a/server/src/http/cloud/cancel_team_user_invite.rs b/server/src/http/cloud/cancel_team_user_invite.rs index ff1977df..26857aff 100644 --- a/server/src/http/cloud/cancel_team_user_invite.rs +++ b/server/src/http/cloud/cancel_team_user_invite.rs @@ -44,6 +44,13 @@ pub async fn cancel_team_user_invite( )); } + if team.active == false { + return Err(( + StatusCode::BAD_REQUEST, + CloudApiErrors::TeamDoesNotExist.to_string(), + )); + } + // Check team type if team.personal { return Err(( diff --git a/server/src/http/cloud/change_user_privileges.rs b/server/src/http/cloud/change_user_privileges.rs index c837b5a3..986af6a1 100644 --- a/server/src/http/cloud/change_user_privileges.rs +++ b/server/src/http/cloud/change_user_privileges.rs @@ -64,6 +64,12 @@ pub async fn change_user_privileges( CloudApiErrors::InsufficientPermissions.to_string(), )); } + if team.active == false { + return Err(( + StatusCode::BAD_REQUEST, + CloudApiErrors::TeamDoesNotExist.to_string(), + )); + } // Check team type if team.personal { diff --git a/server/src/http/cloud/delete_app.rs b/server/src/http/cloud/delete_app.rs new file mode 100644 index 00000000..9925984e --- /dev/null +++ b/server/src/http/cloud/delete_app.rs @@ -0,0 +1,214 @@ +use super::utils::{custom_validate_uuid, validate_request}; +use crate::{ + env::is_env_production, + http::cloud::grafana_utils::delete_registered_app::handle_grafana_delete_app, + middlewares::auth_middleware::UserId, structs::cloud::api_cloud_errors::CloudApiErrors, +}; +use axum::{extract::State, http::StatusCode, Extension, Json}; +use database::{db::Db, structs::privilege_level::PrivilegeLevel}; +use garde::Validate; +use log::{error, warn}; +use openapi::apis::configuration::Configuration; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use ts_rs::TS; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Validate)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct HttpDeleteAppRequest { + #[garde(custom(custom_validate_uuid))] + pub app_id: String, +} + +pub async fn delete_app( + State(db): State>, + State(grafana_conf): State>, + Extension(user_id): Extension, + Json(request): Json, +) -> Result, (StatusCode, String)> { + // Validate request + validate_request(&request, &())?; + warn!("Delete app request: {:?}", request); + + // First check if app exists + match db.get_registered_app_by_app_id(&request.app_id).await { + Ok(Some(app)) => { + // Check if app is active + if app.active != true { + return Err(( + StatusCode::BAD_REQUEST, + CloudApiErrors::AppDoesNotExist.to_string(), + )); + } + + // Check if user has admin privileges + match db + .get_privilege_by_user_id_and_app_id(&user_id, &request.app_id) + .await + { + Ok(Some(user_privilege)) => { + if user_privilege.privilege_level != PrivilegeLevel::Admin { + return Err(( + StatusCode::BAD_REQUEST, + CloudApiErrors::InsufficientPermissions.to_string(), + )); + } + } + Ok(None) => { + return Err(( + StatusCode::BAD_REQUEST, + CloudApiErrors::InsufficientPermissions.to_string(), + )); + } + Err(err) => { + error!("Failed to get privileges by app id and user id: {:?}", err); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::DatabaseError.to_string(), + )); + } + } + + // Delete the app + // Start a transaction + let mut tx = db.connection_pool.begin().await.unwrap(); + + if let Err(err) = db.deactivate_app(&mut tx, &request.app_id).await { + let _ = tx + .rollback() + .await + .map_err(|err| error!("Failed to rollback transaction: {:?}", err)); + error!("Failed to deactivate app: {:?}", err); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::DatabaseError.to_string(), + )); + } + if let Err(err) = db + .remove_privileges_for_inactive_app_within_tx(&mut tx, &request.app_id) + .await + { + let _ = tx + .rollback() + .await + .map_err(|err| error!("Failed to rollback transaction: {:?}", err)); + error!("Failed to create app: {:?}", err); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::DatabaseError.to_string(), + )); + } + if let Err(err) = db + .delete_domain_verification_for_inactive_app(&mut tx, &request.app_id) + .await + { + let _ = tx + .rollback() + .await + .map_err(|err| error!("Failed to rollback transaction: {:?}", err)); + error!("Failed to create app: {:?}", err); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::DatabaseError.to_string(), + )); + } + // Grafana, delete app + // TODO, fix this by fixing methods for setting up grafana datasource + if is_env_production() { + handle_grafana_delete_app(&grafana_conf, &request.app_id).await?; + } + + // If nothing failed commit the transaction + tx.commit().await.unwrap(); + return Ok(Json(())); + } + Ok(None) => { + return Err(( + StatusCode::BAD_REQUEST, + CloudApiErrors::AppDoesNotExist.to_string(), + )); + } + Err(err) => { + error!("Failed to get app by app id: {:?}", err); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::DatabaseError.to_string(), + )); + } + } +} + +#[cfg(feature = "cloud_integration_tests")] +#[cfg(test)] +mod tests { + use crate::{ + env::JWT_SECRET, + http::cloud::{ + delete_app::HttpDeleteAppRequest, register_new_app::HttpRegisterNewAppRequest, + }, + structs::cloud::cloud_http_endpoints::HttpCloudEndpoint, + test_utils::test_utils::{ + add_test_app, add_test_team, add_user_to_test_team, convert_response, create_test_app, + generate_valid_name, register_and_login_random_user, + }, + }; + use axum::{ + body::Body, + extract::ConnectInfo, + http::{Method, Request}, + }; + use std::net::SocketAddr; + use tower::ServiceExt; + + #[tokio::test] + async fn test_delete_app() { + let test_app = create_test_app(false).await; + let (auth_token, _email, _password) = register_and_login_random_user(&test_app).await; + let (user_token, user_email, _password) = register_and_login_random_user(&test_app).await; + + // Register new team + let team_name = generate_valid_name(); + let team_id = add_test_team(&team_name, &auth_token, &test_app, false) + .await + .unwrap(); + + // Register app under the team + let app_name = generate_valid_name(); + let request = HttpRegisterNewAppRequest { + team_id: team_id.clone(), + app_name: app_name.clone(), + }; + + // unwrap err as it should have failed + let app_id = add_test_app(&request, &auth_token, &test_app) + .await + .unwrap(); + let _ = + add_user_to_test_team(&team_id, &user_email, &auth_token, &user_token, &test_app).await; + + let request = HttpDeleteAppRequest { + app_id: app_id.clone(), + }; + let ip: ConnectInfo = ConnectInfo(SocketAddr::from(([127, 0, 0, 1], 8080))); + let json = serde_json::to_string(&request).unwrap(); + let auth = auth_token.encode(JWT_SECRET()).unwrap(); + + let req = Request::builder() + .method(Method::POST) + .header("content-type", "application/json") + .header("authorization", format!("Bearer {auth}")) + .uri(format!( + "/cloud/private{}", + HttpCloudEndpoint::DeleteApp.to_string() + )) + .extension(ip) + .body(Body::from(json)) + .unwrap(); + + // Send request + let response = test_app.clone().oneshot(req).await.unwrap(); + convert_response::<()>(response).await.unwrap(); + // Validate response + } +} diff --git a/server/src/http/cloud/delete_team.rs b/server/src/http/cloud/delete_team.rs new file mode 100644 index 00000000..60649a37 --- /dev/null +++ b/server/src/http/cloud/delete_team.rs @@ -0,0 +1,239 @@ +use super::utils::{custom_validate_team_id, validate_request}; +use crate::{ + env::is_env_production, + http::cloud::grafana_utils::delete_team::handle_grafana_delete_team, + middlewares::auth_middleware::UserId, + structs::cloud::{api_cloud_errors::CloudApiErrors, app_info::AppInfo}, +}; +use axum::{extract::State, http::StatusCode, Extension, Json}; +use database::db::Db; +use garde::Validate; +use log::{error, warn}; +use openapi::apis::configuration::Configuration; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use ts_rs::TS; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Validate)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct HttpDeleteTeamRequest { + #[garde(custom(custom_validate_team_id))] + pub team_id: String, +} + +pub async fn delete_team( + State(db): State>, + State(grafana_conf): State>, + Extension(user_id): Extension, + Json(request): Json, +) -> Result, (StatusCode, String)> { + // Validate request + validate_request(&request, &())?; + warn!("Delete team request: {:?}", request); + // Start a transaction + let mut tx = db.connection_pool.begin().await.unwrap(); + + // First check if app exists + let team = match db.get_team_by_team_id(None, &request.team_id).await { + Ok(Some(team)) => { + // Check if team is active + if team.active != true { + return Err(( + StatusCode::BAD_REQUEST, + CloudApiErrors::TeamDoesNotExist.to_string(), + )); + } + if team.team_admin_id != user_id { + return Err(( + StatusCode::BAD_REQUEST, + CloudApiErrors::InsufficientPermissions.to_string(), + )); + } + + // Delete the team + if let Err(err) = db.deactivate_team(&mut tx, &request.team_id).await { + let _ = tx + .rollback() + .await + .map_err(|err| error!("Failed to rollback transaction: {:?}", err)); + error!("Failed to deactivate team: {:?}", err); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::DatabaseError.to_string(), + )); + } + team + } + Ok(None) => { + return Err(( + StatusCode::BAD_REQUEST, + CloudApiErrors::AppDoesNotExist.to_string(), + )); + } + Err(err) => { + error!("Failed to get app by app id: {:?}", err); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::DatabaseError.to_string(), + )); + } + }; + + // Delete all team invites + if let Err(err) = db.cancel_all_team_invites(&mut tx, &request.team_id).await { + let _ = tx + .rollback() + .await + .map_err(|err| error!("Failed to rollback transaction: {:?}", err)); + error!("Failed to deactivate app: {:?}", err); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::DatabaseError.to_string(), + )); + } + + // Get team apps + let mut registered_apps: Vec = + match db.get_registered_apps_by_team_id(&team.team_id).await { + Ok(apps) => apps.into_iter().map(|app| app.into()).collect(), + Err(err) => { + error!("Failed to get registered apps by team_id: {:?}", err); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::DatabaseError.to_string(), + )); + } + }; + + let app_ids: Vec = registered_apps + .iter() + .map(|app| app.app_id.clone()) + .collect(); + + // Delete team apps, privileges and domain verifications + for app in registered_apps.iter_mut() { + if let Err(err) = db.deactivate_app(&mut tx, &app.app_id).await { + let _ = tx + .rollback() + .await + .map_err(|err| error!("Failed to rollback transaction: {:?}", err)); + error!("Failed to deactivate app: {:?}", err); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::DatabaseError.to_string(), + )); + } + if let Err(err) = db + .remove_privileges_for_inactive_app_within_tx(&mut tx, &app.app_id) + .await + { + let _ = tx + .rollback() + .await + .map_err(|err| error!("Failed to rollback transaction: {:?}", err)); + error!("Failed to create app: {:?}", err); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::DatabaseError.to_string(), + )); + } + if let Err(err) = db + .delete_domain_verification_for_inactive_app(&mut tx, &app.app_id) + .await + { + let _ = tx + .rollback() + .await + .map_err(|err| error!("Failed to rollback transaction: {:?}", err)); + error!("Failed to create app: {:?}", err); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::DatabaseError.to_string(), + )); + } + } + + // Grafana, delete team + // TODO, fix this by fixing methods for setting up grafana datasource + if is_env_production() { + handle_grafana_delete_team(&grafana_conf, &request.team_id, &app_ids).await?; + } + + // If nothing failed commit the transaction + tx.commit().await.unwrap(); + return Ok(Json(())); +} + +#[cfg(feature = "cloud_integration_tests")] +#[cfg(test)] +mod tests { + use crate::{ + env::JWT_SECRET, + http::cloud::{ + delete_team::HttpDeleteTeamRequest, register_new_app::HttpRegisterNewAppRequest, + }, + structs::cloud::cloud_http_endpoints::HttpCloudEndpoint, + test_utils::test_utils::{ + add_test_app, add_test_team, add_user_to_test_team, convert_response, create_test_app, + generate_valid_name, register_and_login_random_user, + }, + }; + use axum::{ + body::Body, + extract::ConnectInfo, + http::{Method, Request}, + }; + use std::net::SocketAddr; + use tower::ServiceExt; + + #[tokio::test] + async fn test_delete_team() { + let test_app = create_test_app(false).await; + let (auth_token, _email, _password) = register_and_login_random_user(&test_app).await; + let (user_token, user_email, _password) = register_and_login_random_user(&test_app).await; + + // Register new team + let team_name = generate_valid_name(); + let team_id = add_test_team(&team_name, &auth_token, &test_app, false) + .await + .unwrap(); + + // Register app under the team + let app_name = generate_valid_name(); + let request = HttpRegisterNewAppRequest { + team_id: team_id.clone(), + app_name: app_name.clone(), + }; + + // unwrap err as it should have failed + add_test_app(&request, &auth_token, &test_app) + .await + .unwrap(); + let _ = + add_user_to_test_team(&team_id, &user_email, &auth_token, &user_token, &test_app).await; + + let request = HttpDeleteTeamRequest { + team_id: team_id.clone(), + }; + let ip: ConnectInfo = ConnectInfo(SocketAddr::from(([127, 0, 0, 1], 8080))); + let json = serde_json::to_string(&request).unwrap(); + let auth = auth_token.encode(JWT_SECRET()).unwrap(); + + let req = Request::builder() + .method(Method::POST) + .header("content-type", "application/json") + .header("authorization", format!("Bearer {auth}")) + .uri(format!( + "/cloud/private{}", + HttpCloudEndpoint::DeleteTeam.to_string() + )) + .extension(ip) + .body(Body::from(json)) + .unwrap(); + + // Send request + let response = test_app.clone().oneshot(req).await.unwrap(); + let _ = convert_response::<()>(response).await.unwrap(); + } +} diff --git a/server/src/http/cloud/domains/cancel_pending_domain_request.rs b/server/src/http/cloud/domains/cancel_pending_domain_request.rs index 87f9230a..f2008bd7 100644 --- a/server/src/http/cloud/domains/cancel_pending_domain_request.rs +++ b/server/src/http/cloud/domains/cancel_pending_domain_request.rs @@ -41,7 +41,15 @@ pub async fn cancel_pending_domain_request( // Check if app exists and get data let app = match db.get_registered_app_by_app_id(&request.app_id).await { - Ok(Some(app)) => app, + Ok(Some(app)) => { + if app.active == false { + return Err(( + StatusCode::BAD_REQUEST, + CloudApiErrors::AppDoesNotExist.to_string(), + )); + } + app + } Ok(None) => { return Err(( StatusCode::BAD_REQUEST, diff --git a/server/src/http/cloud/domains/remove_whitelisted_domain.rs b/server/src/http/cloud/domains/remove_whitelisted_domain.rs index 7d63abd2..47ac0303 100644 --- a/server/src/http/cloud/domains/remove_whitelisted_domain.rs +++ b/server/src/http/cloud/domains/remove_whitelisted_domain.rs @@ -41,7 +41,15 @@ pub async fn remove_whitelisted_domain( // Check if app exists and get data let app = match db.get_registered_app_by_app_id(&request.app_id).await { - Ok(Some(app)) => app, + Ok(Some(app)) => { + if app.active == false { + return Err(( + StatusCode::BAD_REQUEST, + CloudApiErrors::AppDoesNotExist.to_string(), + )); + } + app + } Ok(None) => { return Err(( StatusCode::BAD_REQUEST, diff --git a/server/src/http/cloud/domains/verify_domain_finish.rs b/server/src/http/cloud/domains/verify_domain_finish.rs index a7c17a1d..421d5a85 100644 --- a/server/src/http/cloud/domains/verify_domain_finish.rs +++ b/server/src/http/cloud/domains/verify_domain_finish.rs @@ -45,7 +45,15 @@ pub async fn verify_domain_finish( // Check if app exists and get data let app = match db.get_registered_app_by_app_id(&request.app_id).await { - Ok(Some(app)) => app, + Ok(Some(app)) => { + if app.active == false { + return Err(( + StatusCode::BAD_REQUEST, + CloudApiErrors::AppDoesNotExist.to_string(), + )); + } + app + } Ok(None) => { return Err(( StatusCode::BAD_REQUEST, diff --git a/server/src/http/cloud/domains/verify_domain_start.rs b/server/src/http/cloud/domains/verify_domain_start.rs index 3e3fb60b..4831ea37 100644 --- a/server/src/http/cloud/domains/verify_domain_start.rs +++ b/server/src/http/cloud/domains/verify_domain_start.rs @@ -43,7 +43,15 @@ pub async fn verify_domain_start( // Check if app exists and get data let app = match db.get_registered_app_by_app_id(&request.app_id).await { - Ok(Some(app)) => app, + Ok(Some(app)) => { + if app.active == false { + return Err(( + StatusCode::BAD_REQUEST, + CloudApiErrors::AppDoesNotExist.to_string(), + )); + } + app + } Ok(None) => { return Err(( StatusCode::BAD_REQUEST, diff --git a/server/src/http/cloud/get_team_user_invites.rs b/server/src/http/cloud/get_team_user_invites.rs index 05acd024..48bb492f 100644 --- a/server/src/http/cloud/get_team_user_invites.rs +++ b/server/src/http/cloud/get_team_user_invites.rs @@ -48,6 +48,12 @@ pub async fn get_team_user_invites( CloudApiErrors::InsufficientPermissions.to_string(), )); } + if team.active == false { + return Err(( + StatusCode::BAD_REQUEST, + CloudApiErrors::TeamDoesNotExist.to_string(), + )); + } // Check team type if team.personal { diff --git a/server/src/http/cloud/get_team_users_privileges.rs b/server/src/http/cloud/get_team_users_privileges.rs index b54df0ff..7246ab3e 100644 --- a/server/src/http/cloud/get_team_users_privileges.rs +++ b/server/src/http/cloud/get_team_users_privileges.rs @@ -46,6 +46,13 @@ pub async fn get_team_users_privileges( )); } + if team.active == false { + return Err(( + StatusCode::BAD_REQUEST, + CloudApiErrors::TeamDoesNotExist.to_string(), + )); + } + // Check team type if team.personal { return Err(( diff --git a/server/src/http/cloud/grafana_utils/delete_registered_app.rs b/server/src/http/cloud/grafana_utils/delete_registered_app.rs new file mode 100644 index 00000000..740aaec8 --- /dev/null +++ b/server/src/http/cloud/grafana_utils/delete_registered_app.rs @@ -0,0 +1,40 @@ +use crate::structs::cloud::{ + api_cloud_errors::CloudApiErrors, grafana_error::handle_grafana_error, +}; +use axum::http::StatusCode; +use log::warn; +use openapi::apis::{ + configuration::Configuration, + dashboards_api::{delete_dashboard_by_uid, get_dashboard_by_uid}, +}; +use std::sync::Arc; + +pub async fn handle_grafana_delete_app( + grafana_conf: &Arc, + app_id: &String, +) -> Result<(), (StatusCode, String)> { + match get_dashboard_by_uid(&grafana_conf, &app_id).await { + Ok(response) => match response.dashboard { + Some(_) => (), + None => { + warn!("Failed to get dashboard: {:?}", response); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::AppDoesNotExist.to_string(), + )); + } + }, + Err(err) => { + warn!("Failed to get template dashboard: {:?}", err); + return Err(handle_grafana_error(err)); + } + }; + + match delete_dashboard_by_uid(&grafana_conf, &app_id).await { + Ok(_) => return Ok(()), + Err(err) => { + warn!("Failed to delete dashboard: {:?}", err); + return Err(handle_grafana_error(err)); + } + } +} diff --git a/server/src/http/cloud/grafana_utils/delete_team.rs b/server/src/http/cloud/grafana_utils/delete_team.rs new file mode 100644 index 00000000..59decb21 --- /dev/null +++ b/server/src/http/cloud/grafana_utils/delete_team.rs @@ -0,0 +1,50 @@ +use crate::structs::cloud::{ + api_cloud_errors::CloudApiErrors, grafana_error::handle_grafana_error, +}; +use axum::http::StatusCode; +use log::warn; +use openapi::apis::{ + configuration::Configuration, + teams_api::{delete_team_by_id, get_team_by_id}, +}; +use std::sync::Arc; + +use super::delete_registered_app::handle_grafana_delete_app; + +pub async fn handle_grafana_delete_team( + grafana_conf: &Arc, + team_id: &String, + app_ids: &Vec, +) -> Result<(), (StatusCode, String)> { + match get_team_by_id(&grafana_conf, &team_id).await { + Ok(response) => match response.id { + Some(_) => (), + None => { + warn!("Failed to get team: {:?}", response); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::TeamDoesNotExist.to_string(), + )); + } + }, + Err(err) => { + warn!("Failed to get team: {:?}", err); + return Err(handle_grafana_error(err)); + } + }; + + match delete_team_by_id(&grafana_conf, team_id).await { + Ok(_) => (), + Err(err) => { + warn!("Failed to delete team: {:?}", err); + return Err(handle_grafana_error(err)); + } + } + + for app_id in app_ids { + if let Err(err) = handle_grafana_delete_app(&grafana_conf, &app_id).await { + return Err(err); + } + } + return Ok(()); +} diff --git a/server/src/http/cloud/grafana_utils/mod.rs b/server/src/http/cloud/grafana_utils/mod.rs index 8223494b..e1af8f68 100644 --- a/server/src/http/cloud/grafana_utils/mod.rs +++ b/server/src/http/cloud/grafana_utils/mod.rs @@ -1,6 +1,8 @@ pub mod add_user_to_team; pub mod create_new_app; pub mod create_new_team; +pub mod delete_registered_app; +pub mod delete_team; pub mod import_template_dashboard; pub mod remove_user_from_the_team; pub mod setup_database_datasource; diff --git a/server/src/http/cloud/invite_user_to_team.rs b/server/src/http/cloud/invite_user_to_team.rs index 14428314..04ab6aaf 100644 --- a/server/src/http/cloud/invite_user_to_team.rs +++ b/server/src/http/cloud/invite_user_to_team.rs @@ -51,6 +51,13 @@ pub async fn invite_user_to_team( )); } + if team.active != true { + return Err(( + StatusCode::BAD_REQUEST, + CloudApiErrors::TeamDoesNotExist.to_string(), + )); + } + // Check team type if team.personal { return Err(( diff --git a/server/src/http/cloud/leave_team.rs b/server/src/http/cloud/leave_team.rs index c63fa881..93e4946b 100644 --- a/server/src/http/cloud/leave_team.rs +++ b/server/src/http/cloud/leave_team.rs @@ -56,6 +56,12 @@ pub async fn leave_team( )); } + if team.active != true { + return Err(( + StatusCode::BAD_REQUEST, + CloudApiErrors::TeamDoesNotExist.to_string(), + )); + } // Get user data and perform checks let user = match db.get_user_by_user_id(&user_id).await { Ok(Some(user)) => user, diff --git a/server/src/http/cloud/mod.rs b/server/src/http/cloud/mod.rs index f311eef2..52e07ded 100644 --- a/server/src/http/cloud/mod.rs +++ b/server/src/http/cloud/mod.rs @@ -4,7 +4,9 @@ pub mod add_passkey_start; pub mod cancel_team_user_invite; pub mod cancel_user_team_invite; pub mod change_user_privileges; +pub mod delete_app; pub mod delete_passkey; +pub mod delete_team; pub mod domains; pub mod events; pub mod get_events; diff --git a/server/src/http/cloud/register_new_app.rs b/server/src/http/cloud/register_new_app.rs index 01a1bcb1..7ea7ede7 100644 --- a/server/src/http/cloud/register_new_app.rs +++ b/server/src/http/cloud/register_new_app.rs @@ -128,6 +128,8 @@ pub async fn register_new_app( ack_public_keys: vec![], whitelisted_domains: vec![], registration_timestamp: get_current_datetime(), + active: true, + deactivated_at: None, }; if let Err(err) = db @@ -202,7 +204,6 @@ pub async fn register_new_app( } Err(err) => { error!("Failed to get team by team id: {:?}", err); - println!("Failed to get team by team id: {:?}", err); return Err(( StatusCode::INTERNAL_SERVER_ERROR, CloudApiErrors::DatabaseError.to_string(), diff --git a/server/src/http/cloud/register_new_team.rs b/server/src/http/cloud/register_new_team.rs index af5618d8..8eba61bc 100644 --- a/server/src/http/cloud/register_new_team.rs +++ b/server/src/http/cloud/register_new_team.rs @@ -132,6 +132,8 @@ pub async fn register_new_team( subscription: None, personal: request.personal, registration_timestamp: get_current_datetime(), + active: true, + deactivated_at: None, }; if let Err(err) = db.create_new_team(&team).await { diff --git a/server/src/routes/cloud_router.rs b/server/src/routes/cloud_router.rs index 81b59ac1..d94c504e 100644 --- a/server/src/routes/cloud_router.rs +++ b/server/src/routes/cloud_router.rs @@ -6,7 +6,9 @@ use crate::{ cancel_team_user_invite::cancel_team_user_invite, cancel_user_team_invite::cancel_user_team_invite, change_user_privileges::change_user_privileges, + delete_app::delete_app, delete_passkey::delete_passkey, + delete_team::delete_team, domains::{ cancel_pending_domain_request::cancel_pending_domain_request, remove_whitelisted_domain::remove_whitelisted_domain, @@ -221,6 +223,11 @@ pub fn private_router(state: ServerState) -> Router { &HttpCloudEndpoint::ChangeUserPrivileges.to_string(), post(change_user_privileges), ) + .route(&HttpCloudEndpoint::DeleteApp.to_string(), post(delete_app)) .route(&HttpCloudEndpoint::LeaveTeam.to_string(), post(leave_team)) + .route( + &HttpCloudEndpoint::DeleteTeam.to_string(), + post(delete_team), + ) .with_state(state) } diff --git a/server/src/structs/cloud/cloud_http_endpoints.rs b/server/src/structs/cloud/cloud_http_endpoints.rs index d6456e59..598a14b1 100644 --- a/server/src/structs/cloud/cloud_http_endpoints.rs +++ b/server/src/structs/cloud/cloud_http_endpoints.rs @@ -82,6 +82,10 @@ pub enum HttpCloudEndpoint { VerifyCode, #[serde(rename = "/leave_team")] LeaveTeam, + #[serde(rename = "/delete_app")] + DeleteApp, + #[serde(rename = "/delete_team")] + DeleteTeam, } impl HttpCloudEndpoint { @@ -136,6 +140,8 @@ impl HttpCloudEndpoint { HttpCloudEndpoint::RefreshToken => "/refresh_token".to_string(), HttpCloudEndpoint::VerifyCode => "/verify_code".to_string(), HttpCloudEndpoint::LeaveTeam => "/leave_team".to_string(), + HttpCloudEndpoint::DeleteApp => "/delete_app".to_string(), + HttpCloudEndpoint::DeleteTeam => "/delete_team".to_string(), } } } From d962cdc0cff635770ce1a85501eb9eaefd46d909 Mon Sep 17 00:00:00 2001 From: Julia Seweryn Date: Fri, 11 Oct 2024 13:35:50 +0200 Subject: [PATCH 2/9] side panel and footer storybook --- server/src/http/cloud/remove_user_from_team.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/server/src/http/cloud/remove_user_from_team.rs b/server/src/http/cloud/remove_user_from_team.rs index 630c77bf..e9397060 100644 --- a/server/src/http/cloud/remove_user_from_team.rs +++ b/server/src/http/cloud/remove_user_from_team.rs @@ -54,6 +54,13 @@ pub async fn remove_user_from_team( )); } + if team.active != true { + return Err(( + StatusCode::BAD_REQUEST, + CloudApiErrors::TeamDoesNotExist.to_string(), + )); + } + // Get user data and perform checks let user = match db.get_user_by_email(&request.user_email).await { Ok(Some(user)) => user, From 6e4cbf540464a58fbc09abbec11808d9c39c1a3b Mon Sep 17 00:00:00 2001 From: Julia Seweryn Date: Mon, 14 Oct 2024 01:56:05 +0200 Subject: [PATCH 3/9] fixed --- database/migrations/0002_team.sql | 1 - database/migrations/0004_registered_apps.sql | 1 - database/src/tables/registered_app/select.rs | 4 +- .../src/tables/registered_app/table_struct.rs | 4 +- database/src/tables/registered_app/update.rs | 12 ++- database/src/tables/sessions/update.rs | 11 ++- database/src/tables/team/select.rs | 12 +-- database/src/tables/team/table_struct.rs | 4 +- database/src/tables/team/update.rs | 10 +-- .../src/tables/user_app_privileges/select.rs | 10 +-- .../src/tables/user_app_privileges/update.rs | 10 ++- .../src/http/cloud/cancel_team_user_invite.rs | 2 +- .../src/http/cloud/change_user_privileges.rs | 2 +- server/src/http/cloud/delete_app.rs | 62 +++++++++---- server/src/http/cloud/delete_team.rs | 90 ++++++++++++------- .../domains/cancel_pending_domain_request.rs | 2 +- .../domains/remove_whitelisted_domain.rs | 2 +- .../cloud/domains/verify_domain_finish.rs | 2 +- .../http/cloud/domains/verify_domain_start.rs | 2 +- .../src/http/cloud/get_team_user_invites.rs | 2 +- .../http/cloud/get_team_users_privileges.rs | 2 +- .../src/http/cloud/get_user_joined_teams.rs | 3 +- .../grafana_utils/delete_registered_app.rs | 15 +++- .../http/cloud/grafana_utils/delete_team.rs | 18 ++-- server/src/http/cloud/invite_user_to_team.rs | 2 +- server/src/http/cloud/leave_team.rs | 2 +- server/src/http/cloud/register_new_app.rs | 30 +++++-- server/src/http/cloud/register_new_team.rs | 18 ++-- .../src/http/cloud/remove_user_from_team.rs | 2 +- server/src/structs/cloud/api_cloud_errors.rs | 1 + server/src/test_utils.rs | 28 +++++- 31 files changed, 234 insertions(+), 132 deletions(-) diff --git a/database/migrations/0002_team.sql b/database/migrations/0002_team.sql index 153dec8e..ce29310a 100644 --- a/database/migrations/0002_team.sql +++ b/database/migrations/0002_team.sql @@ -5,7 +5,6 @@ CREATE TABLE team( subscription subscription, team_admin_id TEXT NOT NULL, registration_timestamp TIMESTAMPTZ NOT NULL, - active BOOLEAN NOT NULL, deactivated_at TIMESTAMPTZ, PRIMARY KEY (team_name, team_admin_id) ); \ No newline at end of file diff --git a/database/migrations/0004_registered_apps.sql b/database/migrations/0004_registered_apps.sql index a0c2ad34..14ea4edb 100644 --- a/database/migrations/0004_registered_apps.sql +++ b/database/migrations/0004_registered_apps.sql @@ -5,7 +5,6 @@ CREATE TABLE registered_apps( whitelisted_domains TEXT [] NOT NULL, ack_public_keys TEXT [] NOT NULL, registration_timestamp TIMESTAMPTZ NOT NULL, - active BOOLEAN NOT NULL, deactivated_at TIMESTAMPTZ ); diff --git a/database/src/tables/registered_app/select.rs b/database/src/tables/registered_app/select.rs index 69a05d2e..87765983 100644 --- a/database/src/tables/registered_app/select.rs +++ b/database/src/tables/registered_app/select.rs @@ -7,7 +7,7 @@ impl Db { &self, app_id: &String, ) -> Result, DbError> { - let query = format!("SELECT * FROM {REGISTERED_APPS_TABLE_NAME} WHERE app_id = $1 AND active = true"); + let query = format!("SELECT * FROM {REGISTERED_APPS_TABLE_NAME} WHERE app_id = $1 AND deactivated_at IS NULL"); let typed_query = query_as::<_, DbRegisteredApp>(&query); return typed_query @@ -23,7 +23,7 @@ impl Db { team_id: &String, ) -> Result, DbError> { let query = format!( - "SELECT * FROM {REGISTERED_APPS_TABLE_NAME} WHERE app_name = $1 AND team_id = $2 AND active = true" + "SELECT * FROM {REGISTERED_APPS_TABLE_NAME} WHERE app_name = $1 AND team_id = $2 AND deactivated_at IS NULL" ); let typed_query = query_as::<_, DbRegisteredApp>(&query); diff --git a/database/src/tables/registered_app/table_struct.rs b/database/src/tables/registered_app/table_struct.rs index 9023f32e..94eace7f 100644 --- a/database/src/tables/registered_app/table_struct.rs +++ b/database/src/tables/registered_app/table_struct.rs @@ -6,7 +6,7 @@ use sqlx::{ pub const REGISTERED_APPS_TABLE_NAME: &str = "registered_apps"; pub const REGISTERED_APPS_KEYS: &str = - "team_id, app_id, app_name, whitelisted_domains, ack_public_keys, registration_timestamp, active, deactivated_at"; + "team_id, app_id, app_name, whitelisted_domains, ack_public_keys, registration_timestamp, deactivated_at"; #[derive(Clone, Debug, Eq, PartialEq)] pub struct DbRegisteredApp { @@ -16,7 +16,6 @@ pub struct DbRegisteredApp { pub whitelisted_domains: Vec, pub ack_public_keys: Vec, pub registration_timestamp: DateTime, - pub active: bool, pub deactivated_at: Option>, } @@ -29,7 +28,6 @@ impl FromRow<'_, PgRow> for DbRegisteredApp { whitelisted_domains: row.get("whitelisted_domains"), ack_public_keys: row.get("ack_public_keys"), registration_timestamp: row.get("registration_timestamp"), - active: row.get("active"), deactivated_at: row.get("deactivated_at"), }) } diff --git a/database/src/tables/registered_app/update.rs b/database/src/tables/registered_app/update.rs index 31ad7c9e..6f2d25a3 100644 --- a/database/src/tables/registered_app/update.rs +++ b/database/src/tables/registered_app/update.rs @@ -5,7 +5,7 @@ use sqlx::{query, Transaction}; impl Db { pub async fn register_new_app(&self, app: &DbRegisteredApp) -> Result<(), DbError> { let query_body = format!( - "INSERT INTO {REGISTERED_APPS_TABLE_NAME} ({REGISTERED_APPS_KEYS}) VALUES ($1, $2, $3, $4, $5, $6, $7, NULL)", + "INSERT INTO {REGISTERED_APPS_TABLE_NAME} ({REGISTERED_APPS_KEYS}) VALUES ($1, $2, $3, $4, $5, $6, NULL)", ); let query_result = query(&query_body) @@ -15,7 +15,6 @@ impl Db { .bind(&app.whitelisted_domains) .bind(&app.ack_public_keys) .bind(&app.registration_timestamp) - .bind(&app.active) .execute(&self.connection_pool) .await; @@ -31,7 +30,7 @@ impl Db { app: &DbRegisteredApp, ) -> Result<(), DbError> { let query_body = format!( - "INSERT INTO {REGISTERED_APPS_TABLE_NAME} ({}) VALUES ($1, $2, $3, $4, $5, $6, $7, NULL)", + "INSERT INTO {REGISTERED_APPS_TABLE_NAME} ({}) VALUES ($1, $2, $3, $4, $5, $6, NULL)", REGISTERED_APPS_KEYS ); @@ -42,7 +41,6 @@ impl Db { .bind(&app.whitelisted_domains) .bind(&app.ack_public_keys) .bind(&app.registration_timestamp) - .bind(&app.active) .execute(&mut **tx) .await; @@ -59,7 +57,7 @@ impl Db { domain: &str, ) -> Result<(), DbError> { let query_body = format!( - "UPDATE {REGISTERED_APPS_TABLE_NAME} SET whitelisted_domains = array_append(whitelisted_domains, $1) WHERE app_id = $2 AND active = true", + "UPDATE {REGISTERED_APPS_TABLE_NAME} SET whitelisted_domains = array_append(whitelisted_domains, $1) WHERE app_id = $2 AND deactivated_at IS NULL", ); let query_result = query(&query_body) @@ -81,7 +79,7 @@ impl Db { domain: &str, ) -> Result<(), DbError> { let query_body = format!( - "UPDATE {REGISTERED_APPS_TABLE_NAME} SET whitelisted_domains = array_remove(whitelisted_domains, $1) WHERE app_id = $2 AND active = true", + "UPDATE {REGISTERED_APPS_TABLE_NAME} SET whitelisted_domains = array_remove(whitelisted_domains, $1) WHERE app_id = $2 AND deactivated_at IS NULL", ); let query_result = query(&query_body) @@ -102,7 +100,7 @@ impl Db { app_id: &str, ) -> Result<(), DbError> { let query_body = format!( - "UPDATE {REGISTERED_APPS_TABLE_NAME} SET active = false, deactivated_at = $1 WHERE app_id = $2 AND active = true", + "UPDATE {REGISTERED_APPS_TABLE_NAME} SET deactivated_at = $1 WHERE app_id = $2 AND deactivated_at IS NULL", ); let query_result = query(&query_body) diff --git a/database/src/tables/sessions/update.rs b/database/src/tables/sessions/update.rs index b4263067..218b6798 100644 --- a/database/src/tables/sessions/update.rs +++ b/database/src/tables/sessions/update.rs @@ -49,8 +49,10 @@ impl Db { return Err(err); } - tx.commit().await.unwrap(); - + if let Err(err) = tx.commit().await { + error!("Failed to commit transaction: {:?}", err); + return Err(err).map_err(|err| err.into()); + } Ok(()) } @@ -230,7 +232,10 @@ impl Db { return Err(err); } - tx.commit().await.unwrap(); + if let Err(err) = tx.commit().await { + error!("Failed to commit transaction: {:?}", err); + return Err(err).map_err(|err| err.into()); + } Ok(()) } diff --git a/database/src/tables/team/select.rs b/database/src/tables/team/select.rs index 75b6123b..848ea4a3 100644 --- a/database/src/tables/team/select.rs +++ b/database/src/tables/team/select.rs @@ -11,7 +11,7 @@ impl Db { tx: Option<&mut Transaction<'_, sqlx::Postgres>>, team_id: &String, ) -> Result, DbError> { - let query = format!("SELECT * FROM {TEAM_TABLE_NAME} WHERE team_id = $1 AND active = true"); + let query = format!("SELECT * FROM {TEAM_TABLE_NAME} WHERE team_id = $1 AND deactivated_at IS NULL"); let typed_query = query_as::<_, Team>(&query); match tx { @@ -39,7 +39,7 @@ impl Db { let query = format!( "SELECT r.* FROM {REGISTERED_APPS_TABLE_NAME} r INNER JOIN team t ON r.team_id = t.team_id - WHERE t.team_id = $1 AND r.active = true AND t.active = true + WHERE t.team_id = $1 AND r.deactivated_at IS NULL AND t.deactivated_at IS NULL ORDER BY t.registration_timestamp DESC" ); let typed_query = query_as::<_, DbRegisteredApp>(&query); @@ -56,7 +56,7 @@ impl Db { admin_id: &String, ) -> Result, DbError> { let query = format!( - "SELECT * FROM {TEAM_TABLE_NAME} WHERE team_admin_id = $1 AND personal = false AND active = true" + "SELECT * FROM {TEAM_TABLE_NAME} WHERE team_admin_id = $1 AND personal = false AND deactivated_at IS NULL" ); let typed_query = query_as::<_, Team>(&query); @@ -72,7 +72,7 @@ impl Db { admin_id: &String, ) -> Result, DbError> { let query = - format!("SELECT * FROM {TEAM_TABLE_NAME} WHERE team_admin_id = $1 AND personal = true AND active = true"); + format!("SELECT * FROM {TEAM_TABLE_NAME} WHERE team_admin_id = $1 AND personal = true AND deactivated_at IS NULL"); let typed_query = query_as::<_, Team>(&query); return typed_query @@ -88,7 +88,7 @@ impl Db { admin_id: &String, ) -> Result, DbError> { let query = - format!("SELECT * FROM {TEAM_TABLE_NAME} WHERE team_name = $1 AND active = true AND team_admin_id = $2"); + format!("SELECT * FROM {TEAM_TABLE_NAME} WHERE team_name = $1 AND deactivated_at IS NULL AND team_admin_id = $2"); let typed_query = query_as::<_, Team>(&query); return typed_query @@ -100,7 +100,7 @@ impl Db { } pub async fn get_team_by_admin_id(&self, admin_id: &String) -> Result, DbError> { - let query = format!("SELECT * FROM {TEAM_TABLE_NAME} WHERE team_admin_id = $1 AND active = true"); + 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 diff --git a/database/src/tables/team/table_struct.rs b/database/src/tables/team/table_struct.rs index b1ad1af7..51553e27 100644 --- a/database/src/tables/team/table_struct.rs +++ b/database/src/tables/team/table_struct.rs @@ -7,7 +7,7 @@ use sqlx::{ pub const TEAM_TABLE_NAME: &str = "team"; pub const TEAM_KEYS: &str = - "team_id, team_name, personal, subscription, team_admin_id, registration_timestamp, active, deactivated_at"; + "team_id, team_name, personal, subscription, team_admin_id, registration_timestamp, deactivated_at"; #[derive(Clone, Debug, Eq, PartialEq)] pub struct Team { @@ -18,7 +18,6 @@ pub struct Team { pub subscription: Option, pub team_admin_id: String, pub registration_timestamp: DateTime, - pub active: bool, pub deactivated_at: Option>, } @@ -31,7 +30,6 @@ impl FromRow<'_, PgRow> for Team { subscription: row.get("subscription"), registration_timestamp: row.get("registration_timestamp"), team_admin_id: row.get("team_admin_id"), - active: row.get("active"), deactivated_at: row.get("deactivated_at"), }) } diff --git a/database/src/tables/team/update.rs b/database/src/tables/team/update.rs index cd30c3cf..a594802a 100644 --- a/database/src/tables/team/update.rs +++ b/database/src/tables/team/update.rs @@ -16,7 +16,7 @@ impl Db { team: &Team, ) -> Result<(), DbError> { let query_body = format!( - "INSERT INTO {TEAM_TABLE_NAME} ({TEAM_KEYS}) VALUES ($1, $2, $3, $4, $5, $6, $7, NULL)" + "INSERT INTO {TEAM_TABLE_NAME} ({TEAM_KEYS}) VALUES ($1, $2, $3, $4, $5, $6, NULL)" ); let query_result = query(&query_body) @@ -26,7 +26,6 @@ impl Db { .bind(&team.subscription) .bind(&team.team_admin_id) .bind(&team.registration_timestamp) - .bind(&team.active) .execute(&mut **tx) .await; @@ -38,7 +37,7 @@ impl Db { pub async fn create_new_team(&self, team: &Team) -> Result<(), DbError> { let query_body = format!( - "INSERT INTO {TEAM_TABLE_NAME} ({TEAM_KEYS}) VALUES ($1, $2, $3, $4, $5, $6, $7, NULL)" + "INSERT INTO {TEAM_TABLE_NAME} ({TEAM_KEYS}) VALUES ($1, $2, $3, $4, $5, $6, NULL)" ); let query_result = query(&query_body) @@ -48,7 +47,6 @@ impl Db { .bind(&team.subscription) .bind(&team.team_admin_id) .bind(&team.registration_timestamp) - .bind(&team.active) .execute(&self.connection_pool) .await; @@ -64,7 +62,7 @@ impl Db { subscription: &Subscription, ) -> Result<(), DbError> { let query_body = format!( - "UPDATE {TEAM_TABLE_NAME} SET subscription = $1 WHERE team_id = $2 AND active = true" + "UPDATE {TEAM_TABLE_NAME} SET subscription = $1 WHERE team_id = $2 AND deactivated_at IS NULL" ); let query_result = query(&query_body) .bind(subscription) @@ -84,7 +82,7 @@ impl Db { team_id: &str, ) -> Result<(), DbError> { let query_body = format!( - "UPDATE {TEAM_TABLE_NAME} SET active = false, deactivated_at = $1 WHERE team_id = $2 AND active = true", + "UPDATE {TEAM_TABLE_NAME} SET deactivated_at = $1 WHERE team_id = $2 AND deactivated_at IS NULL", ); let query_result = query(&query_body) diff --git a/database/src/tables/user_app_privileges/select.rs b/database/src/tables/user_app_privileges/select.rs index 44320216..b1628df1 100644 --- a/database/src/tables/user_app_privileges/select.rs +++ b/database/src/tables/user_app_privileges/select.rs @@ -115,7 +115,7 @@ impl Db { "WITH RelevantTeams AS ( SELECT DISTINCT t.team_id, t.team_name, t.personal, t.subscription, t.registration_timestamp, gu.email AS team_admin_email, - gu.user_id AS team_admin_id, + gu.user_id AS team_admin_id, t.deactivated_at, CASE WHEN t.team_admin_id = $1 THEN t.registration_timestamp ELSE MAX(uap.creation_timestamp) OVER (PARTITION BY t.team_id) @@ -124,17 +124,17 @@ impl Db { LEFT JOIN {REGISTERED_APPS_TABLE_NAME} ra ON t.team_id = ra.team_id LEFT JOIN {USER_APP_PRIVILEGES_TABLE_NAME} uap ON ra.app_id = uap.app_id AND uap.user_id = $1 JOIN {USERS_TABLE_NAME} gu ON t.team_admin_id = gu.user_id - WHERE (t.team_admin_id = $1 OR uap.user_id = $1) AND ra.active = true + WHERE (t.team_admin_id = $1 OR uap.user_id = $1) AND ra.deactivated_at IS NULL ) SELECT rt.team_id, rt.team_name, rt.personal, rt.subscription, rt.registration_timestamp, rt.team_admin_email, rt.team_admin_id, ra.app_id, ra.app_name, ra.whitelisted_domains, ra.ack_public_keys, ra.registration_timestamp AS app_registration_timestamp, uap.user_id, uap.privilege_level, uap.creation_timestamp AS privilege_creation_timestamp, - rt.user_joined_team_timestamp + rt.user_joined_team_timestamp, ra.deactivated_at FROM RelevantTeams rt LEFT JOIN {REGISTERED_APPS_TABLE_NAME} ra ON rt.team_id = ra.team_id LEFT JOIN {USER_APP_PRIVILEGES_TABLE_NAME} uap ON ra.app_id = uap.app_id AND uap.user_id = $1 - WHERE ra.active = true + WHERE ra.deactivated_at IS NULL ORDER BY rt.team_id, ra.app_id" ); @@ -168,7 +168,6 @@ impl Db { subscription: row.get("subscription"), registration_timestamp: row.get("registration_timestamp"), team_admin_id: row.get("team_admin_id"), - active: row.get("active"), deactivated_at: row.get("deactivated_at"), }; @@ -190,7 +189,6 @@ impl Db { whitelisted_domains: row.get("whitelisted_domains"), ack_public_keys: row.get("ack_public_keys"), registration_timestamp: row.get("app_registration_timestamp"), - active: row.get("active"), deactivated_at: row.get("deactivated_at"), }; diff --git a/database/src/tables/user_app_privileges/update.rs b/database/src/tables/user_app_privileges/update.rs index db544358..7a179c63 100644 --- a/database/src/tables/user_app_privileges/update.rs +++ b/database/src/tables/user_app_privileges/update.rs @@ -9,6 +9,7 @@ use crate::tables::user_app_privileges::table_struct::{ USER_APP_PRIVILEGES_KEYS, USER_APP_PRIVILEGES_TABLE_NAME, }; use crate::tables::utils::get_current_datetime; +use log::error; use sqlx::query; use sqlx::Transaction; @@ -63,7 +64,7 @@ impl Db { ) -> Result<(), DbError> { // Retrieve all apps associated with the team let apps_query = format!( - "SELECT app_id FROM {REGISTERED_APPS_TABLE_NAME} WHERE team_id = $1 AND active = true" + "SELECT app_id FROM {REGISTERED_APPS_TABLE_NAME} WHERE team_id = $1 AND deactivated_at IS NULL" ); let apps: Vec = sqlx::query_as(&apps_query) .bind(team_id) @@ -112,7 +113,7 @@ impl Db { ) -> Result<(), DbError> { // Retrieve all apps associated with the team let apps_query = format!( - "SELECT app_id FROM {REGISTERED_APPS_TABLE_NAME} WHERE team_id = $1 AND active = true" + "SELECT app_id FROM {REGISTERED_APPS_TABLE_NAME} WHERE team_id = $1 AND deactivated_at IS NULL" ); let apps: Vec = sqlx::query_as(&apps_query) .bind(team_id) @@ -150,7 +151,10 @@ impl Db { } // Commit the transaction - tx.commit().await?; + if let Err(err) = tx.commit().await { + error!("Failed to commit transaction: {:?}", err); + return Err(err).map_err(|err| err.into()); + } Ok(()) } diff --git a/server/src/http/cloud/cancel_team_user_invite.rs b/server/src/http/cloud/cancel_team_user_invite.rs index 26857aff..1bb670ef 100644 --- a/server/src/http/cloud/cancel_team_user_invite.rs +++ b/server/src/http/cloud/cancel_team_user_invite.rs @@ -44,7 +44,7 @@ pub async fn cancel_team_user_invite( )); } - if team.active == false { + if team.deactivated_at != None { return Err(( StatusCode::BAD_REQUEST, CloudApiErrors::TeamDoesNotExist.to_string(), diff --git a/server/src/http/cloud/change_user_privileges.rs b/server/src/http/cloud/change_user_privileges.rs index 986af6a1..2abe1325 100644 --- a/server/src/http/cloud/change_user_privileges.rs +++ b/server/src/http/cloud/change_user_privileges.rs @@ -64,7 +64,7 @@ pub async fn change_user_privileges( CloudApiErrors::InsufficientPermissions.to_string(), )); } - if team.active == false { + if team.deactivated_at != None { return Err(( StatusCode::BAD_REQUEST, CloudApiErrors::TeamDoesNotExist.to_string(), diff --git a/server/src/http/cloud/delete_app.rs b/server/src/http/cloud/delete_app.rs index 9925984e..549d42a3 100644 --- a/server/src/http/cloud/delete_app.rs +++ b/server/src/http/cloud/delete_app.rs @@ -34,14 +34,6 @@ pub async fn delete_app( // First check if app exists match db.get_registered_app_by_app_id(&request.app_id).await { Ok(Some(app)) => { - // Check if app is active - if app.active != true { - return Err(( - StatusCode::BAD_REQUEST, - CloudApiErrors::AppDoesNotExist.to_string(), - )); - } - // Check if user has admin privileges match db .get_privilege_by_user_id_and_app_id(&user_id, &request.app_id) @@ -54,6 +46,13 @@ pub async fn delete_app( CloudApiErrors::InsufficientPermissions.to_string(), )); } + // Check if app is active + if app.deactivated_at != None { + return Err(( + StatusCode::BAD_REQUEST, + CloudApiErrors::AppDoesNotExist.to_string(), + )); + } } Ok(None) => { return Err(( @@ -69,7 +68,6 @@ pub async fn delete_app( )); } } - // Delete the app // Start a transaction let mut tx = db.connection_pool.begin().await.unwrap(); @@ -93,7 +91,7 @@ pub async fn delete_app( .rollback() .await .map_err(|err| error!("Failed to rollback transaction: {:?}", err)); - error!("Failed to create app: {:?}", err); + error!("Failed to delete app: {:?}", err); return Err(( StatusCode::INTERNAL_SERVER_ERROR, CloudApiErrors::DatabaseError.to_string(), @@ -107,7 +105,7 @@ pub async fn delete_app( .rollback() .await .map_err(|err| error!("Failed to rollback transaction: {:?}", err)); - error!("Failed to create app: {:?}", err); + error!("Failed to delete app: {:?}", err); return Err(( StatusCode::INTERNAL_SERVER_ERROR, CloudApiErrors::DatabaseError.to_string(), @@ -116,11 +114,26 @@ pub async fn delete_app( // Grafana, delete app // TODO, fix this by fixing methods for setting up grafana datasource if is_env_production() { - handle_grafana_delete_app(&grafana_conf, &request.app_id).await?; + match handle_grafana_delete_app(&grafana_conf, &request.app_id).await { + Ok(_) => {} + Err(err) => { + error!("Failed to delete app from grafana: {:?}", err); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::GrafanaError.to_string(), + )); + } + } } // If nothing failed commit the transaction - tx.commit().await.unwrap(); + if let Err(err) = tx.commit().await { + error!("Failed to commit transaction: {:?}", err); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::DatabaseError.to_string(), + )); + } return Ok(Json(())); } Ok(None) => { @@ -139,7 +152,7 @@ pub async fn delete_app( } } -#[cfg(feature = "cloud_integration_tests")] +#[cfg(feature = "cloud_intsegration_tests")] #[cfg(test)] mod tests { use crate::{ @@ -150,7 +163,7 @@ mod tests { structs::cloud::cloud_http_endpoints::HttpCloudEndpoint, test_utils::test_utils::{ add_test_app, add_test_team, add_user_to_test_team, convert_response, create_test_app, - generate_valid_name, register_and_login_random_user, + generate_valid_name, get_test_app_data, register_and_login_random_user, }, }; use axum::{ @@ -184,6 +197,14 @@ mod tests { let app_id = add_test_app(&request, &auth_token, &test_app) .await .unwrap(); + + let request = HttpRegisterNewAppRequest { + team_id: team_id.clone(), + app_name: generate_valid_name(), + }; + let app_id_2 = add_test_app(&request, &auth_token, &test_app) + .await + .unwrap(); let _ = add_user_to_test_team(&team_id, &user_email, &auth_token, &user_token, &test_app).await; @@ -209,6 +230,15 @@ mod tests { // Send request let response = test_app.clone().oneshot(req).await.unwrap(); convert_response::<()>(response).await.unwrap(); - // Validate response + let err = get_test_app_data(&team_id, &app_id, &auth_token, &test_app) + .await + .unwrap_err(); + + assert_eq!(err.to_string(), "App not found".to_string()); + assert!( + get_test_app_data(&team_id, &app_id_2, &auth_token, &test_app) + .await + .is_ok() + ); } } diff --git a/server/src/http/cloud/delete_team.rs b/server/src/http/cloud/delete_team.rs index 60649a37..5f6b36a7 100644 --- a/server/src/http/cloud/delete_team.rs +++ b/server/src/http/cloud/delete_team.rs @@ -34,16 +34,9 @@ pub async fn delete_team( // Start a transaction let mut tx = db.connection_pool.begin().await.unwrap(); - // First check if app exists + // First check if team exists let team = match db.get_team_by_team_id(None, &request.team_id).await { Ok(Some(team)) => { - // Check if team is active - if team.active != true { - return Err(( - StatusCode::BAD_REQUEST, - CloudApiErrors::TeamDoesNotExist.to_string(), - )); - } if team.team_admin_id != user_id { return Err(( StatusCode::BAD_REQUEST, @@ -51,16 +44,11 @@ pub async fn delete_team( )); } - // Delete the team - if let Err(err) = db.deactivate_team(&mut tx, &request.team_id).await { - let _ = tx - .rollback() - .await - .map_err(|err| error!("Failed to rollback transaction: {:?}", err)); - error!("Failed to deactivate team: {:?}", err); + // Check if team is active + if team.deactivated_at != None { return Err(( - StatusCode::INTERNAL_SERVER_ERROR, - CloudApiErrors::DatabaseError.to_string(), + StatusCode::BAD_REQUEST, + CloudApiErrors::TeamDoesNotExist.to_string(), )); } team @@ -80,6 +68,19 @@ pub async fn delete_team( } }; + // Delete the team + if let Err(err) = db.deactivate_team(&mut tx, &request.team_id).await { + let _ = tx + .rollback() + .await + .map_err(|err| error!("Failed to rollback transaction: {:?}", err)); + error!("Failed to deactivate team: {:?}", err); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::DatabaseError.to_string(), + )); + } + // Delete all team invites if let Err(err) = db.cancel_all_team_invites(&mut tx, &request.team_id).await { let _ = tx @@ -94,17 +95,17 @@ pub async fn delete_team( } // Get team apps - let mut registered_apps: Vec = - match db.get_registered_apps_by_team_id(&team.team_id).await { - Ok(apps) => apps.into_iter().map(|app| app.into()).collect(), - Err(err) => { - error!("Failed to get registered apps by team_id: {:?}", err); - return Err(( - StatusCode::INTERNAL_SERVER_ERROR, - CloudApiErrors::DatabaseError.to_string(), - )); - } - }; + let registered_apps: Vec = match db.get_registered_apps_by_team_id(&team.team_id).await + { + Ok(apps) => apps.into_iter().map(|app| app.into()).collect(), + Err(err) => { + error!("Failed to get registered apps by team_id: {:?}", err); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::DatabaseError.to_string(), + )); + } + }; let app_ids: Vec = registered_apps .iter() @@ -112,7 +113,7 @@ pub async fn delete_team( .collect(); // Delete team apps, privileges and domain verifications - for app in registered_apps.iter_mut() { + for app in registered_apps.iter() { if let Err(err) = db.deactivate_app(&mut tx, &app.app_id).await { let _ = tx .rollback() @@ -157,11 +158,26 @@ pub async fn delete_team( // Grafana, delete team // TODO, fix this by fixing methods for setting up grafana datasource if is_env_production() { - handle_grafana_delete_team(&grafana_conf, &request.team_id, &app_ids).await?; + match handle_grafana_delete_team(&grafana_conf, &request.team_id, &app_ids).await { + Ok(_) => {} + Err(err) => { + error!("Failed to delete team from grafana: {:?}", err); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::GrafanaError.to_string(), + )); + } + }; } // If nothing failed commit the transaction - tx.commit().await.unwrap(); + if let Err(err) = tx.commit().await { + error!("Failed to commit transaction: {:?}", err); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::DatabaseError.to_string(), + )); + } return Ok(Json(())); } @@ -176,7 +192,7 @@ mod tests { structs::cloud::cloud_http_endpoints::HttpCloudEndpoint, test_utils::test_utils::{ add_test_app, add_test_team, add_user_to_test_team, convert_response, create_test_app, - generate_valid_name, register_and_login_random_user, + generate_valid_name, get_test_team_data, register_and_login_random_user, }, }; use axum::{ @@ -220,6 +236,11 @@ mod tests { let json = serde_json::to_string(&request).unwrap(); let auth = auth_token.encode(JWT_SECRET()).unwrap(); + let team = get_test_team_data(&team_id, &auth_token, &test_app) + .await + .unwrap(); + assert_eq!(team.team_metadata.team_id, team_id.clone()); + let req = Request::builder() .method(Method::POST) .header("content-type", "application/json") @@ -235,5 +256,10 @@ mod tests { // Send request let response = test_app.clone().oneshot(req).await.unwrap(); let _ = convert_response::<()>(response).await.unwrap(); + let err = get_test_team_data(&team_id, &auth_token, &test_app) + .await + .unwrap_err(); + + assert_eq!(err.to_string(), "TeamDoesNotExist".to_string()); } } diff --git a/server/src/http/cloud/domains/cancel_pending_domain_request.rs b/server/src/http/cloud/domains/cancel_pending_domain_request.rs index f2008bd7..6bd0ca48 100644 --- a/server/src/http/cloud/domains/cancel_pending_domain_request.rs +++ b/server/src/http/cloud/domains/cancel_pending_domain_request.rs @@ -42,7 +42,7 @@ pub async fn cancel_pending_domain_request( // Check if app exists and get data let app = match db.get_registered_app_by_app_id(&request.app_id).await { Ok(Some(app)) => { - if app.active == false { + if app.deactivated_at != None { return Err(( StatusCode::BAD_REQUEST, CloudApiErrors::AppDoesNotExist.to_string(), diff --git a/server/src/http/cloud/domains/remove_whitelisted_domain.rs b/server/src/http/cloud/domains/remove_whitelisted_domain.rs index 47ac0303..25b1a0e6 100644 --- a/server/src/http/cloud/domains/remove_whitelisted_domain.rs +++ b/server/src/http/cloud/domains/remove_whitelisted_domain.rs @@ -42,7 +42,7 @@ pub async fn remove_whitelisted_domain( // Check if app exists and get data let app = match db.get_registered_app_by_app_id(&request.app_id).await { Ok(Some(app)) => { - if app.active == false { + if app.deactivated_at != None { return Err(( StatusCode::BAD_REQUEST, CloudApiErrors::AppDoesNotExist.to_string(), diff --git a/server/src/http/cloud/domains/verify_domain_finish.rs b/server/src/http/cloud/domains/verify_domain_finish.rs index 421d5a85..b6106ea3 100644 --- a/server/src/http/cloud/domains/verify_domain_finish.rs +++ b/server/src/http/cloud/domains/verify_domain_finish.rs @@ -46,7 +46,7 @@ pub async fn verify_domain_finish( // Check if app exists and get data let app = match db.get_registered_app_by_app_id(&request.app_id).await { Ok(Some(app)) => { - if app.active == false { + if app.deactivated_at != None { return Err(( StatusCode::BAD_REQUEST, CloudApiErrors::AppDoesNotExist.to_string(), diff --git a/server/src/http/cloud/domains/verify_domain_start.rs b/server/src/http/cloud/domains/verify_domain_start.rs index 4831ea37..18b32134 100644 --- a/server/src/http/cloud/domains/verify_domain_start.rs +++ b/server/src/http/cloud/domains/verify_domain_start.rs @@ -44,7 +44,7 @@ pub async fn verify_domain_start( // Check if app exists and get data let app = match db.get_registered_app_by_app_id(&request.app_id).await { Ok(Some(app)) => { - if app.active == false { + if app.deactivated_at != None { return Err(( StatusCode::BAD_REQUEST, CloudApiErrors::AppDoesNotExist.to_string(), diff --git a/server/src/http/cloud/get_team_user_invites.rs b/server/src/http/cloud/get_team_user_invites.rs index 48bb492f..0ebed4d1 100644 --- a/server/src/http/cloud/get_team_user_invites.rs +++ b/server/src/http/cloud/get_team_user_invites.rs @@ -48,7 +48,7 @@ pub async fn get_team_user_invites( CloudApiErrors::InsufficientPermissions.to_string(), )); } - if team.active == false { + if team.deactivated_at != None { return Err(( StatusCode::BAD_REQUEST, CloudApiErrors::TeamDoesNotExist.to_string(), diff --git a/server/src/http/cloud/get_team_users_privileges.rs b/server/src/http/cloud/get_team_users_privileges.rs index 7246ab3e..4576ee98 100644 --- a/server/src/http/cloud/get_team_users_privileges.rs +++ b/server/src/http/cloud/get_team_users_privileges.rs @@ -46,7 +46,7 @@ pub async fn get_team_users_privileges( )); } - if team.active == false { + if team.deactivated_at != None { return Err(( StatusCode::BAD_REQUEST, CloudApiErrors::TeamDoesNotExist.to_string(), diff --git a/server/src/http/cloud/get_user_joined_teams.rs b/server/src/http/cloud/get_user_joined_teams.rs index 83ff061c..8b3b8630 100644 --- a/server/src/http/cloud/get_user_joined_teams.rs +++ b/server/src/http/cloud/get_user_joined_teams.rs @@ -36,12 +36,11 @@ pub async fn get_user_joined_teams( let mut teams_apps = HashMap::new(); let mut user_privileges = HashMap::new(); let mut team_members = HashMap::new(); - for (team, admin_email, joined_timestamp, registered_apps) in joined_teams { let team_id = team.team_id.clone(); ///// TEMP FIX: Get team members - let mut team_privileges = match db.get_privileges_by_team_id(&team_id).await { + let team_privileges = match db.get_privileges_by_team_id(&team_id).await { Ok(privileges) => privileges, Err(err) => { error!("Failed to get team privileges: {:?}", err); diff --git a/server/src/http/cloud/grafana_utils/delete_registered_app.rs b/server/src/http/cloud/grafana_utils/delete_registered_app.rs index 740aaec8..7f4264e6 100644 --- a/server/src/http/cloud/grafana_utils/delete_registered_app.rs +++ b/server/src/http/cloud/grafana_utils/delete_registered_app.rs @@ -17,7 +17,10 @@ pub async fn handle_grafana_delete_app( Ok(response) => match response.dashboard { Some(_) => (), None => { - warn!("Failed to get dashboard: {:?}", response); + warn!( + "Failed to get dashboard: {:?}, dashboard_id:{:?}", + response, app_id + ); return Err(( StatusCode::INTERNAL_SERVER_ERROR, CloudApiErrors::AppDoesNotExist.to_string(), @@ -25,7 +28,10 @@ pub async fn handle_grafana_delete_app( } }, Err(err) => { - warn!("Failed to get template dashboard: {:?}", err); + warn!( + "Failed to delete dashboard: {:?}, dashboard_id: {:?}", + err, app_id + ); return Err(handle_grafana_error(err)); } }; @@ -33,7 +39,10 @@ pub async fn handle_grafana_delete_app( match delete_dashboard_by_uid(&grafana_conf, &app_id).await { Ok(_) => return Ok(()), Err(err) => { - warn!("Failed to delete dashboard: {:?}", err); + warn!( + "Failed to delete dashboard: {:?}, dashboard_id: {:?}", + err, app_id + ); return Err(handle_grafana_error(err)); } } diff --git a/server/src/http/cloud/grafana_utils/delete_team.rs b/server/src/http/cloud/grafana_utils/delete_team.rs index 59decb21..0f9c0329 100644 --- a/server/src/http/cloud/grafana_utils/delete_team.rs +++ b/server/src/http/cloud/grafana_utils/delete_team.rs @@ -5,12 +5,11 @@ use axum::http::StatusCode; use log::warn; use openapi::apis::{ configuration::Configuration, + folders_api::delete_folder, teams_api::{delete_team_by_id, get_team_by_id}, }; use std::sync::Arc; -use super::delete_registered_app::handle_grafana_delete_app; - pub async fn handle_grafana_delete_team( grafana_conf: &Arc, team_id: &String, @@ -20,7 +19,7 @@ pub async fn handle_grafana_delete_team( Ok(response) => match response.id { Some(_) => (), None => { - warn!("Failed to get team: {:?}", response); + warn!("Failed to get team: {:?}, team_id: {:?}", response, team_id); return Err(( StatusCode::INTERNAL_SERVER_ERROR, CloudApiErrors::TeamDoesNotExist.to_string(), @@ -28,7 +27,7 @@ pub async fn handle_grafana_delete_team( } }, Err(err) => { - warn!("Failed to get team: {:?}", err); + warn!("Failed to get team: {:?}, team_id: {:?}", err, team_id); return Err(handle_grafana_error(err)); } }; @@ -36,15 +35,16 @@ pub async fn handle_grafana_delete_team( match delete_team_by_id(&grafana_conf, team_id).await { Ok(_) => (), Err(err) => { - warn!("Failed to delete team: {:?}", err); + warn!("Failed to delete team: {:?}, team_id: {:?}", err, team_id); return Err(handle_grafana_error(err)); } } - for app_id in app_ids { - if let Err(err) = handle_grafana_delete_app(&grafana_conf, &app_id).await { - return Err(err); + let _: () = match delete_folder(&grafana_conf, team_id, None).await { + Ok(_) => {} + Err(err) => { + return Err(handle_grafana_error(err)); } - } + }; return Ok(()); } diff --git a/server/src/http/cloud/invite_user_to_team.rs b/server/src/http/cloud/invite_user_to_team.rs index 04ab6aaf..0642c4ba 100644 --- a/server/src/http/cloud/invite_user_to_team.rs +++ b/server/src/http/cloud/invite_user_to_team.rs @@ -51,7 +51,7 @@ pub async fn invite_user_to_team( )); } - if team.active != true { + if team.deactivated_at != None { return Err(( StatusCode::BAD_REQUEST, CloudApiErrors::TeamDoesNotExist.to_string(), diff --git a/server/src/http/cloud/leave_team.rs b/server/src/http/cloud/leave_team.rs index 93e4946b..0de0f125 100644 --- a/server/src/http/cloud/leave_team.rs +++ b/server/src/http/cloud/leave_team.rs @@ -56,7 +56,7 @@ pub async fn leave_team( )); } - if team.active != true { + if team.deactivated_at != None { return Err(( StatusCode::BAD_REQUEST, CloudApiErrors::TeamDoesNotExist.to_string(), diff --git a/server/src/http/cloud/register_new_app.rs b/server/src/http/cloud/register_new_app.rs index 7ea7ede7..295d9dbd 100644 --- a/server/src/http/cloud/register_new_app.rs +++ b/server/src/http/cloud/register_new_app.rs @@ -56,17 +56,24 @@ pub async fn register_new_app( CloudApiErrors::InsufficientPermissions.to_string(), )); } - + if team.deactivated_at != None { + return Err(( + StatusCode::BAD_REQUEST, + CloudApiErrors::TeamDoesNotExist.to_string(), + )); + } // Check if user has already registered an app with this name in this team match db .get_registered_app_by_app_name_and_team_id(&request.app_name, &request.team_id) .await { - Ok(Some(_)) => { - return Err(( - StatusCode::BAD_REQUEST, - CloudApiErrors::AppAlreadyExists.to_string(), - )); + Ok(Some(team)) => { + if team.deactivated_at == None { + return Err(( + StatusCode::BAD_REQUEST, + CloudApiErrors::AppAlreadyExists.to_string(), + )); + } } Ok(None) => {} Err(err) => { @@ -99,7 +106,6 @@ pub async fn register_new_app( )); } } - // Generate a new app id let app_id = uuid7().to_string(); @@ -128,7 +134,6 @@ pub async fn register_new_app( ack_public_keys: vec![], whitelisted_domains: vec![], registration_timestamp: get_current_datetime(), - active: true, deactivated_at: None, }; @@ -193,7 +198,14 @@ pub async fn register_new_app( } // If nothing failed commit the transaction - tx.commit().await.unwrap(); + // Commit transaction + if let Err(err) = tx.commit().await { + error!("Failed to commit transaction: {:?}", err); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::DatabaseError.to_string(), + )); + } return Ok(Json(HttpRegisterNewAppResponse { app_id })); } Ok(None) => { diff --git a/server/src/http/cloud/register_new_team.rs b/server/src/http/cloud/register_new_team.rs index 8eba61bc..4b6ea7b3 100644 --- a/server/src/http/cloud/register_new_team.rs +++ b/server/src/http/cloud/register_new_team.rs @@ -50,13 +50,16 @@ pub async fn register_new_team( .get_team_by_team_name_and_admin_id(&request.team_name, &user_id) .await { - Ok(Some(_)) => { - return Err(( - StatusCode::BAD_REQUEST, - CloudApiErrors::TeamAlreadyExists.to_string(), - )); - } - Ok(None) => { + Ok(team) => { + if let Some(team) = team { + if team.deactivated_at == None { + return Err(( + StatusCode::BAD_REQUEST, + CloudApiErrors::TeamAlreadyExists.to_string(), + )); + } + } + // Check how many teams the user has match db.get_user_created_teams_without_personal(&user_id).await { Ok(teams) => { @@ -132,7 +135,6 @@ pub async fn register_new_team( subscription: None, personal: request.personal, registration_timestamp: get_current_datetime(), - active: true, deactivated_at: None, }; diff --git a/server/src/http/cloud/remove_user_from_team.rs b/server/src/http/cloud/remove_user_from_team.rs index e9397060..f8f44984 100644 --- a/server/src/http/cloud/remove_user_from_team.rs +++ b/server/src/http/cloud/remove_user_from_team.rs @@ -54,7 +54,7 @@ pub async fn remove_user_from_team( )); } - if team.active != true { + if team.deactivated_at != None { return Err(( StatusCode::BAD_REQUEST, CloudApiErrors::TeamDoesNotExist.to_string(), diff --git a/server/src/structs/cloud/api_cloud_errors.rs b/server/src/structs/cloud/api_cloud_errors.rs index 447565f4..80c730db 100644 --- a/server/src/structs/cloud/api_cloud_errors.rs +++ b/server/src/structs/cloud/api_cloud_errors.rs @@ -53,4 +53,5 @@ pub enum CloudApiErrors { InvalidOrigin, InvalidAction, AdminCannotLeaveTeam, + GrafanaError, } diff --git a/server/src/test_utils.rs b/server/src/test_utils.rs index 1938d34b..96c54fda 100644 --- a/server/src/test_utils.rs +++ b/server/src/test_utils.rs @@ -16,6 +16,7 @@ pub mod test_utils { HttpVerifyDomainStartRequest, HttpVerifyDomainStartResponse, }, }, + get_team_metadata::HttpGetTeamMetadataResponse, get_team_user_invites::HttpGetTeamUserInvitesResponse, get_user_joined_teams::HttpGetUserJoinedTeamsResponse, get_user_team_invites::HttpGetUserTeamInvitesResponse, @@ -540,7 +541,6 @@ pub mod test_utils { app: &Router, ) -> anyhow::Result { let user_joined_teams = get_test_user_joined_teams(user_token, app).await?; - match user_joined_teams.teams_apps.get(team_id) { Some(apps) => match apps.iter().find(|app| &app.app_id == app_id) { Some(app) => Ok(app.clone()), @@ -550,6 +550,32 @@ pub mod test_utils { } } + pub async fn get_test_team_data( + team_id: &String, + user_token: &AuthToken, + app: &Router, + ) -> anyhow::Result { + let ip: ConnectInfo = ConnectInfo(SocketAddr::from(([127, 0, 0, 1], 8080))); + let auth = user_token.encode(JWT_SECRET()).unwrap(); + + let req = Request::builder() + .method(Method::GET) + .header("content-type", "application/json") + .header("authorization", format!("Bearer {auth}")) + .uri(format!( + "/cloud/private{}?teamId={team_id}", + HttpCloudEndpoint::GetTeamMetadata.to_string() + )) + .extension(ip.clone()) + .body(Body::empty()) + .unwrap(); + + // Send request + let response = app.clone().oneshot(req).await.unwrap(); + // Validate response + convert_response::(response).await + } + pub async fn body_to_vec(response: Response) -> anyhow::Result> { match to_bytes(response.into_body(), usize::MAX).await { Ok(body) => Ok(body.to_vec()), From ab13fcd22658304e6564a8c29e005ff38344f21a Mon Sep 17 00:00:00 2001 From: Julia Seweryn Date: Mon, 14 Oct 2024 02:06:19 +0200 Subject: [PATCH 4/9] fixed --- database/src/tables/test_utils.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/database/src/tables/test_utils.rs b/database/src/tables/test_utils.rs index 7f84c30e..571c5a25 100644 --- a/database/src/tables/test_utils.rs +++ b/database/src/tables/test_utils.rs @@ -102,7 +102,6 @@ pub mod test_utils { subscription: None, team_admin_id: user_id.clone(), registration_timestamp: registration_timestamp, - active: true, deactivated_at: None, }; @@ -113,7 +112,6 @@ pub mod test_utils { whitelisted_domains: vec!["localhost".to_string()], ack_public_keys: vec!["key".to_string()], registration_timestamp: registration_timestamp, - active: true, deactivated_at: None, }; From 79726d0fbabad33584f039492cd68eb9b1714028 Mon Sep 17 00:00:00 2001 From: Julia Seweryn Date: Mon, 14 Oct 2024 02:11:51 +0200 Subject: [PATCH 5/9] fixed --- database/src/tables/team/update.rs | 1 - database/src/tables/user_app_privileges/update.rs | 3 --- 2 files changed, 4 deletions(-) diff --git a/database/src/tables/team/update.rs b/database/src/tables/team/update.rs index a594802a..a398db51 100644 --- a/database/src/tables/team/update.rs +++ b/database/src/tables/team/update.rs @@ -126,7 +126,6 @@ mod tests { subscription: None, team_admin_id: "test_team_admin_id".to_string(), registration_timestamp: to_microsecond_precision(&Utc::now()), - active: true, deactivated_at: None, }; diff --git a/database/src/tables/user_app_privileges/update.rs b/database/src/tables/user_app_privileges/update.rs index 7a179c63..f647e7bd 100644 --- a/database/src/tables/user_app_privileges/update.rs +++ b/database/src/tables/user_app_privileges/update.rs @@ -351,7 +351,6 @@ mod tests { app_name: format!("test_app_name_{}", i), team_id: team_id.clone(), registration_timestamp: to_microsecond_precision(&Utc::now()), - active: true, deactivated_at: None, }; @@ -377,7 +376,6 @@ mod tests { subscription: None, team_admin_id: "test_team_admin_id".to_string(), registration_timestamp: to_microsecond_precision(&Utc::now()), - active: true, deactivated_at: None, }; @@ -393,7 +391,6 @@ mod tests { app_name: format!("test_app_name_{}", i), team_id: team_id.clone(), registration_timestamp: to_microsecond_precision(&Utc::now()), - active: true, deactivated_at: None, }; From 358a282a56185a564a9fd3a951631218447ceb9e Mon Sep 17 00:00:00 2001 From: Julia Seweryn Date: Mon, 14 Oct 2024 09:48:54 +0200 Subject: [PATCH 6/9] fixed --- server/src/http/cloud/delete_team.rs | 7 +------ server/src/http/cloud/grafana_utils/delete_team.rs | 1 - 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/server/src/http/cloud/delete_team.rs b/server/src/http/cloud/delete_team.rs index 5f6b36a7..35a88709 100644 --- a/server/src/http/cloud/delete_team.rs +++ b/server/src/http/cloud/delete_team.rs @@ -107,11 +107,6 @@ pub async fn delete_team( } }; - let app_ids: Vec = registered_apps - .iter() - .map(|app| app.app_id.clone()) - .collect(); - // Delete team apps, privileges and domain verifications for app in registered_apps.iter() { if let Err(err) = db.deactivate_app(&mut tx, &app.app_id).await { @@ -158,7 +153,7 @@ pub async fn delete_team( // Grafana, delete team // TODO, fix this by fixing methods for setting up grafana datasource if is_env_production() { - match handle_grafana_delete_team(&grafana_conf, &request.team_id, &app_ids).await { + match handle_grafana_delete_team(&grafana_conf, &request.team_id).await { Ok(_) => {} Err(err) => { error!("Failed to delete team from grafana: {:?}", err); diff --git a/server/src/http/cloud/grafana_utils/delete_team.rs b/server/src/http/cloud/grafana_utils/delete_team.rs index 0f9c0329..e64758ad 100644 --- a/server/src/http/cloud/grafana_utils/delete_team.rs +++ b/server/src/http/cloud/grafana_utils/delete_team.rs @@ -13,7 +13,6 @@ use std::sync::Arc; pub async fn handle_grafana_delete_team( grafana_conf: &Arc, team_id: &String, - app_ids: &Vec, ) -> Result<(), (StatusCode, String)> { match get_team_by_id(&grafana_conf, &team_id).await { Ok(response) => match response.id { From 54d14928e08d2c1c0ee0058931d43bf006e8eae6 Mon Sep 17 00:00:00 2001 From: Julia Seweryn Date: Mon, 14 Oct 2024 17:26:27 +0200 Subject: [PATCH 7/9] fixed --- server/src/http/cloud/accept_team_invite.rs | 16 ++++++++--- server/src/http/cloud/delete_app.rs | 15 +++++------ server/src/http/cloud/delete_team.rs | 17 +++++------- server/src/http/cloud/leave_team.rs | 19 ++++++++++--- server/src/http/cloud/register_new_app.rs | 11 ++++++-- server/src/http/cloud/register_new_team.rs | 27 ++++++++++++++----- .../src/http/cloud/remove_user_from_team.rs | 23 +++++++++++----- 7 files changed, 88 insertions(+), 40 deletions(-) diff --git a/server/src/http/cloud/accept_team_invite.rs b/server/src/http/cloud/accept_team_invite.rs index 5cafb8f8..c34818c4 100644 --- a/server/src/http/cloud/accept_team_invite.rs +++ b/server/src/http/cloud/accept_team_invite.rs @@ -3,7 +3,8 @@ use super::{ utils::{custom_validate_team_id, validate_request}, }; use crate::{ - middlewares::auth_middleware::UserId, structs::cloud::api_cloud_errors::CloudApiErrors, + env::is_env_production, middlewares::auth_middleware::UserId, + structs::cloud::api_cloud_errors::CloudApiErrors, }; use axum::{extract::State, http::StatusCode, Extension, Json}; use database::db::Db; @@ -101,8 +102,17 @@ pub async fn accept_team_invite( } // Grafana add user to the team - handle_grafana_add_user_to_team(&grafana_conf, &request.team_id, &user.email).await?; - + if is_env_production() { + if let Err(err) = + handle_grafana_add_user_to_team(&grafana_conf, &request.team_id, &user.email).await + { + error!("Failed to add user to the team in grafana: {:?}", err); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::GrafanaError.to_string(), + )); + }; + } // Accept invite let mut tx = match db.connection_pool.begin().await { Ok(tx) => tx, diff --git a/server/src/http/cloud/delete_app.rs b/server/src/http/cloud/delete_app.rs index 549d42a3..8636b4b7 100644 --- a/server/src/http/cloud/delete_app.rs +++ b/server/src/http/cloud/delete_app.rs @@ -114,15 +114,12 @@ pub async fn delete_app( // Grafana, delete app // TODO, fix this by fixing methods for setting up grafana datasource if is_env_production() { - match handle_grafana_delete_app(&grafana_conf, &request.app_id).await { - Ok(_) => {} - Err(err) => { - error!("Failed to delete app from grafana: {:?}", err); - return Err(( - StatusCode::INTERNAL_SERVER_ERROR, - CloudApiErrors::GrafanaError.to_string(), - )); - } + if let Err(err) = handle_grafana_delete_app(&grafana_conf, &request.app_id).await { + error!("Failed to delete app from grafana: {:?}", err); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::GrafanaError.to_string(), + )); } } diff --git a/server/src/http/cloud/delete_team.rs b/server/src/http/cloud/delete_team.rs index 35a88709..4975d90c 100644 --- a/server/src/http/cloud/delete_team.rs +++ b/server/src/http/cloud/delete_team.rs @@ -56,7 +56,7 @@ pub async fn delete_team( Ok(None) => { return Err(( StatusCode::BAD_REQUEST, - CloudApiErrors::AppDoesNotExist.to_string(), + CloudApiErrors::TeamDoesNotExist.to_string(), )); } Err(err) => { @@ -153,15 +153,12 @@ pub async fn delete_team( // Grafana, delete team // TODO, fix this by fixing methods for setting up grafana datasource if is_env_production() { - match handle_grafana_delete_team(&grafana_conf, &request.team_id).await { - Ok(_) => {} - Err(err) => { - error!("Failed to delete team from grafana: {:?}", err); - return Err(( - StatusCode::INTERNAL_SERVER_ERROR, - CloudApiErrors::GrafanaError.to_string(), - )); - } + if let Err(err) = handle_grafana_delete_team(&grafana_conf, &request.team_id).await { + error!("Failed to delete team from grafana: {:?}", err); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::GrafanaError.to_string(), + )); }; } diff --git a/server/src/http/cloud/leave_team.rs b/server/src/http/cloud/leave_team.rs index 0de0f125..239bdab5 100644 --- a/server/src/http/cloud/leave_team.rs +++ b/server/src/http/cloud/leave_team.rs @@ -3,6 +3,7 @@ use super::{ utils::{custom_validate_team_id, validate_request}, }; use crate::{ + env::is_env_production, mailer::{ mail_requests::{SendEmailRequest, TeamLeavingNotification}, mailer::Mailer, @@ -104,9 +105,21 @@ pub async fn leave_team( } // Grafana, remove user from the team - handle_grafana_remove_user_from_team(&grafana_conf, &request.team_id, &user.email) - .await?; - + if is_env_production() { + if let Err(err) = handle_grafana_remove_user_from_team( + &grafana_conf, + &request.team_id, + &user.email, + ) + .await + { + error!("Grafana, failed to left the team: {:?}", err); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::GrafanaError.to_string(), + )); + }; + } // Remove user from the team if let Err(err) = db .remove_user_from_the_team(&user_id, &request.team_id) diff --git a/server/src/http/cloud/register_new_app.rs b/server/src/http/cloud/register_new_app.rs index 295d9dbd..458dfd1d 100644 --- a/server/src/http/cloud/register_new_app.rs +++ b/server/src/http/cloud/register_new_app.rs @@ -112,13 +112,20 @@ pub async fn register_new_app( // Grafana, add new app // TODO, fix this by fixing methods for setting up grafana datasource if is_env_production() { - handle_grafana_create_new_app( + if let Err(err) = handle_grafana_create_new_app( &grafana_conf, &request.app_name, &app_id, &team.team_id, ) - .await?; + .await + { + error!("Failed to create new app in grafana: {:?}", err); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::GrafanaError.to_string(), + )); + }; } // Register a new app under this team diff --git a/server/src/http/cloud/register_new_team.rs b/server/src/http/cloud/register_new_team.rs index 4b6ea7b3..0b10cdd6 100644 --- a/server/src/http/cloud/register_new_team.rs +++ b/server/src/http/cloud/register_new_team.rs @@ -3,8 +3,8 @@ use super::{ utils::{custom_validate_name, validate_request}, }; use crate::{ - middlewares::auth_middleware::UserId, statics::TEAMS_AMOUNT_LIMIT_PER_USER, - structs::cloud::api_cloud_errors::CloudApiErrors, + env::is_env_production, middlewares::auth_middleware::UserId, + statics::TEAMS_AMOUNT_LIMIT_PER_USER, structs::cloud::api_cloud_errors::CloudApiErrors, }; use axum::{extract::State, http::StatusCode, Extension, Json}; use database::{ @@ -121,11 +121,26 @@ pub async fn register_new_team( )); } }; - + let mut grafana_team_id: i64 = 0; // Grafana, add new team - let grafana_team_id = - handle_grafana_create_new_team(&grafana_conf, &admin_email, &request.team_name) - .await?; + if is_env_production() { + grafana_team_id = match handle_grafana_create_new_team( + &grafana_conf, + &admin_email, + &request.team_name, + ) + .await + { + Ok(id) => id, + Err(err) => { + error!("Failed to create team in grafana: {:?}", err); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::GrafanaError.to_string(), + )); + } + } + } // Create a new team let team = Team { diff --git a/server/src/http/cloud/remove_user_from_team.rs b/server/src/http/cloud/remove_user_from_team.rs index f8f44984..bdc11d06 100644 --- a/server/src/http/cloud/remove_user_from_team.rs +++ b/server/src/http/cloud/remove_user_from_team.rs @@ -3,6 +3,7 @@ use super::{ utils::{custom_validate_team_id, validate_request}, }; use crate::{ + env::is_env_production, mailer::{ mail_requests::{SendEmailRequest, TeamRemovalNotification}, mailer::Mailer, @@ -106,13 +107,21 @@ pub async fn remove_user_from_team( } // Grafana, remove user from the team - handle_grafana_remove_user_from_team( - &grafana_conf, - &request.team_id, - &request.user_email, - ) - .await?; - + if is_env_production() { + if let Err(err) = handle_grafana_remove_user_from_team( + &grafana_conf, + &request.team_id, + &request.user_email, + ) + .await + { + error!("Failed to remove user from the team in grafana: {:?}", err); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::GrafanaError.to_string(), + )); + }; + } // Remove user from the team if let Err(err) = db .remove_user_from_the_team(&user.user_id, &request.team_id) From bee4743ec450a9fcda984e244a7aa4c017cf1bf8 Mon Sep 17 00:00:00 2001 From: Julia Seweryn Date: Tue, 15 Oct 2024 14:18:29 +0200 Subject: [PATCH 8/9] fixed --- openapi/src/apis/folders_api.rs | 265 +++++++++++++----- .../src/models/delete_folder_200_response.rs | 26 +- .../cloud/grafana_utils/create_new_app.rs | 2 +- .../http/cloud/grafana_utils/delete_team.rs | 4 +- 4 files changed, 221 insertions(+), 76 deletions(-) diff --git a/openapi/src/apis/folders_api.rs b/openapi/src/apis/folders_api.rs index b9a70bc4..015952b4 100755 --- a/openapi/src/apis/folders_api.rs +++ b/openapi/src/apis/folders_api.rs @@ -8,12 +8,10 @@ * Generated by: https://openapi-generator.tech */ - use reqwest; +use super::{configuration, Error}; use crate::{apis::ResponseContent, models}; -use super::{Error, configuration}; - /// struct for typed errors of method [`create_folder`] #[derive(Debug, Clone, Serialize, Deserialize)] @@ -106,18 +104,22 @@ pub enum UpdateFolderError { UnknownValue(serde_json::Value), } - /// If nested folders are enabled then it additionally expects the parent folder UID. -pub async fn create_folder(configuration: &configuration::Configuration, create_folder_command: models::CreateFolderCommand) -> Result> { +pub async fn create_folder( + configuration: &configuration::Configuration, + create_folder_command: models::CreateFolderCommand, +) -> Result> { let local_var_configuration = configuration; let local_var_client = &local_var_configuration.client; let local_var_uri_str = format!("{}/folders", local_var_configuration.base_path); - let mut local_var_req_builder = local_var_client.request(reqwest::Method::POST, local_var_uri_str.as_str()); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::POST, local_var_uri_str.as_str()); if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { - local_var_req_builder = local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); } if let Some(ref local_var_apikey) = local_var_configuration.api_key { let local_var_key = local_var_apikey.key.clone(); @@ -128,7 +130,10 @@ pub async fn create_folder(configuration: &configuration::Configuration, create_ local_var_req_builder = local_var_req_builder.header("Authorization", local_var_value); }; if let Some(ref local_var_auth_conf) = local_var_configuration.basic_auth { - local_var_req_builder = local_var_req_builder.basic_auth(local_var_auth_conf.0.to_owned(), local_var_auth_conf.1.to_owned()); + local_var_req_builder = local_var_req_builder.basic_auth( + local_var_auth_conf.0.to_owned(), + local_var_auth_conf.1.to_owned(), + ); }; local_var_req_builder = local_var_req_builder.json(&create_folder_command); @@ -141,26 +146,43 @@ pub async fn create_folder(configuration: &configuration::Configuration, create_ if !local_var_status.is_client_error() && !local_var_status.is_server_error() { serde_json::from_str(&local_var_content).map_err(Error::from) } else { - let local_var_entity: Option = serde_json::from_str(&local_var_content).ok(); - let local_var_error = ResponseContent { status: local_var_status, content: local_var_content, entity: local_var_entity }; + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; Err(Error::ResponseError(local_var_error)) } } +// Response for this method has been modified - errors in the original OpenAPI spec /// Deletes an existing folder identified by UID along with all dashboards (and their alerts) stored in the folder. This operation cannot be reverted. If nested folders are enabled then it also deletes all the subfolders. -pub async fn delete_folder(configuration: &configuration::Configuration, folder_uid: &str, force_delete_rules: Option) -> Result> { +pub async fn delete_folder( + configuration: &configuration::Configuration, + folder_uid: &str, + force_delete_rules: Option, +) -> Result> { let local_var_configuration = configuration; let local_var_client = &local_var_configuration.client; - let local_var_uri_str = format!("{}/folders/{folder_uid}", local_var_configuration.base_path, folder_uid=crate::apis::urlencode(folder_uid)); - let mut local_var_req_builder = local_var_client.request(reqwest::Method::DELETE, local_var_uri_str.as_str()); + let local_var_uri_str = format!( + "{}/folders/{folder_uid}", + local_var_configuration.base_path, + folder_uid = crate::apis::urlencode(folder_uid) + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::DELETE, local_var_uri_str.as_str()); if let Some(ref local_var_str) = force_delete_rules { - local_var_req_builder = local_var_req_builder.query(&[("forceDeleteRules", &local_var_str.to_string())]); + local_var_req_builder = + local_var_req_builder.query(&[("forceDeleteRules", &local_var_str.to_string())]); } if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { - local_var_req_builder = local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); } if let Some(ref local_var_apikey) = local_var_configuration.api_key { let local_var_key = local_var_apikey.key.clone(); @@ -171,10 +193,14 @@ pub async fn delete_folder(configuration: &configuration::Configuration, folder_ local_var_req_builder = local_var_req_builder.header("Authorization", local_var_value); }; if let Some(ref local_var_auth_conf) = local_var_configuration.basic_auth { - local_var_req_builder = local_var_req_builder.basic_auth(local_var_auth_conf.0.to_owned(), local_var_auth_conf.1.to_owned()); + local_var_req_builder = local_var_req_builder.basic_auth( + local_var_auth_conf.0.to_owned(), + local_var_auth_conf.1.to_owned(), + ); }; let local_var_req = local_var_req_builder.build()?; + println!("req: {:?}", local_var_req); let local_var_resp = local_var_client.execute(local_var_req).await?; let local_var_status = local_var_resp.status(); @@ -183,23 +209,37 @@ pub async fn delete_folder(configuration: &configuration::Configuration, folder_ if !local_var_status.is_client_error() && !local_var_status.is_server_error() { serde_json::from_str(&local_var_content).map_err(Error::from) } else { - let local_var_entity: Option = serde_json::from_str(&local_var_content).ok(); - let local_var_error = ResponseContent { status: local_var_status, content: local_var_content, entity: local_var_entity }; + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; Err(Error::ResponseError(local_var_error)) } } /// Returns the folder identified by id. This is deprecated. Please refer to [updated API](#/folders/getFolderByUID) instead -pub async fn get_folder_by_id(configuration: &configuration::Configuration, folder_id: i64) -> Result> { +pub async fn get_folder_by_id( + configuration: &configuration::Configuration, + folder_id: i64, +) -> Result> { let local_var_configuration = configuration; let local_var_client = &local_var_configuration.client; - let local_var_uri_str = format!("{}/folders/id/{folder_id}", local_var_configuration.base_path, folder_id=folder_id); - let mut local_var_req_builder = local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); + let local_var_uri_str = format!( + "{}/folders/id/{folder_id}", + local_var_configuration.base_path, + folder_id = folder_id + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { - local_var_req_builder = local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); } if let Some(ref local_var_apikey) = local_var_configuration.api_key { let local_var_key = local_var_apikey.key.clone(); @@ -210,7 +250,10 @@ pub async fn get_folder_by_id(configuration: &configuration::Configuration, fold local_var_req_builder = local_var_req_builder.header("Authorization", local_var_value); }; if let Some(ref local_var_auth_conf) = local_var_configuration.basic_auth { - local_var_req_builder = local_var_req_builder.basic_auth(local_var_auth_conf.0.to_owned(), local_var_auth_conf.1.to_owned()); + local_var_req_builder = local_var_req_builder.basic_auth( + local_var_auth_conf.0.to_owned(), + local_var_auth_conf.1.to_owned(), + ); }; let local_var_req = local_var_req_builder.build()?; @@ -222,22 +265,36 @@ pub async fn get_folder_by_id(configuration: &configuration::Configuration, fold if !local_var_status.is_client_error() && !local_var_status.is_server_error() { serde_json::from_str(&local_var_content).map_err(Error::from) } else { - let local_var_entity: Option = serde_json::from_str(&local_var_content).ok(); - let local_var_error = ResponseContent { status: local_var_status, content: local_var_content, entity: local_var_entity }; + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; Err(Error::ResponseError(local_var_error)) } } -pub async fn get_folder_by_uid(configuration: &configuration::Configuration, folder_uid: &str) -> Result> { +pub async fn get_folder_by_uid( + configuration: &configuration::Configuration, + folder_uid: &str, +) -> Result> { let local_var_configuration = configuration; let local_var_client = &local_var_configuration.client; - let local_var_uri_str = format!("{}/folders/{folder_uid}", local_var_configuration.base_path, folder_uid=crate::apis::urlencode(folder_uid)); - let mut local_var_req_builder = local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); + let local_var_uri_str = format!( + "{}/folders/{folder_uid}", + local_var_configuration.base_path, + folder_uid = crate::apis::urlencode(folder_uid) + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { - local_var_req_builder = local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); } if let Some(ref local_var_apikey) = local_var_configuration.api_key { let local_var_key = local_var_apikey.key.clone(); @@ -248,7 +305,10 @@ pub async fn get_folder_by_uid(configuration: &configuration::Configuration, fol local_var_req_builder = local_var_req_builder.header("Authorization", local_var_value); }; if let Some(ref local_var_auth_conf) = local_var_configuration.basic_auth { - local_var_req_builder = local_var_req_builder.basic_auth(local_var_auth_conf.0.to_owned(), local_var_auth_conf.1.to_owned()); + local_var_req_builder = local_var_req_builder.basic_auth( + local_var_auth_conf.0.to_owned(), + local_var_auth_conf.1.to_owned(), + ); }; let local_var_req = local_var_req_builder.build()?; @@ -260,22 +320,36 @@ pub async fn get_folder_by_uid(configuration: &configuration::Configuration, fol if !local_var_status.is_client_error() && !local_var_status.is_server_error() { serde_json::from_str(&local_var_content).map_err(Error::from) } else { - let local_var_entity: Option = serde_json::from_str(&local_var_content).ok(); - let local_var_error = ResponseContent { status: local_var_status, content: local_var_content, entity: local_var_entity }; + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; Err(Error::ResponseError(local_var_error)) } } -pub async fn get_folder_descendant_counts(configuration: &configuration::Configuration, folder_uid: &str) -> Result, Error> { +pub async fn get_folder_descendant_counts( + configuration: &configuration::Configuration, + folder_uid: &str, +) -> Result, Error> { let local_var_configuration = configuration; let local_var_client = &local_var_configuration.client; - let local_var_uri_str = format!("{}/folders/{folder_uid}/counts", local_var_configuration.base_path, folder_uid=crate::apis::urlencode(folder_uid)); - let mut local_var_req_builder = local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); + let local_var_uri_str = format!( + "{}/folders/{folder_uid}/counts", + local_var_configuration.base_path, + folder_uid = crate::apis::urlencode(folder_uid) + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { - local_var_req_builder = local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); } if let Some(ref local_var_apikey) = local_var_configuration.api_key { let local_var_key = local_var_apikey.key.clone(); @@ -286,7 +360,10 @@ pub async fn get_folder_descendant_counts(configuration: &configuration::Configu local_var_req_builder = local_var_req_builder.header("Authorization", local_var_value); }; if let Some(ref local_var_auth_conf) = local_var_configuration.basic_auth { - local_var_req_builder = local_var_req_builder.basic_auth(local_var_auth_conf.0.to_owned(), local_var_auth_conf.1.to_owned()); + local_var_req_builder = local_var_req_builder.basic_auth( + local_var_auth_conf.0.to_owned(), + local_var_auth_conf.1.to_owned(), + ); }; let local_var_req = local_var_req_builder.build()?; @@ -298,35 +375,52 @@ pub async fn get_folder_descendant_counts(configuration: &configuration::Configu if !local_var_status.is_client_error() && !local_var_status.is_server_error() { serde_json::from_str(&local_var_content).map_err(Error::from) } else { - let local_var_entity: Option = serde_json::from_str(&local_var_content).ok(); - let local_var_error = ResponseContent { status: local_var_status, content: local_var_content, entity: local_var_entity }; + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; Err(Error::ResponseError(local_var_error)) } } /// It returns all folders that the authenticated user has permission to view. If nested folders are enabled, it expects an additional query parameter with the parent folder UID and returns the immediate subfolders that the authenticated user has permission to view. If the parameter is not supplied then it returns immediate subfolders under the root that the authenticated user has permission to view. -pub async fn get_folders(configuration: &configuration::Configuration, limit: Option, page: Option, parent_uid: Option<&str>, permission: Option<&str>) -> Result, Error> { +pub async fn get_folders( + configuration: &configuration::Configuration, + limit: Option, + page: Option, + parent_uid: Option<&str>, + permission: Option<&str>, +) -> Result, Error> { let local_var_configuration = configuration; let local_var_client = &local_var_configuration.client; let local_var_uri_str = format!("{}/folders", local_var_configuration.base_path); - let mut local_var_req_builder = local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); if let Some(ref local_var_str) = limit { - local_var_req_builder = local_var_req_builder.query(&[("limit", &local_var_str.to_string())]); + local_var_req_builder = + local_var_req_builder.query(&[("limit", &local_var_str.to_string())]); } if let Some(ref local_var_str) = page { - local_var_req_builder = local_var_req_builder.query(&[("page", &local_var_str.to_string())]); + local_var_req_builder = + local_var_req_builder.query(&[("page", &local_var_str.to_string())]); } if let Some(ref local_var_str) = parent_uid { - local_var_req_builder = local_var_req_builder.query(&[("parentUid", &local_var_str.to_string())]); + local_var_req_builder = + local_var_req_builder.query(&[("parentUid", &local_var_str.to_string())]); } if let Some(ref local_var_str) = permission { - local_var_req_builder = local_var_req_builder.query(&[("permission", &local_var_str.to_string())]); + local_var_req_builder = + local_var_req_builder.query(&[("permission", &local_var_str.to_string())]); } if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { - local_var_req_builder = local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); } if let Some(ref local_var_apikey) = local_var_configuration.api_key { let local_var_key = local_var_apikey.key.clone(); @@ -337,7 +431,10 @@ pub async fn get_folders(configuration: &configuration::Configuration, limit: Op local_var_req_builder = local_var_req_builder.header("Authorization", local_var_value); }; if let Some(ref local_var_auth_conf) = local_var_configuration.basic_auth { - local_var_req_builder = local_var_req_builder.basic_auth(local_var_auth_conf.0.to_owned(), local_var_auth_conf.1.to_owned()); + local_var_req_builder = local_var_req_builder.basic_auth( + local_var_auth_conf.0.to_owned(), + local_var_auth_conf.1.to_owned(), + ); }; let local_var_req = local_var_req_builder.build()?; @@ -349,22 +446,37 @@ pub async fn get_folders(configuration: &configuration::Configuration, limit: Op if !local_var_status.is_client_error() && !local_var_status.is_server_error() { serde_json::from_str(&local_var_content).map_err(Error::from) } else { - let local_var_entity: Option = serde_json::from_str(&local_var_content).ok(); - let local_var_error = ResponseContent { status: local_var_status, content: local_var_content, entity: local_var_entity }; + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; Err(Error::ResponseError(local_var_error)) } } -pub async fn move_folder(configuration: &configuration::Configuration, folder_uid: &str, move_folder_command: models::MoveFolderCommand) -> Result> { +pub async fn move_folder( + configuration: &configuration::Configuration, + folder_uid: &str, + move_folder_command: models::MoveFolderCommand, +) -> Result> { let local_var_configuration = configuration; let local_var_client = &local_var_configuration.client; - let local_var_uri_str = format!("{}/folders/{folder_uid}/move", local_var_configuration.base_path, folder_uid=crate::apis::urlencode(folder_uid)); - let mut local_var_req_builder = local_var_client.request(reqwest::Method::POST, local_var_uri_str.as_str()); + let local_var_uri_str = format!( + "{}/folders/{folder_uid}/move", + local_var_configuration.base_path, + folder_uid = crate::apis::urlencode(folder_uid) + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::POST, local_var_uri_str.as_str()); if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { - local_var_req_builder = local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); } if let Some(ref local_var_apikey) = local_var_configuration.api_key { let local_var_key = local_var_apikey.key.clone(); @@ -375,7 +487,10 @@ pub async fn move_folder(configuration: &configuration::Configuration, folder_ui local_var_req_builder = local_var_req_builder.header("Authorization", local_var_value); }; if let Some(ref local_var_auth_conf) = local_var_configuration.basic_auth { - local_var_req_builder = local_var_req_builder.basic_auth(local_var_auth_conf.0.to_owned(), local_var_auth_conf.1.to_owned()); + local_var_req_builder = local_var_req_builder.basic_auth( + local_var_auth_conf.0.to_owned(), + local_var_auth_conf.1.to_owned(), + ); }; local_var_req_builder = local_var_req_builder.json(&move_folder_command); @@ -388,22 +503,37 @@ pub async fn move_folder(configuration: &configuration::Configuration, folder_ui if !local_var_status.is_client_error() && !local_var_status.is_server_error() { serde_json::from_str(&local_var_content).map_err(Error::from) } else { - let local_var_entity: Option = serde_json::from_str(&local_var_content).ok(); - let local_var_error = ResponseContent { status: local_var_status, content: local_var_content, entity: local_var_entity }; + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; Err(Error::ResponseError(local_var_error)) } } -pub async fn update_folder(configuration: &configuration::Configuration, folder_uid: &str, update_folder_command: models::UpdateFolderCommand) -> Result> { +pub async fn update_folder( + configuration: &configuration::Configuration, + folder_uid: &str, + update_folder_command: models::UpdateFolderCommand, +) -> Result> { let local_var_configuration = configuration; let local_var_client = &local_var_configuration.client; - let local_var_uri_str = format!("{}/folders/{folder_uid}", local_var_configuration.base_path, folder_uid=crate::apis::urlencode(folder_uid)); - let mut local_var_req_builder = local_var_client.request(reqwest::Method::PUT, local_var_uri_str.as_str()); + let local_var_uri_str = format!( + "{}/folders/{folder_uid}", + local_var_configuration.base_path, + folder_uid = crate::apis::urlencode(folder_uid) + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::PUT, local_var_uri_str.as_str()); if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { - local_var_req_builder = local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); } if let Some(ref local_var_apikey) = local_var_configuration.api_key { let local_var_key = local_var_apikey.key.clone(); @@ -414,7 +544,10 @@ pub async fn update_folder(configuration: &configuration::Configuration, folder_ local_var_req_builder = local_var_req_builder.header("Authorization", local_var_value); }; if let Some(ref local_var_auth_conf) = local_var_configuration.basic_auth { - local_var_req_builder = local_var_req_builder.basic_auth(local_var_auth_conf.0.to_owned(), local_var_auth_conf.1.to_owned()); + local_var_req_builder = local_var_req_builder.basic_auth( + local_var_auth_conf.0.to_owned(), + local_var_auth_conf.1.to_owned(), + ); }; local_var_req_builder = local_var_req_builder.json(&update_folder_command); @@ -427,9 +560,13 @@ pub async fn update_folder(configuration: &configuration::Configuration, folder_ if !local_var_status.is_client_error() && !local_var_status.is_server_error() { serde_json::from_str(&local_var_content).map_err(Error::from) } else { - let local_var_entity: Option = serde_json::from_str(&local_var_content).ok(); - let local_var_error = ResponseContent { status: local_var_status, content: local_var_content, entity: local_var_entity }; + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; Err(Error::ResponseError(local_var_error)) } } - diff --git a/openapi/src/models/delete_folder_200_response.rs b/openapi/src/models/delete_folder_200_response.rs index 29242300..c0bd3c85 100755 --- a/openapi/src/models/delete_folder_200_response.rs +++ b/openapi/src/models/delete_folder_200_response.rs @@ -10,26 +10,32 @@ use crate::models; +// Response has been modified - errors in the original OpenAPI spec #[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] pub struct DeleteFolder200Response { - /// ID Identifier of the deleted folder. - #[serde(rename = "id")] - pub id: i64, /// Message Message of the deleted folder. #[serde(rename = "message")] pub message: String, - /// Title of the deleted folder. - #[serde(rename = "title")] - pub title: String, } +// pub struct DeleteFolder200Response { +// /// ID Identifier of the deleted folder. +// #[serde(rename = "id")] +// pub id: i64, +// /// Message Message of the deleted folder. +// #[serde(rename = "message")] +// pub message: String, +// /// Title of the deleted folder. +// #[serde(rename = "title")] +// pub title: String, +// } + impl DeleteFolder200Response { - pub fn new(id: i64, message: String, title: String) -> DeleteFolder200Response { + pub fn new(message: String) -> DeleteFolder200Response { DeleteFolder200Response { - id, + // id, message, - title, + // title, } } } - diff --git a/server/src/http/cloud/grafana_utils/create_new_app.rs b/server/src/http/cloud/grafana_utils/create_new_app.rs index c728bbed..863196c3 100644 --- a/server/src/http/cloud/grafana_utils/create_new_app.rs +++ b/server/src/http/cloud/grafana_utils/create_new_app.rs @@ -74,7 +74,7 @@ pub async fn handle_grafana_create_new_app( dashboard: Some(template_dashboard), folder_id: None, folder_uid: Some(team_id.clone()), // When we create a new team, we create a folder with the same uid as the team id - inputs: None, + inputs: None, overwrite: Some(false), path: None, plugin_id: None, diff --git a/server/src/http/cloud/grafana_utils/delete_team.rs b/server/src/http/cloud/grafana_utils/delete_team.rs index e64758ad..5bcd0f9e 100644 --- a/server/src/http/cloud/grafana_utils/delete_team.rs +++ b/server/src/http/cloud/grafana_utils/delete_team.rs @@ -39,9 +39,11 @@ pub async fn handle_grafana_delete_team( } } - let _: () = match delete_folder(&grafana_conf, team_id, None).await { + // Response for this method has been modified - errors in the original OpenAPI spec + let _: () = match delete_folder(&grafana_conf, team_id, Some(false)).await { Ok(_) => {} Err(err) => { + warn!("Failed to delete folder: {:?}, team_id: {:?}", err, team_id); return Err(handle_grafana_error(err)); } }; From 8b603f75e74d1c59182d7bb1c647dc4c9e7d2246 Mon Sep 17 00:00:00 2001 From: Julia Seweryn Date: Tue, 15 Oct 2024 14:20:44 +0200 Subject: [PATCH 9/9] fixed --- server/src/http/cloud/grafana_utils/delete_team.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/server/src/http/cloud/grafana_utils/delete_team.rs b/server/src/http/cloud/grafana_utils/delete_team.rs index 5bcd0f9e..aa73ed1e 100644 --- a/server/src/http/cloud/grafana_utils/delete_team.rs +++ b/server/src/http/cloud/grafana_utils/delete_team.rs @@ -40,12 +40,9 @@ pub async fn handle_grafana_delete_team( } // Response for this method has been modified - errors in the original OpenAPI spec - let _: () = match delete_folder(&grafana_conf, team_id, Some(false)).await { - Ok(_) => {} - Err(err) => { - warn!("Failed to delete folder: {:?}, team_id: {:?}", err, team_id); - return Err(handle_grafana_error(err)); - } + if let Err(err) = delete_folder(&grafana_conf, team_id, Some(false)).await { + warn!("Failed to delete folder: {:?}, team_id: {:?}", err, team_id); + return Err(handle_grafana_error(err)); }; return Ok(()); }