feat(legal): Consent-Datenmodell, Migration & Registrierungs-Zustimmung (v1.7.1)

Teil 2 des Compliance-Updates — revisionssichere Zustimmungsprotokollierung:

- Prisma: Modelle LegalDocument (versioniert, contentHash, isActive, unique[type,version])
  und LegalAcceptance (userId, organizationId?, documentType/-Version, contentHash, context,
  unique gegen Duplikate, User-Relation)
- migrate.js: idempotente Tabellen + Indizes + Seeding/Aktivierung der 5 Rechtsdokumente
  (eine aktive Version je Typ; contentHash aus bewusster Versionsangabe)
- src/lib/legal.ts: getActiveDocuments, getPendingAcceptances, recordAcceptances (skipDuplicates,
  nie überschreiben), Pflichtsets (alle: TERMS+PRIVACY; Org-Admin zusätzlich ORGANIZATION_DECLARATION)
- API: GET /api/legal/status (offene Zustimmungen), POST /api/legal/accept (erfassen, CSRF-Check)
- Registrierung: zwei getrennte, nicht vorausgewählte Zustimmungen (Nutzungsbedingungen akzeptieren
  + Datenschutz-Kenntnisnahme), serverseitig via zod erzwungen, Version protokolliert

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Pepe Ziberi
2026-07-23 12:02:20 +02:00
parent 872b8927cc
commit 8cf1aa4dbe
8 changed files with 343 additions and 11 deletions

View File

@@ -0,0 +1,29 @@
import { NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { getActiveDocuments, getPendingAcceptances } from '@/lib/legal'
// GET: aktive Rechtsdokumente + offene Pflicht-Zustimmungen des aktuellen Benutzers.
// Grundlage für den blockierenden Re-Consent-Dialog.
export async function GET() {
try {
const user = await getSession()
if (!user) return NextResponse.json({ error: 'Nicht autorisiert' }, { status: 401 })
const isOrgAdmin = user.role === 'TENANT_ADMIN'
const [active, pending] = await Promise.all([
getActiveDocuments(),
getPendingAcceptances(user.id, isOrgAdmin),
])
return NextResponse.json({
isOrgAdmin,
organizationId: user.tenantId ?? null,
documents: active,
pending, // leer = alles akzeptiert
needsAcceptance: pending.length > 0,
})
} catch (error) {
console.error('Error fetching legal status:', error)
return NextResponse.json({ error: 'Serverfehler' }, { status: 500 })
}
}