feat(legal): Organisationsbestätigung, Re-Consent-Gate & Admin-Ansicht (v1.7.2)
Teil 3 des Compliance-Updates — Zustimmungs-UI und Nachweis-Verwaltung: - LegalConsentGate (blockierender Dialog): erkennt offene Pflicht-Zustimmungen nach Login, zeigt Organisationsbestätigung mit freundlicher Erklärung + 7 einzeln anzukreuzenden Pflichtbestätigungen (keine Vorauswahl) für Org-Admins, sonst Re-Consent für aktualisierte Nutzungsbedingungen/Datenschutz. Abmelden/Dokumente-Lesen bleiben möglich (Allowlist); keine stillschweigende Zustimmung. Tastatur-/Screenreader-tauglich (role=dialog) - /konto/datenschutz: eigene Zustimmungen einsehen, Datenexport/Kontolöschung anfordern, Datenschutzkontakt (Route via Middleware geschützt) - /admin/legal (nur SERVER_ADMIN): aktive Dokumentversionen, Zustimmungszahlen, fehlende Zustimmungen, Suche nach Benutzer, CSV-Export (minimale Nachweisdaten) - APIs: /api/legal/my-acceptances, /api/admin/legal, /api/admin/legal/acceptances (+CSV) - Gate ins Root-Layout eingehängt Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
65
src/app/api/admin/legal/acceptances/route.ts
Normal file
65
src/app/api/admin/legal/acceptances/route.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { prisma } from '@/lib/db'
|
||||
import { getSession } from '@/lib/auth'
|
||||
|
||||
// GET: Zustimmungshistorie suchen (nach Benutzer-E-Mail/Name oder Organisation).
|
||||
// Query: ?q=<suchbegriff>&format=json|csv (nur SERVER_ADMIN)
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const user = await getSession()
|
||||
if (!user || user.role !== 'SERVER_ADMIN') {
|
||||
return NextResponse.json({ error: 'Keine Berechtigung' }, { status: 403 })
|
||||
}
|
||||
|
||||
const q = (req.nextUrl.searchParams.get('q') || '').trim()
|
||||
const format = req.nextUrl.searchParams.get('format') || 'json'
|
||||
|
||||
// Passende Benutzer ermitteln (falls Suchbegriff gesetzt)
|
||||
let userFilter: any = {}
|
||||
if (q) {
|
||||
const users = await (prisma as any).user.findMany({
|
||||
where: { OR: [{ email: { contains: q, mode: 'insensitive' } }, { name: { contains: q, mode: 'insensitive' } }] },
|
||||
select: { id: true },
|
||||
take: 500,
|
||||
})
|
||||
userFilter = { userId: { in: users.map((u: any) => u.id) } }
|
||||
}
|
||||
|
||||
const acceptances = await (prisma as any).legalAcceptance.findMany({
|
||||
where: userFilter,
|
||||
orderBy: { acceptedAt: 'desc' },
|
||||
take: 2000,
|
||||
select: { id: true, userId: true, organizationId: true, documentType: true, documentVersion: true, context: true, acceptedAt: true },
|
||||
})
|
||||
|
||||
// Nur die notwendigen Nachweisdaten anreichern (E-Mail/Name) — keine überflüssigen Personendaten.
|
||||
const userIds = Array.from(new Set(acceptances.map((a: any) => a.userId)))
|
||||
const users = await (prisma as any).user.findMany({ where: { id: { in: userIds } }, select: { id: true, email: true, name: true } })
|
||||
const uMap = new Map<string, any>(users.map((u: any) => [u.id, u]))
|
||||
const rows = acceptances.map((a: any) => ({
|
||||
...a,
|
||||
email: uMap.get(a.userId)?.email || '',
|
||||
name: uMap.get(a.userId)?.name || '',
|
||||
}))
|
||||
|
||||
if (format === 'csv') {
|
||||
const header = ['acceptedAt', 'email', 'name', 'documentType', 'documentVersion', 'context', 'organizationId']
|
||||
const esc = (v: any) => `"${String(v ?? '').replace(/"/g, '""')}"`
|
||||
const lines = [header.join(',')]
|
||||
for (const r of rows) {
|
||||
lines.push([new Date(r.acceptedAt).toISOString(), r.email, r.name, r.documentType, r.documentVersion, r.context, r.organizationId || ''].map(esc).join(','))
|
||||
}
|
||||
return new NextResponse(lines.join('\r\n'), {
|
||||
headers: {
|
||||
'Content-Type': 'text/csv; charset=utf-8',
|
||||
'Content-Disposition': `attachment; filename="zustimmungen-${new Date().toISOString().slice(0, 10)}.csv"`,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return NextResponse.json({ acceptances: rows })
|
||||
} catch (error) {
|
||||
console.error('Error searching acceptances:', error)
|
||||
return NextResponse.json({ error: 'Serverfehler' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user