From 50f0a85ff3126dfbc534174ea920c74c375920d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CGiems=E2=80=9D?= <“hubert.wabia@gmail.com”> Date: Mon, 11 Mar 2024 09:57:29 +0100 Subject: [PATCH 1/8] remove obsolete fields in apps db table --- database/migrations/0004_registered_apps.sql | 4 +- .../requests_stats.rs | 3 -- .../src/tables/registered_app/table_struct.rs | 11 +---- database/src/tables/registered_app/update.rs | 8 +--- database/src/tables/test_utils.rs | 3 -- .../src/tables/user_app_privileges/update.rs | 6 --- .../src/http/cloud/get_user_joined_teams.rs | 47 +++++++++++++++++++ server/src/http/cloud/register_new_app.rs | 3 -- 8 files changed, 52 insertions(+), 33 deletions(-) create mode 100644 server/src/http/cloud/get_user_joined_teams.rs diff --git a/database/migrations/0004_registered_apps.sql b/database/migrations/0004_registered_apps.sql index 45b4eb9d..dd741ac7 100644 --- a/database/migrations/0004_registered_apps.sql +++ b/database/migrations/0004_registered_apps.sql @@ -4,9 +4,7 @@ CREATE TABLE registered_apps( app_name TEXT NOT NULL, whitelisted_domains TEXT [] NOT NULL, ack_public_keys TEXT [] NOT NULL, - email TEXT, - registration_timestamp TIMESTAMPTZ NOT NULL, - pass_hash TEXT + registration_timestamp TIMESTAMPTZ NOT NULL ); 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/requests_stats.rs b/database/src/aggregated_views_queries/requests_stats.rs index 7f15b879..d98c26e6 100644 --- a/database/src/aggregated_views_queries/requests_stats.rs +++ b/database/src/aggregated_views_queries/requests_stats.rs @@ -315,11 +315,8 @@ mod test { app_id: second_app_id.to_string(), app_name: "test_app_name".to_string(), whitelisted_domains: vec!["test_domain".to_string()], - subscription: None, ack_public_keys: vec!["test_key".to_string()], - email: None, registration_timestamp: to_microsecond_precision(&Utc::now()), - pass_hash: None, }; db_arc.register_new_app(&app).await.unwrap(); diff --git a/database/src/tables/registered_app/table_struct.rs b/database/src/tables/registered_app/table_struct.rs index 5f9cfe7e..528671c3 100644 --- a/database/src/tables/registered_app/table_struct.rs +++ b/database/src/tables/registered_app/table_struct.rs @@ -1,4 +1,3 @@ -use crate::structs::subscription::Subscription; use sqlx::{ postgres::PgRow, types::chrono::{DateTime, Utc}, @@ -6,7 +5,8 @@ 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, email, registration_timestamp, pass_hash"; +pub const REGISTERED_APPS_KEYS: &str = + "team_id, app_id, app_name, whitelisted_domains, ack_public_keys, registration_timestamp"; #[derive(Clone, Debug, Eq, PartialEq)] pub struct DbRegisteredApp { @@ -14,11 +14,8 @@ pub struct DbRegisteredApp { pub app_id: String, pub app_name: String, pub whitelisted_domains: Vec, - pub subscription: Option, pub ack_public_keys: Vec, - pub email: Option, pub registration_timestamp: DateTime, - pub pass_hash: Option, } impl FromRow<'_, PgRow> for DbRegisteredApp { @@ -28,12 +25,8 @@ impl FromRow<'_, PgRow> for DbRegisteredApp { app_id: row.get("app_id"), app_name: row.get("app_name"), whitelisted_domains: row.get("whitelisted_domains"), - // TEMP - subscription: None, ack_public_keys: row.get("ack_public_keys"), - email: row.get("email"), registration_timestamp: row.get("registration_timestamp"), - pass_hash: row.get("pass_hash"), }) } } diff --git a/database/src/tables/registered_app/update.rs b/database/src/tables/registered_app/update.rs index 3b9f5707..4545cd02 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, $8)" + "INSERT INTO {REGISTERED_APPS_TABLE_NAME} ({REGISTERED_APPS_KEYS}) VALUES ($1, $2, $3, $4, $5, $6)" ); let query_result = query(&query_body) @@ -14,9 +14,7 @@ impl Db { .bind(&app.app_name) .bind(&app.whitelisted_domains) .bind(&app.ack_public_keys) - .bind(&app.email) .bind(&app.registration_timestamp) - .bind(&app.pass_hash) .execute(&self.connection_pool) .await; @@ -32,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, $8)", + "INSERT INTO {REGISTERED_APPS_TABLE_NAME} ({}) VALUES ($1, $2, $3, $4, $5, $6)", REGISTERED_APPS_KEYS ); @@ -42,9 +40,7 @@ impl Db { .bind(&app.app_name) .bind(&app.whitelisted_domains) .bind(&app.ack_public_keys) - .bind(&app.email) .bind(&app.registration_timestamp) - .bind(&app.pass_hash) .execute(&mut **tx) .await; diff --git a/database/src/tables/test_utils.rs b/database/src/tables/test_utils.rs index 7106abe4..55e46514 100644 --- a/database/src/tables/test_utils.rs +++ b/database/src/tables/test_utils.rs @@ -104,10 +104,7 @@ pub mod test_utils { app_id: app_id.clone(), app_name: "test_app".to_string(), whitelisted_domains: vec!["localhost".to_string()], - subscription: None, ack_public_keys: vec!["key".to_string()], - email: None, - pass_hash: None, registration_timestamp: registration_timestamp, }; diff --git a/database/src/tables/user_app_privileges/update.rs b/database/src/tables/user_app_privileges/update.rs index e069ae6b..3f9572d7 100644 --- a/database/src/tables/user_app_privileges/update.rs +++ b/database/src/tables/user_app_privileges/update.rs @@ -284,10 +284,7 @@ mod tests { app_id: app_id.clone(), app_name: format!("test_app_name_{}", i), team_id: team_id.clone(), - email: None, - pass_hash: None, registration_timestamp: to_microsecond_precision(&Utc::now()), - subscription: None, }; db.register_new_app(&app).await.unwrap(); @@ -323,10 +320,7 @@ mod tests { app_id: app_id.clone(), app_name: format!("test_app_name_{}", i), team_id: team_id.clone(), - email: None, - pass_hash: None, registration_timestamp: to_microsecond_precision(&Utc::now()), - subscription: None, }; db.register_new_app(&app).await.unwrap(); diff --git a/server/src/http/cloud/get_user_joined_teams.rs b/server/src/http/cloud/get_user_joined_teams.rs new file mode 100644 index 00000000..09ea91e1 --- /dev/null +++ b/server/src/http/cloud/get_user_joined_teams.rs @@ -0,0 +1,47 @@ +use crate::{ + auth::auth_middleware::UserId, statics::USERS_AMOUNT_LIMIT_PER_TEAM, + structs::api_cloud_errors::CloudApiErrors, utils::validate_request, +}; +use axum::{extract::State, http::StatusCode, Extension, Json}; +use database::db::Db; +use log::error; +use serde::{Deserialize, Serialize}; +use std::{collections::HashSet, sync::Arc}; +use ts_rs::TS; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)] +#[ts(export)] +pub struct HttpGetUserJoinedTeamsRequest {} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)] +#[ts(export)] +pub struct HttpGetUserJoinedTeamsResponse { + // pub teams_map: +} + +pub async fn add_user_to_team( + State(db): State>>, + Extension(user_id): Extension, + Json(request): Json, +) -> Result, (StatusCode, String)> { + // Db connection has already been checked in the middleware + let db = db.as_ref().ok_or(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::CloudFeatureDisabled.to_string(), + ))?; + + // Check if user already belongs to the team + match db.get_teams_and_apps_membership_by_user_id(&user_id).await { + Ok(teams) => {} + Err(err) => { + error!( + "Failed to get teams and apps membership by user id: {:?}", + err + ); + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + CloudApiErrors::DatabaseError.to_string(), + )); + } + } +} diff --git a/server/src/http/cloud/register_new_app.rs b/server/src/http/cloud/register_new_app.rs index c9c908af..ebd50512 100644 --- a/server/src/http/cloud/register_new_app.rs +++ b/server/src/http/cloud/register_new_app.rs @@ -117,10 +117,7 @@ pub async fn register_new_app( app_name: request.app_name.clone(), ack_public_keys: request.ack_public_keys.clone(), whitelisted_domains: request.whitelisted_domains.clone(), - email: None, - pass_hash: None, registration_timestamp: get_current_datetime(), - subscription: None, }; if let Err(err) = db From 34d2c5d623d1cd7c5be3396f08644fe2c14f64ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CGiems=E2=80=9D?= <“hubert.wabia@gmail.com”> Date: Mon, 11 Mar 2024 13:21:48 +0100 Subject: [PATCH 2/8] get user joined teams endpoint --- database/src/structs/privilege_level.rs | 5 +- .../src/tables/user_app_privileges/select.rs | 120 +++++++++++++++++- .../src/http/cloud/get_user_joined_teams.rs | 67 ++++++++-- server/src/http/cloud/mod.rs | 1 + server/src/structs/app_info.rs | 30 +++++ server/src/structs/joined_team.rs | 16 +++ server/src/structs/mod.rs | 3 + server/src/structs/user_privilege.rs | 26 ++++ 8 files changed, 250 insertions(+), 18 deletions(-) create mode 100644 server/src/structs/app_info.rs create mode 100644 server/src/structs/joined_team.rs create mode 100644 server/src/structs/user_privilege.rs diff --git a/database/src/structs/privilege_level.rs b/database/src/structs/privilege_level.rs index 3fe5706c..87b97e1a 100644 --- a/database/src/structs/privilege_level.rs +++ b/database/src/structs/privilege_level.rs @@ -1,6 +1,9 @@ +use serde::{Deserialize, Serialize}; use sqlx::Type; +use ts_rs::TS; -#[derive(Debug, Clone, Eq, PartialEq, Type)] +#[derive(Debug, Clone, Eq, PartialEq, Type, Serialize, Deserialize, TS)] +#[ts(export)] #[sqlx(type_name = "privilege_level_enum")] pub enum PrivilegeLevel { Read, diff --git a/database/src/tables/user_app_privileges/select.rs b/database/src/tables/user_app_privileges/select.rs index ba28cf42..ce3986e7 100644 --- a/database/src/tables/user_app_privileges/select.rs +++ b/database/src/tables/user_app_privileges/select.rs @@ -1,9 +1,17 @@ use super::table_struct::UserAppPrivilege; -use crate::db::Db; -use crate::structs::db_error::DbError; -use crate::tables::registered_app::table_struct::REGISTERED_APPS_TABLE_NAME; -use crate::tables::user_app_privileges::table_struct::USER_APP_PRIVILEGES_TABLE_NAME; -use sqlx::query_as; +use crate::{ + db::Db, + structs::db_error::DbError, + tables::{ + grafana_users::table_struct::GRAFANA_USERS_TABLE_NAME, + registered_app::table_struct::{DbRegisteredApp, REGISTERED_APPS_TABLE_NAME}, + team::table_struct::{Team, TEAM_TABLE_NAME}, + user_app_privileges::table_struct::USER_APP_PRIVILEGES_TABLE_NAME, + }, +}; +use sqlx::{query_as, types::chrono::DateTime}; +use sqlx::{types::chrono::Utc, Row}; +use std::collections::HashMap; impl Db { pub async fn get_privilege_by_user_id_and_app_id( @@ -89,4 +97,106 @@ impl Db { .await .map_err(|e| e.into()); } + + pub async fn get_joined_teams_by_user_id( + &self, + user_id: &String, + ) -> Result< + Vec<( + Team, + String, + DateTime, + Vec<(DbRegisteredApp, UserAppPrivilege)>, + )>, + DbError, + > { + let query = format!( + "WITH TeamJoinTimes AS ( + SELECT + uap.team_id, + MAX(uap.creation_timestamp) as user_joined_team_timestamp + FROM + {USER_APP_PRIVILEGES_TABLE_NAME} uap + GROUP BY + uap.team_id + ) + SELECT + t.team_id, t.team_name, t.personal, t.subscription, t.registration_timestamp, gu.email AS team_admin_email, + ra.app_id, ra.app_name, ra.whitelisted_domains, ra.ack_public_keys, ra.registration_timestamp, uap.user_id, + uap.privilege_level, uap.creation_timestamp, tjt.user_joined_team_timestamp + FROM + {TEAM_TABLE_NAME} t + JOIN + {REGISTERED_APPS_TABLE_NAME} ra ON t.team_id = ra.team_id + JOIN + {USER_APP_PRIVILEGES_TABLE_NAME} uap ON ra.app_id = uap.app_id + JOIN + {GRAFANA_USERS_TABLE_NAME} gu ON t.team_admin_id = gu.user_id + LEFT JOIN + TeamJoinTimes tjt ON t.team_id = tjt.team_id + WHERE + uap.user_id = $1 + ORDER BY + t.team_id, ra.app_id" + ); + let rows = sqlx::query(&query) + .bind(user_id) + .fetch_all(&self.connection_pool) + .await + .map_err(|e| e.into()); + + if rows.is_err() { + return Err(rows.err().unwrap()); + } + + let mut team_app_map: HashMap< + String, + ( + Team, + String, + DateTime, + Vec<(DbRegisteredApp, UserAppPrivilege)>, + ), + > = HashMap::new(); + + // Safe unwrap + for row in rows.unwrap() { + let team = Team { + team_id: row.get("team_id"), + personal: row.get("personal"), + team_name: row.get("team_name"), + subscription: row.get("subscription"), + registration_timestamp: row.get("registration_timestamp"), + team_admin_id: row.get("team_admin_id"), + }; + + let admin_email = row.get("team_admin_email"); + + let app = DbRegisteredApp { + team_id: row.get("team_id"), + app_id: row.get("app_id"), + app_name: row.get("app_name"), + whitelisted_domains: row.get("whitelisted_domains"), + ack_public_keys: row.get("ack_public_keys"), + registration_timestamp: row.get("registration_timestamp"), + }; + + let privilege = UserAppPrivilege { + user_id: row.get("user_id"), + app_id: row.get("app_id"), + privilege_level: row.get("privilege_level"), + creation_timestamp: row.get("creation_timestamp"), + }; + + let user_joined_team_timestamp: DateTime = row.get("user_joined_team_timestamp"); + + team_app_map + .entry(team.team_id.clone()) + .or_insert_with(|| (team, admin_email, user_joined_team_timestamp, Vec::new())) + .3 + .push((app, privilege)); + } + + Ok(team_app_map.into_values().collect()) + } } diff --git a/server/src/http/cloud/get_user_joined_teams.rs b/server/src/http/cloud/get_user_joined_teams.rs index 09ea91e1..5fd59cc0 100644 --- a/server/src/http/cloud/get_user_joined_teams.rs +++ b/server/src/http/cloud/get_user_joined_teams.rs @@ -1,28 +1,31 @@ use crate::{ - auth::auth_middleware::UserId, statics::USERS_AMOUNT_LIMIT_PER_TEAM, - structs::api_cloud_errors::CloudApiErrors, utils::validate_request, + auth::auth_middleware::UserId, + state::AppId, + structs::{ + api_cloud_errors::CloudApiErrors, + app_info::AppInfo, + joined_team::{JoinedTeam, TeamId}, + user_privilege::UserPrivilege, + }, }; use axum::{extract::State, http::StatusCode, Extension, Json}; use database::db::Db; use log::error; use serde::{Deserialize, Serialize}; -use std::{collections::HashSet, sync::Arc}; +use std::{collections::HashMap, sync::Arc}; use ts_rs::TS; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)] -#[ts(export)] -pub struct HttpGetUserJoinedTeamsRequest {} - #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)] #[ts(export)] pub struct HttpGetUserJoinedTeamsResponse { - // pub teams_map: + pub teams: HashMap, + pub teams_apps: HashMap>, + pub user_privileges: HashMap>, } -pub async fn add_user_to_team( +pub async fn get_user_joined_teams( State(db): State>>, Extension(user_id): Extension, - Json(request): Json, ) -> Result, (StatusCode, String)> { // Db connection has already been checked in the middleware let db = db.as_ref().ok_or(( @@ -31,8 +34,48 @@ pub async fn add_user_to_team( ))?; // Check if user already belongs to the team - match db.get_teams_and_apps_membership_by_user_id(&user_id).await { - Ok(teams) => {} + match db.get_joined_teams_by_user_id(&user_id).await { + Ok(joined_teams) => { + let mut teams = HashMap::new(); + let mut teams_apps = HashMap::new(); + let mut user_privileges = HashMap::new(); + + for (team, admin_email, joined_timestamp, registered_apps) in joined_teams { + let team_id = team.team_id.clone(); + + // Parse joined team + let joined_team = JoinedTeam { + team_id: team.team_id.clone(), + team_name: team.team_name, + created_at: team.registration_timestamp, + creator_email: admin_email, + personal: team.personal, + joined_at: joined_timestamp, + }; + teams.insert(team_id.clone(), joined_team); + + // Parse teams apps and user privileges + let mut apps_info = Vec::new(); + let mut privileges = HashMap::new(); + + for (app, privilege) in registered_apps { + let app_info: AppInfo = app.into(); + let privilege: UserPrivilege = privilege.into(); + + privileges.insert(app_info.app_id.clone(), privilege); + apps_info.push(app_info); + } + + teams_apps.insert(team_id.clone(), apps_info); + user_privileges.insert(team_id, privileges); + } + + Ok(Json(HttpGetUserJoinedTeamsResponse { + teams, + teams_apps, + user_privileges, + })) + } Err(err) => { error!( "Failed to get teams and apps membership by user id: {:?}", diff --git a/server/src/http/cloud/mod.rs b/server/src/http/cloud/mod.rs index 0e05c86e..03cc8d72 100644 --- a/server/src/http/cloud/mod.rs +++ b/server/src/http/cloud/mod.rs @@ -1,5 +1,6 @@ pub mod add_user_to_team; pub mod cloud_middleware; +pub mod get_user_joined_teams; pub mod login_with_password; pub mod register_new_app; pub mod register_new_team; diff --git a/server/src/structs/app_info.rs b/server/src/structs/app_info.rs new file mode 100644 index 00000000..f9d7316a --- /dev/null +++ b/server/src/structs/app_info.rs @@ -0,0 +1,30 @@ +use super::joined_team::TeamId; +use crate::state::AppId; +use chrono::{DateTime, Utc}; +use database::tables::registered_app::table_struct::DbRegisteredApp; +use serde::{Deserialize, Serialize}; +use ts_rs::TS; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)] +#[ts(export)] +pub struct AppInfo { + pub team_id: TeamId, + pub app_id: AppId, + pub app_name: String, + pub registered_at: DateTime, + pub whitelisted_domains: Vec, + pub ack_public_keys: Vec, +} + +impl From for AppInfo { + fn from(app_info: DbRegisteredApp) -> Self { + AppInfo { + team_id: app_info.team_id, + app_id: app_info.app_id, + app_name: app_info.app_name, + registered_at: app_info.registration_timestamp, + whitelisted_domains: app_info.whitelisted_domains, + ack_public_keys: app_info.ack_public_keys, + } + } +} diff --git a/server/src/structs/joined_team.rs b/server/src/structs/joined_team.rs new file mode 100644 index 00000000..cbc07a24 --- /dev/null +++ b/server/src/structs/joined_team.rs @@ -0,0 +1,16 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use ts_rs::TS; + +pub type TeamId = String; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)] +#[ts(export)] +pub struct JoinedTeam { + pub team_id: TeamId, + pub team_name: String, + pub creator_email: String, + pub created_at: DateTime, + pub joined_at: DateTime, + pub personal: bool, +} diff --git a/server/src/structs/mod.rs b/server/src/structs/mod.rs index 7515b1ae..0419513e 100644 --- a/server/src/structs/mod.rs +++ b/server/src/structs/mod.rs @@ -1,11 +1,14 @@ pub mod api_cloud_errors; +pub mod app_info; pub mod app_messages; pub mod client_messages; pub mod cloud_http_endpoints; pub mod common; pub mod http_endpoints; +pub mod joined_team; pub mod notification_msg; pub mod session; +pub mod user_privilege; pub mod wallet_metadata; pub mod wallet_type; pub mod wallets; diff --git a/server/src/structs/user_privilege.rs b/server/src/structs/user_privilege.rs new file mode 100644 index 00000000..c9e80bec --- /dev/null +++ b/server/src/structs/user_privilege.rs @@ -0,0 +1,26 @@ +use crate::state::AppId; +use chrono::{DateTime, Utc}; +use database::{ + structs::privilege_level::PrivilegeLevel, + tables::user_app_privileges::table_struct::UserAppPrivilege, +}; +use serde::{Deserialize, Serialize}; +use ts_rs::TS; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)] +#[ts(export)] +pub struct UserPrivilege { + pub app_id: AppId, + pub granted_at: DateTime, + pub privilege: PrivilegeLevel, +} + +impl From for UserPrivilege { + fn from(user_app_privilege: UserAppPrivilege) -> Self { + UserPrivilege { + app_id: user_app_privilege.app_id, + granted_at: user_app_privilege.creation_timestamp, + privilege: user_app_privilege.privilege_level, + } + } +} From cfa743997341b1c8221203dcc5ee36e8d2e2af88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CGiems=E2=80=9D?= <“hubert.wabia@gmail.com”> Date: Tue, 12 Mar 2024 09:43:33 +0100 Subject: [PATCH 3/8] add user to team endpoint tests --- server/bindings/AppInfo.ts | 3 + server/bindings/CloudApiErrors.ts | 2 +- .../HttpGetUserJoinedTeamsResponse.ts | 6 + server/bindings/JoinedTeam.ts | 3 + server/bindings/UserPrivilege.ts | 4 + server/src/http/cloud/add_user_to_team.rs | 297 +++++++++++++++++- .../src/http/cloud/remove_user_from_team.rs | 5 +- server/src/test_utils.rs | 68 ++++ 8 files changed, 385 insertions(+), 3 deletions(-) create mode 100644 server/bindings/AppInfo.ts create mode 100644 server/bindings/HttpGetUserJoinedTeamsResponse.ts create mode 100644 server/bindings/JoinedTeam.ts create mode 100644 server/bindings/UserPrivilege.ts diff --git a/server/bindings/AppInfo.ts b/server/bindings/AppInfo.ts new file mode 100644 index 00000000..b6ca091d --- /dev/null +++ b/server/bindings/AppInfo.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export interface AppInfo { team_id: string, app_id: string, app_name: string, registered_at: string, whitelisted_domains: Array, ack_public_keys: Array, } \ No newline at end of file diff --git a/server/bindings/CloudApiErrors.ts b/server/bindings/CloudApiErrors.ts index 330fdb2f..043cb0cf 100644 --- a/server/bindings/CloudApiErrors.ts +++ b/server/bindings/CloudApiErrors.ts @@ -1,3 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type CloudApiErrors = "TeamDoesNotExist" | "UserDoesNotExist" | "CloudFeatureDisabled" | "InsufficientPermissions" | "TeamHasNoRegisteredApps" | "DatabaseError" | "MaximumUsersPerTeamReached" | "UserAlreadyBelongsToTheTeam" | "IncorrectPassword" | "AccessTokenFailure" | "RefreshTokenFailure" | "AppAlreadyExists" | "MaximumAppsPerTeamReached" | "TeamAlreadyExists" | "PersonalTeamAlreadyExists" | "EmailAlreadyExists" | "InternalServerError" | "UserDoesNotBelongsToTheTeam"; \ No newline at end of file +export type CloudApiErrors = "TeamDoesNotExist" | "UserDoesNotExist" | "CloudFeatureDisabled" | "InsufficientPermissions" | "TeamHasNoRegisteredApps" | "DatabaseError" | "MaximumUsersPerTeamReached" | "UserAlreadyBelongsToTheTeam" | "IncorrectPassword" | "AccessTokenFailure" | "RefreshTokenFailure" | "AppAlreadyExists" | "MaximumAppsPerTeamReached" | "TeamAlreadyExists" | "PersonalTeamAlreadyExists" | "EmailAlreadyExists" | "InternalServerError" | "UserDoesNotBelongsToTheTeam" | "InvalidName"; \ No newline at end of file diff --git a/server/bindings/HttpGetUserJoinedTeamsResponse.ts b/server/bindings/HttpGetUserJoinedTeamsResponse.ts new file mode 100644 index 00000000..65783cf5 --- /dev/null +++ b/server/bindings/HttpGetUserJoinedTeamsResponse.ts @@ -0,0 +1,6 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AppInfo } from "./AppInfo"; +import type { JoinedTeam } from "./JoinedTeam"; +import type { UserPrivilege } from "./UserPrivilege"; + +export interface HttpGetUserJoinedTeamsResponse { teams: Record, teams_apps: Record>, user_privileges: Record>, } \ No newline at end of file diff --git a/server/bindings/JoinedTeam.ts b/server/bindings/JoinedTeam.ts new file mode 100644 index 00000000..2ead7c1d --- /dev/null +++ b/server/bindings/JoinedTeam.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export interface JoinedTeam { team_id: string, team_name: string, creator_email: string, created_at: string, joined_at: string, personal: boolean, } \ No newline at end of file diff --git a/server/bindings/UserPrivilege.ts b/server/bindings/UserPrivilege.ts new file mode 100644 index 00000000..5117c022 --- /dev/null +++ b/server/bindings/UserPrivilege.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { PrivilegeLevel } from "./PrivilegeLevel"; + +export interface UserPrivilege { app_id: string, granted_at: string, privilege: PrivilegeLevel, } \ No newline at end of file diff --git a/server/src/http/cloud/add_user_to_team.rs b/server/src/http/cloud/add_user_to_team.rs index 634d5bc8..626815e2 100644 --- a/server/src/http/cloud/add_user_to_team.rs +++ b/server/src/http/cloud/add_user_to_team.rs @@ -144,9 +144,11 @@ pub async fn add_user_to_team( .await { Ok(_) => { + println!("HERE 9"); return Ok(Json(HttpAddUserToTeamResponse {})); } Err(err) => { + println!("HERE 10"); error!("Failed to add user to the team: {:?}", err); return Err(( StatusCode::INTERNAL_SERVER_ERROR, @@ -156,7 +158,10 @@ pub async fn add_user_to_team( } } Ok(None) => { - return Err((StatusCode::BAD_REQUEST, "Team does not exist".to_string())); + return Err(( + StatusCode::BAD_REQUEST, + CloudApiErrors::TeamDoesNotExist.to_string(), + )); } Err(err) => { error!("Failed to get team: {:?}", err); @@ -167,3 +172,293 @@ pub async fn add_user_to_team( } } } + +#[cfg(test)] +mod tests { + use crate::{ + env::JWT_SECRET, + http::cloud::{ + add_user_to_team::{HttpAddUserToTeamRequest, HttpAddUserToTeamResponse}, + register_new_app::HttpRegisterNewAppRequest, + }, + statics::USERS_AMOUNT_LIMIT_PER_TEAM, + structs::{api_cloud_errors::CloudApiErrors, cloud_http_endpoints::HttpCloudEndpoint}, + test_utils::test_utils::{ + add_test_app, add_test_team, add_user_to_test_team, convert_response, create_test_app, + register_and_login_random_user, truncate_all_tables, + }, + }; + use axum::{ + body::Body, + extract::ConnectInfo, + http::{Method, Request}, + }; + use database::db::Db; + use std::net::SocketAddr; + use tower::ServiceExt; + + #[tokio::test] + async fn test_add_user_to_team() { + let test_app = create_test_app(false).await; + + // Truncate db + let mut db = Db::connect_to_the_pool().await; + truncate_all_tables(&mut db).await.unwrap(); + + let (auth_token, _email, _password) = register_and_login_random_user(&test_app).await; + + // Register new team + let team_name = "MyFirstTeam".to_string(); + let team_id = add_test_team(&team_name, &auth_token, &test_app) + .await + .unwrap(); + + // Register app under the team + let app_name = "MyFirstApp".to_string(); + let request = HttpRegisterNewAppRequest { + team_id: team_id.clone(), + app_name: app_name.clone(), + whitelisted_domains: vec![], + ack_public_keys: vec![], + }; + + // unwrap err as it should have failed + let _ = add_test_app(&request, &auth_token, &test_app) + .await + .unwrap(); + + // Register new user + let (_test_user_auth_token, test_user_email, _test_user_password) = + register_and_login_random_user(&test_app).await; + + // Add user to the team + let request = HttpAddUserToTeamRequest { + team_id: team_id.clone(), + user_email: test_user_email.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::AddUserToTeam.to_string() + )) + .extension(ip.clone()) + .body(Body::from(json)) + .unwrap(); + + // Send request + let response = test_app.clone().oneshot(req).await.unwrap(); + // Validate response + convert_response::(response) + .await + .unwrap(); + + // Try to add user to the team again, should fail as user is already in the team + let request = HttpAddUserToTeamRequest { + team_id: team_id.clone(), + user_email: test_user_email.clone(), + }; + + 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::AddUserToTeam.to_string() + )) + .extension(ip) + .body(Body::from(json)) + .unwrap(); + + // Send request + let response = test_app.clone().oneshot(req).await.unwrap(); + // Validate response + let err = convert_response::(response) + .await + .unwrap_err(); + + assert_eq!( + err.to_string(), + CloudApiErrors::UserAlreadyBelongsToTheTeam.to_string() + ); + } + + #[tokio::test] + async fn test_add_user_to_team_team_not_found() { + let test_app = create_test_app(false).await; + + // Truncate db + let mut db = Db::connect_to_the_pool().await; + truncate_all_tables(&mut db).await.unwrap(); + + let (auth_token, _email, _password) = register_and_login_random_user(&test_app).await; + + // Team does not exist, use random uuid + let resp = add_user_to_test_team( + &uuid7::uuid7().to_string(), + &"test_user_email@gmail.com".to_string(), + &auth_token, + &test_app, + ) + .await + .unwrap_err(); + + assert_eq!( + resp.to_string(), + CloudApiErrors::TeamDoesNotExist.to_string() + ); + } + + #[tokio::test] + async fn test_add_user_to_team_no_registered_apps() { + let test_app = create_test_app(false).await; + + // Truncate db + let mut db = Db::connect_to_the_pool().await; + truncate_all_tables(&mut db).await.unwrap(); + + let (auth_token, _email, _password) = register_and_login_random_user(&test_app).await; + + // Register new team + let team_name = "MyFirstTeam".to_string(); + let team_id = add_test_team(&team_name, &auth_token, &test_app) + .await + .unwrap(); + + // Team does not exist, use random uuid + let resp = add_user_to_test_team( + &team_id.to_string(), + &"test_user_email@gmail.com".to_string(), + &auth_token, + &test_app, + ) + .await + .unwrap_err(); + + assert_eq!( + resp.to_string(), + CloudApiErrors::TeamHasNoRegisteredApps.to_string() + ); + } + + #[tokio::test] + async fn test_add_user_to_team_user_limit_reached() { + let test_app = create_test_app(false).await; + + // Truncate db + let mut db = Db::connect_to_the_pool().await; + truncate_all_tables(&mut db).await.unwrap(); + + let (auth_token, _email, _password) = register_and_login_random_user(&test_app).await; + + // Register new team + let team_name = "MyFirstTeam".to_string(); + let team_id = add_test_team(&team_name, &auth_token, &test_app) + .await + .unwrap(); + + // Register app under the team + let app_name = "MyFirstApp".to_string(); + let request = HttpRegisterNewAppRequest { + team_id: team_id.clone(), + app_name: app_name.clone(), + whitelisted_domains: vec![], + ack_public_keys: vec![], + }; + + let _ = add_test_app(&request, &auth_token, &test_app) + .await + .unwrap(); + + // Add [USERS_AMOUNT_LIMIT_PER_TEAM] users to the team + for _ in 1..USERS_AMOUNT_LIMIT_PER_TEAM { + let (_, test_user_email, _) = register_and_login_random_user(&test_app).await; + + // Add user to the team + add_user_to_test_team( + &team_id.to_string(), + &test_user_email.to_string(), + &auth_token, + &test_app, + ) + .await + .unwrap(); + } + + // Try to add another user to the team, should fail as user limit has been reached + let (_, test_user_email, _) = register_and_login_random_user(&test_app).await; + + // Add user to the team + let resp = add_user_to_test_team( + &team_id.to_string(), + &test_user_email.to_string(), + &auth_token, + &test_app, + ) + .await + .unwrap_err(); + + assert_eq!( + resp.to_string(), + CloudApiErrors::MaximumUsersPerTeamReached.to_string() + ); + } + + #[tokio::test] + async fn test_add_user_to_team_user_does_not_exist() { + let test_app = create_test_app(false).await; + + // Truncate db + let mut db = Db::connect_to_the_pool().await; + truncate_all_tables(&mut db).await.unwrap(); + + let (auth_token, _email, _password) = register_and_login_random_user(&test_app).await; + + // Register new team + let team_name = "MyFirstTeam".to_string(); + let team_id = add_test_team(&team_name, &auth_token, &test_app) + .await + .unwrap(); + + // Register app under the team + let app_name = "MyFirstApp".to_string(); + let request = HttpRegisterNewAppRequest { + team_id: team_id.clone(), + app_name: app_name.clone(), + whitelisted_domains: vec![], + ack_public_keys: vec![], + }; + + let _ = add_test_app(&request, &auth_token, &test_app) + .await + .unwrap(); + + // Try to add non-existing user to the team, should fail as user limit has been reached + + // Add user to the team + let resp = add_user_to_test_team( + &team_id.to_string(), + &"non-existing-user@gmail.com".to_string(), + &auth_token, + &test_app, + ) + .await + .unwrap_err(); + + assert_eq!( + resp.to_string(), + CloudApiErrors::UserDoesNotExist.to_string() + ); + } +} diff --git a/server/src/http/cloud/remove_user_from_team.rs b/server/src/http/cloud/remove_user_from_team.rs index 8f0f575b..e0dce7db 100644 --- a/server/src/http/cloud/remove_user_from_team.rs +++ b/server/src/http/cloud/remove_user_from_team.rs @@ -112,7 +112,10 @@ pub async fn remove_user_from_team( } } Ok(None) => { - return Err((StatusCode::BAD_REQUEST, "Team does not exist".to_string())); + return Err(( + StatusCode::BAD_REQUEST, + CloudApiErrors::TeamDoesNotExist.to_string(), + )); } Err(err) => { error!("Failed to get team: {:?}", err); diff --git a/server/src/test_utils.rs b/server/src/test_utils.rs index 576f60fd..bbe9cd98 100644 --- a/server/src/test_utils.rs +++ b/server/src/test_utils.rs @@ -4,7 +4,9 @@ pub mod test_utils { auth::AuthToken, env::JWT_SECRET, http::cloud::{ + add_user_to_team::{HttpAddUserToTeamRequest, HttpAddUserToTeamResponse}, login_with_password::{HttpLoginRequest, HttpLoginResponse}, + register_new_app::{HttpRegisterNewAppRequest, HttpRegisterNewAppResponse}, register_new_team::{HttpRegisterNewTeamRequest, HttpRegisterNewTeamResponse}, register_with_password::HttpRegisterWithPasswordRequest, }, @@ -184,6 +186,72 @@ pub mod test_utils { .map(|response| Ok(response.team_id))? } + pub async fn add_test_app( + request: &HttpRegisterNewAppRequest, + admin_token: &AuthToken, + app: &Router, + ) -> anyhow::Result { + // Register new app + let ip: ConnectInfo = ConnectInfo(SocketAddr::from(([127, 0, 0, 1], 8080))); + let json = serde_json::to_string(&request).unwrap(); + let auth = admin_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::RegisterNewApp.to_string() + )) + .extension(ip) + .body(Body::from(json)) + .unwrap(); + + // Send request + let response = app.clone().oneshot(req).await.unwrap(); + // Validate response + convert_response::(response) + .await + .map(|response| Ok(response.app_id))? + } + + pub async fn add_user_to_test_team( + team_id: &String, + user_email: &String, + admin_token: &AuthToken, + app: &Router, + ) -> anyhow::Result<()> { + // Add user to test team + let request = HttpAddUserToTeamRequest { + team_id: team_id.clone(), + user_email: user_email.clone(), + }; + + let ip: ConnectInfo = ConnectInfo(SocketAddr::from(([127, 0, 0, 1], 8080))); + let json = serde_json::to_string(&request).unwrap(); + let auth = admin_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::AddUserToTeam.to_string() + )) + .extension(ip) + .body(Body::from(json)) + .unwrap(); + + // Send request + let response = app.clone().oneshot(req).await.unwrap(); + // Validate response + convert_response::(response) + .await + .map(|_| Ok(()))? + } + 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 5cb0c57d4650a25f7012964ea7993ecf923ec0a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CGiems=E2=80=9D?= <“hubert.wabia@gmail.com”> Date: Tue, 12 Mar 2024 10:22:55 +0100 Subject: [PATCH 4/8] remove user from team endpoint tests --- .../src/tables/user_app_privileges/update.rs | 3 + .../src/http/cloud/remove_user_from_team.rs | 184 +++++++++++++++++- server/src/test_utils.rs | 39 ++++ 3 files changed, 225 insertions(+), 1 deletion(-) diff --git a/database/src/tables/user_app_privileges/update.rs b/database/src/tables/user_app_privileges/update.rs index 3f9572d7..6724a985 100644 --- a/database/src/tables/user_app_privileges/update.rs +++ b/database/src/tables/user_app_privileges/update.rs @@ -148,6 +148,9 @@ impl Db { } } + // Commit the transaction + tx.commit().await?; + Ok(()) } diff --git a/server/src/http/cloud/remove_user_from_team.rs b/server/src/http/cloud/remove_user_from_team.rs index e0dce7db..a8685602 100644 --- a/server/src/http/cloud/remove_user_from_team.rs +++ b/server/src/http/cloud/remove_user_from_team.rs @@ -74,6 +74,7 @@ pub async fn remove_user_from_team( .await { Ok(teams) => { + println!("TEAMS {:?}", teams); // This won't check if user has permissions to all apps in the team if !teams.iter().any(|(team_id, _)| team_id == &request.team_id) { return Err(( @@ -94,7 +95,7 @@ pub async fn remove_user_from_team( } } - // Add user to the team + // Remove user from the team match db .remove_user_from_the_team(&user.user_id, &request.team_id) .await @@ -126,3 +127,184 @@ pub async fn remove_user_from_team( } } } + +#[cfg(test)] +mod tests { + use crate::{ + env::JWT_SECRET, + http::cloud::{ + register_new_app::HttpRegisterNewAppRequest, + remove_user_from_team::{ + HttpRemoveUserFromTeamRequest, HttpRemoveUserFromTeamResponse, + }, + }, + structs::{api_cloud_errors::CloudApiErrors, cloud_http_endpoints::HttpCloudEndpoint}, + test_utils::test_utils::{ + add_test_app, add_test_team, add_user_to_test_team, convert_response, create_test_app, + register_and_login_random_user, remove_user_from_test_team, truncate_all_tables, + }, + }; + use axum::{ + body::Body, + extract::{ConnectInfo, Request}, + http::Method, + }; + use database::db::Db; + use std::net::SocketAddr; + use tower::ServiceExt; + + #[tokio::test] + async fn test_remove_user_from_team() { + let test_app = create_test_app(false).await; + + // Truncate db + let mut db = Db::connect_to_the_pool().await; + truncate_all_tables(&mut db).await.unwrap(); + + let (auth_token, _email, _password) = register_and_login_random_user(&test_app).await; + + // Register new team + let team_name = "MyFirstTeam".to_string(); + let team_id = add_test_team(&team_name, &auth_token, &test_app) + .await + .unwrap(); + + // Register app under the team + let app_name = "MyFirstApp".to_string(); + let request = HttpRegisterNewAppRequest { + team_id: team_id.clone(), + app_name: app_name.clone(), + whitelisted_domains: vec![], + ack_public_keys: vec![], + }; + + // unwrap err as it should have failed + let _ = add_test_app(&request, &auth_token, &test_app) + .await + .unwrap(); + + // Register new user + let (_test_user_auth_token, test_user_email, _test_user_password) = + register_and_login_random_user(&test_app).await; + + // Add user to the team + add_user_to_test_team(&team_id, &test_user_email, &auth_token, &test_app) + .await + .unwrap(); + + // Remove user from the team + let request = HttpRemoveUserFromTeamRequest { + team_id: team_id.clone(), + user_email: test_user_email.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::RemoveUserFromTeam.to_string() + )) + .extension(ip.clone()) + .body(Body::from(json)) + .unwrap(); + + // Send request + let response = test_app.clone().oneshot(req).await.unwrap(); + // Validate response + convert_response::(response) + .await + .unwrap(); + + // Try to remove user from the team again, should fail as user is not in the team + 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::RemoveUserFromTeam.to_string() + )) + .extension(ip) + .body(Body::from(json)) + .unwrap(); + + // Send request + let response = test_app.clone().oneshot(req).await.unwrap(); + // Validate response + let err = convert_response::(response) + .await + .unwrap_err(); + + assert_eq!( + err.to_string(), + CloudApiErrors::UserDoesNotBelongsToTheTeam.to_string() + ); + } + + #[tokio::test] + async fn test_remove_user_from_team_team_not_found() { + let test_app = create_test_app(false).await; + + // Truncate db + let mut db = Db::connect_to_the_pool().await; + truncate_all_tables(&mut db).await.unwrap(); + + let (auth_token, _email, _password) = register_and_login_random_user(&test_app).await; + + // Team does not exist, use random uuid + let resp = remove_user_from_test_team( + &uuid7::uuid7().to_string(), + &"test_user_email@gmail.com".to_string(), + &auth_token, + &test_app, + ) + .await + .unwrap_err(); + + assert_eq!( + resp.to_string(), + CloudApiErrors::TeamDoesNotExist.to_string() + ); + } + + #[tokio::test] + async fn test_remove_user_from_team_user_does_not_exist() { + let test_app = create_test_app(false).await; + + // Truncate db + let mut db = Db::connect_to_the_pool().await; + truncate_all_tables(&mut db).await.unwrap(); + + let (auth_token, _email, _password) = register_and_login_random_user(&test_app).await; + + // Register new team + let team_name = "MyFirstTeam".to_string(); + let team_id = add_test_team(&team_name, &auth_token, &test_app) + .await + .unwrap(); + + // Team does not exist, use random uuid + let resp = remove_user_from_test_team( + &team_id.to_string(), + &"test_user_email@gmail.com".to_string(), + &auth_token, + &test_app, + ) + .await + .unwrap_err(); + + assert_eq!( + resp.to_string(), + CloudApiErrors::UserDoesNotExist.to_string() + ); + } +} diff --git a/server/src/test_utils.rs b/server/src/test_utils.rs index bbe9cd98..fb1138e8 100644 --- a/server/src/test_utils.rs +++ b/server/src/test_utils.rs @@ -9,6 +9,9 @@ pub mod test_utils { register_new_app::{HttpRegisterNewAppRequest, HttpRegisterNewAppResponse}, register_new_team::{HttpRegisterNewTeamRequest, HttpRegisterNewTeamResponse}, register_with_password::HttpRegisterWithPasswordRequest, + remove_user_from_team::{ + HttpRemoveUserFromTeamRequest, HttpRemoveUserFromTeamResponse, + }, }, routes::router::get_router, structs::cloud_http_endpoints::HttpCloudEndpoint, @@ -252,6 +255,42 @@ pub mod test_utils { .map(|_| Ok(()))? } + pub async fn remove_user_from_test_team( + team_id: &String, + user_email: &String, + admin_token: &AuthToken, + app: &Router, + ) -> anyhow::Result<()> { + // Add user to test team + let request = HttpRemoveUserFromTeamRequest { + team_id: team_id.clone(), + user_email: user_email.clone(), + }; + + let ip: ConnectInfo = ConnectInfo(SocketAddr::from(([127, 0, 0, 1], 8080))); + let json = serde_json::to_string(&request).unwrap(); + let auth = admin_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::RemoveUserFromTeam.to_string() + )) + .extension(ip) + .body(Body::from(json)) + .unwrap(); + + // Send request + let response = app.clone().oneshot(req).await.unwrap(); + // Validate response + convert_response::(response) + .await + .map(|_| Ok(()))? + } + 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 30ee1e0c9f20d55f773aa9e3cbeb416febf5f2a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CGiems=E2=80=9D?= <“hubert.wabia@gmail.com”> Date: Tue, 12 Mar 2024 15:00:16 +0100 Subject: [PATCH 5/8] get joined teams endpoint tests --- .../src/tables/user_app_privileges/select.rs | 104 ++++---- server/bindings/HttpCloudEndpoint.ts | 2 +- server/src/bin/nightly-connect-server.rs | 2 +- server/src/http/cloud/add_user_to_team.rs | 10 +- .../src/http/cloud/get_user_joined_teams.rs | 239 +++++++++++++++++- server/src/http/cloud/register_new_app.rs | 4 +- .../src/http/cloud/remove_user_from_team.rs | 5 +- server/src/routes/cloud_router.rs | 16 +- server/src/structs/cloud_http_endpoints.rs | 3 + server/src/test_utils.rs | 44 +++- 10 files changed, 354 insertions(+), 75 deletions(-) diff --git a/database/src/tables/user_app_privileges/select.rs b/database/src/tables/user_app_privileges/select.rs index ce3986e7..c2b6f01d 100644 --- a/database/src/tables/user_app_privileges/select.rs +++ b/database/src/tables/user_app_privileges/select.rs @@ -111,44 +111,42 @@ impl Db { DbError, > { let query = format!( - "WITH TeamJoinTimes AS ( - SELECT - uap.team_id, - MAX(uap.creation_timestamp) as user_joined_team_timestamp - FROM - {USER_APP_PRIVILEGES_TABLE_NAME} uap - GROUP BY - uap.team_id + "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, + CASE + WHEN t.team_admin_id = $1 THEN t.registration_timestamp + ELSE MAX(uap.creation_timestamp) OVER (PARTITION BY t.team_id) + END as user_joined_team_timestamp + FROM {TEAM_TABLE_NAME} t + 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 {GRAFANA_USERS_TABLE_NAME} gu ON t.team_admin_id = gu.user_id + WHERE t.team_admin_id = $1 OR uap.user_id = $1 ) - SELECT - t.team_id, t.team_name, t.personal, t.subscription, t.registration_timestamp, gu.email AS team_admin_email, - ra.app_id, ra.app_name, ra.whitelisted_domains, ra.ack_public_keys, ra.registration_timestamp, uap.user_id, - uap.privilege_level, uap.creation_timestamp, tjt.user_joined_team_timestamp - FROM - {TEAM_TABLE_NAME} t - JOIN - {REGISTERED_APPS_TABLE_NAME} ra ON t.team_id = ra.team_id - JOIN - {USER_APP_PRIVILEGES_TABLE_NAME} uap ON ra.app_id = uap.app_id - JOIN - {GRAFANA_USERS_TABLE_NAME} gu ON t.team_admin_id = gu.user_id - LEFT JOIN - TeamJoinTimes tjt ON t.team_id = tjt.team_id - WHERE - uap.user_id = $1 - ORDER BY - t.team_id, ra.app_id" + 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 + 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 + ORDER BY rt.team_id, ra.app_id" ); + let rows = sqlx::query(&query) .bind(user_id) .fetch_all(&self.connection_pool) .await .map_err(|e| e.into()); - if rows.is_err() { - return Err(rows.err().unwrap()); + if let Err(e) = rows { + return Err(e); } + let rows = rows.unwrap(); let mut team_app_map: HashMap< String, ( @@ -159,8 +157,7 @@ impl Db { ), > = HashMap::new(); - // Safe unwrap - for row in rows.unwrap() { + for row in rows { let team = Team { team_id: row.get("team_id"), personal: row.get("personal"), @@ -171,30 +168,35 @@ impl Db { }; let admin_email = row.get("team_admin_email"); - - let app = DbRegisteredApp { - team_id: row.get("team_id"), - app_id: row.get("app_id"), - app_name: row.get("app_name"), - whitelisted_domains: row.get("whitelisted_domains"), - ack_public_keys: row.get("ack_public_keys"), - registration_timestamp: row.get("registration_timestamp"), - }; - - let privilege = UserAppPrivilege { - user_id: row.get("user_id"), - app_id: row.get("app_id"), - privilege_level: row.get("privilege_level"), - creation_timestamp: row.get("creation_timestamp"), - }; - let user_joined_team_timestamp: DateTime = row.get("user_joined_team_timestamp"); - team_app_map + let team_id = team.team_id.clone(); + let team_entry = team_app_map .entry(team.team_id.clone()) - .or_insert_with(|| (team, admin_email, user_joined_team_timestamp, Vec::new())) - .3 - .push((app, privilege)); + .or_insert_with(|| (team, admin_email, user_joined_team_timestamp, Vec::new())); + + if let Ok(app_id) = row.try_get("app_id") { + if app_id != "" { + // Checking if app_id is present and not an empty string + let app = DbRegisteredApp { + team_id: team_id.clone(), + app_id, + app_name: row.get("app_name"), + whitelisted_domains: row.get("whitelisted_domains"), + ack_public_keys: row.get("ack_public_keys"), + registration_timestamp: row.get("app_registration_timestamp"), + }; + + let privilege = UserAppPrivilege { + user_id: row.get("user_id"), + app_id: app.app_id.clone(), + privilege_level: row.get("privilege_level"), + creation_timestamp: row.get("privilege_creation_timestamp"), + }; + + team_entry.3.push((app, privilege)); + } + } } Ok(team_app_map.into_values().collect()) diff --git a/server/bindings/HttpCloudEndpoint.ts b/server/bindings/HttpCloudEndpoint.ts index bf727a80..ad92decb 100644 --- a/server/bindings/HttpCloudEndpoint.ts +++ b/server/bindings/HttpCloudEndpoint.ts @@ -1,3 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type HttpCloudEndpoint = "/register_new_app" | "/register_with_password" | "/login_with_password" | "/register_new_team" | "/add_user_to_team" | "/remove_user_from_team"; \ No newline at end of file +export type HttpCloudEndpoint = "/register_new_app" | "/register_with_password" | "/login_with_password" | "/register_new_team" | "/add_user_to_team" | "/remove_user_from_team" | "/get_user_joined_teams"; \ No newline at end of file diff --git a/server/src/bin/nightly-connect-server.rs b/server/src/bin/nightly-connect-server.rs index 1a0e1e5f..60b91f5a 100644 --- a/server/src/bin/nightly-connect-server.rs +++ b/server/src/bin/nightly-connect-server.rs @@ -6,7 +6,7 @@ use tracing_subscriber::EnvFilter; #[tokio::main] async fn main() { - let filter: EnvFilter = "debug,tower_http=trace,hyper=warn" + let filter: EnvFilter = "debug,sqlx=warn,tower_http=trace,hyper=warn" .parse() .expect("filter should parse"); diff --git a/server/src/http/cloud/add_user_to_team.rs b/server/src/http/cloud/add_user_to_team.rs index 626815e2..286e0a0e 100644 --- a/server/src/http/cloud/add_user_to_team.rs +++ b/server/src/http/cloud/add_user_to_team.rs @@ -144,11 +144,9 @@ pub async fn add_user_to_team( .await { Ok(_) => { - println!("HERE 9"); return Ok(Json(HttpAddUserToTeamResponse {})); } Err(err) => { - println!("HERE 10"); error!("Failed to add user to the team: {:?}", err); return Err(( StatusCode::INTERNAL_SERVER_ERROR, @@ -209,7 +207,7 @@ mod tests { // Register new team let team_name = "MyFirstTeam".to_string(); - let team_id = add_test_team(&team_name, &auth_token, &test_app) + let team_id = add_test_team(&team_name, &auth_token, &test_app, false) .await .unwrap(); @@ -332,7 +330,7 @@ mod tests { // Register new team let team_name = "MyFirstTeam".to_string(); - let team_id = add_test_team(&team_name, &auth_token, &test_app) + let team_id = add_test_team(&team_name, &auth_token, &test_app, false) .await .unwrap(); @@ -364,7 +362,7 @@ mod tests { // Register new team let team_name = "MyFirstTeam".to_string(); - let team_id = add_test_team(&team_name, &auth_token, &test_app) + let team_id = add_test_team(&team_name, &auth_token, &test_app, false) .await .unwrap(); @@ -427,7 +425,7 @@ mod tests { // Register new team let team_name = "MyFirstTeam".to_string(); - let team_id = add_test_team(&team_name, &auth_token, &test_app) + let team_id = add_test_team(&team_name, &auth_token, &test_app, false) .await .unwrap(); diff --git a/server/src/http/cloud/get_user_joined_teams.rs b/server/src/http/cloud/get_user_joined_teams.rs index 5fd59cc0..31011110 100644 --- a/server/src/http/cloud/get_user_joined_teams.rs +++ b/server/src/http/cloud/get_user_joined_teams.rs @@ -66,8 +66,13 @@ pub async fn get_user_joined_teams( apps_info.push(app_info); } - teams_apps.insert(team_id.clone(), apps_info); - user_privileges.insert(team_id, privileges); + if !apps_info.is_empty() { + teams_apps.insert(team_id.clone(), apps_info); + } + + if !privileges.is_empty() { + user_privileges.insert(team_id.clone(), privileges); + } } Ok(Json(HttpGetUserJoinedTeamsResponse { @@ -88,3 +93,233 @@ pub async fn get_user_joined_teams( } } } + +#[cfg(test)] +mod tests { + use crate::test_utils::test_utils::{add_user_to_test_team, get_test_user_joined_teams}; + use crate::{ + env::JWT_SECRET, + http::cloud::register_new_app::HttpRegisterNewAppRequest, + structs::cloud_http_endpoints::HttpCloudEndpoint, + test_utils::test_utils::{ + add_test_app, add_test_team, convert_response, create_test_app, + register_and_login_random_user, truncate_all_tables, + }, + }; + use axum::{ + body::Body, + extract::ConnectInfo, + http::{Method, Request}, + }; + use database::db::Db; + use database::tables::utils::get_current_datetime; + use std::net::SocketAddr; + use tower::ServiceExt; + use tracing_subscriber::EnvFilter; + + #[tokio::test] + async fn test_get_user_joined_teams() { + let test_app = create_test_app(false).await; + + // Truncate db + let mut db = Db::connect_to_the_pool().await; + truncate_all_tables(&mut db).await.unwrap(); + + let (auth_token, _email, _password) = register_and_login_random_user(&test_app).await; + + let num_of_teams = 4; + let mut team_ids = Vec::new(); + + // Create teams + for i in 0..num_of_teams { + let team_name = format!("MyFirstTeam{}", i); + let team_id = add_test_team(&team_name, &auth_token, &test_app, false) + .await + .unwrap(); + team_ids.push(team_id); + } + + let mut app_ids = Vec::new(); + // Register 3 + [team index] apps for each team + for (j, team_id) in team_ids.iter().enumerate() { + let mut team_app_ids = Vec::new(); + for i in 0..3 + j { + let app_name = format!("MyFirstApp{}{}", j, i); + let request = HttpRegisterNewAppRequest { + team_id: team_id.clone(), + app_name: app_name.clone(), + whitelisted_domains: vec![], + ack_public_keys: vec![], + }; + let app_id = add_test_app(&request, &auth_token, &test_app) + .await + .unwrap(); + team_app_ids.push(app_id); + } + + app_ids.push(team_app_ids); + } + + // Register new user + let (app_user_auth_token, app_user_email, _app_user_password) = + register_and_login_random_user(&test_app).await; + + // Add user to first team + let before_first_join = get_current_datetime(); + add_user_to_test_team(&team_ids[0], &app_user_email, &auth_token, &test_app) + .await + .unwrap(); + let after_first_join = get_current_datetime(); + + // Wait for 1 second + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + + // Add user to second team + add_user_to_test_team(&team_ids[1], &app_user_email, &auth_token, &test_app) + .await + .unwrap(); + let after_second_join = get_current_datetime(); + + // Get user joined teams + let ip: ConnectInfo = ConnectInfo(SocketAddr::from(([127, 0, 0, 1], 8080))); + let auth = app_user_auth_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{}", + HttpCloudEndpoint::GetUserJoinedTeams.to_string() + )) + .extension(ip) + .body(Body::empty()) + .unwrap(); + + // Send request + let response = test_app.clone().oneshot(req).await.unwrap(); + // Validate response + let response = convert_response::(response) + .await + .unwrap(); + + // Check if dates for team join are correct + assert!(response.teams.get(&team_ids[0]).unwrap().joined_at >= before_first_join); + assert!(response.teams.get(&team_ids[0]).unwrap().joined_at <= after_first_join); + assert!(response.teams.get(&team_ids[1]).unwrap().joined_at >= after_first_join); + assert!(response.teams.get(&team_ids[1]).unwrap().joined_at <= after_second_join); + + // Check returned data + assert!(response.teams.len() == 2); + assert!(response.teams_apps.len() == 2); + assert!(response.teams_apps.get(&team_ids[0]).unwrap().len() == 3); + assert!(response.teams_apps.get(&team_ids[1]).unwrap().len() == 4); + assert!(response.user_privileges.len() == 2); + assert!(response.user_privileges.get(&team_ids[0]).unwrap().len() == 3); + assert!(response.user_privileges.get(&team_ids[1]).unwrap().len() == 4); + + // Create personal team as a test user + let personal_team_name = "PersonalTeam".to_string(); + let personal_team_id = + add_test_team(&personal_team_name, &app_user_auth_token, &test_app, true) + .await + .unwrap(); + + // Get user joined teams + let ip: ConnectInfo = ConnectInfo(SocketAddr::from(([127, 0, 0, 1], 8080))); + let auth = app_user_auth_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{}", + HttpCloudEndpoint::GetUserJoinedTeams.to_string() + )) + .extension(ip) + .body(Body::empty()) + .unwrap(); + + // Send request + let response = test_app.clone().oneshot(req).await.unwrap(); + // Validate response + let response = convert_response::(response) + .await + .unwrap(); + + // Check returned data + assert!(response.teams.len() == 3); + assert!(response.teams.get(&personal_team_id).unwrap().personal); + assert!(response.teams_apps.len() == 2); + assert!(response.teams_apps.get(&team_ids[0]).unwrap().len() == 3); + assert!(response.teams_apps.get(&team_ids[1]).unwrap().len() == 4); + assert!(response.user_privileges.len() == 2); + assert!(response.user_privileges.get(&team_ids[0]).unwrap().len() == 3); + assert!(response.user_privileges.get(&team_ids[1]).unwrap().len() == 4); + } + + #[tokio::test] + async fn test_get_user_joined_teams_empty_teams() { + let test_app = create_test_app(false).await; + + // Truncate db + let mut db = Db::connect_to_the_pool().await; + truncate_all_tables(&mut db).await.unwrap(); + + let (auth_token, _email, _password) = register_and_login_random_user(&test_app).await; + + // Create personal team + let personal_team_name = "PersonalTeam".to_string(); + let personal_team_id = add_test_team(&personal_team_name, &auth_token, &test_app, true) + .await + .unwrap(); + + // Get user joined teams + let response = get_test_user_joined_teams(&auth_token, &test_app) + .await + .unwrap(); + + assert!(response.teams.len() == 1); + assert!(response.teams.get(&personal_team_id).unwrap().personal); + assert!(response.teams_apps.len() == 0); + assert!(response.user_privileges.len() == 0); + + // Add new "normal" team + let team_name = "MyFirstTeam".to_string(); + let team_id = add_test_team(&team_name, &auth_token, &test_app, false) + .await + .unwrap(); + + // Get user joined teams + let response = get_test_user_joined_teams(&auth_token, &test_app) + .await + .unwrap(); + + assert!(response.teams.len() == 2); + assert!(response.teams.get(&team_id).unwrap().team_name == team_name); + assert!(response.teams.get(&team_id).unwrap().personal == false); + assert!(response.teams_apps.len() == 0); + assert!(response.user_privileges.len() == 0); + } + + #[tokio::test] + async fn test_get_user_joined_teams_empty_account() { + let test_app = create_test_app(false).await; + + // Truncate db + let mut db = Db::connect_to_the_pool().await; + truncate_all_tables(&mut db).await.unwrap(); + + let (auth_token, _email, _password) = register_and_login_random_user(&test_app).await; + + // Get user joined teams + let response = get_test_user_joined_teams(&auth_token, &test_app) + .await + .unwrap(); + + assert!(response.teams.len() == 0); + assert!(response.teams_apps.len() == 0); + assert!(response.user_privileges.len() == 0); + } +} diff --git a/server/src/http/cloud/register_new_app.rs b/server/src/http/cloud/register_new_app.rs index ebd50512..13bd9b5c 100644 --- a/server/src/http/cloud/register_new_app.rs +++ b/server/src/http/cloud/register_new_app.rs @@ -232,7 +232,7 @@ mod tests { // Register new team let team_name = "MyFirstTeam".to_string(); - let team_id = add_test_team(&team_name, &auth_token, &test_app) + let team_id = add_test_team(&team_name, &auth_token, &test_app, false) .await .unwrap(); @@ -314,7 +314,7 @@ mod tests { // Register new team let team_name = "MyFirstTeam".to_string(); - let team_id = add_test_team(&team_name, &auth_token, &test_app) + let team_id = add_test_team(&team_name, &auth_token, &test_app, false) .await .unwrap(); diff --git a/server/src/http/cloud/remove_user_from_team.rs b/server/src/http/cloud/remove_user_from_team.rs index a8685602..d5ad303f 100644 --- a/server/src/http/cloud/remove_user_from_team.rs +++ b/server/src/http/cloud/remove_user_from_team.rs @@ -74,7 +74,6 @@ pub async fn remove_user_from_team( .await { Ok(teams) => { - println!("TEAMS {:?}", teams); // This won't check if user has permissions to all apps in the team if !teams.iter().any(|(team_id, _)| team_id == &request.team_id) { return Err(( @@ -165,7 +164,7 @@ mod tests { // Register new team let team_name = "MyFirstTeam".to_string(); - let team_id = add_test_team(&team_name, &auth_token, &test_app) + let team_id = add_test_team(&team_name, &auth_token, &test_app, false) .await .unwrap(); @@ -288,7 +287,7 @@ mod tests { // Register new team let team_name = "MyFirstTeam".to_string(); - let team_id = add_test_team(&team_name, &auth_token, &test_app) + let team_id = add_test_team(&team_name, &auth_token, &test_app, false) .await .unwrap(); diff --git a/server/src/routes/cloud_router.rs b/server/src/routes/cloud_router.rs index dd273b96..559f8b71 100644 --- a/server/src/routes/cloud_router.rs +++ b/server/src/routes/cloud_router.rs @@ -1,15 +1,19 @@ use crate::{ auth::auth_middleware::access_auth_middleware, http::cloud::{ - add_user_to_team::add_user_to_team, login_with_password::login_with_password, - register_new_app::register_new_app, register_new_team::register_new_team, - register_with_password::register_with_password, + add_user_to_team::add_user_to_team, get_user_joined_teams::get_user_joined_teams, + login_with_password::login_with_password, register_new_app::register_new_app, + register_new_team::register_new_team, register_with_password::register_with_password, remove_user_from_team::remove_user_from_team, }, state::ServerState, structs::cloud_http_endpoints::HttpCloudEndpoint, }; -use axum::{middleware, routing::post, Router}; +use axum::{ + middleware, + routing::{get, post}, + Router, +}; pub fn cloud_router(state: ServerState) -> Router { Router::new() @@ -55,5 +59,9 @@ pub fn private_router(state: ServerState) -> Router { &HttpCloudEndpoint::RemoveUserFromTeam.to_string(), post(remove_user_from_team), ) + .route( + &HttpCloudEndpoint::GetUserJoinedTeams.to_string(), + get(get_user_joined_teams), + ) .with_state(state) } diff --git a/server/src/structs/cloud_http_endpoints.rs b/server/src/structs/cloud_http_endpoints.rs index 4f32a313..bd493632 100644 --- a/server/src/structs/cloud_http_endpoints.rs +++ b/server/src/structs/cloud_http_endpoints.rs @@ -16,6 +16,8 @@ pub enum HttpCloudEndpoint { AddUserToTeam, #[serde(rename = "/remove_user_from_team")] RemoveUserFromTeam, + #[serde(rename = "/get_user_joined_teams")] + GetUserJoinedTeams, } impl HttpCloudEndpoint { @@ -27,6 +29,7 @@ impl HttpCloudEndpoint { HttpCloudEndpoint::RegisterNewTeam => "/register_new_team".to_string(), HttpCloudEndpoint::AddUserToTeam => "/add_user_to_team".to_string(), HttpCloudEndpoint::RemoveUserFromTeam => "/remove_user_from_team".to_string(), + HttpCloudEndpoint::GetUserJoinedTeams => "/get_user_joined_teams".to_string(), } } } diff --git a/server/src/test_utils.rs b/server/src/test_utils.rs index fb1138e8..0c8a51ee 100644 --- a/server/src/test_utils.rs +++ b/server/src/test_utils.rs @@ -5,6 +5,7 @@ pub mod test_utils { env::JWT_SECRET, http::cloud::{ add_user_to_team::{HttpAddUserToTeamRequest, HttpAddUserToTeamResponse}, + get_user_joined_teams::HttpGetUserJoinedTeamsResponse, login_with_password::{HttpLoginRequest, HttpLoginResponse}, register_new_app::{HttpRegisterNewAppRequest, HttpRegisterNewAppResponse}, register_new_team::{HttpRegisterNewTeamRequest, HttpRegisterNewTeamResponse}, @@ -158,11 +159,12 @@ pub mod test_utils { team_name: &String, admin_token: &AuthToken, app: &Router, + personal: bool, ) -> anyhow::Result { // Register new team let request = HttpRegisterNewTeamRequest { team_name: team_name.clone(), - personal: false, + personal: personal, }; let ip: ConnectInfo = ConnectInfo(SocketAddr::from(([127, 0, 0, 1], 8080))); @@ -291,6 +293,31 @@ pub mod test_utils { .map(|_| Ok(()))? } + pub async fn get_test_user_joined_teams( + 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{}", + HttpCloudEndpoint::GetUserJoinedTeams.to_string() + )) + .extension(ip) + .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()), @@ -313,12 +340,19 @@ pub mod test_utils { { match response.status() { StatusCode::OK => { - let payload = serde_json::from_slice(&body_to_vec(response).await?)?; - return Ok(payload); + let body = body_to_vec(response).await?; + match serde_json::from_slice::(&body) { + Ok(payload) => Ok(payload), + Err(e) => bail!("Error deserializing response: {}", e), + } } - _ => { + StatusCode::INTERNAL_SERVER_ERROR | StatusCode::BAD_REQUEST => { let error_message = convert_response_into_error_string(response).await?; - bail!(error_message); + bail!("{}", error_message) + } + _ => { + let status = response.status(); + bail!("{}", status) } } } From c9de2df7e142b28e6404d17d467139bc4c1d1795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CGiems=E2=80=9D?= <“hubert.wabia@gmail.com”> Date: Tue, 12 Mar 2024 15:31:31 +0100 Subject: [PATCH 6/8] generate names for apps and teams --- server/src/http/cloud/add_user_to_team.rs | 37 ++++--------------- .../src/http/cloud/get_user_joined_teams.rs | 34 ++++++----------- server/src/http/cloud/register_new_app.rs | 21 +++-------- server/src/http/cloud/register_new_team.rs | 21 ++--------- .../src/http/cloud/remove_user_from_team.rs | 21 ++--------- server/src/test_utils.rs | 34 ++++++++++++++++- 6 files changed, 66 insertions(+), 102 deletions(-) diff --git a/server/src/http/cloud/add_user_to_team.rs b/server/src/http/cloud/add_user_to_team.rs index 286e0a0e..97a1ef4f 100644 --- a/server/src/http/cloud/add_user_to_team.rs +++ b/server/src/http/cloud/add_user_to_team.rs @@ -183,7 +183,7 @@ mod tests { structs::{api_cloud_errors::CloudApiErrors, cloud_http_endpoints::HttpCloudEndpoint}, test_utils::test_utils::{ add_test_app, add_test_team, add_user_to_test_team, convert_response, create_test_app, - register_and_login_random_user, truncate_all_tables, + generate_valid_name, register_and_login_random_user, }, }; use axum::{ @@ -191,7 +191,6 @@ mod tests { extract::ConnectInfo, http::{Method, Request}, }; - use database::db::Db; use std::net::SocketAddr; use tower::ServiceExt; @@ -199,20 +198,16 @@ mod tests { async fn test_add_user_to_team() { let test_app = create_test_app(false).await; - // Truncate db - let mut db = Db::connect_to_the_pool().await; - truncate_all_tables(&mut db).await.unwrap(); - let (auth_token, _email, _password) = register_and_login_random_user(&test_app).await; // Register new team - let team_name = "MyFirstTeam".to_string(); + 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 = "MyFirstApp".to_string(); + let app_name = generate_valid_name(); let request = HttpRegisterNewAppRequest { team_id: team_id.clone(), app_name: app_name.clone(), @@ -296,10 +291,6 @@ mod tests { async fn test_add_user_to_team_team_not_found() { let test_app = create_test_app(false).await; - // Truncate db - let mut db = Db::connect_to_the_pool().await; - truncate_all_tables(&mut db).await.unwrap(); - let (auth_token, _email, _password) = register_and_login_random_user(&test_app).await; // Team does not exist, use random uuid @@ -322,14 +313,10 @@ mod tests { async fn test_add_user_to_team_no_registered_apps() { let test_app = create_test_app(false).await; - // Truncate db - let mut db = Db::connect_to_the_pool().await; - truncate_all_tables(&mut db).await.unwrap(); - let (auth_token, _email, _password) = register_and_login_random_user(&test_app).await; // Register new team - let team_name = "MyFirstTeam".to_string(); + let team_name = generate_valid_name(); let team_id = add_test_team(&team_name, &auth_token, &test_app, false) .await .unwrap(); @@ -354,20 +341,16 @@ mod tests { async fn test_add_user_to_team_user_limit_reached() { let test_app = create_test_app(false).await; - // Truncate db - let mut db = Db::connect_to_the_pool().await; - truncate_all_tables(&mut db).await.unwrap(); - let (auth_token, _email, _password) = register_and_login_random_user(&test_app).await; // Register new team - let team_name = "MyFirstTeam".to_string(); + 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 = "MyFirstApp".to_string(); + let app_name = generate_valid_name(); let request = HttpRegisterNewAppRequest { team_id: team_id.clone(), app_name: app_name.clone(), @@ -417,20 +400,16 @@ mod tests { async fn test_add_user_to_team_user_does_not_exist() { let test_app = create_test_app(false).await; - // Truncate db - let mut db = Db::connect_to_the_pool().await; - truncate_all_tables(&mut db).await.unwrap(); - let (auth_token, _email, _password) = register_and_login_random_user(&test_app).await; // Register new team - let team_name = "MyFirstTeam".to_string(); + 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 = "MyFirstApp".to_string(); + let app_name = generate_valid_name(); let request = HttpRegisterNewAppRequest { team_id: team_id.clone(), app_name: app_name.clone(), diff --git a/server/src/http/cloud/get_user_joined_teams.rs b/server/src/http/cloud/get_user_joined_teams.rs index 31011110..d4b1f318 100644 --- a/server/src/http/cloud/get_user_joined_teams.rs +++ b/server/src/http/cloud/get_user_joined_teams.rs @@ -96,14 +96,16 @@ pub async fn get_user_joined_teams( #[cfg(test)] mod tests { - use crate::test_utils::test_utils::{add_user_to_test_team, get_test_user_joined_teams}; + use crate::test_utils::test_utils::{ + add_user_to_test_team, generate_valid_name, get_test_user_joined_teams, + }; use crate::{ env::JWT_SECRET, http::cloud::register_new_app::HttpRegisterNewAppRequest, structs::cloud_http_endpoints::HttpCloudEndpoint, test_utils::test_utils::{ add_test_app, add_test_team, convert_response, create_test_app, - register_and_login_random_user, truncate_all_tables, + register_and_login_random_user, }, }; use axum::{ @@ -111,28 +113,22 @@ mod tests { extract::ConnectInfo, http::{Method, Request}, }; - use database::db::Db; use database::tables::utils::get_current_datetime; use std::net::SocketAddr; use tower::ServiceExt; - use tracing_subscriber::EnvFilter; #[tokio::test] async fn test_get_user_joined_teams() { let test_app = create_test_app(false).await; - // Truncate db - let mut db = Db::connect_to_the_pool().await; - truncate_all_tables(&mut db).await.unwrap(); - let (auth_token, _email, _password) = register_and_login_random_user(&test_app).await; let num_of_teams = 4; let mut team_ids = Vec::new(); // Create teams - for i in 0..num_of_teams { - let team_name = format!("MyFirstTeam{}", i); + for _ in 0..num_of_teams { + let team_name = generate_valid_name(); let team_id = add_test_team(&team_name, &auth_token, &test_app, false) .await .unwrap(); @@ -143,8 +139,8 @@ mod tests { // Register 3 + [team index] apps for each team for (j, team_id) in team_ids.iter().enumerate() { let mut team_app_ids = Vec::new(); - for i in 0..3 + j { - let app_name = format!("MyFirstApp{}{}", j, i); + for _ in 0..3 + j { + let app_name = generate_valid_name(); let request = HttpRegisterNewAppRequest { team_id: team_id.clone(), app_name: app_name.clone(), @@ -219,7 +215,7 @@ mod tests { assert!(response.user_privileges.get(&team_ids[1]).unwrap().len() == 4); // Create personal team as a test user - let personal_team_name = "PersonalTeam".to_string(); + let personal_team_name = generate_valid_name(); let personal_team_id = add_test_team(&personal_team_name, &app_user_auth_token, &test_app, true) .await @@ -263,14 +259,10 @@ mod tests { async fn test_get_user_joined_teams_empty_teams() { let test_app = create_test_app(false).await; - // Truncate db - let mut db = Db::connect_to_the_pool().await; - truncate_all_tables(&mut db).await.unwrap(); - let (auth_token, _email, _password) = register_and_login_random_user(&test_app).await; // Create personal team - let personal_team_name = "PersonalTeam".to_string(); + let personal_team_name = generate_valid_name(); let personal_team_id = add_test_team(&personal_team_name, &auth_token, &test_app, true) .await .unwrap(); @@ -286,7 +278,7 @@ mod tests { assert!(response.user_privileges.len() == 0); // Add new "normal" team - let team_name = "MyFirstTeam".to_string(); + let team_name = generate_valid_name(); let team_id = add_test_team(&team_name, &auth_token, &test_app, false) .await .unwrap(); @@ -307,10 +299,6 @@ mod tests { async fn test_get_user_joined_teams_empty_account() { let test_app = create_test_app(false).await; - // Truncate db - let mut db = Db::connect_to_the_pool().await; - truncate_all_tables(&mut db).await.unwrap(); - let (auth_token, _email, _password) = register_and_login_random_user(&test_app).await; // Get user joined teams diff --git a/server/src/http/cloud/register_new_app.rs b/server/src/http/cloud/register_new_app.rs index 13bd9b5c..3a8faaa5 100644 --- a/server/src/http/cloud/register_new_app.rs +++ b/server/src/http/cloud/register_new_app.rs @@ -207,8 +207,8 @@ mod tests { http::cloud::register_new_app::{HttpRegisterNewAppRequest, HttpRegisterNewAppResponse}, structs::{api_cloud_errors::CloudApiErrors, cloud_http_endpoints::HttpCloudEndpoint}, test_utils::test_utils::{ - add_test_team, convert_response, create_test_app, register_and_login_random_user, - truncate_all_tables, + add_test_team, convert_response, create_test_app, generate_valid_name, + register_and_login_random_user, }, }; use axum::{ @@ -216,7 +216,6 @@ mod tests { extract::ConnectInfo, http::{Method, Request}, }; - use database::db::Db; use std::net::SocketAddr; use tower::ServiceExt; @@ -224,20 +223,16 @@ mod tests { async fn test_register_new_app() { let test_app = create_test_app(false).await; - // Truncate db - let mut db = Db::connect_to_the_pool().await; - truncate_all_tables(&mut db).await.unwrap(); - let (auth_token, _email, _password) = register_and_login_random_user(&test_app).await; // Register new team - let team_name = "MyFirstTeam".to_string(); + let team_name = generate_valid_name(); let team_id = add_test_team(&team_name, &auth_token, &test_app, false) .await .unwrap(); // Register new app - let app_name = "MyFirstApp".to_string(); + let app_name = generate_valid_name(); let request = HttpRegisterNewAppRequest { team_id: team_id.clone(), app_name: app_name.clone(), @@ -306,20 +301,16 @@ mod tests { async fn test_invalid_app_name() { let test_app = create_test_app(false).await; - // Truncate db - let mut db = Db::connect_to_the_pool().await; - truncate_all_tables(&mut db).await.unwrap(); - let (auth_token, _email, _password) = register_and_login_random_user(&test_app).await; // Register new team - let team_name = "MyFirstTeam".to_string(); + let team_name = generate_valid_name(); let team_id = add_test_team(&team_name, &auth_token, &test_app, false) .await .unwrap(); // Register new app - let app_name = "MyINVALIDAPP_NAME!!!!".to_string(); + let app_name = generate_valid_name() + "18702dhb12n1902hd89b1n28sd1 02n>>>>>>>>>>>>>>>"; let request = HttpRegisterNewAppRequest { team_id: team_id.clone(), app_name: app_name.clone(), diff --git a/server/src/http/cloud/register_new_team.rs b/server/src/http/cloud/register_new_team.rs index 61b00acf..805aecc3 100644 --- a/server/src/http/cloud/register_new_team.rs +++ b/server/src/http/cloud/register_new_team.rs @@ -141,7 +141,7 @@ mod tests { http::cloud::register_new_team::{HttpRegisterNewTeamRequest, HttpRegisterNewTeamResponse}, structs::{api_cloud_errors::CloudApiErrors, cloud_http_endpoints::HttpCloudEndpoint}, test_utils::test_utils::{ - convert_response, create_test_app, register_and_login_random_user, truncate_all_tables, + convert_response, create_test_app, generate_valid_name, register_and_login_random_user, }, }; use axum::{ @@ -149,7 +149,6 @@ mod tests { extract::ConnectInfo, http::{Method, Request}, }; - use database::db::Db; use std::net::SocketAddr; use tower::ServiceExt; @@ -157,14 +156,10 @@ mod tests { async fn test_register_new_normal_team() { let test_app = create_test_app(false).await; - // Truncate db - let mut db = Db::connect_to_the_pool().await; - truncate_all_tables(&mut db).await.unwrap(); - let (auth_token, _email, _password) = register_and_login_random_user(&test_app).await; // Register new team - let first_team_name = "MyFirstTeam".to_string(); + let first_team_name = generate_valid_name(); let request = HttpRegisterNewTeamRequest { team_name: first_team_name.clone(), personal: false, @@ -224,14 +219,10 @@ mod tests { async fn test_register_new_personal_team() { let test_app = create_test_app(false).await; - // Truncate db - let mut db = Db::connect_to_the_pool().await; - truncate_all_tables(&mut db).await.unwrap(); - let (auth_token, _email, _password) = register_and_login_random_user(&test_app).await; // Register new team - let first_team_name = "MyFirstTeam".to_string(); + let first_team_name = generate_valid_name(); let request = HttpRegisterNewTeamRequest { team_name: first_team_name.clone(), personal: true, @@ -297,14 +288,10 @@ mod tests { async fn test_invalid_team_name() { let test_app = create_test_app(false).await; - // Truncate db - let mut db = Db::connect_to_the_pool().await; - truncate_all_tables(&mut db).await.unwrap(); - let (auth_token, _email, _password) = register_and_login_random_user(&test_app).await; let request = HttpRegisterNewTeamRequest { - team_name: "My_Invalid_Team!!!".to_string(), + team_name: generate_valid_name() + "1827389012hds012hd!!>>>>>>>>.", personal: true, }; diff --git a/server/src/http/cloud/remove_user_from_team.rs b/server/src/http/cloud/remove_user_from_team.rs index d5ad303f..d6a9601a 100644 --- a/server/src/http/cloud/remove_user_from_team.rs +++ b/server/src/http/cloud/remove_user_from_team.rs @@ -140,7 +140,7 @@ mod tests { structs::{api_cloud_errors::CloudApiErrors, cloud_http_endpoints::HttpCloudEndpoint}, test_utils::test_utils::{ add_test_app, add_test_team, add_user_to_test_team, convert_response, create_test_app, - register_and_login_random_user, remove_user_from_test_team, truncate_all_tables, + generate_valid_name, register_and_login_random_user, remove_user_from_test_team, }, }; use axum::{ @@ -148,7 +148,6 @@ mod tests { extract::{ConnectInfo, Request}, http::Method, }; - use database::db::Db; use std::net::SocketAddr; use tower::ServiceExt; @@ -156,20 +155,16 @@ mod tests { async fn test_remove_user_from_team() { let test_app = create_test_app(false).await; - // Truncate db - let mut db = Db::connect_to_the_pool().await; - truncate_all_tables(&mut db).await.unwrap(); - let (auth_token, _email, _password) = register_and_login_random_user(&test_app).await; // Register new team - let team_name = "MyFirstTeam".to_string(); + 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 = "MyFirstApp".to_string(); + let app_name = generate_valid_name(); let request = HttpRegisterNewAppRequest { team_id: team_id.clone(), app_name: app_name.clone(), @@ -253,10 +248,6 @@ mod tests { async fn test_remove_user_from_team_team_not_found() { let test_app = create_test_app(false).await; - // Truncate db - let mut db = Db::connect_to_the_pool().await; - truncate_all_tables(&mut db).await.unwrap(); - let (auth_token, _email, _password) = register_and_login_random_user(&test_app).await; // Team does not exist, use random uuid @@ -279,14 +270,10 @@ mod tests { async fn test_remove_user_from_team_user_does_not_exist() { let test_app = create_test_app(false).await; - // Truncate db - let mut db = Db::connect_to_the_pool().await; - truncate_all_tables(&mut db).await.unwrap(); - let (auth_token, _email, _password) = register_and_login_random_user(&test_app).await; // Register new team - let team_name = "MyFirstTeam".to_string(); + let team_name = generate_valid_name(); let team_id = add_test_team(&team_name, &auth_token, &test_app, false) .await .unwrap(); diff --git a/server/src/test_utils.rs b/server/src/test_utils.rs index 0c8a51ee..3b704aef 100644 --- a/server/src/test_utils.rs +++ b/server/src/test_utils.rs @@ -15,6 +15,7 @@ pub mod test_utils { }, }, routes::router::get_router, + statics::NAME_REGEX, structs::cloud_http_endpoints::HttpCloudEndpoint, }; use anyhow::bail; @@ -25,7 +26,10 @@ pub mod test_utils { Router, }; use database::db::Db; - use rand::{distributions::Alphanumeric, thread_rng, Rng}; + use rand::{ + distributions::{Alphanumeric, Uniform}, + thread_rng, Rng, + }; use sqlx::Row; use std::net::SocketAddr; use tower::ServiceExt; @@ -356,4 +360,32 @@ pub mod test_utils { } } } + + pub fn generate_valid_name() -> String { + let mut rng = rand::thread_rng(); + + // Define ranges for alphanumeric characters and individual characters for underscore and slash. + let char_ranges = ['a'..'z', 'A'..'Z', '0'..'9']; + let single_chars = ['_', '/']; + + // Flatten the char_ranges into a single collection of characters and add individual characters. + let mut chars: Vec = char_ranges.into_iter().flat_map(|range| range).collect(); + chars.extend(single_chars.iter()); + + // Ensure the distribution covers the range of available characters. + let dist = Uniform::from(0..chars.len()); + + // Define minimum and maximum length based on the regex pattern. + let min_len = 3; + let max_len = 30; + let name_len = rng.gen_range(min_len..=max_len); + + // Generate a random string of valid characters within the defined length. + let name: String = (0..name_len).map(|_| chars[rng.sample(&dist)]).collect(); + + // Ensure the generated name matches the regex pattern. + assert!(NAME_REGEX.is_match(&name)); + + name + } } From 00702921aa3c3748964b6f6b8a63e43d52246e89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CGiems=E2=80=9D?= <“hubert.wabia@gmail.com”> Date: Wed, 13 Mar 2024 10:57:55 +0100 Subject: [PATCH 7/8] cr fixes --- .../src/tables/user_app_privileges/select.rs | 113 +++++++++--------- server/bindings/AppInfo.ts | 2 +- server/bindings/JoinedTeam.ts | 2 +- server/bindings/UserPrivilege.ts | 2 +- server/src/statics.rs | 2 +- server/src/structs/app_info.rs | 1 + server/src/structs/joined_team.rs | 1 + server/src/structs/user_privilege.rs | 1 + server/src/test_utils.rs | 2 +- 9 files changed, 65 insertions(+), 61 deletions(-) diff --git a/database/src/tables/user_app_privileges/select.rs b/database/src/tables/user_app_privileges/select.rs index c2b6f01d..debc9b3b 100644 --- a/database/src/tables/user_app_privileges/select.rs +++ b/database/src/tables/user_app_privileges/select.rs @@ -139,66 +139,67 @@ impl Db { let rows = sqlx::query(&query) .bind(user_id) .fetch_all(&self.connection_pool) - .await - .map_err(|e| e.into()); - - if let Err(e) = rows { - return Err(e); - } - - let rows = rows.unwrap(); - let mut team_app_map: HashMap< - String, - ( - Team, - String, - DateTime, - Vec<(DbRegisteredApp, UserAppPrivilege)>, - ), - > = HashMap::new(); - - for row in rows { - let team = Team { - team_id: row.get("team_id"), - personal: row.get("personal"), - team_name: row.get("team_name"), - subscription: row.get("subscription"), - registration_timestamp: row.get("registration_timestamp"), - team_admin_id: row.get("team_admin_id"), - }; - - let admin_email = row.get("team_admin_email"); - let user_joined_team_timestamp: DateTime = row.get("user_joined_team_timestamp"); - - let team_id = team.team_id.clone(); - let team_entry = team_app_map - .entry(team.team_id.clone()) - .or_insert_with(|| (team, admin_email, user_joined_team_timestamp, Vec::new())); - - if let Ok(app_id) = row.try_get("app_id") { - if app_id != "" { - // Checking if app_id is present and not an empty string - let app = DbRegisteredApp { - team_id: team_id.clone(), - app_id, - app_name: row.get("app_name"), - whitelisted_domains: row.get("whitelisted_domains"), - ack_public_keys: row.get("ack_public_keys"), - registration_timestamp: row.get("app_registration_timestamp"), - }; - - let privilege = UserAppPrivilege { - user_id: row.get("user_id"), - app_id: app.app_id.clone(), - privilege_level: row.get("privilege_level"), - creation_timestamp: row.get("privilege_creation_timestamp"), + .await; + + match rows { + Err(e) => return Err(e.into()), + Ok(rows) => { + let mut team_app_map: HashMap< + String, + ( + Team, + String, + DateTime, + Vec<(DbRegisteredApp, UserAppPrivilege)>, + ), + > = HashMap::new(); + + for row in rows { + let team = Team { + team_id: row.get("team_id"), + personal: row.get("personal"), + team_name: row.get("team_name"), + subscription: row.get("subscription"), + registration_timestamp: row.get("registration_timestamp"), + team_admin_id: row.get("team_admin_id"), }; - team_entry.3.push((app, privilege)); + let admin_email = row.get("team_admin_email"); + let user_joined_team_timestamp: DateTime = + row.get("user_joined_team_timestamp"); + + let team_id = team.team_id.clone(); + let team_entry = + team_app_map.entry(team.team_id.clone()).or_insert_with(|| { + (team, admin_email, user_joined_team_timestamp, Vec::new()) + }); + + if let Ok(app_id) = row.try_get("app_id") { + if app_id != "" { + // Checking if app_id is present and not an empty string + let app = DbRegisteredApp { + team_id: team_id.clone(), + app_id, + app_name: row.get("app_name"), + whitelisted_domains: row.get("whitelisted_domains"), + ack_public_keys: row.get("ack_public_keys"), + registration_timestamp: row.get("app_registration_timestamp"), + }; + + let privilege = UserAppPrivilege { + user_id: row.get("user_id"), + app_id: app.app_id.clone(), + privilege_level: row.get("privilege_level"), + creation_timestamp: row.get("privilege_creation_timestamp"), + }; + + team_entry.3.push((app, privilege)); + } + } } + + Ok(team_app_map.into_values().collect()) } } - - Ok(team_app_map.into_values().collect()) } } diff --git a/server/bindings/AppInfo.ts b/server/bindings/AppInfo.ts index b6ca091d..93723b88 100644 --- a/server/bindings/AppInfo.ts +++ b/server/bindings/AppInfo.ts @@ -1,3 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export interface AppInfo { team_id: string, app_id: string, app_name: string, registered_at: string, whitelisted_domains: Array, ack_public_keys: Array, } \ No newline at end of file +export interface AppInfo { teamId: string, appId: string, appName: string, registeredAt: string, whitelistedDomains: Array, ackPublicKeys: Array, } \ No newline at end of file diff --git a/server/bindings/JoinedTeam.ts b/server/bindings/JoinedTeam.ts index 2ead7c1d..fa7345c0 100644 --- a/server/bindings/JoinedTeam.ts +++ b/server/bindings/JoinedTeam.ts @@ -1,3 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export interface JoinedTeam { team_id: string, team_name: string, creator_email: string, created_at: string, joined_at: string, personal: boolean, } \ No newline at end of file +export interface JoinedTeam { teamId: string, teamName: string, creatorEmail: string, createdAt: string, joinedAt: string, personal: boolean, } \ No newline at end of file diff --git a/server/bindings/UserPrivilege.ts b/server/bindings/UserPrivilege.ts index 5117c022..c81dade8 100644 --- a/server/bindings/UserPrivilege.ts +++ b/server/bindings/UserPrivilege.ts @@ -1,4 +1,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { PrivilegeLevel } from "./PrivilegeLevel"; -export interface UserPrivilege { app_id: string, granted_at: string, privilege: PrivilegeLevel, } \ No newline at end of file +export interface UserPrivilege { appId: string, grantedAt: string, privilege: PrivilegeLevel, } \ No newline at end of file diff --git a/server/src/statics.rs b/server/src/statics.rs index baefc0aa..44717e26 100644 --- a/server/src/statics.rs +++ b/server/src/statics.rs @@ -7,7 +7,7 @@ pub const USERS_AMOUNT_LIMIT_PER_TEAM: usize = 50; // Name must be 3-30 characters long and include only alphanumeric characters, underscores, or slashes. pub static NAME_REGEX: Lazy = - Lazy::new(|| Regex::new(r"^[a-zA-Z0-9_\/]{3,30}$").expect("Regex creation failed")); + Lazy::new(|| Regex::new(r"^[a-zA-Z0-9_-]{3,30}$").expect("Regex creation failed")); pub struct PasswordValidator { pub re: Regex, diff --git a/server/src/structs/app_info.rs b/server/src/structs/app_info.rs index f9d7316a..e7af7fc6 100644 --- a/server/src/structs/app_info.rs +++ b/server/src/structs/app_info.rs @@ -7,6 +7,7 @@ use ts_rs::TS; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)] #[ts(export)] +#[serde(rename_all = "camelCase")] pub struct AppInfo { pub team_id: TeamId, pub app_id: AppId, diff --git a/server/src/structs/joined_team.rs b/server/src/structs/joined_team.rs index cbc07a24..3b4117c7 100644 --- a/server/src/structs/joined_team.rs +++ b/server/src/structs/joined_team.rs @@ -6,6 +6,7 @@ pub type TeamId = String; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)] #[ts(export)] +#[serde(rename_all = "camelCase")] pub struct JoinedTeam { pub team_id: TeamId, pub team_name: String, diff --git a/server/src/structs/user_privilege.rs b/server/src/structs/user_privilege.rs index c9e80bec..6baead37 100644 --- a/server/src/structs/user_privilege.rs +++ b/server/src/structs/user_privilege.rs @@ -9,6 +9,7 @@ use ts_rs::TS; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)] #[ts(export)] +#[serde(rename_all = "camelCase")] pub struct UserPrivilege { pub app_id: AppId, pub granted_at: DateTime, diff --git a/server/src/test_utils.rs b/server/src/test_utils.rs index 3b704aef..84ed7130 100644 --- a/server/src/test_utils.rs +++ b/server/src/test_utils.rs @@ -366,7 +366,7 @@ pub mod test_utils { // Define ranges for alphanumeric characters and individual characters for underscore and slash. let char_ranges = ['a'..'z', 'A'..'Z', '0'..'9']; - let single_chars = ['_', '/']; + let single_chars = ['_', '-']; // Flatten the char_ranges into a single collection of characters and add individual characters. let mut chars: Vec = char_ranges.into_iter().flat_map(|range| range).collect(); From c4c5e12517718469fe6783b090033204be7a0130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CGiems=E2=80=9D?= <“hubert.wabia@gmail.com”> Date: Wed, 13 Mar 2024 11:22:32 +0100 Subject: [PATCH 8/8] remove indent --- database/bindings/PrivilegeLevel.ts | 3 + .../src/tables/user_app_privileges/select.rs | 110 +++++++++--------- 2 files changed, 58 insertions(+), 55 deletions(-) create mode 100644 database/bindings/PrivilegeLevel.ts diff --git a/database/bindings/PrivilegeLevel.ts b/database/bindings/PrivilegeLevel.ts new file mode 100644 index 00000000..3ea85174 --- /dev/null +++ b/database/bindings/PrivilegeLevel.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type PrivilegeLevel = "Read" | "Edit" | "Admin"; \ No newline at end of file diff --git a/database/src/tables/user_app_privileges/select.rs b/database/src/tables/user_app_privileges/select.rs index debc9b3b..2c5728ca 100644 --- a/database/src/tables/user_app_privileges/select.rs +++ b/database/src/tables/user_app_privileges/select.rs @@ -141,65 +141,65 @@ impl Db { .fetch_all(&self.connection_pool) .await; - match rows { - Err(e) => return Err(e.into()), - Ok(rows) => { - let mut team_app_map: HashMap< - String, - ( - Team, - String, - DateTime, - Vec<(DbRegisteredApp, UserAppPrivilege)>, - ), - > = HashMap::new(); - - for row in rows { - let team = Team { - team_id: row.get("team_id"), - personal: row.get("personal"), - team_name: row.get("team_name"), - subscription: row.get("subscription"), - registration_timestamp: row.get("registration_timestamp"), - team_admin_id: row.get("team_admin_id"), + let rows = match rows { + Ok(rows) => rows, + Err(err) => { + return Err(err.into()); + } + }; + + let mut team_app_map: HashMap< + String, + ( + Team, + String, + DateTime, + Vec<(DbRegisteredApp, UserAppPrivilege)>, + ), + > = HashMap::new(); + + for row in rows { + let team = Team { + team_id: row.get("team_id"), + personal: row.get("personal"), + team_name: row.get("team_name"), + subscription: row.get("subscription"), + registration_timestamp: row.get("registration_timestamp"), + team_admin_id: row.get("team_admin_id"), + }; + + let admin_email = row.get("team_admin_email"); + let user_joined_team_timestamp: DateTime = row.get("user_joined_team_timestamp"); + + let team_id = team.team_id.clone(); + let team_entry = team_app_map + .entry(team.team_id.clone()) + .or_insert_with(|| (team, admin_email, user_joined_team_timestamp, Vec::new())); + + if let Ok(app_id) = row.try_get("app_id") { + if app_id != "" { + // Checking if app_id is present and not an empty string + let app = DbRegisteredApp { + team_id: team_id.clone(), + app_id, + app_name: row.get("app_name"), + whitelisted_domains: row.get("whitelisted_domains"), + ack_public_keys: row.get("ack_public_keys"), + registration_timestamp: row.get("app_registration_timestamp"), }; - let admin_email = row.get("team_admin_email"); - let user_joined_team_timestamp: DateTime = - row.get("user_joined_team_timestamp"); - - let team_id = team.team_id.clone(); - let team_entry = - team_app_map.entry(team.team_id.clone()).or_insert_with(|| { - (team, admin_email, user_joined_team_timestamp, Vec::new()) - }); - - if let Ok(app_id) = row.try_get("app_id") { - if app_id != "" { - // Checking if app_id is present and not an empty string - let app = DbRegisteredApp { - team_id: team_id.clone(), - app_id, - app_name: row.get("app_name"), - whitelisted_domains: row.get("whitelisted_domains"), - ack_public_keys: row.get("ack_public_keys"), - registration_timestamp: row.get("app_registration_timestamp"), - }; - - let privilege = UserAppPrivilege { - user_id: row.get("user_id"), - app_id: app.app_id.clone(), - privilege_level: row.get("privilege_level"), - creation_timestamp: row.get("privilege_creation_timestamp"), - }; - - team_entry.3.push((app, privilege)); - } - } - } + let privilege = UserAppPrivilege { + user_id: row.get("user_id"), + app_id: app.app_id.clone(), + privilege_level: row.get("privilege_level"), + creation_timestamp: row.get("privilege_creation_timestamp"), + }; - Ok(team_app_map.into_values().collect()) + team_entry.3.push((app, privilege)); + } } } + + Ok(team_app_map.into_values().collect()) } }