feat: Package 4 Phase 3 — Finale Fixes + Dokumentation (MkDocs, SDK Flow, StepHeader)
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 48s
CI / test-python-backend-compliance (push) Successful in 40s
CI / test-python-document-crawler (push) Successful in 23s
CI / test-python-dsms-gateway (push) Successful in 18s
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 48s
CI / test-python-backend-compliance (push) Successful in 40s
CI / test-python-document-crawler (push) Successful in 23s
CI / test-python-dsms-gateway (push) Successful in 18s
Finale Fixes (5 Bugs): - workflow/page.tsx: Array-Format-Fix fuer loadVersions — Array.isArray statt data.versions - einwilligungen_routes.py: ip_address + user_agent in GET /consents Response ergaenzt - consent/page.tsx: Bearbeiten/Vorschau/Veroeffentlichen + Quick Actions verdrahtet (useRouter + Preview Modal) - cookie-banner/page.tsx: BannerTexts State + Controlled Inputs + DB-Persistenz (banner_texts) - embed-code/route.ts: In-Memory configStorage → DB-fetch aus Backend, embed_code Key korrigiert Dokumentation: - docs-src/services/sdk-modules/rechtliche-texte.md: Neue MkDocs-Seite fuer Paket 4 (Einwilligungen, Rechtliche Vorlagen, Cookie Banner, Document Workflow) - mkdocs.yml: Nav-Eintrag 'Rechtliche Texte (Paket 4)' ergaenzt - dokumentations-module.md: Datenfluss-Diagramm um Paket-4-Module erweitert - flow-data.ts: Paket-4-Steps mit korrekten dbTables/dbMode und aktualisierten Beschreibungen - StepHeader.tsx: cookie-banner + workflow STEP_EXPLANATIONS auf Persistenz und Funktionsumfang aktualisiert Tests: 24/24 bestanden (test_einwilligungen_routes.py) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -506,14 +506,14 @@ export const SDK_FLOW_STEPS: SDKFlowStep[] = [
|
||||
checkpointId: 'CP-CONS',
|
||||
checkpointType: 'REQUIRED',
|
||||
checkpointReviewer: 'NONE',
|
||||
description: 'Definition aller erforderlichen Einwilligungserklaerungen fuer Datenverarbeitungen.',
|
||||
descriptionLong: 'Basierend auf dem VVT und den aktivierten Modulen werden alle Verarbeitungen identifiziert, die eine Einwilligung erfordern (Art. 6 Abs. 1a DSGVO). Fuer jede Einwilligung wird ein rechtskonformer Text generiert, der: den Zweck klar benennt, freiwillig erteilt werden kann, informiert und spezifisch ist. Einwilligungen werden mit Widerrufsmechanismus, Versionierung und Nachweispflicht versehen.',
|
||||
description: 'Definition aller Einwilligungserklaerungen — vollstaendig backend-persistent mit Nachweis-Tracking.',
|
||||
descriptionLong: 'Basierend auf dem VVT und den aktivierten Modulen werden alle Verarbeitungen identifiziert, die eine Einwilligung erfordern (Art. 6 Abs. 1a DSGVO). Der Datenkatalog definiert einwilligungspflichtige Datenpunkte. Erteilte und widerrufene Einwilligungen werden mit vollstaendigem Audit-Trail gespeichert: Zeitpunkt, Version, IP-Adresse, User-Agent, Quelle. Widerruf setzt `revoked_at` ohne den urspruenglichen Eintrag zu loeschen (Nachweispflicht). Cookie-Banner-Konfiguration ist ebenfalls hier zentral verwaltet.',
|
||||
legalBasis: 'Art. 6 Abs. 1a, Art. 7 DSGVO (Einwilligung)',
|
||||
inputs: ['vvt', 'modules'],
|
||||
outputs: ['consents'],
|
||||
prerequisiteSteps: ['vvt'],
|
||||
dbTables: [],
|
||||
dbMode: 'none',
|
||||
dbTables: ['compliance_einwilligungen_catalog', 'compliance_einwilligungen_company', 'compliance_einwilligungen_consents', 'compliance_einwilligungen_cookies'],
|
||||
dbMode: 'read/write',
|
||||
ragCollections: ['bp_compliance_datenschutz'],
|
||||
ragPurpose: 'Einwilligungsvorlagen DSGVO',
|
||||
isOptional: false,
|
||||
@@ -528,14 +528,14 @@ export const SDK_FLOW_STEPS: SDKFlowStep[] = [
|
||||
checkpointId: 'CP-DOC',
|
||||
checkpointType: 'REQUIRED',
|
||||
checkpointReviewer: 'NONE',
|
||||
description: 'Generierung von Datenschutzerklaerung, AGB und Nutzungsbedingungen.',
|
||||
descriptionLong: 'In diesem Schritt werden die zentralen rechtlichen Dokumente generiert: Datenschutzerklaerung (nach Art. 13/14 DSGVO), AGB, Nutzungsbedingungen und Informationspflichten. Die Dokumente werden aus dem Unternehmensprofil, VVT und den Einwilligungen automatisch zusammengestellt. Die RAG-Collection bp_legal_templates liefert branchenspezifische Vorlagen, die an die spezifischen Verarbeitungen des Unternehmens angepasst werden.',
|
||||
description: 'Verwaltung von Datenschutzerklaerung, AGB und Nutzungsbedingungen — vollstaendig backend-persistent.',
|
||||
descriptionLong: 'In diesem Schritt werden die zentralen rechtlichen Dokumente verwaltet: Datenschutzerklaerung (Art. 13/14 DSGVO), AGB, Cookie-Richtlinie, Impressum und AVV. Jedes Dokument wird in `compliance_legal_documents` gespeichert. Ueber den Vorschau-Button kann die aktuell veroffentlichte HTML-Version im Browser angezeigt werden. Per Bearbeiten-Button gelangt man direkt in den Document Workflow. Schnellaktionen navigieren zum Dokumentengenerator fuer KI-gestuetzte Template-Generierung.',
|
||||
legalBasis: 'Art. 13, 14 DSGVO (Informationspflichten)',
|
||||
inputs: ['companyProfile', 'vvt', 'consents'],
|
||||
outputs: ['documents'],
|
||||
prerequisiteSteps: ['einwilligungen'],
|
||||
dbTables: [],
|
||||
dbMode: 'none',
|
||||
dbTables: ['compliance_legal_documents', 'compliance_legal_document_versions'],
|
||||
dbMode: 'read/write',
|
||||
ragCollections: ['bp_legal_templates'],
|
||||
ragPurpose: 'AGB, DSE, Nutzungsbedingungen Templates',
|
||||
generates: ['Datenschutzerklaerung', 'AGB', 'Nutzungsbedingungen'],
|
||||
@@ -551,14 +551,14 @@ export const SDK_FLOW_STEPS: SDKFlowStep[] = [
|
||||
checkpointId: 'CP-COOK',
|
||||
checkpointType: 'REQUIRED',
|
||||
checkpointReviewer: 'NONE',
|
||||
description: 'Konfiguration eines rechtskonformen Cookie-Banners mit Consent-Management.',
|
||||
descriptionLong: 'Der Cookie-Banner wird basierend auf den definierten Einwilligungen und dem Unternehmensprofil konfiguriert. Er unterscheidet zwischen technisch notwendigen Cookies (kein Consent noetig), funktionalen Cookies, Analyse-Cookies und Marketing-Cookies. Der Banner implementiert "Privacy by Default" — nur notwendige Cookies sind vorausgewaehlt. Die Konfiguration umfasst: Kategorien, Zweckbeschreibungen, Anbieter, Laufzeiten und Opt-in/Opt-out-Mechanismen.',
|
||||
description: 'Konfiguration eines rechtskonformen Cookie-Banners — Texte und Kategorien persistent in DB.',
|
||||
descriptionLong: 'Der Cookie-Banner wird basierend auf den definierten Einwilligungen konfiguriert. Alle Einstellungen (Position, Stil, Farbe, Texte) und Kategorien werden in `compliance_einwilligungen_cookies` gespeichert. Banner-Texte (Ueberschrift, Beschreibung, Datenschutz-Link) sind Controlled Inputs und werden beim Speichern persistiert. Die Live-Vorschau aktualisiert sich in Echtzeit. Der Embed-Code wird beim Export aus der DB-Konfiguration generiert — kein In-Memory-Speicher, kein Datenverlust bei Neustart. Implementiert "Privacy by Default": nur notwendige Cookies sind vorausgewaehlt.',
|
||||
legalBasis: 'Art. 5 Abs. 3 ePrivacy-RL, TTDSG § 25',
|
||||
inputs: ['consents', 'companyProfile'],
|
||||
outputs: ['cookieBanner'],
|
||||
prerequisiteSteps: ['consent'],
|
||||
dbTables: [],
|
||||
dbMode: 'none',
|
||||
dbTables: ['compliance_einwilligungen_cookies'],
|
||||
dbMode: 'read/write',
|
||||
ragCollections: ['bp_compliance_datenschutz'],
|
||||
ragPurpose: 'Cookie-Consent Richtlinien',
|
||||
isOptional: false,
|
||||
@@ -596,13 +596,13 @@ export const SDK_FLOW_STEPS: SDKFlowStep[] = [
|
||||
checkpointId: 'CP-WRKF',
|
||||
checkpointType: 'REQUIRED',
|
||||
checkpointReviewer: 'NONE',
|
||||
description: 'Freigabe-Workflow fuer alle generierten rechtlichen Dokumente.',
|
||||
descriptionLong: 'Der Document Workflow steuert den Freigabeprozess fuer alle generierten Dokumente. Jedes Dokument durchlaeuft definierte Phasen: Entwurf, Review, Freigabe, Veroeffentlichung. Je nach Dokumenttyp werden unterschiedliche Reviewer zugewiesen (DSB, Rechtsabteilung, Geschaeftsfuehrung). Der Workflow protokolliert alle Aenderungen, Kommentare und Freigaben fuer die Audit-Spur.',
|
||||
description: 'Freigabe-Workflow fuer alle rechtlichen Dokumente — vollstaendig backend-persistent mit Versionierung.',
|
||||
descriptionLong: 'Der Document Workflow steuert den Freigabeprozess fuer alle rechtlichen Dokumente. Split-View-Editor: linkes Panel zeigt die veroffentlichte Version, rechtes Panel den Entwurf. Status-Workflow: draft → review → approved → published (oder rejected). Alle Versionen werden in `compliance_legal_document_versions` gespeichert. Der Versions-Endpoint gibt ein direktes JSON-Array zurueck. DOCX-Import via multipart/form-data moeglich. Freigabe-Historie wird in `compliance_legal_document_approvals` protokolliert (Zeitstempel, Benutzer, Kommentar). Veroeffentlichte Versionen sind unveraenderlich — Aenderungen erzeugen stets eine neue Version.',
|
||||
inputs: ['documents', 'generatedDocuments'],
|
||||
outputs: ['approvedDocuments'],
|
||||
prerequisiteSteps: ['cookie-banner'],
|
||||
dbTables: [],
|
||||
dbMode: 'none',
|
||||
dbTables: ['compliance_legal_documents', 'compliance_legal_document_versions', 'compliance_legal_document_approvals'],
|
||||
dbMode: 'read/write',
|
||||
ragCollections: [],
|
||||
isOptional: false,
|
||||
url: '/sdk/workflow',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useSDK } from '@/lib/sdk'
|
||||
import { StepHeader, STEP_EXPLANATIONS } from '@/components/sdk/StepHeader'
|
||||
|
||||
@@ -67,7 +68,17 @@ function transformApiDocument(doc: ApiDocument): LegalDocument {
|
||||
// COMPONENTS
|
||||
// =============================================================================
|
||||
|
||||
function DocumentCard({ document, onDelete }: { document: LegalDocument; onDelete: (id: string) => void }) {
|
||||
function DocumentCard({
|
||||
document,
|
||||
onDelete,
|
||||
onEdit,
|
||||
onPreview,
|
||||
}: {
|
||||
document: LegalDocument
|
||||
onDelete: (id: string) => void
|
||||
onEdit: (id: string) => void
|
||||
onPreview: (doc: LegalDocument) => void
|
||||
}) {
|
||||
const typeColors = {
|
||||
'privacy-policy': 'bg-blue-100 text-blue-700',
|
||||
terms: 'bg-green-100 text-green-700',
|
||||
@@ -138,14 +149,23 @@ function DocumentCard({ document, onDelete }: { document: LegalDocument; onDelet
|
||||
<span>Aktualisiert: {document.lastUpdated.toLocaleDateString('de-DE')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button className="px-3 py-1 text-purple-600 hover:bg-purple-50 rounded-lg transition-colors">
|
||||
<button
|
||||
onClick={() => onEdit(document.id)}
|
||||
className="px-3 py-1 text-purple-600 hover:bg-purple-50 rounded-lg transition-colors"
|
||||
>
|
||||
Bearbeiten
|
||||
</button>
|
||||
<button className="px-3 py-1 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors">
|
||||
<button
|
||||
onClick={() => onPreview(document)}
|
||||
className="px-3 py-1 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
>
|
||||
Vorschau
|
||||
</button>
|
||||
{document.status === 'draft' && (
|
||||
<button className="px-3 py-1 bg-green-50 text-green-700 hover:bg-green-100 rounded-lg transition-colors">
|
||||
<button
|
||||
onClick={() => onEdit(document.id)}
|
||||
className="px-3 py-1 bg-green-50 text-green-700 hover:bg-green-100 rounded-lg transition-colors"
|
||||
>
|
||||
Veroeffentlichen
|
||||
</button>
|
||||
)}
|
||||
@@ -167,6 +187,7 @@ function DocumentCard({ document, onDelete }: { document: LegalDocument; onDelet
|
||||
|
||||
export default function ConsentPage() {
|
||||
const { state } = useSDK()
|
||||
const router = useRouter()
|
||||
const [documents, setDocuments] = useState<LegalDocument[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
@@ -174,6 +195,7 @@ export default function ConsentPage() {
|
||||
const [showCreateModal, setShowCreateModal] = useState(false)
|
||||
const [newDocForm, setNewDocForm] = useState({ type: 'privacy_policy', name: '', description: '' })
|
||||
const [creating, setCreating] = useState(false)
|
||||
const [previewDoc, setPreviewDoc] = useState<{ name: string; content: string } | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
loadDocuments()
|
||||
@@ -246,6 +268,30 @@ export default function ConsentPage() {
|
||||
}
|
||||
}
|
||||
|
||||
function handleEdit(id: string) {
|
||||
router.push('/sdk/workflow')
|
||||
}
|
||||
|
||||
async function handlePreview(doc: LegalDocument) {
|
||||
try {
|
||||
const token = localStorage.getItem('bp_admin_token')
|
||||
const res = await fetch(`/api/admin/consent/documents/${doc.id}/versions`, {
|
||||
headers: token ? { 'Authorization': `Bearer ${token}` } : {}
|
||||
})
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
const versions = Array.isArray(data) ? data : (data.versions || [])
|
||||
const published = versions.find((v: { status: string; content?: string }) => v.status === 'published')
|
||||
const latest = published || versions[0]
|
||||
setPreviewDoc({ name: doc.name, content: latest?.content || '<p>Kein Inhalt verfügbar.</p>' })
|
||||
} else {
|
||||
setPreviewDoc({ name: doc.name, content: '<p>Vorschau nicht verfügbar.</p>' })
|
||||
}
|
||||
} catch {
|
||||
setPreviewDoc({ name: doc.name, content: '<p>Fehler beim Laden der Vorschau.</p>' })
|
||||
}
|
||||
}
|
||||
|
||||
const filteredDocuments = filter === 'all'
|
||||
? documents
|
||||
: documents.filter(d => d.type === filter || d.status === filter)
|
||||
@@ -312,25 +358,37 @@ export default function ConsentPage() {
|
||||
<div className="bg-gradient-to-r from-purple-50 to-blue-50 rounded-xl border border-purple-200 p-6">
|
||||
<h3 className="font-semibold text-gray-900 mb-4">Schnellaktionen</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<button className="p-4 bg-white rounded-lg border border-gray-200 hover:border-purple-300 hover:shadow transition-all text-center">
|
||||
<button
|
||||
onClick={() => router.push('/sdk/document-generator')}
|
||||
className="p-4 bg-white rounded-lg border border-gray-200 hover:border-purple-300 hover:shadow transition-all text-center"
|
||||
>
|
||||
<svg className="w-8 h-8 mx-auto text-blue-600 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
<span className="text-sm font-medium">Datenschutz generieren</span>
|
||||
</button>
|
||||
<button className="p-4 bg-white rounded-lg border border-gray-200 hover:border-purple-300 hover:shadow transition-all text-center">
|
||||
<button
|
||||
onClick={() => router.push('/sdk/document-generator')}
|
||||
className="p-4 bg-white rounded-lg border border-gray-200 hover:border-purple-300 hover:shadow transition-all text-center"
|
||||
>
|
||||
<svg className="w-8 h-8 mx-auto text-green-600 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
|
||||
</svg>
|
||||
<span className="text-sm font-medium">AGB generieren</span>
|
||||
</button>
|
||||
<button className="p-4 bg-white rounded-lg border border-gray-200 hover:border-purple-300 hover:shadow transition-all text-center">
|
||||
<button
|
||||
onClick={() => router.push('/sdk/document-generator')}
|
||||
className="p-4 bg-white rounded-lg border border-gray-200 hover:border-purple-300 hover:shadow transition-all text-center"
|
||||
>
|
||||
<svg className="w-8 h-8 mx-auto text-yellow-600 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" />
|
||||
</svg>
|
||||
<span className="text-sm font-medium">Cookie-Richtlinie</span>
|
||||
</button>
|
||||
<button className="p-4 bg-white rounded-lg border border-gray-200 hover:border-purple-300 hover:shadow transition-all text-center">
|
||||
<button
|
||||
onClick={() => router.push('/sdk/document-generator')}
|
||||
className="p-4 bg-white rounded-lg border border-gray-200 hover:border-purple-300 hover:shadow transition-all text-center"
|
||||
>
|
||||
<svg className="w-8 h-8 mx-auto text-purple-600 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
|
||||
</svg>
|
||||
@@ -365,7 +423,13 @@ export default function ConsentPage() {
|
||||
{/* Documents List */}
|
||||
<div className="space-y-4">
|
||||
{filteredDocuments.map(document => (
|
||||
<DocumentCard key={document.id} document={document} onDelete={handleDeleteDocument} />
|
||||
<DocumentCard
|
||||
key={document.id}
|
||||
document={document}
|
||||
onDelete={handleDeleteDocument}
|
||||
onEdit={handleEdit}
|
||||
onPreview={handlePreview}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -381,6 +445,29 @@ export default function ConsentPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Preview Modal */}
|
||||
{previewDoc && (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-3xl max-h-[80vh] flex flex-col">
|
||||
<div className="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
|
||||
<h2 className="text-lg font-bold text-gray-900">{previewDoc.name}</h2>
|
||||
<button
|
||||
onClick={() => setPreviewDoc(null)}
|
||||
className="text-gray-400 hover:text-gray-600"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="p-6 overflow-y-auto prose max-w-none"
|
||||
dangerouslySetInnerHTML={{ __html: previewDoc.content }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Create Document Modal */}
|
||||
{showCreateModal && (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||
|
||||
@@ -98,7 +98,13 @@ const defaultConfig: BannerConfig = {
|
||||
// COMPONENTS
|
||||
// =============================================================================
|
||||
|
||||
function BannerPreview({ config, categories }: { config: BannerConfig; categories: CookieCategory[] }) {
|
||||
interface BannerTexts {
|
||||
title: string
|
||||
description: string
|
||||
privacyLink: string
|
||||
}
|
||||
|
||||
function BannerPreview({ config, categories, bannerTexts }: { config: BannerConfig; categories: CookieCategory[]; bannerTexts: BannerTexts }) {
|
||||
return (
|
||||
<div className="relative bg-gray-100 rounded-xl p-8 min-h-64 flex items-end justify-center">
|
||||
<div className="absolute inset-0 flex items-center justify-center text-gray-400 text-sm">
|
||||
@@ -110,9 +116,9 @@ function BannerPreview({ config, categories }: { config: BannerConfig; categorie
|
||||
}`}
|
||||
style={{ borderColor: config.primaryColor }}
|
||||
>
|
||||
<h4 className="font-semibold text-gray-900">Wir verwenden Cookies</h4>
|
||||
<h4 className="font-semibold text-gray-900">{bannerTexts.title}</h4>
|
||||
<p className="text-sm text-gray-600 mt-2">
|
||||
Wir nutzen Cookies, um Ihnen die bestmoegliche Nutzung unserer Website zu ermoeglichen.
|
||||
{bannerTexts.description}
|
||||
</p>
|
||||
<div className="mt-4 flex items-center gap-3">
|
||||
<button
|
||||
@@ -217,10 +223,17 @@ function CategoryCard({
|
||||
// MAIN PAGE
|
||||
// =============================================================================
|
||||
|
||||
const defaultBannerTexts: BannerTexts = {
|
||||
title: 'Wir verwenden Cookies',
|
||||
description: 'Wir nutzen Cookies, um Ihnen die bestmoegliche Nutzung unserer Website zu ermoeglichen.',
|
||||
privacyLink: '/datenschutz',
|
||||
}
|
||||
|
||||
export default function CookieBannerPage() {
|
||||
const { state } = useSDK()
|
||||
const [categories, setCategories] = useState<CookieCategory[]>([])
|
||||
const [config, setConfig] = useState<BannerConfig>(defaultConfig)
|
||||
const [bannerTexts, setBannerTexts] = useState<BannerTexts>(defaultBannerTexts)
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
const [exportToast, setExportToast] = useState<string | null>(null)
|
||||
|
||||
@@ -238,6 +251,10 @@ export default function CookieBannerPage() {
|
||||
}
|
||||
if (data.config && Object.keys(data.config).length > 0) {
|
||||
setConfig(prev => ({ ...prev, ...data.config }))
|
||||
const savedTexts = data.config.banner_texts || data.config.texts
|
||||
if (savedTexts) {
|
||||
setBannerTexts(prev => ({ ...prev, ...savedTexts }))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setCategories(mockCategories)
|
||||
@@ -289,7 +306,7 @@ export default function CookieBannerPage() {
|
||||
await fetch('/api/sdk/v1/einwilligungen/cookie-banner/config', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ categories, config }),
|
||||
body: JSON.stringify({ categories, config: { ...config, banner_texts: bannerTexts } }),
|
||||
})
|
||||
} catch {
|
||||
// Silently ignore
|
||||
@@ -365,7 +382,7 @@ export default function CookieBannerPage() {
|
||||
{/* Preview */}
|
||||
<div className="bg-white rounded-xl border border-gray-200 p-6">
|
||||
<h3 className="font-semibold text-gray-900 mb-4">Banner-Vorschau</h3>
|
||||
<BannerPreview config={config} categories={categories} />
|
||||
<BannerPreview config={config} categories={categories} bannerTexts={bannerTexts} />
|
||||
</div>
|
||||
|
||||
{/* Configuration */}
|
||||
@@ -445,7 +462,8 @@ export default function CookieBannerPage() {
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Ueberschrift</label>
|
||||
<input
|
||||
type="text"
|
||||
defaultValue="Wir verwenden Cookies"
|
||||
value={bannerTexts.title}
|
||||
onChange={(e) => setBannerTexts({ ...bannerTexts, title: e.target.value })}
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500"
|
||||
/>
|
||||
</div>
|
||||
@@ -453,7 +471,8 @@ export default function CookieBannerPage() {
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Beschreibung</label>
|
||||
<textarea
|
||||
rows={3}
|
||||
defaultValue="Wir nutzen Cookies, um Ihnen die bestmoegliche Nutzung unserer Website zu ermoeglichen."
|
||||
value={bannerTexts.description}
|
||||
onChange={(e) => setBannerTexts({ ...bannerTexts, description: e.target.value })}
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500"
|
||||
/>
|
||||
</div>
|
||||
@@ -461,7 +480,8 @@ export default function CookieBannerPage() {
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Link zur Datenschutzerklaerung</label>
|
||||
<input
|
||||
type="text"
|
||||
defaultValue="/datenschutz"
|
||||
value={bannerTexts.privacyLink}
|
||||
onChange={(e) => setBannerTexts({ ...bannerTexts, privacyLink: e.target.value })}
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -173,7 +173,7 @@ export default function WorkflowPage() {
|
||||
const res = await fetch(`/api/admin/consent/documents/${docId}/versions`)
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
const versionList = data.versions || []
|
||||
const versionList = Array.isArray(data) ? data : (data.versions || [])
|
||||
setVersions(versionList)
|
||||
|
||||
const published = versionList.find((v: Version) => v.status === 'published')
|
||||
|
||||
@@ -5,20 +5,20 @@
|
||||
*/
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { CookieBannerConfig, CookieBannerEmbedCode } from '@/lib/sdk/einwilligungen/types'
|
||||
import { CookieBannerConfig } from '@/lib/sdk/einwilligungen/types'
|
||||
import {
|
||||
generateCookieBannerConfig,
|
||||
generateEmbedCode,
|
||||
} from '@/lib/sdk/einwilligungen/generator/cookie-banner'
|
||||
import { PREDEFINED_DATA_POINTS } from '@/lib/sdk/einwilligungen/catalog/loader'
|
||||
|
||||
// In-Memory Storage (in Produktion mit configStorage aus config/route.ts teilen)
|
||||
const configStorage = new Map<string, CookieBannerConfig>()
|
||||
const BACKEND_URL = process.env.BACKEND_URL || 'http://backend-compliance:8002'
|
||||
const DEFAULT_TENANT_ID = process.env.DEFAULT_TENANT_ID || '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e'
|
||||
|
||||
/**
|
||||
* GET /api/sdk/v1/einwilligungen/cookie-banner/embed-code
|
||||
*
|
||||
* Generiert den Embed-Code fuer den Cookie Banner
|
||||
* Generiert den Embed-Code fuer den Cookie Banner (DB-backed)
|
||||
*
|
||||
* Query Parameters:
|
||||
* - privacyPolicyUrl: string - URL zur Datenschutzerklaerung (default: /datenschutz)
|
||||
@@ -26,25 +26,37 @@ const configStorage = new Map<string, CookieBannerConfig>()
|
||||
*/
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const tenantId = request.headers.get('X-Tenant-ID')
|
||||
|
||||
if (!tenantId) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Tenant ID required' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
||||
const clientTenantId = request.headers.get('X-Tenant-ID') || request.headers.get('x-tenant-id')
|
||||
const tenantId = (clientTenantId && uuidRegex.test(clientTenantId)) ? clientTenantId : DEFAULT_TENANT_ID
|
||||
|
||||
const { searchParams } = new URL(request.url)
|
||||
const privacyPolicyUrl = searchParams.get('privacyPolicyUrl') || '/datenschutz'
|
||||
const format = searchParams.get('format') || 'combined'
|
||||
|
||||
// Hole oder erstelle Konfiguration
|
||||
let config = configStorage.get(tenantId)
|
||||
|
||||
if (!config) {
|
||||
// Lade Konfiguration aus DB (Backend), fallback auf generierte Standardkonfiguration
|
||||
let config: CookieBannerConfig
|
||||
try {
|
||||
const configRes = await fetch(
|
||||
`${BACKEND_URL}/api/compliance/einwilligungen/cookies?tenant_id=${tenantId}`,
|
||||
{ signal: AbortSignal.timeout(5000) }
|
||||
)
|
||||
if (configRes.ok) {
|
||||
const configData = await configRes.json()
|
||||
config = generateCookieBannerConfig(tenantId, PREDEFINED_DATA_POINTS)
|
||||
if (configData.config && Object.keys(configData.config).length > 0) {
|
||||
const saved = configData.config
|
||||
config = {
|
||||
...config,
|
||||
styling: { ...config.styling, ...(saved.styling || {}) },
|
||||
texts: { ...config.texts, ...(saved.banner_texts || saved.texts || {}) },
|
||||
}
|
||||
}
|
||||
} else {
|
||||
config = generateCookieBannerConfig(tenantId, PREDEFINED_DATA_POINTS)
|
||||
}
|
||||
} catch {
|
||||
config = generateCookieBannerConfig(tenantId, PREDEFINED_DATA_POINTS)
|
||||
configStorage.set(tenantId, config)
|
||||
}
|
||||
|
||||
// Generiere Embed-Code
|
||||
@@ -110,6 +122,7 @@ ${embedCode.js}
|
||||
`.trim()
|
||||
|
||||
return NextResponse.json({
|
||||
embed_code: combinedCode,
|
||||
embedCode: combinedCode,
|
||||
scriptTag: embedCode.scriptTag,
|
||||
config: {
|
||||
@@ -143,14 +156,9 @@ ${embedCode.js}
|
||||
*/
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const tenantId = request.headers.get('X-Tenant-ID')
|
||||
|
||||
if (!tenantId) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Tenant ID required' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
||||
const clientTenantId = request.headers.get('X-Tenant-ID') || request.headers.get('x-tenant-id')
|
||||
const tenantId = (clientTenantId && uuidRegex.test(clientTenantId)) ? clientTenantId : DEFAULT_TENANT_ID
|
||||
|
||||
const body = await request.json()
|
||||
const {
|
||||
@@ -160,25 +168,13 @@ export async function POST(request: NextRequest) {
|
||||
language = 'de',
|
||||
} = body
|
||||
|
||||
// Hole oder erstelle Konfiguration
|
||||
let config = configStorage.get(tenantId)
|
||||
|
||||
if (!config) {
|
||||
config = generateCookieBannerConfig(tenantId, PREDEFINED_DATA_POINTS, texts, styling)
|
||||
} else {
|
||||
// Wende temporaere Anpassungen an
|
||||
if (styling) {
|
||||
config = {
|
||||
...config,
|
||||
styling: { ...config.styling, ...styling },
|
||||
}
|
||||
}
|
||||
if (texts) {
|
||||
config = {
|
||||
...config,
|
||||
texts: { ...config.texts, ...texts },
|
||||
}
|
||||
}
|
||||
// Erstelle Konfiguration mit optionalen Overrides
|
||||
let config: CookieBannerConfig = generateCookieBannerConfig(tenantId, PREDEFINED_DATA_POINTS, texts, styling)
|
||||
if (styling) {
|
||||
config = { ...config, styling: { ...config.styling, ...styling } }
|
||||
}
|
||||
if (texts) {
|
||||
config = { ...config, texts: { ...config.texts, ...texts } }
|
||||
}
|
||||
|
||||
const embedCode = generateEmbedCode(config, privacyPolicyUrl)
|
||||
|
||||
@@ -582,8 +582,8 @@ export const STEP_EXPLANATIONS = {
|
||||
},
|
||||
'cookie-banner': {
|
||||
title: 'Cookie Banner',
|
||||
description: 'Generieren Sie einen DSGVO-konformen Cookie Banner',
|
||||
explanation: 'Der Cookie Banner Generator erstellt einen rechtssicheren Banner mit Opt-In fuer nicht-essentielle Cookies. Der generierte Code kann direkt eingebunden werden.',
|
||||
description: 'Konfigurieren Sie einen DSGVO-konformen Cookie Banner mit persistenter DB-Speicherung',
|
||||
explanation: 'Der Cookie Banner Generator erstellt einen rechtssicheren Banner mit Opt-In fuer nicht-essentielle Cookies. Alle Einstellungen — einschliesslich Ueberschrift, Beschreibung und Datenschutz-Link — werden in der Datenbank gespeichert und bleiben auch nach einem Neustart erhalten. Der generierte Embed-Code wird direkt aus der gespeicherten Konfiguration erzeugt.',
|
||||
tips: [
|
||||
{
|
||||
icon: 'warning' as const,
|
||||
@@ -592,8 +592,13 @@ export const STEP_EXPLANATIONS = {
|
||||
},
|
||||
{
|
||||
icon: 'lightbulb' as const,
|
||||
title: 'Design',
|
||||
description: 'Der Banner kann an Ihr Corporate Design angepasst werden.',
|
||||
title: 'Design und Texte',
|
||||
description: 'Passen Sie Ueberschrift, Beschreibung und Farben an Ihr Corporate Design an. Aenderungen werden in der Vorschau sofort sichtbar.',
|
||||
},
|
||||
{
|
||||
icon: 'info' as const,
|
||||
title: 'Embed-Code',
|
||||
description: 'Der Code exportiert einen vollstaendigen HTML+CSS+JS-Block aus Ihrer gespeicherten Konfiguration — einfach vor dem schliessenden </body>-Tag einbinden.',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -767,8 +772,8 @@ export const STEP_EXPLANATIONS = {
|
||||
},
|
||||
'workflow': {
|
||||
title: 'Document Workflow',
|
||||
description: 'Verwalten Sie den Freigabe-Prozess Ihrer rechtlichen Dokumente',
|
||||
explanation: 'Der Document Workflow bietet einen Split-View-Editor mit synchronisiertem Scrollen. Dokumente durchlaufen den Status Draft → Review → Approved → Published. Aenderungen werden versioniert und der Freigabeprozess wird protokolliert.',
|
||||
description: 'Freigabe-Workflow mit Split-View-Editor und DB-persistenter Versionierung',
|
||||
explanation: 'Der Document Workflow bietet einen Split-View-Editor: links die veroffentlichte Version, rechts der aktuelle Entwurf. Dokumente durchlaufen den Status Draft → Review → Approved → Published. Alle Versionen werden in der Datenbank gespeichert. Word-Dokumente koennen direkt als neue Version importiert werden.',
|
||||
tips: [
|
||||
{
|
||||
icon: 'warning' as const,
|
||||
@@ -778,7 +783,12 @@ export const STEP_EXPLANATIONS = {
|
||||
{
|
||||
icon: 'info' as const,
|
||||
title: 'Versionierung',
|
||||
description: 'Jede Aenderung wird als neue Version gespeichert. So koennen Sie jederzeit den Stand eines Dokuments nachvollziehen.',
|
||||
description: 'Jede Aenderung wird als neue Version gespeichert. Veroeffentlichte Versionen sind unveraenderlich — Aenderungen erzeugen stets eine neue Version.',
|
||||
},
|
||||
{
|
||||
icon: 'lightbulb' as const,
|
||||
title: 'DOCX-Import',
|
||||
description: 'Bestehende Word-Dokumente koennen direkt hochgeladen und als Basis fuer neue Versionen verwendet werden.',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -323,6 +323,8 @@ async def list_consents(
|
||||
"revoked_at": c.revoked_at,
|
||||
"consent_version": c.consent_version,
|
||||
"source": c.source,
|
||||
"ip_address": c.ip_address,
|
||||
"user_agent": c.user_agent,
|
||||
"created_at": c.created_at,
|
||||
}
|
||||
for c in consents
|
||||
|
||||
@@ -309,10 +309,16 @@ graph LR
|
||||
F --> G[Löschfristen]
|
||||
G --> H[VVT]
|
||||
|
||||
I[Document Generator] --> J[Workflow]
|
||||
K[Academy] --> L[Training Engine]
|
||||
H --> I[Einwilligungen]
|
||||
I --> J[Rechtliche Vorlagen]
|
||||
J --> K[Cookie Banner]
|
||||
K --> L[Document Workflow]
|
||||
|
||||
M[Document Generator] --> L
|
||||
N[Academy] --> O[Training Engine]
|
||||
```
|
||||
|
||||
Die **Source Policy** bildet die Grundlage für alle nachfolgenden Analyse-Schritte.
|
||||
Das **VVT** ist der abschließende Schritt der Dokumentationsphase und baut auf TOMs und Löschfristen auf.
|
||||
Das **VVT** ist der abschließende Schritt der Dokumentationsphase und Ausgangspunkt von Paket 4.
|
||||
Die **Training Engine** operiert parallel im Betrieb-Paket und liefert Evidence für Audits.
|
||||
Paket 4 (Rechtliche Texte) ist vollständig dokumentiert unter [Rechtliche Texte (Paket 4)](rechtliche-texte.md).
|
||||
|
||||
281
docs-src/services/sdk-modules/rechtliche-texte.md
Normal file
281
docs-src/services/sdk-modules/rechtliche-texte.md
Normal file
@@ -0,0 +1,281 @@
|
||||
# Rechtliche Texte (Paket 4)
|
||||
|
||||
Paket 4 deckt alle Module ab, die rechtlich verbindliche Dokumente und Einwilligungen erzeugen,
|
||||
verwalten und veroffentlichen: **Einwilligungen**, **Rechtliche Vorlagen (Consent)**, **Cookie Banner**
|
||||
und den **Document Workflow**.
|
||||
Alle vier Module sind vollstaendig backend-persistent und bieten CRUD-Operationen ueber die REST-API.
|
||||
|
||||
---
|
||||
|
||||
## Uebersicht
|
||||
|
||||
| Modul | SDK-Route | Checkpoint | DB-Persistenz | Status |
|
||||
|-------|-----------|-----------|---------------|--------|
|
||||
| [Einwilligungen](#einwilligungen) | `/sdk/einwilligungen` | CP-CONS (REQUIRED) | ✅ Migration 008 | 100% |
|
||||
| [Rechtliche Vorlagen](#rechtliche-vorlagen-consent) | `/sdk/consent` | CP-DOC (REQUIRED) | ✅ Migration 007 | 100% |
|
||||
| [Cookie Banner](#cookie-banner) | `/sdk/cookie-banner` | CP-COOK (REQUIRED) | ✅ Migration 008 | 100% |
|
||||
| [Document Workflow](#document-workflow) | `/sdk/workflow` | CP-WRKF (REQUIRED) | ✅ Migration 007 | 100% |
|
||||
|
||||
---
|
||||
|
||||
## Einwilligungen
|
||||
|
||||
**Route:** `/sdk/einwilligungen` | **Backend:** `backend-compliance:8002` | **Rechtsgrundlage:** Art. 6 Abs. 1a, Art. 7 DSGVO
|
||||
|
||||
### Funktionen
|
||||
|
||||
- **Datenkatalog:** Definition aller einwilligungspflichtigen Datenpunkte mit Zweck, Kategorie und Pflicht-Flag
|
||||
- **Unternehmens-Consent-Konfiguration:** globale Einwilligungseinstellungen pro Tenant
|
||||
- **Einwilligungsnachweise:** Erfassung erteilter/widerrufener Einwilligungen mit vollstaendigem Audit-Trail
|
||||
- Gespeichert: `user_id`, `data_point_id`, `granted`, `granted_at`, `revoked_at`, `consent_version`, `source`, `ip_address`, `user_agent`
|
||||
- **Statistiken:** Uebersicht ueber aktive Einwilligungen nach Datenpunkt und Nutzer
|
||||
- **Widerruf:** Einwilligung per PUT widerrufen (setzt `revoked_at`, behalt urspruenglichen Eintrag)
|
||||
- **Cookie Banner Config:** Konfiguration und Kategorienverwaltung des Cookie Banners (s.u.)
|
||||
|
||||
### API-Endpoints
|
||||
|
||||
| Methode | Pfad | Beschreibung |
|
||||
|---------|------|--------------|
|
||||
| `GET` | `/api/compliance/einwilligungen/catalog` | Datenpunkt-Katalog laden |
|
||||
| `PUT` | `/api/compliance/einwilligungen/catalog` | Datenpunkt-Katalog speichern |
|
||||
| `GET` | `/api/compliance/einwilligungen/company` | Unternehmens-Consent-Config laden |
|
||||
| `PUT` | `/api/compliance/einwilligungen/company` | Unternehmens-Consent-Config speichern |
|
||||
| `GET` | `/api/compliance/einwilligungen/consents/stats` | Einwilligungsstatistiken |
|
||||
| `GET` | `/api/compliance/einwilligungen/consents` | Einwilligungen (Filter: user_id, data_point_id, granted) |
|
||||
| `POST` | `/api/compliance/einwilligungen/consents` | Neue Einwilligung erfassen |
|
||||
| `PUT` | `/api/compliance/einwilligungen/consents/{id}/revoke` | Einwilligung widerrufen |
|
||||
|
||||
**Frontend-Proxies:**
|
||||
|
||||
| Frontend-Route | Ziel |
|
||||
|----------------|------|
|
||||
| `GET/PUT /api/sdk/v1/einwilligungen/catalog` | `backend:8002/api/compliance/einwilligungen/catalog` |
|
||||
| `GET/POST /api/sdk/v1/einwilligungen/consent` | `backend:8002/api/compliance/einwilligungen/consents` |
|
||||
| `GET/POST/PUT /api/sdk/v1/einwilligungen/cookie-banner/config` | `backend:8002/api/compliance/einwilligungen/cookies` |
|
||||
| `GET /api/sdk/v1/einwilligungen/cookie-banner/embed-code` | generiert Code aus DB-Config (kein In-Memory) |
|
||||
|
||||
### DB-Tabellen (Migration 008)
|
||||
|
||||
| Tabelle | Modus | Beschreibung |
|
||||
|---------|-------|--------------|
|
||||
| `compliance_einwilligungen_catalog` | read/write | Datenpunkte und Einwilligungsdefinitionen |
|
||||
| `compliance_einwilligungen_company` | read/write | Unternehmens-Consent-Konfiguration |
|
||||
| `compliance_einwilligungen_cookies` | read/write | Cookie Banner Konfiguration (JSON) |
|
||||
| `compliance_einwilligungen_consents` | read/write | Erteilte und widerrufene Einwilligungen |
|
||||
|
||||
### Datenmodell (Einwilligung)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"tenant_id": "uuid",
|
||||
"user_id": "nutzer@beispiel.de",
|
||||
"data_point_id": "dp_analytics",
|
||||
"granted": true,
|
||||
"granted_at": "2024-01-15T10:30:00Z",
|
||||
"revoked_at": null,
|
||||
"consent_version": "v1.2",
|
||||
"source": "web_banner",
|
||||
"ip_address": "192.168.1.1",
|
||||
"user_agent": "Mozilla/5.0...",
|
||||
"created_at": "2024-01-15T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rechtliche Vorlagen (Consent)
|
||||
|
||||
**Route:** `/sdk/consent` | **Backend:** `backend-compliance:8002` | **Rechtsgrundlage:** Art. 13, 14 DSGVO
|
||||
|
||||
### Funktionen
|
||||
|
||||
- **Dokumentenverwaltung:** CRUD fuer rechtliche Dokumente (Datenschutzerklaerung, AGB, Cookie-Richtlinie, Impressum, AVV)
|
||||
- **Vorschau:** HTML-Vorschau der jeweils veroffentlichten Version direkt im Browser
|
||||
- **Bearbeiten:** Weiterleitung zum Document Workflow fuer die Bearbeitung
|
||||
- **Schnellaktionen:** Direktnavigation zum Dokumentengenerator fuer neue Dokumente
|
||||
- **Filter:** nach Dokumenttyp (`privacy-policy`, `terms`, `cookie-policy`, `imprint`, `dpa`) und Status
|
||||
- **Statistiken:** Gesamt, Aktiv, Entwuerfe, Sprachen
|
||||
|
||||
**Dokumenttypen:**
|
||||
|
||||
| Typ | Rechtsgrundlage |
|
||||
|-----|-----------------|
|
||||
| `privacy_policy` | Art. 13/14 DSGVO |
|
||||
| `terms` | BGB, Fernabsatzgesetz |
|
||||
| `cookie_policy` | TTDSG § 25, Art. 5 Abs. 3 ePrivacy-RL |
|
||||
| `imprint` | DDG § 5 |
|
||||
| `dpa` (AVV) | Art. 28 DSGVO |
|
||||
|
||||
### API-Endpoints
|
||||
|
||||
| Methode | Pfad | Beschreibung |
|
||||
|---------|------|--------------|
|
||||
| `GET` | `/api/compliance/legal-documents/documents` | Alle Dokumente (Filter: tenant_id) |
|
||||
| `POST` | `/api/compliance/legal-documents/documents` | Neues Dokument anlegen |
|
||||
| `GET` | `/api/compliance/legal-documents/documents/{id}` | Einzelnes Dokument |
|
||||
| `DELETE` | `/api/compliance/legal-documents/documents/{id}` | Dokument loeschen |
|
||||
| `GET` | `/api/compliance/legal-documents/documents/{id}/versions` | Versions-Liste (gibt **Array** zurueck, nicht `{versions:[...]}`) |
|
||||
|
||||
**Frontend-Proxy:** `/api/admin/consent/[[...path]]` → `backend:8002/api/compliance/legal-documents/*`
|
||||
|
||||
### DB-Tabellen (Migration 007)
|
||||
|
||||
| Tabelle | Modus | Beschreibung |
|
||||
|---------|-------|--------------|
|
||||
| `compliance_legal_documents` | read/write | Dokument-Stammdaten |
|
||||
| `compliance_legal_document_versions` | read/write | Versionierte Inhalte (HTML) |
|
||||
| `compliance_legal_document_approvals` | write | Freigabe-Verlauf |
|
||||
|
||||
---
|
||||
|
||||
## Cookie Banner
|
||||
|
||||
**Route:** `/sdk/cookie-banner` | **Backend:** `backend-compliance:8002` | **Rechtsgrundlage:** TTDSG § 25, Art. 5 Abs. 3 ePrivacy-RL
|
||||
|
||||
### Funktionen
|
||||
|
||||
- **Banner-Konfiguration:** Position (oben/unten/zentriert), Stil (Balken/Popup/Modal), Farbe, Optionen
|
||||
- **Banner-Texte:** Ueberschrift, Beschreibung und Datenschutz-Link — alle Felder sind **Controlled Inputs**
|
||||
und werden beim Speichern in der DB persistiert (`banner_texts`-Schluessel im `config`-JSON)
|
||||
- **Kategorieverwaltung:** Notwendig (immer aktiv), Analyse, Marketing, Praeferenzen
|
||||
- **Cookie-Details:** Anbieter, Zweck, Laufzeit, First-Party vs. Third-Party
|
||||
- **Live-Vorschau:** Banner-Preview aktualisiert sich beim Aendern von Texten und Einstellungen in Echtzeit
|
||||
- **Embed-Code Export:** Generiert HTML+CSS+JS-Block aus der gespeicherten DB-Konfiguration
|
||||
(kein In-Memory-Speicher — Neustart des Containers verliert keine Konfiguration)
|
||||
- **Privacy by Default:** Nur notwendige Cookies sind vorausgewaehlt
|
||||
|
||||
### API-Endpoints
|
||||
|
||||
| Methode | Pfad | Beschreibung |
|
||||
|---------|------|--------------|
|
||||
| `GET` | `/api/compliance/einwilligungen/cookies` | Cookie-Banner-Konfiguration laden |
|
||||
| `PUT` | `/api/compliance/einwilligungen/cookies` | Cookie-Banner-Konfiguration speichern |
|
||||
|
||||
**Frontend-Proxies:**
|
||||
|
||||
| Frontend-Route | Methoden | Beschreibung |
|
||||
|----------------|----------|--------------|
|
||||
| `/api/sdk/v1/einwilligungen/cookie-banner/config` | GET, POST, PUT | Config laden/speichern/Kategorie togglen |
|
||||
| `/api/sdk/v1/einwilligungen/cookie-banner/embed-code` | GET | Embed-Code aus DB-Config generieren |
|
||||
|
||||
### DB-Tabellen (Migration 008)
|
||||
|
||||
| Tabelle | Modus | Beschreibung |
|
||||
|---------|-------|--------------|
|
||||
| `compliance_einwilligungen_cookies` | read/write | Gesamte Banner-Konfiguration als JSON (inkl. `banner_texts`) |
|
||||
|
||||
### Konfigurationsformat
|
||||
|
||||
```json
|
||||
{
|
||||
"config": {
|
||||
"position": "bottom",
|
||||
"style": "bar",
|
||||
"primaryColor": "#6366f1",
|
||||
"showDeclineAll": true,
|
||||
"showSettings": true,
|
||||
"blockScripts": true,
|
||||
"banner_texts": {
|
||||
"title": "Wir verwenden Cookies",
|
||||
"description": "Wir nutzen Cookies...",
|
||||
"privacyLink": "/datenschutz"
|
||||
}
|
||||
},
|
||||
"categories": [
|
||||
{ "id": "necessary", "name": "Notwendig", "isRequired": true, "defaultEnabled": true }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Document Workflow
|
||||
|
||||
**Route:** `/sdk/workflow` | **Backend:** `backend-compliance:8002` | **Rechtsgrundlage:** Art. 5 Abs. 2 DSGVO (Rechenschaftspflicht)
|
||||
|
||||
### Funktionen
|
||||
|
||||
- **Split-View-Editor:** Linkes Panel zeigt die veroffentlichte Version, rechtes Panel zeigt den Entwurf
|
||||
- **Status-Workflow:** `draft` → `review` → `approved` → `published` (oder `rejected`)
|
||||
- **Versions-Loading:** Ladet alle Versionen eines Dokuments via
|
||||
`GET /documents/{id}/versions` — Endpunkt gibt ein **direktes JSON-Array** zurueck
|
||||
- **DOCX-Upload:** Word-Dokumente als Basis fuer neue Versionen importieren
|
||||
(multipart/form-data via `/versions/upload-word`)
|
||||
- **Freigabe-Aktionen:**
|
||||
- Entwurf einreichen → Review
|
||||
- Review freigeben → Approved
|
||||
- Freigabe veroeffentlichen → Published
|
||||
- Ablehnen (mit Begruendung) → Rejected → neuer Entwurf erforderlich
|
||||
- **Freigabe-Historie:** Vollstaendige Protokollierung aller Freigabe-Schritte mit Zeitstempel und Kommentar
|
||||
- **Versionierte Inhalte:** Jede Aenderung erzeugt eine neue Version; Published-Versionen sind unveraenderlich
|
||||
|
||||
### API-Endpoints
|
||||
|
||||
| Methode | Pfad | Beschreibung |
|
||||
|---------|------|--------------|
|
||||
| `GET` | `/api/compliance/legal-documents/documents/{id}/versions` | Alle Versionen (**Array-Response**) |
|
||||
| `POST` | `/api/compliance/legal-documents/versions` | Neue Version anlegen |
|
||||
| `PUT` | `/api/compliance/legal-documents/versions/{id}` | Version-Inhalt aktualisieren |
|
||||
| `GET` | `/api/compliance/legal-documents/versions/{id}` | Einzelne Version laden |
|
||||
| `POST` | `/api/compliance/legal-documents/versions/upload-word` | DOCX-Datei importieren |
|
||||
| `POST` | `/api/compliance/legal-documents/versions/{id}/submit-review` | Zur Pruefung einreichen |
|
||||
| `POST` | `/api/compliance/legal-documents/versions/{id}/approve` | Freigeben |
|
||||
| `POST` | `/api/compliance/legal-documents/versions/{id}/reject` | Ablehnen |
|
||||
| `POST` | `/api/compliance/legal-documents/versions/{id}/publish` | Veroeffentlichen |
|
||||
| `GET` | `/api/compliance/legal-documents/versions/{id}/approval-history` | Freigabe-Historie |
|
||||
|
||||
**Frontend-Proxy:** `/api/admin/consent/[[...path]]` → `backend:8002/api/compliance/legal-documents/*`
|
||||
|
||||
!!! warning "Array-Response bei Versions-Endpoint"
|
||||
`GET /documents/{id}/versions` gibt ein **direktes JSON-Array** `[{...}]` zurueck,
|
||||
**nicht** `{"versions": [...]}`. Frontend muss `Array.isArray(data) ? data : (data.versions || [])` pruefen.
|
||||
|
||||
### DB-Tabellen (Migration 007)
|
||||
|
||||
| Tabelle | Modus | Beschreibung |
|
||||
|---------|-------|--------------|
|
||||
| `compliance_legal_documents` | read | Dokument-Metadaten und aktueller Status |
|
||||
| `compliance_legal_document_versions` | read/write | Versionierte HTML-Inhalte |
|
||||
| `compliance_legal_document_approvals` | write | Freigabe-Verlauf mit Kommentaren |
|
||||
|
||||
### Datenmodell (Version)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"document_id": "uuid",
|
||||
"version_number": 3,
|
||||
"title": "Datenschutzerklärung v3",
|
||||
"summary": "DSGVO Art. 13 aktualisiert",
|
||||
"content": "<h1>Datenschutzerklärung</h1>...",
|
||||
"status": "published",
|
||||
"created_by": "admin",
|
||||
"created_at": "2024-01-20T14:00:00Z",
|
||||
"published_at": "2024-01-21T09:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Datenfluss Paket 4
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[VVT] --> B[Einwilligungen]
|
||||
B --> C[Rechtliche Vorlagen]
|
||||
C --> D[Cookie Banner]
|
||||
D --> E[Document Workflow]
|
||||
D --> F[Dokumentengenerator]
|
||||
E --> G[DSR Portal]
|
||||
|
||||
B -.->|Catalog + Consents| DB1[(compliance_einwilligungen_*)]
|
||||
C -.->|Documents| DB2[(compliance_legal_documents)]
|
||||
E -.->|Versions + Approvals| DB2
|
||||
D -.->|Cookie Config| DB1
|
||||
```
|
||||
|
||||
Die **Einwilligungen** legen fest, welche Datenpunkte einer Einwilligung beduerften.
|
||||
Die **Rechtlichen Vorlagen** erstellen die zugehoerigen Dokumente (DSE, AGB, etc.).
|
||||
Der **Cookie Banner** konfiguriert das Frontend-Consent-Widget.
|
||||
Der **Document Workflow** fuhrt alle Dokumente durch den Freigabeprozess vor der Veroeffentlichung.
|
||||
@@ -67,6 +67,7 @@ nav:
|
||||
- SDK Module:
|
||||
- Analyse-Module (Paket 2): services/sdk-modules/analyse-module.md
|
||||
- Dokumentations-Module (Paket 3+): services/sdk-modules/dokumentations-module.md
|
||||
- Rechtliche Texte (Paket 4): services/sdk-modules/rechtliche-texte.md
|
||||
- Academy: services/sdk-modules/academy.md
|
||||
- Whistleblower: services/sdk-modules/whistleblower.md
|
||||
- Incidents: services/sdk-modules/incidents.md
|
||||
|
||||
Reference in New Issue
Block a user