fixing post with links
This commit is contained in:
@@ -4,8 +4,8 @@ use sqlx::{PgPool, Arguments, Postgres};
|
||||
use sqlx::postgres::PgArguments;
|
||||
use chrono::{DateTime, Utc};
|
||||
use common::proto::multieko2::tables_data::{PutTableDataRequest, PutTableDataResponse};
|
||||
use std::collections::HashMap;
|
||||
use crate::shared::schema_qualifier::qualify_table_name_for_data; // Import schema qualifier
|
||||
use crate::shared::schema_qualifier::qualify_table_name_for_data;
|
||||
use prost_types::value::Kind;
|
||||
|
||||
pub async fn put_table_data(
|
||||
db_pool: &PgPool,
|
||||
@@ -15,20 +15,9 @@ pub async fn put_table_data(
|
||||
let table_name = request.table_name;
|
||||
let record_id = request.id;
|
||||
|
||||
// Preprocess and validate data
|
||||
let mut processed_data = HashMap::new();
|
||||
let mut null_fields = Vec::new();
|
||||
|
||||
// CORRECTED: Generic handling for all fields.
|
||||
// Any field with an empty string will be added to the null_fields list.
|
||||
// The special, hardcoded logic for "firma" has been removed.
|
||||
for (key, value) in request.data {
|
||||
let trimmed = value.trim().to_string();
|
||||
if trimmed.is_empty() {
|
||||
null_fields.push(key);
|
||||
} else {
|
||||
processed_data.insert(key, trimmed);
|
||||
}
|
||||
// If no data is provided to update, it's an invalid request.
|
||||
if request.data.is_empty() {
|
||||
return Err(Status::invalid_argument("No fields provided to update."));
|
||||
}
|
||||
|
||||
// Lookup profile
|
||||
@@ -70,14 +59,29 @@ pub async fn put_table_data(
|
||||
columns.push((name, sql_type));
|
||||
}
|
||||
|
||||
// CORRECTED: "firma" is not a system column.
|
||||
// It should be treated as a user-defined column.
|
||||
let system_columns = ["deleted"];
|
||||
// Get all foreign key columns for this table (needed for validation)
|
||||
let fk_columns = sqlx::query!(
|
||||
r#"SELECT ltd.table_name
|
||||
FROM table_definition_links tdl
|
||||
JOIN table_definitions ltd ON tdl.linked_table_id = ltd.id
|
||||
WHERE tdl.source_table_id = $1"#,
|
||||
table_def.id
|
||||
)
|
||||
.fetch_all(db_pool)
|
||||
.await
|
||||
.map_err(|e| Status::internal(format!("Foreign key lookup error: {}", e)))?;
|
||||
|
||||
let mut system_columns = vec!["deleted".to_string()];
|
||||
for fk in fk_columns {
|
||||
let base_name = fk.table_name.split_once('_').map_or(fk.table_name.as_str(), |(_, rest)| rest);
|
||||
system_columns.push(format!("{}_id", base_name));
|
||||
}
|
||||
let system_columns_set: std::collections::HashSet<_> = system_columns.iter().map(|s| s.as_str()).collect();
|
||||
let user_columns: Vec<&String> = columns.iter().map(|(name, _)| name).collect();
|
||||
|
||||
// Validate input columns
|
||||
for key in processed_data.keys() {
|
||||
if !system_columns.contains(&key.as_str()) && !user_columns.contains(&key) {
|
||||
for key in request.data.keys() {
|
||||
if !system_columns_set.contains(key.as_str()) && !user_columns.contains(&key) {
|
||||
return Err(Status::invalid_argument(format!("Invalid column: {}", key)));
|
||||
}
|
||||
}
|
||||
@@ -87,54 +91,65 @@ pub async fn put_table_data(
|
||||
let mut set_clauses = Vec::new();
|
||||
let mut param_idx = 1;
|
||||
|
||||
// Add data parameters for non-empty fields
|
||||
for (col, value) in &processed_data {
|
||||
// CORRECTED: The logic for "firma" is removed from this match.
|
||||
// It will now fall through to the `else` block and have its type
|
||||
// correctly looked up from the `columns` vector.
|
||||
let sql_type = if system_columns.contains(&col.as_str()) {
|
||||
for (col, proto_value) in request.data {
|
||||
let sql_type = if system_columns_set.contains(col.as_str()) {
|
||||
match col.as_str() {
|
||||
"deleted" => "BOOLEAN",
|
||||
_ if col.ends_with("_id") => "BIGINT",
|
||||
_ => return Err(Status::invalid_argument("Invalid system column")),
|
||||
}
|
||||
} else {
|
||||
columns.iter()
|
||||
.find(|(name, _)| name == col)
|
||||
.find(|(name, _)| name == &col)
|
||||
.map(|(_, sql_type)| sql_type.as_str())
|
||||
.ok_or_else(|| Status::invalid_argument(format!("Column not found: {}", col)))?
|
||||
};
|
||||
|
||||
// A provided value cannot be null or empty in a PUT request.
|
||||
// To clear a field, it should be set to an empty string "" for text,
|
||||
// or a specific value for other types if needed (though typically not done).
|
||||
// For now, we reject nulls.
|
||||
let kind = proto_value.kind.ok_or_else(|| {
|
||||
Status::invalid_argument(format!("Value for column '{}' cannot be empty in a PUT request. To clear a text field, send an empty string.", col))
|
||||
})?;
|
||||
|
||||
match sql_type {
|
||||
"TEXT" | "VARCHAR(15)" | "VARCHAR(255)" => {
|
||||
if let Some(max_len) = sql_type.strip_prefix("VARCHAR(")
|
||||
.and_then(|s| s.strip_suffix(')'))
|
||||
.and_then(|s| s.parse::<usize>().ok())
|
||||
{
|
||||
if value.len() > max_len {
|
||||
return Err(Status::internal(format!("Value too long for {}", col)));
|
||||
}
|
||||
if let Kind::StringValue(value) = kind {
|
||||
params.add(value)
|
||||
.map_err(|e| Status::internal(format!("Failed to add text parameter for {}: {}", col, e)))?;
|
||||
} else {
|
||||
return Err(Status::invalid_argument(format!("Expected string for column '{}'", col)));
|
||||
}
|
||||
params.add(value)
|
||||
.map_err(|e| Status::internal(format!("Failed to add text parameter for {}: {}", col, e)))?;
|
||||
},
|
||||
"BOOLEAN" => {
|
||||
let val = value.parse::<bool>()
|
||||
.map_err(|_| Status::invalid_argument(format!("Invalid boolean for {}", col)))?;
|
||||
params.add(val)
|
||||
.map_err(|e| Status::internal(format!("Failed to add boolean parameter for {}: {}", col, e)))?;
|
||||
if let Kind::BoolValue(val) = kind {
|
||||
params.add(val)
|
||||
.map_err(|e| Status::internal(format!("Failed to add boolean parameter for {}: {}", col, e)))?;
|
||||
} else {
|
||||
return Err(Status::invalid_argument(format!("Expected boolean for column '{}'", col)));
|
||||
}
|
||||
},
|
||||
"TIMESTAMPTZ" => {
|
||||
let dt = DateTime::parse_from_rfc3339(value)
|
||||
.map_err(|_| Status::invalid_argument(format!("Invalid timestamp for {}", col)))?;
|
||||
params.add(dt.with_timezone(&Utc))
|
||||
.map_err(|e| Status::internal(format!("Failed to add timestamp parameter for {}: {}", col, e)))?;
|
||||
if let Kind::StringValue(value) = kind {
|
||||
let dt = DateTime::parse_from_rfc3339(&value)
|
||||
.map_err(|_| Status::invalid_argument(format!("Invalid timestamp for {}", col)))?;
|
||||
params.add(dt.with_timezone(&Utc))
|
||||
.map_err(|e| Status::internal(format!("Failed to add timestamp parameter for {}: {}", col, e)))?;
|
||||
} else {
|
||||
return Err(Status::invalid_argument(format!("Expected ISO 8601 string for column '{}'", col)));
|
||||
}
|
||||
},
|
||||
// ADDED: BIGINT handling for completeness, if needed for other columns.
|
||||
"BIGINT" => {
|
||||
let val = value.parse::<i64>()
|
||||
.map_err(|_| Status::invalid_argument(format!("Invalid integer for {}", col)))?;
|
||||
params.add(val)
|
||||
.map_err(|e| Status::internal(format!("Failed to add integer parameter for {}: {}", col, e)))?;
|
||||
if let Kind::NumberValue(val) = kind {
|
||||
if val.fract() != 0.0 {
|
||||
return Err(Status::invalid_argument(format!("Expected integer for column '{}', but got a float", col)));
|
||||
}
|
||||
params.add(val as i64)
|
||||
.map_err(|e| Status::internal(format!("Failed to add integer parameter for {}: {}", col, e)))?;
|
||||
} else {
|
||||
return Err(Status::invalid_argument(format!("Expected number for column '{}'", col)));
|
||||
}
|
||||
},
|
||||
_ => return Err(Status::invalid_argument(format!("Unsupported type {}", sql_type))),
|
||||
}
|
||||
@@ -143,27 +158,10 @@ pub async fn put_table_data(
|
||||
param_idx += 1;
|
||||
}
|
||||
|
||||
// Add NULL clauses for empty fields
|
||||
for field in null_fields {
|
||||
// Make sure the field is valid
|
||||
if !system_columns.contains(&field.as_str()) && !user_columns.contains(&&field) {
|
||||
return Err(Status::invalid_argument(format!("Invalid column to set NULL: {}", field)));
|
||||
}
|
||||
set_clauses.push(format!("\"{}\" = NULL", field));
|
||||
}
|
||||
|
||||
// Ensure we have at least one field to update
|
||||
if set_clauses.is_empty() {
|
||||
return Err(Status::invalid_argument("No valid fields to update"));
|
||||
}
|
||||
|
||||
// Add ID parameter at the end
|
||||
params.add(record_id)
|
||||
.map_err(|e| Status::internal(format!("Failed to add record_id parameter: {}", e)))?;
|
||||
|
||||
// Qualify table name with schema
|
||||
let qualified_table = qualify_table_name_for_data(&table_name)?;
|
||||
|
||||
let set_clause = set_clauses.join(", ");
|
||||
let sql = format!(
|
||||
"UPDATE {} SET {} WHERE id = ${} AND deleted = FALSE RETURNING id",
|
||||
@@ -184,7 +182,6 @@ pub async fn put_table_data(
|
||||
}),
|
||||
Ok(None) => Err(Status::not_found("Record not found or already deleted")),
|
||||
Err(e) => {
|
||||
// Handle "relation does not exist" error specifically
|
||||
if let Some(db_err) = e.as_database_error() {
|
||||
if db_err.code() == Some(std::borrow::Cow::Borrowed("42P01")) {
|
||||
return Err(Status::internal(format!(
|
||||
|
||||
Reference in New Issue
Block a user