syncova-policies/app/api/privacy-policies/[id]/route.ts
Weapie 265ca8ac38
All checks were successful
Container-Image bauen und veröffentlichen / build-and-push (push) Successful in 5m15s
Improve accessibility and access controls; verify production and database recovery
2026-09-14 11:44:41 +02:00

223 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { NextRequest, NextResponse } from 'next/server'
import { parsePolicyInput, type PolicyInput } from '@/lib/policy-input'
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
import { prisma } from "@/lib/prisma"
/**
* 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: PolicyInput, 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: PolicyInput) {
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 = parsePolicyInput(await request.json().catch(() => null))
if (!data) return NextResponse.json({ error: 'Ungültige Feldtypen oder zu lange Texte.' }, { status: 400 })
// 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 }
)
}
}