From e1ea44c68c2eee5790f70e2daaca0cacbcbc20bd Mon Sep 17 00:00:00 2001 From: Priec Date: Fri, 12 Sep 2025 23:06:42 +0200 Subject: [PATCH] inputing data for the client for the validation --- ...12195658_create_table_validation_rules.sql | 15 ++++ server/src/lib.rs | 1 + .../handlers/post_table_definition.rs | 23 +++++ server/src/table_validation/mod.rs | 4 + server/src/table_validation/repo.rs | 52 ++++++++++++ server/src/table_validation/service.rs | 83 +++++++++++++++++++ 6 files changed, 178 insertions(+) create mode 100644 server/migrations/20250912195658_create_table_validation_rules.sql create mode 100644 server/src/table_validation/mod.rs create mode 100644 server/src/table_validation/repo.rs create mode 100644 server/src/table_validation/service.rs diff --git a/server/migrations/20250912195658_create_table_validation_rules.sql b/server/migrations/20250912195658_create_table_validation_rules.sql new file mode 100644 index 0000000..5c750d8 --- /dev/null +++ b/server/migrations/20250912195658_create_table_validation_rules.sql @@ -0,0 +1,15 @@ +-- Add migration script here + +CREATE TABLE table_validation_rules ( + id BIGSERIAL PRIMARY KEY, + table_def_id BIGINT NOT NULL + REFERENCES table_definitions(id) + ON DELETE CASCADE, + data_key TEXT NOT NULL, + config JSONB NOT NULL, + updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, + UNIQUE (table_def_id, data_key) +); + +CREATE INDEX idx_table_validation_rules_table + ON table_validation_rules (table_def_id); diff --git a/server/src/lib.rs b/server/src/lib.rs index ae890e9..7e6777e 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -10,6 +10,7 @@ pub mod table_definition; pub mod tables_data; pub mod table_script; pub mod steel; +pub mod table_validation; // Re-export run_server from the inner server module: pub use server::run_server; diff --git a/server/src/table_definition/handlers/post_table_definition.rs b/server/src/table_definition/handlers/post_table_definition.rs index a58c3d9..2413df1 100644 --- a/server/src/table_definition/handlers/post_table_definition.rs +++ b/server/src/table_definition/handlers/post_table_definition.rs @@ -351,6 +351,29 @@ async fn execute_table_definition( Status::internal(format!("Database error: {}", e)) })?; + for col_def in &columns { + // Column string looks like "\"name\" TYPE", split out identifier + let col_name = col_def.split_whitespace().next().unwrap_or(""); + let clean_col = col_name.trim_matches('"'); + + // Default empty config — currently only character_limits block, none set. + let default_cfg = serde_json::json!({ + "character_limits": { "min": 0, "max": 0, "warn_at": null, "count_mode": "CHARS" } + }); + + sqlx::query!( + r#"INSERT INTO table_validation_rules (table_def_id, data_key, config) + VALUES ($1, $2, $3) + ON CONFLICT (table_def_id, data_key) DO NOTHING"#, + table_def.id, + clean_col, + default_cfg + ) + .execute(&mut **tx) + .await + .map_err(|e| Status::internal(format!("Failed to insert default validation rule for column {}: {}", clean_col, e)))?; + } + for (linked_id, is_required) in links { sqlx::query!( "INSERT INTO table_definition_links diff --git a/server/src/table_validation/mod.rs b/server/src/table_validation/mod.rs new file mode 100644 index 0000000..86a73dc --- /dev/null +++ b/server/src/table_validation/mod.rs @@ -0,0 +1,4 @@ +// src/table_validation/mod.rs + +pub mod repo; +pub mod service; diff --git a/server/src/table_validation/repo.rs b/server/src/table_validation/repo.rs new file mode 100644 index 0000000..4a74bb5 --- /dev/null +++ b/server/src/table_validation/repo.rs @@ -0,0 +1,52 @@ +// src/table_validation/repo.rs + +use sqlx::PgPool; +use anyhow::Result; +use serde_json::Value; + +pub struct ValidationRuleRow { + pub data_key: String, + pub config: Value, +} + +pub async fn get_table_def_id( + db: &PgPool, + profile_name: &str, + table_name: &str, +) -> Result { + let rec = sqlx::query!( + r#" + SELECT td.id + FROM table_definitions td + JOIN schemas s ON td.schema_id = s.id + WHERE s.name = $1 AND td.table_name = $2 + "#, + profile_name, + table_name + ) + .fetch_one(db) + .await?; + + Ok(rec.id) +} + +pub async fn get_validations( + db: &PgPool, + table_def_id: i64, +) -> Result> { + let rows = sqlx::query!( + r#" + SELECT data_key, config + FROM table_validation_rules + WHERE table_def_id = $1 + "#, + table_def_id + ) + .fetch_all(db) + .await?; + + Ok(rows.into_iter().map(|r| ValidationRuleRow { + data_key: r.data_key, + config: r.config, + }).collect()) +} diff --git a/server/src/table_validation/service.rs b/server/src/table_validation/service.rs new file mode 100644 index 0000000..b6aab87 --- /dev/null +++ b/server/src/table_validation/service.rs @@ -0,0 +1,83 @@ +// src/table_validation/service.rs + +use tonic::{Request, Response, Status}; +use sqlx::PgPool; +use serde::Deserialize; +use common::proto::komp_ac::table_validation::{ + table_validation_service_server::TableValidationService, + GetTableValidationRequest, TableValidationResponse, + FieldValidation, CharacterLimits, CountMode as PbCountMode, +}; +use crate::table_validation::repo; + +#[derive(Deserialize)] +struct FieldConfig { + #[serde(default)] + character_limits: Option, +} + +#[derive(Deserialize)] +struct CharacterLimitsCfg { + #[serde(default)] min: Option, + #[serde(default)] max: Option, + #[serde(default)] warn_at: Option, + #[serde(default)] count_mode: Option, // "CHARS"/"BYTES"/"DISPLAY_WIDTH" +} + +pub struct TableValidationSvc { + pub db: PgPool, +} + +#[tonic::async_trait] +impl TableValidationService for TableValidationSvc { + async fn get_table_validation( + &self, + req: Request, + ) -> Result, Status> { + let req = req.into_inner(); + + // 1. Get table_def_id + let table_def_id = repo::get_table_def_id(&self.db, &req.profile_name, &req.table_name) + .await + .map_err(|_| Status::not_found("Table definition not found"))?; + + // 2. Get validations + let rules = repo::get_validations(&self.db, table_def_id) + .await + .map_err(|e| Status::internal(format!("Failed to fetch rules: {}", e)))?; + + // 3. Map JSON -> proto + let mut fields_out = Vec::new(); + for r in rules { + let cfg: FieldConfig = match serde_json::from_value(r.config) { + Ok(c) => c, + Err(e) => { + tracing::warn!("Invalid config for {}: {}", r.data_key, e); + continue; + } + }; + + if let Some(cl) = cfg.character_limits { + let pb_mode = match cl.count_mode.as_deref() { + Some("BYTES") => PbCountMode::Bytes as i32, + Some("DISPLAY_WIDTH") => PbCountMode::DisplayWidth as i32, + _ => PbCountMode::Chars as i32, + }; + + let limits = CharacterLimits { + min: cl.min.unwrap_or(0), + max: cl.max.unwrap_or(0), + warn_at: cl.warn_at, + count_mode: pb_mode, + }; + + fields_out.push(FieldValidation { + data_key: r.data_key, + limits: Some(limits), + }); + } + } + + Ok(Response::new(TableValidationResponse { fields: fields_out })) + } +}