import { NextRequest, NextResponse } from 'next/server' import { PrismaClient } from '@prisma/client' import { isAdminSession, requireAdmin } from '@/lib/require-admin' import { DEFAULTED_FIELDS, } from '@/lib/policy-defaults' import { readPolicyDefaults, toApiPolicy } from '@/lib/policy-defaults.server' import { describeMissing, missingRequiredFields } from '@/lib/policy-completeness' import { resolveSlug } from '@/lib/policy-slug.server' import { PolicyDefaults } from '@/types/privacy-policy' // Prisma client instance const prisma = new PrismaClient() /** * Prüft die Angaben einer Erklärung. * * Zwei Stufen: Ohne Titel, Verarbeitungstätigkeit und Empfänger lässt sich * nichts speichern. Die übrigen Pflichtangaben aus Art. 13/14 DSGVO werden * erst beim Veröffentlichen verlangt – ein Entwurf darf unvollständig sein, * eine öffentlich sichtbare Erklärung nicht. */ function validatePrivacyPolicyData(data: any, defaults: PolicyDefaults) { const errors: string[] = [] // Ohne diese Angaben ist der Datensatz nicht einmal als Entwurf brauchbar if (!data.title?.trim()) errors.push('Titel ist erforderlich') if (!data.processingDescription?.trim()) errors.push('Verarbeitungstätigkeit ist erforderlich') if (!data.recipients?.trim()) errors.push('Empfänger sind erforderlich') // Optional boolean validation if (data.isPublic !== undefined && typeof data.isPublic !== 'boolean') { errors.push('isPublic muss ein Boolean-Wert sein') } // Vollständigkeit nur verlangen, wenn die Erklärung öffentlich sein soll if (data.isPublic !== false && errors.length === 0) { for (const entry of missingRequiredFields(data, defaults)) { errors.push(`${describeMissing(entry)} – zum Veröffentlichen erforderlich`) } } return errors } /** * Übernimmt die Baustein-Felder aus der Anfrage: leer wird zu `null` und * bedeutet damit „Standardtext verwenden“. */ function defaultedFieldsFrom(data: any) { return Object.fromEntries( DEFAULTED_FIELDS.map((field) => [field, data[field]?.trim() || null]) ) as Record<(typeof DEFAULTED_FIELDS)[number], string | null> } // GET /api/privacy-policies/[id] - returns a single privacy policy export async function GET( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { try { const { id: rawId } = await params const id = parseInt(rawId) // Validate ID is a number if (isNaN(id)) { return NextResponse.json( { error: 'Ungültige ID' }, { status: 400 } ) } // Find privacy policy by ID const [policy, defaults] = await Promise.all([ prisma.privacyPolicy.findUnique({ where: { id } }), readPolicyDefaults(prisma), ]) // Nicht veröffentlichte Erklärungen sind Entwürfe: Ohne Admin-Sitzung // verhalten sie sich, als gäbe es sie nicht – sonst ließe sich über die // fortlaufende ID ermitteln, welche Datensätze existieren. if (!policy || (!policy.isPublic && !(await isAdminSession()))) { return NextResponse.json( { error: 'Datenschutzerklärung nicht gefunden' }, { status: 404 } ) } // Standardtexte einsetzen, damit Konsumenten den geltenden Text sehen return NextResponse.json(toApiPolicy(policy, defaults)) } catch (error) { console.error('Error fetching privacy policy:', error) return NextResponse.json( { error: 'Fehler beim Abrufen der Datenschutzerklärung' }, { status: 500 } ) } } // PUT /api/privacy-policies/[id] - updates a privacy policy (Admin) export async function PUT( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { try { const denied = await requireAdmin() if (denied) return denied const { id: rawId } = await params const id = parseInt(rawId) const data = await request.json() // Validate ID is a number if (isNaN(id)) { return NextResponse.json( { error: 'Ungültige ID' }, { status: 400 } ) } const defaults = await readPolicyDefaults(prisma) // Validate required fields const validationErrors = validatePrivacyPolicyData(data, defaults) if (validationErrors.length > 0) { return NextResponse.json( { error: 'Validierungsfehler', details: validationErrors }, { status: 400 } ) } // Check if policy exists const existingPolicy = await prisma.privacyPolicy.findUnique({ where: { id } }) if (!existingPolicy) { return NextResponse.json( { error: 'Datenschutzerklärung nicht gefunden' }, { status: 404 } ) } // Update privacy policy const updatedPolicy = await prisma.privacyPolicy.update({ where: { id }, data: { title: data.title.trim(), slug: await resolveSlug(prisma, data.title, existingPolicy), ...defaultedFieldsFrom(data), processingDescription: data.processingDescription.trim(), processingPurposes: data.processingPurposes?.trim() || null, legalBasis: data.legalBasis?.trim() || null, legitimateInterests: data.legitimateInterests?.trim() || null, recipients: data.recipients.trim(), storageDuration: data.storageDuration?.trim() || null, dataSource: data.dataSource?.trim() || null, dataCategories: data.dataCategories?.trim() || null, isPublic: data.isPublic !== undefined ? data.isPublic : existingPolicy.isPublic } }) return NextResponse.json(toApiPolicy(updatedPolicy, defaults)) } catch (error) { console.error('Error updating privacy policy:', error) return NextResponse.json( { error: 'Fehler beim Aktualisieren der Datenschutzerklärung' }, { status: 500 } ) } } // DELETE /api/privacy-policies/[id] - deletes a privacy policy (Admin) export async function DELETE( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { try { const denied = await requireAdmin() if (denied) return denied const { id: rawId } = await params const id = parseInt(rawId) // Validate ID is a number if (isNaN(id)) { return NextResponse.json( { error: 'Ungültige ID' }, { status: 400 } ) } // Check if policy exists const existingPolicy = await prisma.privacyPolicy.findUnique({ where: { id } }) if (!existingPolicy) { return NextResponse.json( { error: 'Datenschutzerklärung nicht gefunden' }, { status: 404 } ) } // Delete privacy policy await prisma.privacyPolicy.delete({ where: { id } }) return NextResponse.json( { message: 'Datenschutzerklärung erfolgreich gelöscht' }, { status: 200 } ) } catch (error) { console.error('Error deleting privacy policy:', error) return NextResponse.json( { error: 'Fehler beim Löschen der Datenschutzerklärung' }, { status: 500 } ) } }