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
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die README; an keiner Stelle Bedeutung, nur Zeichensetzung.
120 lines
3.3 KiB
TypeScript
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),
|
|
)
|
|
}
|