Next.js 15 App Router mit öffentlicher Übersicht und geschütztem Admin-Bereich zur Pflege von Datenschutzerklärungen nach Art. 12ff. DSGVO. - Oberfläche auf Basis von shadcn/ui, Inter lokal eingebunden - Prisma/SQLite, Auth.js mit Credentials-Provider und Rollen - Massenimport der Merkblätter des Amtes Leezen aus PDF - Docker-Image (standalone) und Gitea-Workflow zur Veröffentlichung Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
191 lines
5.7 KiB
TypeScript
191 lines
5.7 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
|
import { PrismaClient } from '@prisma/client'
|
|
import { requireAdmin } from '@/lib/require-admin'
|
|
|
|
// Prisma client instance
|
|
const prisma = new PrismaClient()
|
|
|
|
// Validation function for privacy policy data
|
|
function validatePrivacyPolicyData(data: any) {
|
|
const errors: string[] = []
|
|
|
|
// Required fields validation
|
|
if (!data.title?.trim()) errors.push('Title ist erforderlich')
|
|
if (!data.responsible?.trim()) errors.push('Verantwortlicher ist erforderlich')
|
|
if (!data.contactDPO?.trim()) errors.push('Kontaktdaten des Datenschutzbeauftragten sind erforderlich')
|
|
if (!data.dataSubjectsRights?.trim()) errors.push('Betroffenen-Rechte sind erforderlich')
|
|
if (!data.complaintRight?.trim()) errors.push('Beschwerderecht ist erforderlich')
|
|
if (!data.withdrawalRight?.trim()) errors.push('Widerrufsrecht 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')
|
|
}
|
|
|
|
return errors
|
|
}
|
|
|
|
// 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 = await prisma.privacyPolicy.findUnique({
|
|
where: { id }
|
|
})
|
|
|
|
if (!policy) {
|
|
return NextResponse.json(
|
|
{ error: 'Datenschutzerklärung nicht gefunden' },
|
|
{ status: 404 }
|
|
)
|
|
}
|
|
|
|
return NextResponse.json(policy)
|
|
} 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 }
|
|
)
|
|
}
|
|
|
|
// Validate required fields
|
|
const validationErrors = validatePrivacyPolicyData(data)
|
|
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(),
|
|
responsible: data.responsible.trim(),
|
|
contactDPO: data.contactDPO.trim(),
|
|
dataSubjectsRights: data.dataSubjectsRights.trim(),
|
|
complaintRight: data.complaintRight.trim(),
|
|
withdrawalRight: data.withdrawalRight.trim(),
|
|
processingDescription: data.processingDescription.trim(),
|
|
processingPurposes: data.processingPurposes?.trim() || null,
|
|
legalBasis: data.legalBasis?.trim() || null,
|
|
recipients: data.recipients.trim(),
|
|
storageDuration: data.storageDuration?.trim() || null,
|
|
dataSource: data.dataSource?.trim() || null,
|
|
provisionObligation: data.provisionObligation?.trim() || null,
|
|
provisionConsequences: data.provisionConsequences?.trim() || null,
|
|
isPublic: data.isPublic !== undefined ? data.isPublic : existingPolicy.isPublic
|
|
}
|
|
})
|
|
|
|
return NextResponse.json(updatedPolicy)
|
|
} 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 }
|
|
)
|
|
}
|
|
} |