From ff70338b21c02c8a8716fb9ef46ab18d157741ff Mon Sep 17 00:00:00 2001 From: florianfederspiel Date: Mon, 18 May 2026 21:35:16 +0200 Subject: [PATCH] KI-AGENT: Serialisiere JSON-Felder beim Mandantenimport --- backend/src/utils/tenantFullExport.ts | 49 +++++++++++++++++++-------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/backend/src/utils/tenantFullExport.ts b/backend/src/utils/tenantFullExport.ts index 50f856d..f508361 100644 --- a/backend/src/utils/tenantFullExport.ts +++ b/backend/src/utils/tenantFullExport.ts @@ -6,6 +6,10 @@ import { s3 } from "./s3" import { secrets } from "./secrets" type TableRows = Record[]> +type TableMetadata = { + columns: string[] + jsonColumns: Set +} export type TenantFullExport = { format: "fedeo.tenant-full-export" @@ -33,17 +37,25 @@ const quoteIdent = (value: string) => `"${value.replace(/"/g, '""')}"` const tableColumns = async (client: any) => { const result = await client.query(` - select table_name, column_name + select table_name, column_name, data_type from information_schema.columns where table_schema = 'public' order by table_name, ordinal_position `) - const columnsByTable = new Map() + const columnsByTable = new Map() for (const row of result.rows) { - const columns = columnsByTable.get(row.table_name) || [] - columns.push(row.column_name) - columnsByTable.set(row.table_name, columns) + const metadata = columnsByTable.get(row.table_name) || { + columns: [], + jsonColumns: new Set(), + } + + metadata.columns.push(row.column_name) + if (row.data_type === "json" || row.data_type === "jsonb") { + metadata.jsonColumns.add(row.column_name) + } + + columnsByTable.set(row.table_name, metadata) } return columnsByTable @@ -103,8 +115,9 @@ export const buildTenantFullExport = async (server: FastifyInstance, tenantId: n addRows(tables, "tenants", tenantRows) - for (const [table, columns] of columnsByTable.entries()) { + for (const [table, metadata] of columnsByTable.entries()) { if (table === "tenants") continue + const { columns } = metadata const tenantColumn = columns.includes("tenant") ? "tenant" @@ -196,18 +209,25 @@ const restoreFiles = async (exportData: TenantFullExport) => { return { restored, skipped } } -const insertRows = async (client: any, table: string, rows: Record[], columns: string[]) => { +const prepareColumnValue = (value: any, isJsonColumn: boolean) => { + if (!isJsonColumn || value === null || typeof value === "undefined") return value + if (typeof value === "string") return value + + return JSON.stringify(value) +} + +const insertRows = async (client: any, table: string, rows: Record[], metadata: TableMetadata) => { if (!rows.length) return 0 let inserted = 0 - const availableColumns = new Set(columns) + const availableColumns = new Set(metadata.columns) for (const row of rows) { const rowColumns = Object.keys(row).filter((column) => availableColumns.has(column)) if (!rowColumns.length) continue const placeholders = rowColumns.map((_, index) => `$${index + 1}`).join(", ") - const values = rowColumns.map((column) => row[column]) + const values = rowColumns.map((column) => prepareColumnValue(row[column], metadata.jsonColumns.has(column))) await client.query( `insert into ${quoteIdent(table)} (${rowColumns.map(quoteIdent).join(", ")}) values (${placeholders}) on conflict do nothing`, @@ -219,8 +239,9 @@ const insertRows = async (client: any, table: string, rows: Record[ return inserted } -const refreshSequences = async (client: any, columnsByTable: Map) => { - for (const [table, columns] of columnsByTable.entries()) { +const refreshSequences = async (client: any, columnsByTable: Map) => { + for (const [table, metadata] of columnsByTable.entries()) { + const { columns } = metadata if (!columns.includes("id")) continue const sequenceResult = await client.query("select pg_get_serial_sequence($1, $2) as sequence_name", [`public.${table}`, "id"]) @@ -269,10 +290,10 @@ export const importTenantFullExport = async (server: FastifyInstance, exportData const importedTables: { table: string; rows: number }[] = [] for (const table of tableNames) { const rows = exportData.tables[table] || [] - const columns = columnsByTable.get(table) - if (!columns) continue + const metadata = columnsByTable.get(table) + if (!metadata) continue - const count = await insertRows(client, table, rows, columns) + const count = await insertRows(client, table, rows, metadata) importedTables.push({ table, rows: count }) }