KI-AGENT: Serialisiere JSON-Felder beim Mandantenimport
This commit is contained in:
@@ -6,6 +6,10 @@ import { s3 } from "./s3"
|
|||||||
import { secrets } from "./secrets"
|
import { secrets } from "./secrets"
|
||||||
|
|
||||||
type TableRows = Record<string, Record<string, any>[]>
|
type TableRows = Record<string, Record<string, any>[]>
|
||||||
|
type TableMetadata = {
|
||||||
|
columns: string[]
|
||||||
|
jsonColumns: Set<string>
|
||||||
|
}
|
||||||
|
|
||||||
export type TenantFullExport = {
|
export type TenantFullExport = {
|
||||||
format: "fedeo.tenant-full-export"
|
format: "fedeo.tenant-full-export"
|
||||||
@@ -33,17 +37,25 @@ const quoteIdent = (value: string) => `"${value.replace(/"/g, '""')}"`
|
|||||||
|
|
||||||
const tableColumns = async (client: any) => {
|
const tableColumns = async (client: any) => {
|
||||||
const result = await client.query(`
|
const result = await client.query(`
|
||||||
select table_name, column_name
|
select table_name, column_name, data_type
|
||||||
from information_schema.columns
|
from information_schema.columns
|
||||||
where table_schema = 'public'
|
where table_schema = 'public'
|
||||||
order by table_name, ordinal_position
|
order by table_name, ordinal_position
|
||||||
`)
|
`)
|
||||||
|
|
||||||
const columnsByTable = new Map<string, string[]>()
|
const columnsByTable = new Map<string, TableMetadata>()
|
||||||
for (const row of result.rows) {
|
for (const row of result.rows) {
|
||||||
const columns = columnsByTable.get(row.table_name) || []
|
const metadata = columnsByTable.get(row.table_name) || {
|
||||||
columns.push(row.column_name)
|
columns: [],
|
||||||
columnsByTable.set(row.table_name, columns)
|
jsonColumns: new Set<string>(),
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
return columnsByTable
|
||||||
@@ -103,8 +115,9 @@ export const buildTenantFullExport = async (server: FastifyInstance, tenantId: n
|
|||||||
|
|
||||||
addRows(tables, "tenants", tenantRows)
|
addRows(tables, "tenants", tenantRows)
|
||||||
|
|
||||||
for (const [table, columns] of columnsByTable.entries()) {
|
for (const [table, metadata] of columnsByTable.entries()) {
|
||||||
if (table === "tenants") continue
|
if (table === "tenants") continue
|
||||||
|
const { columns } = metadata
|
||||||
|
|
||||||
const tenantColumn = columns.includes("tenant")
|
const tenantColumn = columns.includes("tenant")
|
||||||
? "tenant"
|
? "tenant"
|
||||||
@@ -196,18 +209,25 @@ const restoreFiles = async (exportData: TenantFullExport) => {
|
|||||||
return { restored, skipped }
|
return { restored, skipped }
|
||||||
}
|
}
|
||||||
|
|
||||||
const insertRows = async (client: any, table: string, rows: Record<string, any>[], 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<string, any>[], metadata: TableMetadata) => {
|
||||||
if (!rows.length) return 0
|
if (!rows.length) return 0
|
||||||
|
|
||||||
let inserted = 0
|
let inserted = 0
|
||||||
const availableColumns = new Set(columns)
|
const availableColumns = new Set(metadata.columns)
|
||||||
|
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
const rowColumns = Object.keys(row).filter((column) => availableColumns.has(column))
|
const rowColumns = Object.keys(row).filter((column) => availableColumns.has(column))
|
||||||
if (!rowColumns.length) continue
|
if (!rowColumns.length) continue
|
||||||
|
|
||||||
const placeholders = rowColumns.map((_, index) => `$${index + 1}`).join(", ")
|
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(
|
await client.query(
|
||||||
`insert into ${quoteIdent(table)} (${rowColumns.map(quoteIdent).join(", ")}) values (${placeholders}) on conflict do nothing`,
|
`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<string, any>[
|
|||||||
return inserted
|
return inserted
|
||||||
}
|
}
|
||||||
|
|
||||||
const refreshSequences = async (client: any, columnsByTable: Map<string, string[]>) => {
|
const refreshSequences = async (client: any, columnsByTable: Map<string, TableMetadata>) => {
|
||||||
for (const [table, columns] of columnsByTable.entries()) {
|
for (const [table, metadata] of columnsByTable.entries()) {
|
||||||
|
const { columns } = metadata
|
||||||
if (!columns.includes("id")) continue
|
if (!columns.includes("id")) continue
|
||||||
|
|
||||||
const sequenceResult = await client.query("select pg_get_serial_sequence($1, $2) as sequence_name", [`public.${table}`, "id"])
|
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 }[] = []
|
const importedTables: { table: string; rows: number }[] = []
|
||||||
for (const table of tableNames) {
|
for (const table of tableNames) {
|
||||||
const rows = exportData.tables[table] || []
|
const rows = exportData.tables[table] || []
|
||||||
const columns = columnsByTable.get(table)
|
const metadata = columnsByTable.get(table)
|
||||||
if (!columns) continue
|
if (!metadata) continue
|
||||||
|
|
||||||
const count = await insertRows(client, table, rows, columns)
|
const count = await insertRows(client, table, rows, metadata)
|
||||||
importedTables.push({ table, rows: count })
|
importedTables.push({ table, rows: count })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user