All checks were successful
Container-Image bauen und veröffentlichen / build-and-push (push) Successful in 5m15s
223 lines
7.1 KiB
TypeScript
223 lines
7.1 KiB
TypeScript
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 }
|
||
)
|
||
}
|
||
}
|