diff --git a/backend/.sqlx/query-08c827d9b2de0b77ce0ea2653760751615112c501b35e931ed817dbefd7c6bdb.json b/backend/.sqlx/query-08c827d9b2de0b77ce0ea2653760751615112c501b35e931ed817dbefd7c6bdb.json new file mode 100644 index 0000000000..11c3c200c0 --- /dev/null +++ b/backend/.sqlx/query-08c827d9b2de0b77ce0ea2653760751615112c501b35e931ed817dbefd7c6bdb.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) FROM app WHERE workspace_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "08c827d9b2de0b77ce0ea2653760751615112c501b35e931ed817dbefd7c6bdb" +} diff --git a/backend/.sqlx/query-2f30274b0fe89aa1579b252b990876e5035ca5b31a68fcf08701102a6457e5c4.json b/backend/.sqlx/query-2f30274b0fe89aa1579b252b990876e5035ca5b31a68fcf08701102a6457e5c4.json new file mode 100644 index 0000000000..27854ceeff --- /dev/null +++ b/backend/.sqlx/query-2f30274b0fe89aa1579b252b990876e5035ca5b31a68fcf08701102a6457e5c4.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) FROM token WHERE email = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "2f30274b0fe89aa1579b252b990876e5035ca5b31a68fcf08701102a6457e5c4" +} diff --git a/backend/.sqlx/query-3b5295a7c4b99aefa52c9a8ae1e0dd12bf4a0be1bf755caf7a1fa863e7950562.json b/backend/.sqlx/query-3b5295a7c4b99aefa52c9a8ae1e0dd12bf4a0be1bf755caf7a1fa863e7950562.json new file mode 100644 index 0000000000..82d5378e40 --- /dev/null +++ b/backend/.sqlx/query-3b5295a7c4b99aefa52c9a8ae1e0dd12bf4a0be1bf755caf7a1fa863e7950562.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) FROM raw_app WHERE workspace_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "3b5295a7c4b99aefa52c9a8ae1e0dd12bf4a0be1bf755caf7a1fa863e7950562" +} diff --git a/backend/.sqlx/query-52032730f2eeaaeab55305f72bea5481d1c50c2eaa92a97a078239430f0d6c13.json b/backend/.sqlx/query-52032730f2eeaaeab55305f72bea5481d1c50c2eaa92a97a078239430f0d6c13.json new file mode 100644 index 0000000000..5f8a8dc277 --- /dev/null +++ b/backend/.sqlx/query-52032730f2eeaaeab55305f72bea5481d1c50c2eaa92a97a078239430f0d6c13.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) FROM flow WHERE workspace_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "52032730f2eeaaeab55305f72bea5481d1c50c2eaa92a97a078239430f0d6c13" +} diff --git a/backend/.sqlx/query-5a31b32659a0ac6a6ad0e122a4d475787240d6714ddadf16296d2b7bd5fdcb52.json b/backend/.sqlx/query-5a31b32659a0ac6a6ad0e122a4d475787240d6714ddadf16296d2b7bd5fdcb52.json new file mode 100644 index 0000000000..a6133a5d59 --- /dev/null +++ b/backend/.sqlx/query-5a31b32659a0ac6a6ad0e122a4d475787240d6714ddadf16296d2b7bd5fdcb52.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) FROM variable WHERE workspace_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "5a31b32659a0ac6a6ad0e122a4d475787240d6714ddadf16296d2b7bd5fdcb52" +} diff --git a/backend/.sqlx/query-7c765f50c67b0ef751bafc1bf9279c4cb8a851dfab406ba7611f77773663e9f3.json b/backend/.sqlx/query-7c765f50c67b0ef751bafc1bf9279c4cb8a851dfab406ba7611f77773663e9f3.json new file mode 100644 index 0000000000..090ac08404 --- /dev/null +++ b/backend/.sqlx/query-7c765f50c67b0ef751bafc1bf9279c4cb8a851dfab406ba7611f77773663e9f3.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) FROM resource WHERE workspace_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "7c765f50c67b0ef751bafc1bf9279c4cb8a851dfab406ba7611f77773663e9f3" +} diff --git a/backend/.sqlx/query-9d488c5ba4b9f5203692721d76ec831f5954861a5576e0d8c1c42a9eca90927f.json b/backend/.sqlx/query-9d488c5ba4b9f5203692721d76ec831f5954861a5576e0d8c1c42a9eca90927f.json new file mode 100644 index 0000000000..5f28f48eda --- /dev/null +++ b/backend/.sqlx/query-9d488c5ba4b9f5203692721d76ec831f5954861a5576e0d8c1c42a9eca90927f.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) FROM workspace WHERE owner = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "9d488c5ba4b9f5203692721d76ec831f5954861a5576e0d8c1c42a9eca90927f" +} diff --git a/backend/.sqlx/query-cb8bde4d92a020278cbae79c5c01a766c198392aceb38fb27e57b73de8f7f279.json b/backend/.sqlx/query-cb8bde4d92a020278cbae79c5c01a766c198392aceb38fb27e57b73de8f7f279.json new file mode 100644 index 0000000000..f9d5456b77 --- /dev/null +++ b/backend/.sqlx/query-cb8bde4d92a020278cbae79c5c01a766c198392aceb38fb27e57b73de8f7f279.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) FROM script WHERE workspace_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "cb8bde4d92a020278cbae79c5c01a766c198392aceb38fb27e57b73de8f7f279" +} diff --git a/backend/windmill-api/src/apps.rs b/backend/windmill-api/src/apps.rs index 6e127ebc60..85d1745caa 100644 --- a/backend/windmill-api/src/apps.rs +++ b/backend/windmill-api/src/apps.rs @@ -917,6 +917,23 @@ async fn create_app_internal<'a>( raw_app: bool, mut app: CreateApp, ) -> Result<(sqlx::Transaction<'a, sqlx::Postgres>, String, i64)> { + if *CLOUD_HOSTED { + let nb_apps = + sqlx::query_scalar!("SELECT COUNT(*) FROM app WHERE workspace_id = $1", &w_id) + .fetch_one(&db) + .await?; + if nb_apps.unwrap_or(0) >= 1000 { + return Err(Error::BadRequest( + "You have reached the maximum number of apps (1000) on cloud. Contact support@windmill.dev to increase the limit" + .to_string(), + )); + } + if app.summary.len() > 300 { + return Err(Error::BadRequest( + "Summary must be less than 300 characters on cloud".to_string(), + )); + } + } let mut tx = user_db.clone().begin(&authed).await?; app.policy.on_behalf_of = Some(username_to_permissioned_as(&authed.username)); app.policy.on_behalf_of_email = Some(authed.email.clone()); diff --git a/backend/windmill-api/src/flows.rs b/backend/windmill-api/src/flows.rs index e8ebe6ccc5..af0c8cef7a 100644 --- a/backend/windmill-api/src/flows.rs +++ b/backend/windmill-api/src/flows.rs @@ -34,7 +34,7 @@ use sqlx::{FromRow, Postgres, Transaction}; use windmill_audit::audit_oss::audit_log; use windmill_audit::ActionKind; use windmill_common::utils::query_elems_from_hub; -use windmill_common::worker::to_raw_value; +use windmill_common::worker::{to_raw_value, CLOUD_HOSTED}; use windmill_common::HUB_BASE_URL; use windmill_common::{ db::UserDB, @@ -358,6 +358,32 @@ async fn create_flow( Path(w_id): Path, Json(nf): Json, ) -> Result<(StatusCode, String)> { + if *CLOUD_HOSTED { + let nb_flows = + sqlx::query_scalar!("SELECT COUNT(*) FROM flow WHERE workspace_id = $1", &w_id) + .fetch_one(&db) + .await?; + if nb_flows.unwrap_or(0) >= 1000 { + return Err(Error::BadRequest( + "You have reached the maximum number of flows (1000) on cloud. Contact support@windmill.dev to increase the limit" + .to_string(), + )); + } + if nf.summary.len() > 300 { + return Err(Error::BadRequest( + "Summary must be less than 300 characters on cloud".to_string(), + )); + } + if nf + .description + .as_ref() + .is_some_and(|desc| desc.len() > 3000) + { + return Err(Error::BadRequest( + "Description must be less than 3000 characters on cloud".to_string(), + )); + } + } #[cfg(not(feature = "enterprise"))] if nf .value diff --git a/backend/windmill-api/src/raw_apps.rs b/backend/windmill-api/src/raw_apps.rs index 3a895bab97..3174102932 100644 --- a/backend/windmill-api/src/raw_apps.rs +++ b/backend/windmill-api/src/raw_apps.rs @@ -29,6 +29,7 @@ use windmill_common::{ db::UserDB, error::{Error, JsonResult, Result}, utils::{not_found_if_none, paginate, Pagination, StripPath}, + worker::CLOUD_HOSTED, }; pub fn workspaced_service() -> Router { @@ -149,9 +150,29 @@ async fn create_app( authed: ApiAuthed, Extension(user_db): Extension, Extension(webhook): Extension, + Extension(db): Extension, Path(w_id): Path, Json(app): Json, ) -> Result<(StatusCode, String)> { + if *CLOUD_HOSTED { + let nb_apps = sqlx::query_scalar!( + "SELECT COUNT(*) FROM raw_app WHERE workspace_id = $1", + &w_id + ) + .fetch_one(&db) + .await?; + if nb_apps.unwrap_or(0) >= 1000 { + return Err(Error::BadRequest( + "You have reached the maximum number of apps (1000) on cloud. Contact support@windmill.dev to increase the limit" + .to_string(), + )); + } + if app.summary.len() > 300 { + return Err(Error::BadRequest( + "Summary must be less than 300 characters on cloud".to_string(), + )); + } + } let mut tx = user_db.begin(&authed).await?; if &app.path == "" { return Err(Error::BadRequest("App path cannot be empty".to_string())); diff --git a/backend/windmill-api/src/resources.rs b/backend/windmill-api/src/resources.rs index 19fd422805..7999356344 100644 --- a/backend/windmill-api/src/resources.rs +++ b/backend/windmill-api/src/resources.rs @@ -33,6 +33,7 @@ use windmill_common::{ error::{Error, JsonResult, Result}, utils::{not_found_if_none, paginate, require_admin, Pagination, StripPath}, variables, + worker::CLOUD_HOSTED, }; pub fn workspaced_service() -> Router { @@ -656,6 +657,20 @@ async fn create_resource( Query(q): Query, Json(resource): Json, ) -> Result<(StatusCode, String)> { + if *CLOUD_HOSTED { + let nb_resources = sqlx::query_scalar!( + "SELECT COUNT(*) FROM resource WHERE workspace_id = $1", + &w_id + ) + .fetch_one(&db) + .await?; + if nb_resources.unwrap_or(0) >= 10000 { + return Err(Error::BadRequest( + "You have reached the maximum number of resources (10000) on cloud. Contact support@windmill.dev to increase the limit" + .to_string(), + )); + } + } let authed = maybe_refresh_folders(&resource.path, &w_id, authed, &db).await; let mut tx = user_db.begin(&authed).await?; diff --git a/backend/windmill-api/src/scripts.rs b/backend/windmill-api/src/scripts.rs index 5e49dc4d1c..6b74c6381b 100644 --- a/backend/windmill-api/src/scripts.rs +++ b/backend/windmill-api/src/scripts.rs @@ -42,7 +42,7 @@ use windmill_audit::audit_oss::audit_log; use windmill_audit::ActionKind; use windmill_worker::process_relative_imports; -use windmill_common::error::to_anyhow; +use windmill_common::{error::to_anyhow, worker::CLOUD_HOSTED}; use windmill_common::{ db::UserDB, @@ -520,6 +520,29 @@ async fn create_script_internal<'c>( .to_string(), )); } + if *CLOUD_HOSTED { + let nb_scripts = + sqlx::query_scalar!("SELECT COUNT(*) FROM script WHERE workspace_id = $1", &w_id) + .fetch_one(&db) + .await?; + if nb_scripts.unwrap_or(0) >= 5000 { + return Err(Error::BadRequest( + "You have reached the maximum number of scripts (5000) on cloud. Contact support@windmill.dev to increase the limit" + .to_string(), + )); + } + + if ns.summary.len() > 300 { + return Err(Error::BadRequest( + "Summary must be less than 300 characters on cloud".to_string(), + )); + } + if ns.description.len() > 3000 { + return Err(Error::BadRequest( + "Description must be less than 3000 characters on cloud".to_string(), + )); + } + } let script_path = ns.path.clone(); let hash = ScriptHash(hash_script(&ns)); let authed = maybe_refresh_folders(&ns.path, &w_id, authed, &db).await; diff --git a/backend/windmill-api/src/users.rs b/backend/windmill-api/src/users.rs index 58e9d2d849..3a4c2be568 100644 --- a/backend/windmill-api/src/users.rs +++ b/backend/windmill-api/src/users.rs @@ -1853,6 +1853,18 @@ async fn create_token( .fetch_optional(&mut *tx) .await? .unwrap_or(false); + if *CLOUD_HOSTED { + let nb_tokens = + sqlx::query_scalar!("SELECT COUNT(*) FROM token WHERE email = $1", &authed.email) + .fetch_one(&db) + .await?; + if nb_tokens.unwrap_or(0) >= 10000 { + return Err(Error::BadRequest( + "You have reached the maximum number of tokens (10000) on cloud. Contact support@windmill.dev to increase the limit" + .to_string(), + )); + } + } sqlx::query!( "INSERT INTO token (token, email, label, expiration, super_admin, scopes, workspace_id) diff --git a/backend/windmill-api/src/variables.rs b/backend/windmill-api/src/variables.rs index c1192feb3b..4a6a970db5 100644 --- a/backend/windmill-api/src/variables.rs +++ b/backend/windmill-api/src/variables.rs @@ -29,6 +29,7 @@ use windmill_common::{ variables::{ build_crypt, get_reserved_variables, ContextualVariable, CreateVariable, ListableVariable, }, + worker::CLOUD_HOSTED, }; use lazy_static::lazy_static; @@ -313,6 +314,20 @@ async fn create_variable( Query(AlreadyEncrypted { already_encrypted }): Query, Json(variable): Json, ) -> Result<(StatusCode, String)> { + if *CLOUD_HOSTED { + let nb_variables = sqlx::query_scalar!( + "SELECT COUNT(*) FROM variable WHERE workspace_id = $1", + &w_id + ) + .fetch_one(&db) + .await?; + if nb_variables.unwrap_or(0) >= 10000 { + return Err(Error::BadRequest( + "You have reached the maximum number of variables (10000) on cloud. Contact support@windmill.dev to increase the limit" + .to_string(), + )); + } + } let authed = maybe_refresh_folders(&variable.path, &w_id, authed, &db).await; check_path_conflict(&db, &w_id, &variable.path).await?; diff --git a/backend/windmill-api/src/workspaces.rs b/backend/windmill-api/src/workspaces.rs index b8e8a813f3..9953d3b41c 100644 --- a/backend/windmill-api/src/workspaces.rs +++ b/backend/windmill-api/src/workspaces.rs @@ -36,7 +36,7 @@ use windmill_common::db::UserDB; use windmill_common::s3_helpers::LargeFileStorage; use windmill_common::users::username_to_permissioned_as; use windmill_common::variables::{build_crypt, decrypt, encrypt}; -use windmill_common::worker::to_raw_value; +use windmill_common::worker::{to_raw_value, CLOUD_HOSTED}; #[cfg(feature = "enterprise")] use windmill_common::workspaces::WorkspaceDeploymentUISettings; #[cfg(feature = "enterprise")] @@ -1454,6 +1454,21 @@ async fn create_workspace( #[cfg(not(feature = "enterprise"))] _check_nb_of_workspaces(&db).await?; + if *CLOUD_HOSTED { + let nb_workspaces = sqlx::query_scalar!( + "SELECT COUNT(*) FROM workspace WHERE owner = $1", + authed.email + ) + .fetch_one(&db) + .await?; + if nb_workspaces.unwrap_or(0) >= 10 { + return Err(Error::BadRequest( + "You have reached the maximum number of workspaces (10) on cloud. Contact support@windmill.dev to increase the limit" + .to_string(), + )); + } + } + let mut tx: Transaction<'_, Postgres> = db.begin().await?; check_w_id_conflict(&mut tx, &nw.id).await?;