From 9477f53432c82e1185d64970a81e81514a1cd892 Mon Sep 17 00:00:00 2001 From: filipriec Date: Fri, 20 Jun 2025 22:31:49 +0200 Subject: [PATCH] big change in the schema, its profile names now and not gen --- .../handlers/post_table_definition.rs | 45 ++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/server/src/table_definition/handlers/post_table_definition.rs b/server/src/table_definition/handlers/post_table_definition.rs index 047e0a0..f05add1 100644 --- a/server/src/table_definition/handlers/post_table_definition.rs +++ b/server/src/table_definition/handlers/post_table_definition.rs @@ -5,8 +5,6 @@ use sqlx::{PgPool, Transaction, Postgres}; use serde_json::json; use common::proto::multieko2::table_definition::{PostTableDefinitionRequest, TableDefinitionResponse}; -const GENERATED_SCHEMA_NAME: &str = "gen"; - const PREDEFINED_FIELD_TYPES: &[(&str, &str)] = &[ ("text", "TEXT"), ("string", "TEXT"), @@ -100,6 +98,13 @@ fn is_invalid_table_name(table_name: &str) -> bool { table_name == "created_at" } +fn is_reserved_schema(schema_name: &str) -> bool { + let lower = schema_name.to_lowercase(); + lower == "public" || + lower == "information_schema" || + lower.starts_with("pg_") +} + pub async fn post_table_definition( db_pool: &PgPool, request: PostTableDefinitionRequest, @@ -108,8 +113,28 @@ pub async fn post_table_definition( return Err(Status::invalid_argument("Profile name cannot be empty")); } + // Apply same sanitization rules as table names + let sanitized_profile_name = sanitize_identifier(&request.profile_name); + + // Add validation to prevent reserved schemas + if is_reserved_schema(&sanitized_profile_name) { + return Err(Status::invalid_argument("Profile name is reserved and cannot be used")); + } + + if !is_valid_identifier(&sanitized_profile_name) { + return Err(Status::invalid_argument("Invalid profile name")); + } + const MAX_IDENTIFIER_LENGTH: usize = 63; + if sanitized_profile_name.len() > MAX_IDENTIFIER_LENGTH { + return Err(Status::invalid_argument(format!( + "Profile name '{}' exceeds the {} character limit.", + sanitized_profile_name, + MAX_IDENTIFIER_LENGTH + ))); + } + let base_name = sanitize_table_name(&request.table_name); if base_name.len() > MAX_IDENTIFIER_LENGTH { return Err(Status::invalid_argument(format!( @@ -140,7 +165,7 @@ pub async fn post_table_definition( let mut tx = db_pool.begin().await .map_err(|e| Status::internal(format!("Failed to start transaction: {}", e)))?; - match execute_table_definition(&mut tx, request, base_name).await { + match execute_table_definition(&mut tx, request, base_name, sanitized_profile_name).await { Ok(response) => { tx.commit().await .map_err(|e| Status::internal(format!("Failed to commit transaction: {}", e)))?; @@ -157,6 +182,7 @@ async fn execute_table_definition( tx: &mut Transaction<'_, Postgres>, mut request: PostTableDefinitionRequest, table_name: String, + profile_name: String, ) -> Result { let profile = sqlx::query!( "INSERT INTO profiles (name) VALUES ($1) @@ -168,6 +194,12 @@ async fn execute_table_definition( .await .map_err(|e| Status::internal(format!("Profile error: {}", e)))?; + // Create schema if it doesn't exist + sqlx::query(&format!("CREATE SCHEMA IF NOT EXISTS \"{}\"", profile_name)) + .execute(&mut **tx) + .await + .map_err(|e| Status::internal(format!("Schema creation failed: {}", e)))?; + let mut links = Vec::new(); for link in request.links.drain(..) { let linked_table = sqlx::query!( @@ -212,7 +244,7 @@ async fn execute_table_definition( indexes.push(idx_name); } - let (create_sql, index_sql) = generate_table_sql(tx, &table_name, &columns, &indexes, &links).await?; + let (create_sql, index_sql) = generate_table_sql(tx, &profile_name, &table_name, &columns, &indexes, &links).await?; let table_def = sqlx::query!( r#"INSERT INTO table_definitions @@ -269,12 +301,13 @@ async fn execute_table_definition( async fn generate_table_sql( tx: &mut Transaction<'_, Postgres>, + profile_name: &str, table_name: &str, columns: &[String], indexes: &[String], links: &[(i64, bool)], ) -> Result<(String, Vec), Status> { - let qualified_table = format!("{}.\"{}\"", GENERATED_SCHEMA_NAME, table_name); + let qualified_table = format!("{}.\"{}\"", profile_name, table_name); let mut system_columns = vec![ "id BIGSERIAL PRIMARY KEY".to_string(), @@ -283,7 +316,7 @@ async fn generate_table_sql( for (linked_id, required) in links { let linked_table = get_table_name_by_id(tx, *linked_id).await?; - let qualified_linked_table = format!("{}.\"{}\"", GENERATED_SCHEMA_NAME, linked_table); + let qualified_linked_table = format!("{}.\"{}\"", profile_name, linked_table); let base_name = linked_table.split_once('_') .map(|(_, rest)| rest) .unwrap_or(&linked_table)