diff --git a/server/src/table_definition/handlers/post_table_definition.rs b/server/src/table_definition/handlers/post_table_definition.rs index d491898..a3cbbac 100644 --- a/server/src/table_definition/handlers/post_table_definition.rs +++ b/server/src/table_definition/handlers/post_table_definition.rs @@ -12,14 +12,10 @@ fn is_valid_data_type(dt: &str) -> bool { } fn is_valid_identifier(s: &str) -> bool { - 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 == '_') + !s.is_empty() && + s.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') && + !s.starts_with('_') && + !s.chars().next().unwrap().is_ascii_digit() } fn sanitize_identifier(s: &str) -> String { @@ -27,8 +23,9 @@ fn sanitize_identifier(s: &str) -> String { let cleaned = s.replace(|c: char| !c.is_ascii_alphanumeric() && c != '_', "") .trim() .to_lowercase(); - - format!("ud_{}_{}", year, cleaned) + + // Format: "currentyear_tablename" + format!("{}_{}", year, cleaned) } pub async fn post_table_definition( @@ -37,7 +34,7 @@ pub async fn post_table_definition( ) -> Result { // Validate and sanitize inputs let table_name = sanitize_identifier(&request.table_name); - if !is_valid_identifier(&table_name) { + if !is_valid_identifier(&request.table_name) { return Err(Status::invalid_argument("Invalid table name")); } @@ -54,11 +51,8 @@ pub async fn post_table_definition( // Validate linked table if provided let linked_table_id; - let linked_table_name = request.linked_table_name - .as_ref() - .map(|lt| sanitize_identifier(lt)); - - if let Some(lt_name) = &linked_table_name { + if let Some(lt_name) = &request.linked_table_name { + // Lookup the table with the EXACT provided name let lt_record = sqlx::query!( "SELECT id FROM table_definitions WHERE profile_id = $1 AND table_name = $2", @@ -81,7 +75,7 @@ pub async fn post_table_definition( let mut columns = Vec::new(); for col_def in request.columns.drain(..) { let col_name = sanitize_identifier(&col_def.name); - if !is_valid_identifier(&col_name) { + if !is_valid_identifier(&col_def.name) { return Err(Status::invalid_argument("Invalid column name")); } if !is_valid_data_type(&col_def.data_type) { @@ -93,7 +87,7 @@ pub async fn post_table_definition( let mut indexes = Vec::new(); for idx in request.indexes.drain(..) { let idx_name = sanitize_identifier(&idx); - if !is_valid_identifier(&idx_name) { + if !is_valid_identifier(&idx) { return Err(Status::invalid_argument(format!("Invalid index name: {}", idx))); } indexes.push(idx_name); @@ -104,7 +98,7 @@ pub async fn post_table_definition( &table_name, &columns, &indexes, - linked_table_name.as_deref() + request.linked_table_name.as_deref() ); // Store definition @@ -162,12 +156,12 @@ 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); - + let parts: Vec<&str> = linked.splitn(2, '_').collect(); + let base_name = parts.get(1).unwrap_or(&linked); // "profile_table" + system_columns.push( - format!("\"{}_id\" BIGINT NOT NULL REFERENCES \"{}\"(id)", - base_name, + format!("\"{}_id\" BIGINT NOT NULL REFERENCES \"{}\"(id)", + base_name, linked ) ); @@ -190,17 +184,9 @@ fn generate_table_sql( ]; if let Some(linked) = linked_table { - let parts: Vec<&str> = linked.splitn(3, '_').collect(); - let base_name = parts.get(2).unwrap_or(&linked); + let parts: Vec<&str> = linked.splitn(2, '_').collect(); + let base_name = parts.get(1).unwrap_or(&linked); - system_columns.push( - format!("\"{}_id\" BIGINT NOT NULL REFERENCES \"{}\"(id)", - base_name, - linked - ) - ); - - // FIXED: Use base_name instead of linked system_indexes.push(format!( "CREATE INDEX idx_{}_{}_id ON \"{}\" (\"{}_id\")", table_name, base_name, table_name, base_name