diff --git a/Cargo.lock b/Cargo.lock index fee4728..cc485b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1381,6 +1381,15 @@ dependencies = [ "libm", ] +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + [[package]] name = "object" version = "0.36.7" @@ -1975,6 +1984,7 @@ dependencies = [ "serde", "serde_json", "sqlx", + "time", "tokio", "tonic", "tonic-reflection", @@ -2419,7 +2429,9 @@ checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", + "libc", "num-conv", + "num_threads", "powerfmt", "serde", "time-core", diff --git a/server/Cargo.toml b/server/Cargo.toml index 754bf10..6fc65c2 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -17,6 +17,7 @@ tokio = { version = "1.43.0", features = ["full", "macros"] } tonic = "0.12.3" tonic-reflection = "0.12.3" tracing = "0.1.41" +time = { version = "0.3.37", features = ["local-offset"] } [lib] name = "server" diff --git a/server/src/table_definition/handlers/post_table_definition.rs b/server/src/table_definition/handlers/post_table_definition.rs index 3083c45..d22a7d0 100644 --- a/server/src/table_definition/handlers/post_table_definition.rs +++ b/server/src/table_definition/handlers/post_table_definition.rs @@ -2,6 +2,7 @@ use tonic::Status; use sqlx::PgPool; use serde_json::json; +use time::OffsetDateTime; use common::proto::multieko2::table_definition::{PostTableDefinitionRequest, TableDefinitionResponse}; const VALID_DATA_TYPES: &[&str] = &["TEXT", "INTEGER", "BIGINT", "BOOLEAN", "TIMESTAMPTZ", "NUMERIC"]; @@ -11,16 +12,23 @@ fn is_valid_data_type(dt: &str) -> bool { } fn is_valid_identifier(s: &str) -> bool { - !s.is_empty() && - s.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') && - !s.starts_with('_') && - !s.chars().next().unwrap().is_ascii_digit() + let parts: Vec<&str> = s.split('_').collect(); + + parts.len() >= 3 && + parts[0] == "ud" && + parts[1].len() == 4 && + parts[1].chars().all(|c| c.is_ascii_digit()) && + parts[2..].iter().all(|p| !p.is_empty()) && + s.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_') } fn sanitize_identifier(s: &str) -> String { - s.replace(|c: char| !c.is_ascii_alphanumeric() && c != '_', "") + let year = OffsetDateTime::now_utc().year(); + let cleaned = s.replace(|c: char| !c.is_ascii_alphanumeric() && c != '_', "") .trim() - .to_lowercase() + .to_lowercase(); + + format!("ud_{}_{}", year, cleaned) } pub async fn post_table_definition( @@ -49,10 +57,10 @@ pub async fn post_table_definition( let linked_table_name = request.linked_table_name .as_ref() .map(|lt| sanitize_identifier(lt)); - + if let Some(lt_name) = &linked_table_name { let lt_record = sqlx::query!( - "SELECT id FROM table_definitions + "SELECT id FROM table_definitions WHERE profile_id = $1 AND table_name = $2", profile.id, lt_name @@ -153,8 +161,15 @@ fn generate_table_sql( ]; if let Some(linked) = linked_table { + // Extract base name without prefix for relationship + let parts: Vec<&str> = linked.splitn(3, '_').collect(); + let base_name = parts.get(2).unwrap_or(&linked); + system_columns.push( - format!("\"{}_id\" BIGINT NOT NULL REFERENCES \"{}\"(id)", linked, linked) + format!("\"{}_id\" BIGINT NOT NULL REFERENCES \"{}\"(id)", + base_name, + linked + ) ); } @@ -184,7 +199,7 @@ fn generate_table_sql( let all_indexes = system_indexes .into_iter() .chain(indexes.iter().map(|idx| { - format!("CREATE INDEX idx_{}_{} ON \"{}\" (\"{}\")", + format!("CREATE INDEX idx_{}_{} ON \"{}\" (\"{}\")", table_name, idx, table_name, idx) })) .collect::>();