Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add method for creating a new team #116

Merged
merged 5 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions database/migrations/0002_team.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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);
);
2 changes: 1 addition & 1 deletion database/src/structs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 6 additions & 2 deletions database/src/tables/registered_app/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Option<DbRegisteredApp>, 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;
}
Expand Down
45 changes: 45 additions & 0 deletions database/src/tables/team/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,51 @@ impl Db {
.await;
}

pub async fn get_user_created_teams_without_personal(
&self,
admin_id: &String,
) -> Result<Vec<Team>, 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<Option<Team>, 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<Option<Team>, 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,
Expand Down
22 changes: 21 additions & 1 deletion database/src/tables/team/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion database/src/tables/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion database/src/tables/user_app_privileges/table_struct.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::structs::privelage_level::PrivilegeLevel;
use crate::structs::privilege_level::PrivilegeLevel;
use sqlx::{
postgres::PgRow,
types::chrono::{DateTime, Utc},
Expand Down
2 changes: 1 addition & 1 deletion database/src/tables/user_app_privileges/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions server/src/http/cloud/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
57 changes: 24 additions & 33 deletions server/src/http/cloud/register_new_app.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
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::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};
Expand All @@ -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<String>,
pub ack_public_keys: Vec<String>,
Expand Down Expand Up @@ -49,44 +48,36 @@ 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(),
));
}
// Check if user has already registered an app with this name in this team
match db
.get_registered_app_by_app_name_and_team_id(&request.app_name, &request.team_id)
.await
{
Ok(Some(_)) => {
return Err((
StatusCode::BAD_REQUEST,
"App with this name already exists".to_string(),
));
}
Ok(None) => {}
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)) => {
if app.app_name == request.app_name {
// 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,
"App with this name already exists".to_string(),
"Team has reached the maximum number of apps".to_string(),
));
}
}
Ok(None) => {}
Err(_) => {
return Err((
StatusCode::INTERNAL_SERVER_ERROR,
Expand Down
113 changes: 113 additions & 0 deletions server/src/http/cloud/register_new_team.rs
Original file line number Diff line number Diff line change
@@ -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<Option<Arc<Db>>>,
Extension(user_id): Extension<UserId>,
Json(request): Json<HttpRegisterNewTeamRequest>,
) -> Result<Json<HttpRegisterNewTeamResponse>, (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_user_created_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(),
));
}
}
}
4 changes: 3 additions & 1 deletion server/src/utils.rs
Original file line number Diff line number Diff line change
@@ -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;

Giems marked this conversation as resolved.
Show resolved Hide resolved
pub fn get_timestamp_in_milliseconds() -> u64 {
let now = SystemTime::now();
Expand Down
Loading