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 - returns privacy policies based on admin parameter export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url) // `?admin=true` schließt die nicht veröffentlichten Erklärungen ein und // setzt deshalb eine angemeldete Admin-Sitzung voraus. Ohne sie wird ganz // normal die öffentliche Liste ausgeliefert, statt einen Fehler zu werfen. const isAdmin = searchParams.get('admin') === 'true' && (await isAdminSession()) // Get policies based on admin flag const [policies, defaults] = await Promise.all([ prisma.privacyPolicy.findMany({ where: isAdmin ? {} : { isPublic: true }, orderBy: { createdAt: 'desc' } }), readPolicyDefaults(prisma), ]) // Standardtexte einsetzen, damit Konsumenten immer den geltenden Text sehen return NextResponse.json( policies.map((policy) => toApiPolicy(policy, defaults)) ) } catch (error) { console.error('Error fetching privacy policies:', error) return NextResponse.json( { error: 'Fehler beim Abrufen der Datenschutzerklärungen' }, { status: 500 } ) } } // POST /api/privacy-policies - creates a new privacy policy (Admin) export async function POST(request: NextRequest) { try { const denied = await requireAdmin() if (denied) return denied const data = await request.json() 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 } ) } // Create new privacy policy const newPolicy = await prisma.privacyPolicy.create({ data: { title: data.title.trim(), slug: await resolveSlug(prisma, data.title), ...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 : true } }) return NextResponse.json(toApiPolicy(newPolicy, defaults), { status: 201 }) } catch (error) { console.error('Error creating privacy policy:', error) return NextResponse.json( { error: 'Fehler beim Erstellen der Datenschutzerklärung' }, { status: 500 } ) } }