Compare commits
3 Commits
ad74825781
...
033e74adda
| Author | SHA1 | Date | |
|---|---|---|---|
| 033e74adda | |||
| ccc66ebd0f | |||
| c660f62120 |
@@ -291,6 +291,16 @@ export function matrixService(server: FastifyInstance) {
|
||||
roomKey: string
|
||||
) => `#${tenantRoomAliasLocalpart(tenant, roomKey)}:${serverName()}`
|
||||
|
||||
const matrixIdentifierServerName = (value?: string | null) => {
|
||||
const match = value?.match(/^[!#@][^:]+:(.+)$/)
|
||||
return match?.[1] || null
|
||||
}
|
||||
|
||||
const belongsToCurrentMatrixServer = (value?: string | null) => {
|
||||
const identifierServerName = matrixIdentifierServerName(value)
|
||||
return !identifierServerName || identifierServerName === serverName()
|
||||
}
|
||||
|
||||
const normalizeTenantRoomOptions = (options: MatrixTenantRoomOptions = {}) => {
|
||||
const fallbackName = options.key || options.name || "Allgemeiner Chat"
|
||||
const key = normalizeMatrixAliasSeed(options.key || fallbackName)
|
||||
@@ -836,9 +846,14 @@ export function matrixService(server: FastifyInstance) {
|
||||
) => {
|
||||
const normalizedOptions = normalizeTenantRoomOptions(options)
|
||||
const existing = await findTenantRoomMetadata(tenant.id, normalizedOptions.key)
|
||||
const expectedAlias = tenantRoomAlias(tenant, normalizedOptions.key)
|
||||
|
||||
if (existing) {
|
||||
const hasStaleMatrixRoomId = !belongsToCurrentMatrixServer(existing.matrixRoomId)
|
||||
const hasStaleMatrixAlias = !belongsToCurrentMatrixServer(existing.matrixAlias)
|
||||
const shouldUpdate =
|
||||
hasStaleMatrixRoomId ||
|
||||
hasStaleMatrixAlias ||
|
||||
(options.name !== undefined && existing.name !== normalizedOptions.name) ||
|
||||
(options.topic !== undefined && existing.topic !== normalizedOptions.topic) ||
|
||||
(options.type !== undefined && existing.type !== normalizedOptions.type) ||
|
||||
@@ -857,6 +872,9 @@ export function matrixService(server: FastifyInstance) {
|
||||
entityType: options.entityType !== undefined ? normalizedOptions.entityType : existing.entityType,
|
||||
entityId: options.entityId !== undefined ? normalizedOptions.entityId : existing.entityId,
|
||||
entityUuid: options.entityUuid !== undefined ? normalizedOptions.entityUuid : existing.entityUuid,
|
||||
matrixRoomId: hasStaleMatrixRoomId ? null : existing.matrixRoomId,
|
||||
matrixAlias: hasStaleMatrixAlias ? expectedAlias : existing.matrixAlias,
|
||||
parentSpaceRoomId: hasStaleMatrixRoomId ? null : existing.parentSpaceRoomId,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(communicationRooms.id, existing.id))
|
||||
@@ -876,7 +894,7 @@ export function matrixService(server: FastifyInstance) {
|
||||
entityType: normalizedOptions.entityType,
|
||||
entityId: normalizedOptions.entityId,
|
||||
entityUuid: normalizedOptions.entityUuid,
|
||||
matrixAlias: tenantRoomAlias(tenant, normalizedOptions.key),
|
||||
matrixAlias: expectedAlias,
|
||||
})
|
||||
.returning()
|
||||
|
||||
@@ -960,8 +978,8 @@ export function matrixService(server: FastifyInstance) {
|
||||
entityUuid: metadata.entityUuid,
|
||||
alias,
|
||||
exists: false,
|
||||
roomId: metadata.matrixRoomId,
|
||||
parentSpaceRoomId: metadata.parentSpaceRoomId,
|
||||
roomId: belongsToCurrentMatrixServer(metadata.matrixRoomId) ? metadata.matrixRoomId : null,
|
||||
parentSpaceRoomId: belongsToCurrentMatrixServer(metadata.parentSpaceRoomId) ? metadata.parentSpaceRoomId : null,
|
||||
servers: [],
|
||||
}
|
||||
}
|
||||
@@ -1141,6 +1159,9 @@ export function matrixService(server: FastifyInstance) {
|
||||
const ensureServiceUserJoinedRoom = async (room: { roomId?: string | null; alias?: string | null }) => {
|
||||
const target = room.roomId || room.alias
|
||||
if (!target) return { ok: false, status: "missing_room" }
|
||||
if (!belongsToCurrentMatrixServer(target)) {
|
||||
return { ok: false, status: "stale_room", roomId: target }
|
||||
}
|
||||
|
||||
const serviceLogin = await ensureServiceAccessToken()
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,73 @@ 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 cleanupImportedCommunicationRooms = async (client: any, exportData: TenantFullExport) => {
|
||||
const rows = exportData.tables.communication_rooms || []
|
||||
if (!rows.length) return 0
|
||||
|
||||
const tenantById = new Map((exportData.tables.tenants || []).map((tenant) => [
|
||||
Number(tenant.id),
|
||||
{
|
||||
id: Number(tenant.id),
|
||||
name: tenant.name,
|
||||
short: tenant.short,
|
||||
},
|
||||
]))
|
||||
let cleaned = 0
|
||||
|
||||
for (const row of rows) {
|
||||
const tenantId = Number(row.tenant_id)
|
||||
const key = String(row.key || "")
|
||||
const tenant = tenantById.get(tenantId)
|
||||
if (!tenantId || !key || !tenant) continue
|
||||
|
||||
const alias = tenantRoomAlias(tenant, key)
|
||||
const result = await client.query(
|
||||
`
|
||||
update communication_rooms
|
||||
set matrix_room_id = null,
|
||||
parent_space_room_id = null,
|
||||
matrix_alias = $3,
|
||||
updated_at = now()
|
||||
where tenant_id = $1 and key = $2
|
||||
`,
|
||||
[tenantId, key, alias]
|
||||
)
|
||||
|
||||
cleaned += result.rowCount || 0
|
||||
}
|
||||
|
||||
return cleaned
|
||||
}
|
||||
|
||||
const prepareColumnValue = (value: any, isJsonColumn: boolean) => {
|
||||
if (!isJsonColumn || value === null || typeof value === "undefined") return value
|
||||
if (typeof value === "string") return value
|
||||
@@ -465,6 +572,7 @@ export const importTenantFullExport = async (
|
||||
|
||||
const exportData = remapTenantScopedExport(rawExportData, options.targetTenantId)
|
||||
encryptEntityBankAccountRowsForImport(exportData)
|
||||
prepareCommunicationRoomsForImport(exportData)
|
||||
const client = await pool.connect()
|
||||
const importOrder = [
|
||||
"tenants",
|
||||
@@ -516,6 +624,11 @@ export const importTenantFullExport = async (
|
||||
importedTables.push({ table, rows: count })
|
||||
}
|
||||
|
||||
const cleanedCommunicationRooms = await cleanupImportedCommunicationRooms(client, exportData)
|
||||
if (cleanedCommunicationRooms) {
|
||||
importedTables.push({ table: "communication_rooms_matrix_reset", rows: cleanedCommunicationRooms })
|
||||
}
|
||||
|
||||
await refreshSequences(client, columnsByTable)
|
||||
await client.query("commit")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user