Compare commits
2 Commits
cc3c405473
...
c7ba7a9cc5
| Author | SHA1 | Date | |
|---|---|---|---|
| c7ba7a9cc5 | |||
| 1c68e6b724 |
@@ -246,6 +246,7 @@ export default async function adminRoutes(server: FastifyInstance) {
|
|||||||
const [currentUser] = await server.db
|
const [currentUser] = await server.db
|
||||||
.select({
|
.select({
|
||||||
id: authUsers.id,
|
id: authUsers.id,
|
||||||
|
email: authUsers.email,
|
||||||
is_admin: authUsers.is_admin,
|
is_admin: authUsers.is_admin,
|
||||||
})
|
})
|
||||||
.from(authUsers)
|
.from(authUsers)
|
||||||
@@ -969,8 +970,47 @@ export default async function adminRoutes(server: FastifyInstance) {
|
|||||||
const currentUser = await requireAdmin(req, reply);
|
const currentUser = await requireAdmin(req, reply);
|
||||||
if (!currentUser) return;
|
if (!currentUser) return;
|
||||||
|
|
||||||
const exportData = req.body as TenantFullExport;
|
const body = req.body as TenantFullExport | { exportData?: TenantFullExport; targetTenantId?: number };
|
||||||
const result = await importTenantFullExport(server, exportData);
|
const exportData = "format" in body ? body : body.exportData;
|
||||||
|
const targetTenantId = "format" in body ? null : Number(body.targetTenantId || 0) || null;
|
||||||
|
|
||||||
|
if (!exportData) {
|
||||||
|
return reply.code(400).send({ error: "exportData required" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await importTenantFullExport(server, exportData, { targetTenantId });
|
||||||
|
const fallbackName = deriveNameFromEmail(currentUser.email);
|
||||||
|
|
||||||
|
await server.db
|
||||||
|
.insert(authTenantUsers)
|
||||||
|
.values({
|
||||||
|
tenant_id: result.tenantId,
|
||||||
|
user_id: currentUser.id,
|
||||||
|
created_by: currentUser.id,
|
||||||
|
})
|
||||||
|
.onConflictDoNothing();
|
||||||
|
|
||||||
|
const [existingAdminProfile] = await server.db
|
||||||
|
.select({ id: authProfiles.id })
|
||||||
|
.from(authProfiles)
|
||||||
|
.where(and(
|
||||||
|
eq(authProfiles.tenant_id, result.tenantId),
|
||||||
|
eq(authProfiles.user_id, currentUser.id)
|
||||||
|
))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!existingAdminProfile) {
|
||||||
|
await server.db
|
||||||
|
.insert(authProfiles)
|
||||||
|
.values({
|
||||||
|
tenant_id: result.tenantId,
|
||||||
|
user_id: currentUser.id,
|
||||||
|
first_name: fallbackName.first_name,
|
||||||
|
last_name: fallbackName.last_name,
|
||||||
|
email: currentUser.email,
|
||||||
|
active: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ type ImportResult = {
|
|||||||
files: { restored: number; skipped: number }
|
files: { restored: number; skipped: number }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ImportOptions = {
|
||||||
|
targetTenantId?: number | null
|
||||||
|
}
|
||||||
|
|
||||||
const quoteIdent = (value: string) => `"${value.replace(/"/g, '""')}"`
|
const quoteIdent = (value: string) => `"${value.replace(/"/g, '""')}"`
|
||||||
|
|
||||||
const tableColumns = async (client: any) => {
|
const tableColumns = async (client: any) => {
|
||||||
@@ -214,6 +218,54 @@ const restoreFiles = async (exportData: TenantFullExport) => {
|
|||||||
return { restored, skipped }
|
return { restored, skipped }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const remapTenantScopedExport = (
|
||||||
|
exportData: TenantFullExport,
|
||||||
|
targetTenantId?: number | null
|
||||||
|
): TenantFullExport => {
|
||||||
|
if (!targetTenantId || targetTenantId === exportData.tenantId) return exportData
|
||||||
|
|
||||||
|
const sourceTenantId = exportData.tenantId
|
||||||
|
const sourcePathPrefix = `${sourceTenantId}/`
|
||||||
|
const targetPathPrefix = `${targetTenantId}/`
|
||||||
|
const tables: TableRows = {}
|
||||||
|
|
||||||
|
for (const [table, rows] of Object.entries(exportData.tables || {})) {
|
||||||
|
tables[table] = rows.map((row) => {
|
||||||
|
const nextRow = { ...row }
|
||||||
|
|
||||||
|
if (table === "tenants" && nextRow.id === sourceTenantId) {
|
||||||
|
nextRow.id = targetTenantId
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextRow.tenant === sourceTenantId) {
|
||||||
|
nextRow.tenant = targetTenantId
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextRow.tenant_id === sourceTenantId) {
|
||||||
|
nextRow.tenant_id = targetTenantId
|
||||||
|
}
|
||||||
|
|
||||||
|
if (table === "files" && typeof nextRow.path === "string" && nextRow.path.startsWith(sourcePathPrefix)) {
|
||||||
|
nextRow.path = `${targetPathPrefix}${nextRow.path.slice(sourcePathPrefix.length)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextRow
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...exportData,
|
||||||
|
tenantId: targetTenantId,
|
||||||
|
tables,
|
||||||
|
files: (exportData.files || []).map((file) => ({
|
||||||
|
...file,
|
||||||
|
path: file.path?.startsWith(sourcePathPrefix)
|
||||||
|
? `${targetPathPrefix}${file.path.slice(sourcePathPrefix.length)}`
|
||||||
|
: file.path,
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const prepareColumnValue = (value: any, isJsonColumn: boolean) => {
|
const prepareColumnValue = (value: any, isJsonColumn: boolean) => {
|
||||||
if (!isJsonColumn || value === null || typeof value === "undefined") return value
|
if (!isJsonColumn || value === null || typeof value === "undefined") return value
|
||||||
if (typeof value === "string") return value
|
if (typeof value === "string") return value
|
||||||
@@ -263,11 +315,16 @@ const refreshSequences = async (client: any, columnsByTable: Map<string, TableMe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const importTenantFullExport = async (server: FastifyInstance, exportData: TenantFullExport): Promise<ImportResult> => {
|
export const importTenantFullExport = async (
|
||||||
if (exportData?.format !== "fedeo.tenant-full-export" || exportData.version !== 1) {
|
server: FastifyInstance,
|
||||||
|
rawExportData: TenantFullExport,
|
||||||
|
options: ImportOptions = {}
|
||||||
|
): Promise<ImportResult> => {
|
||||||
|
if (rawExportData?.format !== "fedeo.tenant-full-export" || rawExportData.version !== 1) {
|
||||||
throw new Error("Ungültiges FEDEO Mandantenexport-Format")
|
throw new Error("Ungültiges FEDEO Mandantenexport-Format")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const exportData = remapTenantScopedExport(rawExportData, options.targetTenantId)
|
||||||
const client = await pool.connect()
|
const client = await pool.connect()
|
||||||
const importOrder = [
|
const importOrder = [
|
||||||
"tenants",
|
"tenants",
|
||||||
|
|||||||
@@ -140,7 +140,10 @@ const importTenantExport = async (event: Event) => {
|
|||||||
try {
|
try {
|
||||||
const rawContent = await file.text()
|
const rawContent = await file.text()
|
||||||
const exportData = JSON.parse(rawContent)
|
const exportData = JSON.parse(rawContent)
|
||||||
const result = await admin.importTenant(exportData)
|
const result = await admin.importTenant({
|
||||||
|
exportData,
|
||||||
|
targetTenantId: tenantForm.value?.id || tenantId,
|
||||||
|
})
|
||||||
const rowCount = (result.tables || []).reduce((sum, table) => sum + table.rows, 0)
|
const rowCount = (result.tables || []).reduce((sum, table) => sum + table.rows, 0)
|
||||||
|
|
||||||
lastImportResult.value = {
|
lastImportResult.value = {
|
||||||
@@ -152,6 +155,8 @@ const importTenantExport = async (event: Event) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await fetchTenant()
|
await fetchTenant()
|
||||||
|
await auth.fetchMe()
|
||||||
|
await auth.switchTenant(String(result.tenantId))
|
||||||
|
|
||||||
toast.add({
|
toast.add({
|
||||||
title: "Mandantenimport abgeschlossen",
|
title: "Mandantenimport abgeschlossen",
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ const importTenantExport = async (event: Event) => {
|
|||||||
const rowCount = (result.tables || []).reduce((sum, table) => sum + table.rows, 0)
|
const rowCount = (result.tables || []).reduce((sum, table) => sum + table.rows, 0)
|
||||||
|
|
||||||
await fetchTenants()
|
await fetchTenants()
|
||||||
|
await auth.fetchMe()
|
||||||
|
|
||||||
toast.add({
|
toast.add({
|
||||||
title: "Mandantenimport abgeschlossen",
|
title: "Mandantenimport abgeschlossen",
|
||||||
@@ -116,6 +117,7 @@ const importTenantExport = async (event: Event) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (result.tenantId) {
|
if (result.tenantId) {
|
||||||
|
await auth.switchTenant(String(result.tenantId))
|
||||||
await router.push(`/administration/tenants/${result.tenantId}`)
|
await router.push(`/administration/tenants/${result.tenantId}`)
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
|||||||
Reference in New Issue
Block a user