Skip to content

Commit

Permalink
Merge pull request #116 from nightly-labs/extend-cloud-api-methods
Browse files Browse the repository at this point in the history
Add method for creating a new team
  • Loading branch information
Giems authored Mar 7, 2024
2 parents d45c8df + f6c1d3b commit 232e678
Show file tree
Hide file tree
Showing 15 changed files with 219 additions and 45 deletions.
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
File renamed without changes.
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, statics::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, statics::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(),
));
}
}
}
1 change: 1 addition & 0 deletions server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
2 changes: 2 additions & 0 deletions server/src/statics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub const TEAMS_AMOUNT_LIMIT_PER_USER: usize = 10;
pub const REGISTERED_APPS_LIMIT_PER_TEAM: usize = 20;
3 changes: 1 addition & 2 deletions server/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
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 fn get_timestamp_in_milliseconds() -> u64 {
let now = SystemTime::now();
let since_the_epoch = now.duration_since(UNIX_EPOCH).expect("Time went backwards");
Expand Down

0 comments on commit 232e678

Please sign in to comment.