From ccc66ebd0f1ee29bee72f11c0bf99122a18e927f Mon Sep 17 00:00:00 2001 From: florianfederspiel Date: Wed, 3 Jun 2026 10:43:40 +0200 Subject: [PATCH] KI-AGENT: Matrix-Daten beim Tenant-Import neu provisionieren --- backend/src/routes/admin.ts | 20 ++++++++ backend/src/utils/tenantFullExport.ts | 69 +++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/backend/src/routes/admin.ts b/backend/src/routes/admin.ts index 3fc766f..c8e981a 100644 --- a/backend/src/routes/admin.ts +++ b/backend/src/routes/admin.ts @@ -18,6 +18,7 @@ import { ensureTenantBaseData } from "../modules/bootstrap.service"; import { buildTenantFullExport, importTenantFullExport } from "../utils/tenantFullExport"; import type { TenantFullExport } from "../utils/tenantFullExport"; import { buildSystemStatus } from "../modules/system-status.service"; +import { matrixService } from "../modules/matrix.service"; export default async function adminRoutes(server: FastifyInstance) { const deriveNameFromEmail = (email: string) => { @@ -1034,8 +1035,27 @@ export default async function adminRoutes(server: FastifyInstance) { }); } + let matrixProvisioned = false; + let matrixProvisioningError: string | null = null; + if (process.env.MATRIX_REGISTRATION_SHARED_SECRET) { + try { + const matrix = matrixService(server); + await matrix.provisionTenantRoom(currentUser.id, result.tenantId, { + key: "allgemein", + name: "Allgemeiner Chat", + type: "general", + }); + matrixProvisioned = true; + } catch (err: any) { + matrixProvisioningError = err?.message || String(err); + req.log.warn({ err }, "Matrix-Räume konnten nach Tenant-Import nicht neu provisioniert werden"); + } + } + return { success: true, + matrixProvisioned, + matrixProvisioningError, ...result, }; } catch (err: any) { diff --git a/backend/src/utils/tenantFullExport.ts b/backend/src/utils/tenantFullExport.ts index 68d9f52..810023d 100644 --- a/backend/src/utils/tenantFullExport.ts +++ b/backend/src/utils/tenantFullExport.ts @@ -48,6 +48,46 @@ const ENTITY_BANKACCOUNT_PLAIN_FIELDS = { } const quoteIdent = (value: string) => `"${value.replace(/"/g, '""')}"` +const matrixServerName = () => + process.env.MATRIX_SERVER_NAME || + secrets.MATRIX_SERVER_NAME || + process.env.DOMAIN || + "localhost" + +const normalizeMatrixLocalpartSeed = (value: string) => { + const normalized = value + .toLowerCase() + .normalize("NFKD") + .replace(/[\u0300-\u036f]/g, "") + .replace(/ä/g, "a") + .replace(/ö/g, "o") + .replace(/ü/g, "u") + .replace(/ß/g, "ss") + .replace(/[^a-z0-9._=-]+/g, "_") + .replace(/_+/g, "_") + .replace(/^[._=-]+|[._=-]+$/g, "") + + return normalized || "user" +} + +const normalizeMatrixAliasSeed = (value: string) => + normalizeMatrixLocalpartSeed(value) + .replace(/[.=]/g, "_") + .replace(/_+/g, "_") + +const tenantRoomAliasLocalpart = ( + tenant: { id: number, short?: string | null, name?: string | null }, + roomKey: string +) => { + const tenantSeed = normalizeMatrixAliasSeed(tenant.short || tenant.name || `tenant_${tenant.id}`) + const roomSeed = normalizeMatrixAliasSeed(roomKey) + return `fedeo_${tenantSeed}_${tenant.id}_${roomSeed}` +} + +const tenantRoomAlias = ( + tenant: { id: number, short?: string | null, name?: string | null }, + roomKey: string +) => `#${tenantRoomAliasLocalpart(tenant, roomKey)}:${matrixServerName()}` const tableColumns = async (client: any) => { const result = await client.query(` @@ -346,6 +386,34 @@ const encryptEntityBankAccountRowsForImport = (exportData: TenantFullExport) => } } +const prepareCommunicationRoomsForImport = (exportData: TenantFullExport) => { + const rows = exportData.tables.communication_rooms || [] + if (!rows.length) return + + const tenantById = new Map((exportData.tables.tenants || []).map((tenant) => [ + Number(tenant.id), + { + id: Number(tenant.id), + name: tenant.name, + short: tenant.short, + }, + ])) + + for (const row of rows) { + const tenantId = Number(row.tenant_id) + const tenant = tenantById.get(tenantId) + + row.matrix_room_id = null + row.parent_space_room_id = null + + if (tenant && row.key) { + row.matrix_alias = tenantRoomAlias(tenant, String(row.key)) + } else { + row.matrix_alias = null + } + } +} + const prepareColumnValue = (value: any, isJsonColumn: boolean) => { if (!isJsonColumn || value === null || typeof value === "undefined") return value if (typeof value === "string") return value @@ -465,6 +533,7 @@ export const importTenantFullExport = async ( const exportData = remapTenantScopedExport(rawExportData, options.targetTenantId) encryptEntityBankAccountRowsForImport(exportData) + prepareCommunicationRoomsForImport(exportData) const client = await pool.connect() const importOrder = [ "tenants",