From 4409d76f30813f1dac80c5bf2aad59779b846e23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CGiems=E2=80=9D?= <“hubert.wabia@gmail.com”> Date: Wed, 6 Mar 2024 15:17:41 +0100 Subject: [PATCH 1/5] add method for creating ne teams --- database/migrations/0002_team.sql | 4 +- database/src/structs/mod.rs | 2 +- ...{privelage_level.rs => privilege_level.rs} | 0 database/src/tables/team/select.rs | 45 +++++++ database/src/tables/team/update.rs | 22 +++- database/src/tables/test_utils.rs | 2 +- .../user_app_privileges/table_struct.rs | 2 +- .../src/tables/user_app_privileges/update.rs | 2 +- server/src/http/cloud/mod.rs | 1 + server/src/http/cloud/register_new_app.rs | 3 +- server/src/http/cloud/register_new_team.rs | 113 ++++++++++++++++++ server/src/utils.rs | 4 +- 12 files changed, 189 insertions(+), 11 deletions(-) rename database/src/structs/{privelage_level.rs => privilege_level.rs} (100%) create mode 100644 server/src/http/cloud/register_new_team.rs diff --git a/database/migrations/0002_team.sql b/database/migrations/0002_team.sql index 15f2d084..e8857ae0 100644 --- a/database/migrations/0002_team.sql +++ b/database/migrations/0002_team.sql @@ -5,6 +5,4 @@ CREATE TABLE team( subscription subscription, team_admin_id TEXT NOT NULL, registration_timestamp TIMESTAMPTZ NOT NULL -); - -CREATE UNIQUE INDEX team_id_idx ON team(team_id); \ No newline at end of file +); \ No newline at end of file diff --git a/database/src/structs/mod.rs b/database/src/structs/mod.rs index df95c646..5c2cc57d 100644 --- a/database/src/structs/mod.rs +++ b/database/src/structs/mod.rs @@ -2,7 +2,7 @@ pub mod client_data; pub mod consts; pub mod entity_type; pub mod filter_requests; -pub mod privelage_level; +pub mod privilege_level; pub mod request_status; pub mod session_type; pub mod subscription; diff --git a/database/src/structs/privelage_level.rs b/database/src/structs/privilege_level.rs similarity index 100% rename from database/src/structs/privelage_level.rs rename to database/src/structs/privilege_level.rs diff --git a/database/src/tables/team/select.rs b/database/src/tables/team/select.rs index 7583891a..799df67e 100644 --- a/database/src/tables/team/select.rs +++ b/database/src/tables/team/select.rs @@ -42,6 +42,51 @@ impl Db { .await; } + pub async fn get_admin_user_teams_without_personal( + &self, + admin_id: &String, + ) -> Result, sqlx::Error> { + let query = format!( + "SELECT * FROM {TEAM_TABLE_NAME} WHERE team_admin_id = $1 AND personal = false" + ); + let typed_query = query_as::<_, Team>(&query); + + return typed_query + .bind(&admin_id) + .fetch_all(&self.connection_pool) + .await; + } + + pub async fn get_personal_team_by_admin_id( + &self, + admin_id: &String, + ) -> Result, sqlx::Error> { + let query = + format!("SELECT * FROM {TEAM_TABLE_NAME} WHERE team_admin_id = $1 AND personal = true"); + let typed_query = query_as::<_, Team>(&query); + + return typed_query + .bind(&admin_id) + .fetch_optional(&self.connection_pool) + .await; + } + + pub async fn get_team_by_team_name_and_admin_id( + &self, + team_name: &String, + team_id: &String, + ) -> Result, sqlx::Error> { + let query = + format!("SELECT * FROM {TEAM_TABLE_NAME} WHERE team_name = $1 AND team_id = $2"); + let typed_query = query_as::<_, Team>(&query); + + return typed_query + .bind(&team_name) + .bind(&team_id) + .fetch_optional(&self.connection_pool) + .await; + } + pub async fn get_team_by_admin_id( &self, admin_id: &String, diff --git a/database/src/tables/team/update.rs b/database/src/tables/team/update.rs index af0d1e3a..4a7b85b7 100644 --- a/database/src/tables/team/update.rs +++ b/database/src/tables/team/update.rs @@ -35,6 +35,26 @@ impl Db { } } + pub async fn create_new_team(&self, team: &Team) -> Result<(), sqlx::Error> { + let query_body = + format!("INSERT INTO {TEAM_TABLE_NAME} ({TEAM_KEYS}) VALUES ($1, $2, $3, $4, $5, $6)"); + + let query_result = query(&query_body) + .bind(&team.team_id) + .bind(&team.team_name) + .bind(&team.personal) + .bind(&team.subscription) + .bind(&team.team_admin_id) + .bind(&team.registration_timestamp) + .execute(&self.connection_pool) + .await; + + match query_result { + Ok(_) => Ok(()), + Err(e) => Err(e), + } + } + pub async fn update_subscription( &self, team_id: &String, @@ -96,7 +116,7 @@ impl Db { #[cfg(test)] mod tests { use crate::{ - structs::privelage_level::PrivilegeLevel, + structs::privilege_level::PrivilegeLevel, tables::{ grafana_users::table_struct::GrafanaUser, registered_app::table_struct::DbRegisteredApp, team::table_struct::Team, diff --git a/database/src/tables/test_utils.rs b/database/src/tables/test_utils.rs index 36b536b0..19b5880c 100644 --- a/database/src/tables/test_utils.rs +++ b/database/src/tables/test_utils.rs @@ -2,7 +2,7 @@ pub mod test_utils { use crate::{ db::Db, - structs::privelage_level::PrivilegeLevel, + structs::privilege_level::PrivilegeLevel, tables::{ grafana_users::table_struct::GrafanaUser, registered_app::table_struct::DbRegisteredApp, team::table_struct::Team, diff --git a/database/src/tables/user_app_privileges/table_struct.rs b/database/src/tables/user_app_privileges/table_struct.rs index 07278d4a..a0e6e327 100644 --- a/database/src/tables/user_app_privileges/table_struct.rs +++ b/database/src/tables/user_app_privileges/table_struct.rs @@ -1,4 +1,4 @@ -use crate::structs::privelage_level::PrivilegeLevel; +use crate::structs::privilege_level::PrivilegeLevel; use sqlx::{ postgres::PgRow, types::chrono::{DateTime, Utc}, diff --git a/database/src/tables/user_app_privileges/update.rs b/database/src/tables/user_app_privileges/update.rs index 5c035de4..4a6a2884 100644 --- a/database/src/tables/user_app_privileges/update.rs +++ b/database/src/tables/user_app_privileges/update.rs @@ -53,7 +53,7 @@ impl Db { #[cfg(test)] mod tests { use crate::{ - structs::privelage_level::PrivilegeLevel, + structs::privilege_level::PrivilegeLevel, tables::{ grafana_users::table_struct::GrafanaUser, user_app_privileges::table_struct::UserAppPrivilege, utils::to_microsecond_precision, diff --git a/server/src/http/cloud/mod.rs b/server/src/http/cloud/mod.rs index 1c01de82..a479f927 100644 --- a/server/src/http/cloud/mod.rs +++ b/server/src/http/cloud/mod.rs @@ -1,4 +1,5 @@ pub mod cloud_middleware; pub mod login_with_password; pub mod register_new_app; +pub mod register_new_team; pub mod register_with_password; diff --git a/server/src/http/cloud/register_new_app.rs b/server/src/http/cloud/register_new_app.rs index 04d80bef..0c06b825 100644 --- a/server/src/http/cloud/register_new_app.rs +++ b/server/src/http/cloud/register_new_app.rs @@ -1,7 +1,7 @@ use crate::auth::auth_middleware::UserId; use axum::{extract::State, http::StatusCode, Extension, Json}; use database::{ - db::Db, structs::privelage_level::PrivilegeLevel, tables::utils::get_current_datetime, + db::Db, structs::privilege_level::PrivilegeLevel, tables::utils::get_current_datetime, }; use log::error; use serde::{Deserialize, Serialize}; @@ -14,7 +14,6 @@ use uuid7::uuid7; #[serde(rename_all = "camelCase")] pub struct HttpRegisterNewAppRequest { pub team_id: String, - pub personal: bool, pub app_name: String, pub whitelisted_domains: Vec, pub ack_public_keys: Vec, diff --git a/server/src/http/cloud/register_new_team.rs b/server/src/http/cloud/register_new_team.rs new file mode 100644 index 00000000..f5fc1d4a --- /dev/null +++ b/server/src/http/cloud/register_new_team.rs @@ -0,0 +1,113 @@ +use crate::{auth::auth_middleware::UserId, utils::TEAMS_AMOUNT_LIMIT_PER_USER}; +use axum::{extract::State, http::StatusCode, Extension, Json}; +use database::{ + db::Db, + tables::{team::table_struct::Team, utils::get_current_datetime}, +}; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use ts_rs::TS; +use uuid7::uuid7; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct HttpRegisterNewTeamRequest { + pub team_name: String, + pub personal: bool, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)] +#[ts(export)] +pub struct HttpRegisterNewTeamResponse { + pub team_id: String, +} + +pub async fn register_new_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, + "Failed to get database connection".to_string(), + ))?; + + // First check if user is creating a new team + // Get team data and perform checks + match db + .get_team_by_team_name_and_admin_id(&request.team_name, &user_id) + .await + { + Ok(Some(_)) => { + return Err((StatusCode::BAD_REQUEST, "Team already exists".to_string())); + } + Ok(None) => { + // Check how many teams the user has + match db.get_admin_user_teams_without_personal(&user_id).await { + Ok(teams) => { + if teams.len() >= TEAMS_AMOUNT_LIMIT_PER_USER { + return Err(( + StatusCode::BAD_REQUEST, + "User has reached the maximum number of teams".to_string(), + )); + } + } + Err(_) => { + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + "Database error".to_string(), + )); + } + } + + // Check if user already has a personal team + if request.personal { + match db.get_personal_team_by_admin_id(&user_id).await { + Ok(Some(_)) => { + return Err(( + StatusCode::BAD_REQUEST, + "User already has a personal team".to_string(), + )); + } + Ok(None) => { + // Continue + } + Err(_) => { + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + "Database error".to_string(), + )); + } + } + } + + // Create a new team + let team_id = uuid7().to_string(); + let team = Team { + team_id: team_id.clone(), + team_name: request.team_name.clone(), + team_admin_id: user_id.clone(), + subscription: None, + personal: request.personal, + registration_timestamp: get_current_datetime(), + }; + + if let Err(_) = db.create_new_team(&team).await { + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + "Failed to create a new team".to_string(), + )); + } + + return Ok(Json(HttpRegisterNewTeamResponse { team_id })); + } + Err(_) => { + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + "Database error".to_string(), + )); + } + } +} diff --git a/server/src/utils.rs b/server/src/utils.rs index 65b6bffc..a0ba0a3f 100644 --- a/server/src/utils.rs +++ b/server/src/utils.rs @@ -1,8 +1,10 @@ +use crate::structs::{wallet_metadata::WalletMetadata, wallets::*}; use axum::http::{header, Method}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use tower_http::cors::{Any, CorsLayer}; -use crate::structs::{wallet_metadata::WalletMetadata, wallets::*}; +pub const TEAMS_AMOUNT_LIMIT_PER_USER: usize = 10; +pub const REGISTERED_APPS_LIMIT_PER_TEAM: usize = 20; pub fn get_timestamp_in_milliseconds() -> u64 { let now = SystemTime::now(); From 262c6781ffd04b7baad9eb6c9e048beea47bf257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CGiems=E2=80=9D?= <“hubert.wabia@gmail.com”> Date: Wed, 6 Mar 2024 15:20:15 +0100 Subject: [PATCH 2/5] fix limit check for apps per team --- server/src/http/cloud/register_new_app.rs | 47 +++++++++-------------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/server/src/http/cloud/register_new_app.rs b/server/src/http/cloud/register_new_app.rs index 0c06b825..fe5ef4e9 100644 --- a/server/src/http/cloud/register_new_app.rs +++ b/server/src/http/cloud/register_new_app.rs @@ -1,4 +1,4 @@ -use crate::auth::auth_middleware::UserId; +use crate::{auth::auth_middleware::UserId, utils::REGISTERED_APPS_LIMIT_PER_TEAM}; use axum::{extract::State, http::StatusCode, Extension, Json}; use database::{ db::Db, structs::privilege_level::PrivilegeLevel, tables::utils::get_current_datetime, @@ -48,33 +48,6 @@ pub async fn register_new_app( )); } - // Check if this is a personal team, check if user has already registered an app - if team.personal { - // Check if user has already registered an app - match db.get_registered_apps_by_team_id(&team.team_id).await { - Ok(apps) => { - // User can only have one app under the personal team - if apps.len() > 0 { - return Err(( - StatusCode::BAD_REQUEST, - "Personal team can only have one app".to_string(), - )); - } - } - Err(_) => { - return Err(( - StatusCode::INTERNAL_SERVER_ERROR, - "Database error".to_string(), - )); - } - } - - return Err(( - StatusCode::BAD_REQUEST, - "Personal team can only have one app".to_string(), - )); - } - // Check if user has already registered an app with this name match db.get_registered_app_by_app_name(&request.app_name).await { Ok(Some(app)) => { @@ -94,6 +67,24 @@ pub async fn register_new_app( } } + // Check how many apps the team has + match db.get_registered_apps_by_team_id(&request.team_id).await { + Ok(apps) => { + if apps.len() >= REGISTERED_APPS_LIMIT_PER_TEAM { + return Err(( + StatusCode::BAD_REQUEST, + "Team has reached the maximum number of apps".to_string(), + )); + } + } + Err(_) => { + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + "Database error".to_string(), + )); + } + } + // Register a new app under this team // Start a transaction let mut tx = db.connection_pool.begin().await.unwrap(); From 21347b669f31b750ff94d83bd5df19ea619d7c72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CGiems=E2=80=9D?= <“hubert.wabia@gmail.com”> Date: Wed, 6 Mar 2024 15:50:05 +0100 Subject: [PATCH 3/5] update method --- database/src/tables/registered_app/select.rs | 8 ++++++-- database/src/tables/team/select.rs | 2 +- server/src/http/cloud/register_new_app.rs | 7 +++++-- server/src/http/cloud/register_new_team.rs | 2 +- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/database/src/tables/registered_app/select.rs b/database/src/tables/registered_app/select.rs index 5cc594cb..48c02d0e 100644 --- a/database/src/tables/registered_app/select.rs +++ b/database/src/tables/registered_app/select.rs @@ -35,15 +35,19 @@ impl Db { .await; } - pub async fn get_registered_app_by_app_name( + pub async fn get_registered_app_by_app_name_and_team_id( &self, app_name: &String, + team_id: &String, ) -> Result, sqlx::Error> { - let query = format!("SELECT * FROM {REGISTERED_APPS_TABLE_NAME} WHERE app_name = $1"); + let query = format!( + "SELECT * FROM {REGISTERED_APPS_TABLE_NAME} WHERE app_name = $1 AND team_id = $2" + ); let typed_query = query_as::<_, DbRegisteredApp>(&query); return typed_query .bind(&app_name) + .bind(&team_id) .fetch_optional(&self.connection_pool) .await; } diff --git a/database/src/tables/team/select.rs b/database/src/tables/team/select.rs index 799df67e..353c318a 100644 --- a/database/src/tables/team/select.rs +++ b/database/src/tables/team/select.rs @@ -42,7 +42,7 @@ impl Db { .await; } - pub async fn get_admin_user_teams_without_personal( + pub async fn get_user_created_teams_without_personal( &self, admin_id: &String, ) -> Result, sqlx::Error> { diff --git a/server/src/http/cloud/register_new_app.rs b/server/src/http/cloud/register_new_app.rs index fe5ef4e9..439146a6 100644 --- a/server/src/http/cloud/register_new_app.rs +++ b/server/src/http/cloud/register_new_app.rs @@ -48,8 +48,11 @@ pub async fn register_new_app( )); } - // Check if user has already registered an app with this name - match db.get_registered_app_by_app_name(&request.app_name).await { + // Check if user has already registered an app with this name in this team + match db + .get_registered_app_by_app_name_and_team_id(&request.app_name, &request.team_id) + .await + { Ok(Some(app)) => { if app.app_name == request.app_name { return Err(( diff --git a/server/src/http/cloud/register_new_team.rs b/server/src/http/cloud/register_new_team.rs index f5fc1d4a..62f8f597 100644 --- a/server/src/http/cloud/register_new_team.rs +++ b/server/src/http/cloud/register_new_team.rs @@ -45,7 +45,7 @@ pub async fn register_new_team( } Ok(None) => { // Check how many teams the user has - match db.get_admin_user_teams_without_personal(&user_id).await { + match db.get_user_created_teams_without_personal(&user_id).await { Ok(teams) => { if teams.len() >= TEAMS_AMOUNT_LIMIT_PER_USER { return Err(( From f27fbf85d80f64341e24782ce5d1e85b8b344e23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CGiems=E2=80=9D?= <“hubert.wabia@gmail.com”> Date: Wed, 6 Mar 2024 15:57:15 +0100 Subject: [PATCH 4/5] fix --- server/src/http/cloud/register_new_app.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/server/src/http/cloud/register_new_app.rs b/server/src/http/cloud/register_new_app.rs index 439146a6..04fdfa90 100644 --- a/server/src/http/cloud/register_new_app.rs +++ b/server/src/http/cloud/register_new_app.rs @@ -53,13 +53,11 @@ pub async fn register_new_app( .get_registered_app_by_app_name_and_team_id(&request.app_name, &request.team_id) .await { - Ok(Some(app)) => { - if app.app_name == request.app_name { - return Err(( - StatusCode::BAD_REQUEST, - "App with this name already exists".to_string(), - )); - } + Ok(Some(_)) => { + return Err(( + StatusCode::BAD_REQUEST, + "App with this name already exists".to_string(), + )); } Ok(None) => {} Err(_) => { From f6c1d3b3227cf0d7fcf868fa342ed912c39ef428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CGiems=E2=80=9D?= <“hubert.wabia@gmail.com”> Date: Thu, 7 Mar 2024 10:37:29 +0100 Subject: [PATCH 5/5] cr fix --- server/src/http/cloud/register_new_app.rs | 2 +- server/src/http/cloud/register_new_team.rs | 2 +- server/src/lib.rs | 1 + server/src/statics.rs | 2 ++ server/src/utils.rs | 3 --- 5 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 server/src/statics.rs diff --git a/server/src/http/cloud/register_new_app.rs b/server/src/http/cloud/register_new_app.rs index 04fdfa90..17af8fa9 100644 --- a/server/src/http/cloud/register_new_app.rs +++ b/server/src/http/cloud/register_new_app.rs @@ -1,4 +1,4 @@ -use crate::{auth::auth_middleware::UserId, utils::REGISTERED_APPS_LIMIT_PER_TEAM}; +use crate::{auth::auth_middleware::UserId, statics::REGISTERED_APPS_LIMIT_PER_TEAM}; use axum::{extract::State, http::StatusCode, Extension, Json}; use database::{ db::Db, structs::privilege_level::PrivilegeLevel, tables::utils::get_current_datetime, diff --git a/server/src/http/cloud/register_new_team.rs b/server/src/http/cloud/register_new_team.rs index 62f8f597..39d82805 100644 --- a/server/src/http/cloud/register_new_team.rs +++ b/server/src/http/cloud/register_new_team.rs @@ -1,4 +1,4 @@ -use crate::{auth::auth_middleware::UserId, utils::TEAMS_AMOUNT_LIMIT_PER_USER}; +use crate::{auth::auth_middleware::UserId, statics::TEAMS_AMOUNT_LIMIT_PER_USER}; use axum::{extract::State, http::StatusCode, Extension, Json}; use database::{ db::Db, diff --git a/server/src/lib.rs b/server/src/lib.rs index e71d7331..14ac2328 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -6,6 +6,7 @@ pub mod http; pub mod routes; mod sesssion_cleaner; pub mod state; +pub mod statics; pub mod structs; pub mod utils; pub mod ws; diff --git a/server/src/statics.rs b/server/src/statics.rs new file mode 100644 index 00000000..814ba478 --- /dev/null +++ b/server/src/statics.rs @@ -0,0 +1,2 @@ +pub const TEAMS_AMOUNT_LIMIT_PER_USER: usize = 10; +pub const REGISTERED_APPS_LIMIT_PER_TEAM: usize = 20; diff --git a/server/src/utils.rs b/server/src/utils.rs index a0ba0a3f..3911457a 100644 --- a/server/src/utils.rs +++ b/server/src/utils.rs @@ -3,9 +3,6 @@ use axum::http::{header, Method}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use tower_http::cors::{Any, CorsLayer}; -pub const TEAMS_AMOUNT_LIMIT_PER_USER: usize = 10; -pub const REGISTERED_APPS_LIMIT_PER_TEAM: usize = 20; - pub fn get_timestamp_in_milliseconds() -> u64 { let now = SystemTime::now(); let since_the_epoch = now.duration_since(UNIX_EPOCH).expect("Time went backwards");