Files
Harlekin e0b03ea336
Prüfung / Lint, Typen, Tests (push) Canceled after 0s
Prüfung / Images bauen (pdf) (push) Canceled after 0s
Prüfung / Images bauen (web) (push) Canceled after 0s
Prüfung / Images bauen (worker) (push) Canceled after 0s
Images / bauen (pdf, docker/pdf.Dockerfile) (push) Canceled after 0s
Images / bauen (web, docker/web.Dockerfile) (push) Canceled after 0s
Images / bauen (worker, docker/worker.Dockerfile) (push) Canceled after 0s
Gedankenstriche durch Bindestriche ersetzt
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die
README; an keiner Stelle Bedeutung, nur Zeichensetzung.
2026-09-04 08:54:52 +02:00

120 lines
3.3 KiB
TypeScript

import { db } from '@/lib/db'
import { writeAudit } from '@/lib/audit/write'
import { AccessError } from '@/lib/errors'
import type { EffectivePermissions } from '@/lib/auth/permissions'
/**
* Vollständiger JSON-Abzug als Umzugs- und Notfallformat.
*
* **Ohne Stufenfilterung**, nur mit Recht `admin`, immer protokolliert - so
* steht es in docs/08-suche-und-export.md. Das ist die eine Stelle im System,
* an der die Klassifizierung nicht greift, und sie ist deshalb absichtlich
* unbequem: kein Knopf an einer Akte, sondern eine eigene Seite im
* Adminbereich.
*
* Ausgelassen wird, was Zugang verschafft statt ihn zu dokumentieren:
* Sitzungen. Sie gehören niemandem ausser dem Browser, aus dem sie stammen.
*/
export async function exportDatabase(p: EffectivePermissions): Promise<Uint8Array> {
if (!p.isAdmin) throw new AccessError('no-permission')
const [
users,
roleMappings,
clearanceLevels,
foundationClearances,
statusTags,
templates,
cases,
caseSections,
caseSectionVersions,
caseEntries,
caseEntryVersions,
participants,
exclusions,
links,
personnel,
assignments,
documents,
documentSections,
documentSectionVersions,
attachments,
messages,
reactions,
auditLog,
] = await Promise.all([
db.user.findMany(),
db.roleMapping.findMany(),
db.clearanceLevelConfig.findMany(),
db.foundationClearance.findMany(),
db.personnelStatusTag.findMany(),
db.documentTemplate.findMany(),
db.case.findMany(),
db.caseSection.findMany(),
db.caseSectionVersion.findMany(),
db.caseEntry.findMany(),
db.caseEntryVersion.findMany(),
db.caseParticipant.findMany(),
db.caseExclusion.findMany(),
db.caseLink.findMany(),
db.personnelFile.findMany(),
db.personnelAssignment.findMany(),
db.document.findMany(),
db.documentSection.findMany(),
db.documentSectionVersion.findMany(),
db.attachment.findMany(),
db.chatMessage.findMany(),
db.chatReaction.findMany(),
// Das Protokoll gehört dazu: ein Abzug ohne die Zugriffsspur wäre als
// Notfallformat wertlos.
db.auditLog.findMany({ orderBy: { id: 'desc' }, take: 50_000 }),
])
const abzug = {
exportedAt: new Date().toISOString(),
exportedBy: p.userId,
note: 'Vollstaendiger Abzug ohne Stufenfilterung. Vertraulich behandeln.',
users,
roleMappings,
clearanceLevels,
foundationClearances,
statusTags,
templates,
cases,
caseSections,
caseSectionVersions,
caseEntries,
caseEntryVersions,
participants,
exclusions,
links,
personnel,
assignments,
documents,
documentSections,
documentSectionVersions,
attachments,
messages,
reactions,
auditLog,
}
await db.$transaction(async (tx) => {
await writeAudit(tx, {
actorId: p.userId,
action: 'export.database',
targetType: 'Database',
targetId: 'all',
clearanceAtTime: p.clearanceLevel,
meta: { cases: cases.length, entries: caseEntries.length, users: users.length },
})
})
// `BigInt` kommt in AuditLog.id vor und ist in JSON nicht vorgesehen.
return new TextEncoder().encode(
JSON.stringify(abzug, (_key, wert: unknown) =>
typeof wert === 'bigint' ? wert.toString() : wert,
2),
)
}