Update CLAUDE.md, MkDocs, nginx docs proxy, .claude/rules

- CLAUDE.md: Comprehensive documentation for core infrastructure
- docs-src: Cleaned 316MB junk, kept only markdown docs
- mkdocs.yml: Updated nav for core-only content
- nginx: Docs proxy targets split (3002->lehrer, 3007->compliance)
- docker-compose: Fixed docs port mapping (8009:80), added INSTALL_LOCK
- .claude/rules: testing, documentation, open-source-policy, night-scheduler

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Boenisch
2026-02-12 00:49:21 +01:00
parent ad111d5e69
commit 2498b0eb1f
54 changed files with 953 additions and 19290 deletions

View File

@@ -1,65 +1,272 @@
# BreakPilot Core Shared Infrastructure
# BreakPilot Core - Shared Infrastructure
## Entwicklungsumgebung
## Entwicklungsumgebung (WICHTIG - IMMER ZUERST LESEN)
### Zwei-Rechner-Setup
| Gerät | Rolle |
|-------|-------|
| **MacBook** | Client/Terminal |
| **Mac Mini** | Server/Docker/Git |
| Geraet | Rolle | Aufgaben |
|--------|-------|----------|
| **MacBook** | Client | Claude Terminal, Browser (Frontend-Tests) |
| **Mac Mini** | Server | Docker, alle Services, Code-Ausfuehrung, Tests, Git |
**WICHTIG:** Die Entwicklung findet vollstaendig auf dem **Mac Mini** statt!
### SSH-Verbindung
```bash
ssh macmini
# Projektverzeichnis:
cd /Users/benjaminadmin/Projekte/breakpilot-core
# Einzelbefehle (BEVORZUGT):
ssh macmini "cd /Users/benjaminadmin/Projekte/breakpilot-core && <cmd>"
```
## Projektübersicht
---
**breakpilot-core** ist das Infrastruktur-Projekt der BreakPilot-Plattform. Es stellt alle gemeinsamen Services bereit, die von **breakpilot-lehrer** und **breakpilot-compliance** genutzt werden.
## Projekt-Kontext
### Enthaltene Services (~28 Container)
**breakpilot-core** ist das Infrastruktur-Fundament der BreakPilot-Plattform. Es stellt alle gemeinsamen Services bereit, die von **breakpilot-lehrer** und **breakpilot-compliance** genutzt werden.
| Service | Port | Beschreibung |
|---------|------|--------------|
| nginx | 80/443 | Reverse Proxy (SSL) |
| postgres | 5432 | PostGIS 16 (3 Schemas: core, lehrer, compliance) |
| valkey | 6379 | Session-Cache |
| vault | 8200 | Secrets Management |
| qdrant | 6333 | Vektordatenbank |
| minio | 9000 | S3 Storage |
| backend-core | 8000 | Shared APIs (Auth, RBAC, Notifications) |
| rag-service | 8097 | RAG: Dokumente, Suche, Embeddings |
| embedding-service | 8087 | Text-Embeddings |
| consent-service | 8081 | Consent-Management |
| health-aggregator | 8099 | Health-Check aller Services |
| gitea | 3003 | Git-Server |
| woodpecker | 8090 | CI/CD |
| camunda | 8089 | BPMN |
| synapse | 8008 | Matrix Chat |
| jitsi | 8443 | Video |
| mailpit | 8025 | E-Mail (Dev) |
### 3-Projekt-Architektur
### Docker-Netzwerk
Alle 3 Projekte teilen sich das `breakpilot-network`:
```
breakpilot-core (dieses Repo — MUSS ZUERST starten)
├── breakpilot-lehrer (haengt von Core ab)
└── breakpilot-compliance (haengt von Core ab)
```
Alle 3 Projekte teilen sich ein Docker-Netzwerk:
```yaml
networks:
breakpilot-network:
driver: bridge
name: breakpilot-network
name: breakpilot-network # Fixer Name, kein Auto-Prefix!
```
### Start-Reihenfolge
---
## Haupt-URLs (via Nginx Reverse Proxy)
### Core-eigene Services
| URL | Service | Beschreibung |
|-----|---------|--------------|
| https://macmini:8000/ | backend-core | Shared APIs (Auth, RBAC, Notifications) |
| https://macmini:8097/ | rag-service | Semantische Suche, Dokument-Upload |
| https://macmini:8443/ | Jitsi Meet | Videokonferenzen |
| http://macmini:8099/ | health-aggregator | Health-Check aller Services |
### Interne Dienste (kein HTTPS)
| URL | Service | Beschreibung |
|-----|---------|--------------|
| http://macmini:3003/ | Gitea | Git-Server (User: pilotadmin) |
| http://macmini:8090/ | Woodpecker CI | CI/CD Server |
| http://macmini:8200/ | Vault | Secrets Management |
| http://macmini:8025/ | Mailpit | E-Mail (Dev) |
| http://macmini:9001/ | MinIO Console | S3 Storage UI |
| http://macmini:8096/ | Night Scheduler | Auto-Shutdown/Startup |
| http://macmini:8089/ | Camunda | BPMN Engine |
### Nginx-Proxy fuer Lehrer/Compliance (durchgereicht)
| URL | Projekt | Service |
|-----|---------|---------|
| https://macmini/ | Lehrer | Studio v2 |
| https://macmini:3000/ | Lehrer | Website |
| https://macmini:3002/ | Lehrer | Admin Lehrer |
| https://macmini:3007/ | Compliance | Admin Compliance |
| https://macmini:8001/ | Lehrer | Backend Lehrer |
| https://macmini:8002/ | Compliance | Backend Compliance |
| https://macmini:8086/ | Lehrer | Klausur-Service |
| https://macmini:8093/ | Compliance | AI Compliance SDK |
---
## Services (~28 Container)
### Infrastruktur
| Service | Tech | Port | Container |
|---------|------|------|-----------|
| nginx | Nginx | 80/443 + viele | bp-core-nginx |
| postgres | PostGIS 16 | 5432 | bp-core-postgres |
| valkey | Valkey 8 | 6379 | bp-core-valkey |
| vault | HashiCorp Vault | 8200 | bp-core-vault |
| vault-agent | Vault | - | bp-core-vault-agent |
| qdrant | Qdrant | 6333/6334 | bp-core-qdrant |
| minio | MinIO | 9000/9001 | bp-core-minio |
| mailpit | Mailpit | 8025/1025 | bp-core-mailpit |
### Shared Backend-Services
| Service | Tech | Port | Container |
|---------|------|------|-----------|
| backend-core | Python/FastAPI | 8000 | bp-core-backend |
| rag-service | Python/FastAPI | 8097 | bp-core-rag-service |
| embedding-service | Python/FastAPI | 8087 | bp-core-embedding-service |
| consent-service | Go/Gin | 8081 | bp-core-consent-service |
| billing-service | Go | 8083 | bp-core-billing-service |
| health-aggregator | Python/FastAPI | 8099 | bp-core-health |
| night-scheduler | Python/FastAPI | 8096 | bp-core-night-scheduler |
### Kommunikation
| Service | Tech | Container |
|---------|------|-----------|
| jitsi-web | Jitsi | bp-core-jitsi-web |
| jitsi-xmpp | Prosody | bp-core-jitsi-xmpp |
| jitsi-jicofo | Jicofo | bp-core-jitsi-jicofo |
| jitsi-jvb | JVB | bp-core-jitsi-jvb |
| synapse + synapse-db | Matrix | bp-core-synapse (Profil: chat) |
### DevOps
| Service | Tech | Port | Container |
|---------|------|------|-----------|
| gitea | Gitea | 3003 | bp-core-gitea |
| gitea-runner | Gitea Actions | - | bp-core-gitea-runner |
| woodpecker-server | Woodpecker | 8090 | bp-core-woodpecker-server |
| woodpecker-agent | Woodpecker | - | bp-core-woodpecker-agent |
| camunda | Camunda | 8089 | bp-core-camunda (Profil: bpmn) |
### Profile (nur bei Bedarf starten)
| Profil | Services | Start mit |
|--------|----------|-----------|
| chat | synapse, synapse-db | `--profile chat` |
| erp | erpnext-* (7 Container) | `--profile erp` |
| bpmn | camunda | `--profile bpmn` |
| docs | docs (MkDocs) | `--profile docs` |
| backup | pg-backup | `--profile backup` |
---
## Datenbank
### 3 Schemas (shared PostgreSQL)
| Schema | Projekt | Tabellen |
|--------|---------|----------|
| `core` | Core | users, sessions, auth, rbac, notifications, email_templates, billing |
| `lehrer` | Lehrer | classroom, units, klausuren, vocab, game, meetings, messenger |
| `compliance` | Compliance | compliance_*, dsr, gdpr, sdk_tenants, consent_admin |
```bash
# 1. Core MUSS zuerst starten
docker compose up -d
# 2. Dann Lehrer und Compliance (warten auf Core Health)
# DB-Zugang
ssh macmini "docker exec bp-core-postgres psql -U breakpilot -d breakpilot_db"
```
### DB-Schemas
- `core` — users, sessions, auth, rbac, notifications
- `lehrer` — classroom, units, klausuren, game
- `compliance` — compliance, dsr, gdpr, sdk
---
## Git Remotes
Immer zu BEIDEN pushen:
- `origin`: lokale Gitea (macmini:3003)
- `gitea`: gitea.meghsakha.com
## Verzeichnisstruktur
```
breakpilot-core/
├── .claude/
│ ├── CLAUDE.md # Diese Datei
│ └── rules/ # Automatische Regeln
├── backend-core/ # Python/FastAPI (Auth, RBAC, Notifications)
├── rag-service/ # Python/FastAPI (Qdrant, MinIO, Embeddings)
├── embedding-service/ # Python/FastAPI (Sentence-Transformers)
├── consent-service/ # Go/Gin (Consent-Management)
├── billing-service/ # Go (Abrechnungsservice)
├── night-scheduler/ # Python/FastAPI (Auto-Shutdown)
├── nginx/conf.d/ # Nginx Reverse Proxy Config
├── vault/ # Vault Config + TLS Certs
├── gitea/ # Gitea Config
├── docs-src/ # MkDocs Quellen
├── mkdocs.yml # MkDocs Config
├── scripts/ # Helper Scripts
└── docker-compose.yml # Haupt-Compose (28+ Services)
```
---
## Haeufige Befehle
### Docker
```bash
# Alle Core-Services starten
ssh macmini "cd /Users/benjaminadmin/Projekte/breakpilot-core && /usr/local/bin/docker compose up -d"
# Einzelnen Service neu bauen
ssh macmini "cd /Users/benjaminadmin/Projekte/breakpilot-core && /usr/local/bin/docker compose build --no-cache <service>"
# Logs
ssh macmini "/usr/local/bin/docker logs -f bp-core-<service>"
# Status
ssh macmini "/usr/local/bin/docker ps --filter name=bp-core"
```
**WICHTIG:** Docker-Pfad auf Mac Mini ist `/usr/local/bin/docker` (nicht im Standard-SSH-PATH).
### Alle 3 Projekte starten
```bash
# 1. Core (MUSS zuerst!)
ssh macmini "cd /Users/benjaminadmin/Projekte/breakpilot-core && /usr/local/bin/docker compose up -d"
# Warten auf Health:
ssh macmini "curl -sf http://127.0.0.1:8099/health"
# 2. Lehrer
ssh macmini "cd /Users/benjaminadmin/Projekte/breakpilot-lehrer && /usr/local/bin/docker compose up -d"
# 3. Compliance
ssh macmini "cd /Users/benjaminadmin/Projekte/breakpilot-compliance && /usr/local/bin/docker compose up -d"
```
### Git
```bash
# Zu BEIDEN Remotes pushen (PFLICHT!):
ssh macmini "cd /Users/benjaminadmin/Projekte/breakpilot-core && git push all main"
# Remotes:
# origin: lokale Gitea (macmini:3003)
# gitea: gitea.meghsakha.com
# all: beide gleichzeitig
```
---
## Kernprinzipien
### 1. Open Source Policy
- **NUR Open Source mit kommerziell nutzbarer Lizenz**
- Erlaubt: MIT, Apache-2.0, BSD, ISC, MPL-2.0, LGPL
- **VERBOTEN:** GPL (ausser LGPL), AGPL, proprietaer
### 2. Testing & Dokumentation
- Tests sind Pflicht bei jeder Aenderung
- MkDocs aktualisieren: `--profile docs` starten
### 3. Sensitive Dateien
**NIEMALS aendern oder committen:**
- `.env`, `.env.local`, Vault-Tokens, SSL-Zertifikate
- `*.pdf`, `*.docx`, kompilierte Binaries, grosse Medien
---
## Nginx Port-Zuordnung (Uebersicht)
| Port | Ziel-Container | Projekt |
|------|----------------|---------|
| 443 | bp-lehrer-studio-v2 | Lehrer |
| 3000 | bp-lehrer-website | Lehrer |
| 3002 | bp-lehrer-admin | Lehrer |
| 3006 | bp-compliance-developer-portal | Compliance |
| 3007 | bp-compliance-admin | Compliance |
| 8000 | bp-core-backend | Core |
| 8001 | bp-lehrer-backend | Lehrer |
| 8002 | bp-compliance-backend | Compliance |
| 8086 | bp-lehrer-klausur-service | Lehrer |
| 8087 | bp-core-embedding-service | Core |
| 8089 | bp-core-camunda | Core |
| 8091 | bp-lehrer-voice-service | Lehrer |
| 8093 | bp-compliance-ai-sdk | Compliance |
| 8097 | bp-core-rag-service | Core |
| 8443 | bp-core-jitsi-web | Core |

View File

@@ -0,0 +1,91 @@
# Dokumentations-Regeln
## Automatische Dokumentations-Aktualisierung
**WICHTIG:** Bei JEDER Code-Änderung muss die entsprechende Dokumentation aktualisiert werden!
## Wann Dokumentation aktualisieren?
### API-Änderungen
Wenn du einen Endpoint änderst, hinzufügst oder entfernst:
- Aktualisiere `/docs/api/consent-service-api.md` (Go Endpoints)
- Aktualisiere `/docs/api/backend-api.md` (Python Endpoints)
### Neue Funktionen/Klassen
Wenn du neue Funktionen, Klassen oder Module erstellst:
- Aktualisiere `/docs/consent-service/README.md` (für Go)
- Aktualisiere `/docs/backend/README.md` (für Python)
### Architektur-Änderungen
Wenn du die Systemarchitektur änderst:
- Aktualisiere `/docs/architecture/system-architecture.md`
- Aktualisiere `/docs/architecture/data-model.md` (bei DB-Änderungen)
### Neue Konfigurationsoptionen
Wenn du neue Umgebungsvariablen oder Konfigurationen hinzufügst:
- Aktualisiere die entsprechende README
- Füge zur `guides/local-development.md` hinzu
## Dokumentations-Format
### API-Endpoints dokumentieren
```markdown
### METHOD /path/to/endpoint
Kurze Beschreibung.
**Request Body:**
\`\`\`json
{
"field": "value"
}
\`\`\`
**Response (200):**
\`\`\`json
{
"result": "value"
}
\`\`\`
**Errors:**
- `400`: Beschreibung
- `401`: Beschreibung
```
### Funktionen dokumentieren
```markdown
### FunctionName (file.go:123)
\`\`\`go
func FunctionName(param Type) ReturnType
\`\`\`
**Beschreibung:** Was macht die Funktion?
**Parameter:**
- `param`: Beschreibung
**Rückgabe:** Beschreibung
```
## Checkliste nach Code-Änderungen
Vor dem Abschluss einer Aufgabe prüfe:
- [ ] Wurden neue API-Endpoints hinzugefügt? → API-Docs aktualisieren
- [ ] Wurden Datenmodelle geändert? → data-model.md aktualisieren
- [ ] Wurden neue Konfigurationen hinzugefügt? → README aktualisieren
- [ ] Wurden neue Abhängigkeiten hinzugefügt? → requirements.txt/go.mod UND Docs
- [ ] Wurde die Architektur geändert? → architecture/ aktualisieren
## Beispiel: Vollständige Dokumentation einer neuen Funktion
Wenn du z.B. `GetUserStats()` im Go Service hinzufügst:
1. **Code schreiben** in `internal/services/stats_service.go`
2. **API-Doc aktualisieren** in `docs/api/consent-service-api.md`
3. **Service-Doc aktualisieren** in `docs/consent-service/README.md`
4. **Test schreiben** (siehe testing.md)

View File

@@ -0,0 +1,297 @@
# Night Scheduler - Entwicklerdokumentation
**Status:** Produktiv
**Letzte Aktualisierung:** 2026-02-09
**URL:** https://macmini:3002/infrastructure/night-mode
**API:** http://macmini:8096
---
## Uebersicht
Der Night Scheduler ermoeglicht die automatische Nachtabschaltung der Docker-Services:
- Zeitgesteuerte Abschaltung (Standard: 22:00)
- Zeitgesteuerter Start (Standard: 06:00)
- Manuelle Sofortaktionen (Start/Stop)
- Dashboard-UI zur Konfiguration
---
## Architektur
```
┌─────────────────────────────────────────────────────────────┐
│ Admin Dashboard (Port 3002) │
│ /infrastructure/night-mode │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ API Proxy: /api/admin/night-mode │
│ - GET: Status abrufen │
│ - POST: Konfiguration speichern │
│ - POST /execute: Sofortaktion (start/stop) │
│ - GET /services: Service-Liste │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ night-scheduler (Port 8096) │
│ - Python/FastAPI Container │
│ - Prueft jede Minute ob Aktion faellig │
│ - Fuehrt docker compose start/stop aus │
│ - Speichert Config in /config/night-mode.json │
└─────────────────────────────────────────────────────────────┘
```
---
## Dateien
| Pfad | Beschreibung |
|------|--------------|
| `night-scheduler/scheduler.py` | Python Scheduler mit FastAPI |
| `night-scheduler/Dockerfile` | Container mit Docker CLI |
| `night-scheduler/requirements.txt` | Dependencies |
| `night-scheduler/config/night-mode.json` | Konfigurationsdatei |
| `night-scheduler/tests/test_scheduler.py` | Unit Tests |
| `admin-v2/app/api/admin/night-mode/route.ts` | API Proxy |
| `admin-v2/app/api/admin/night-mode/execute/route.ts` | Execute Endpoint |
| `admin-v2/app/api/admin/night-mode/services/route.ts` | Services Endpoint |
| `admin-v2/app/(admin)/infrastructure/night-mode/page.tsx` | UI Seite |
---
## API Endpoints
### GET /api/night-mode
Status und Konfiguration abrufen.
**Response:**
```json
{
"config": {
"enabled": true,
"shutdown_time": "22:00",
"startup_time": "06:00",
"last_action": "startup",
"last_action_time": "2026-02-09T06:00:00",
"excluded_services": ["night-scheduler", "nginx"]
},
"current_time": "14:30:00",
"next_action": "shutdown",
"next_action_time": "22:00",
"time_until_next_action": "7h 30min",
"services_status": {
"backend": "running",
"postgres": "running"
}
}
```
### POST /api/night-mode
Konfiguration aktualisieren.
**Request:**
```json
{
"enabled": true,
"shutdown_time": "23:00",
"startup_time": "07:00",
"excluded_services": ["night-scheduler", "nginx", "vault"]
}
```
### POST /api/night-mode/execute
Sofortige Aktion ausfuehren.
**Request:**
```json
{
"action": "stop" // oder "start"
}
```
**Response:**
```json
{
"success": true,
"message": "Aktion 'stop' erfolgreich ausgefuehrt fuer 25 Services"
}
```
### GET /api/night-mode/services
Liste aller Services abrufen.
**Response:**
```json
{
"all_services": ["backend", "postgres", "valkey", ...],
"excluded_services": ["night-scheduler", "nginx"],
"status": {
"backend": "running",
"postgres": "running"
}
}
```
---
## Konfiguration
### Config-Format (night-mode.json)
```json
{
"enabled": true,
"shutdown_time": "22:00",
"startup_time": "06:00",
"last_action": "startup",
"last_action_time": "2026-02-09T06:00:00",
"excluded_services": ["night-scheduler", "nginx"]
}
```
### Umgebungsvariablen
| Variable | Default | Beschreibung |
|----------|---------|--------------|
| `COMPOSE_PROJECT_NAME` | `breakpilot-pwa` | Docker Compose Projektname |
---
## Ausgeschlossene Services
Diese Services werden NICHT gestoppt:
1. **night-scheduler** - Muss laufen, um Services zu starten
2. **nginx** - Optional, fuer HTTPS-Zugriff
Weitere Services koennen ueber die Konfiguration ausgeschlossen werden.
---
## Docker Compose Integration
```yaml
night-scheduler:
build: ./night-scheduler
container_name: breakpilot-pwa-night-scheduler
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./night-scheduler/config:/config
- ./docker-compose.yml:/app/docker-compose.yml:ro
environment:
- COMPOSE_PROJECT_NAME=breakpilot-pwa
ports:
- "8096:8096"
networks:
- breakpilot-pwa-network
restart: unless-stopped
```
---
## Tests ausfuehren
```bash
# Im Container
docker exec -it breakpilot-pwa-night-scheduler pytest -v
# Lokal (mit Dependencies)
cd night-scheduler
pip install -r requirements.txt
pytest -v tests/
```
---
## Deployment
```bash
# 1. Dateien synchronisieren
rsync -avz night-scheduler/ macmini:.../night-scheduler/
# 2. Container bauen
ssh macmini "docker compose -f .../docker-compose.yml build --no-cache night-scheduler"
# 3. Container starten
ssh macmini "docker compose -f .../docker-compose.yml up -d night-scheduler"
# 4. Testen
curl http://macmini:8096/health
curl http://macmini:8096/api/night-mode
```
---
## Troubleshooting
### Problem: Services werden nicht gestoppt/gestartet
1. Pruefen ob Docker Socket gemountet ist:
```bash
docker exec breakpilot-pwa-night-scheduler ls -la /var/run/docker.sock
```
2. Pruefen ob docker compose CLI verfuegbar ist:
```bash
docker exec breakpilot-pwa-night-scheduler docker compose version
```
3. Logs pruefen:
```bash
docker logs breakpilot-pwa-night-scheduler
```
### Problem: Konfiguration wird nicht gespeichert
1. Pruefen ob /config beschreibbar ist:
```bash
docker exec breakpilot-pwa-night-scheduler touch /config/test
```
2. Volume-Mount pruefen in docker-compose.yml
### Problem: API nicht erreichbar
1. Container-Status pruefen:
```bash
docker ps | grep night-scheduler
```
2. Health-Check pruefen:
```bash
curl http://localhost:8096/health
```
---
## Sicherheitshinweise
- Der Container benoetigt Zugriff auf den Docker Socket
- Nur interne Services koennen gestoppt/gestartet werden
- Keine Authentifizierung (internes Netzwerk)
- Keine sensitiven Daten in der Konfiguration
---
## Dependencies (SBOM)
| Package | Version | Lizenz |
|---------|---------|--------|
| FastAPI | 0.109.0 | MIT |
| Uvicorn | 0.27.0 | BSD-3-Clause |
| Pydantic | 2.5.3 | MIT |
| pytest | 8.0.0 | MIT |
| pytest-asyncio | 0.23.0 | Apache-2.0 |
| httpx | 0.26.0 | BSD-3-Clause |
---
## Aenderungshistorie
| Datum | Aenderung |
|-------|-----------|
| 2026-02-09 | Initiale Implementierung |

View File

@@ -0,0 +1,99 @@
# Open Source Policy
## Lizenzprüfung (AUTOMATISCH BEI JEDER DEPENDENCY)
### Erlaubte Lizenzen ✅
| Lizenz | Typ | Kommerziell OK |
|--------|-----|----------------|
| MIT | Permissive | ✅ |
| Apache-2.0 | Permissive | ✅ |
| BSD-2-Clause | Permissive | ✅ |
| BSD-3-Clause | Permissive | ✅ |
| ISC | Permissive | ✅ |
| MPL-2.0 | Weak Copyleft | ✅ |
| LGPL-2.1 / LGPL-3.0 | Weak Copyleft | ✅ (nur linking) |
| CC0-1.0 | Public Domain | ✅ |
| Unlicense | Public Domain | ✅ |
### Verbotene Lizenzen ❌
| Lizenz | Grund |
|--------|-------|
| GPL-2.0 / GPL-3.0 | Copyleft - infiziert Projekt |
| AGPL-3.0 | Network Copyleft - SaaS-Killer |
| SSPL | Server Side Public License |
| BSL | Business Source License |
| "Non-Commercial" | Keine kommerzielle Nutzung |
| "Educational Only" | Nur für Bildung |
| Proprietary | Keine OSS |
---
## Workflow bei neuer Dependency
### 1. Vor dem Hinzufügen prüfen
```bash
# NPM Package
npm view <package> license
# Python Package
pip show <package> | grep License
# Go Module
go-licenses check <module>
```
### 2. Bei Unklarheit
- README.md des Projekts lesen
- LICENSE-Datei prüfen
- SPDX-Identifier suchen
- Im Zweifel: **NICHT verwenden**
### 3. Nach dem Hinzufügen
**SBOM aktualisieren:** https://macmini:3002/infrastructure/sbom
```bash
# SBOM generieren
cd /Users/benjaminadmin/Projekte/breakpilot-pwa
# Python
pip-licenses --format=json > sbom/python-licenses.json
# Node.js
npx license-checker --json > sbom/node-licenses.json
# Go
go-licenses csv ./... > sbom/go-licenses.csv
```
---
## Grenzfälle
### Dual-Licensed Packages
- Wenn MIT **oder** GPL angeboten wird → MIT wählen
- Dokumentieren welche Lizenz gewählt wurde
### Transitive Dependencies
- Auch indirekte Abhängigkeiten prüfen
- `npm ls`, `pip-tree`, `go mod graph`
### Fonts & Assets
- Google Fonts: ✅ (OFL)
- Font Awesome Free: ✅ (CC BY 4.0 / OFL / MIT)
- Icons8: ❌ (Attribution required, kompliziert)
---
## Checkliste bei PR/Commit
Wenn neue Dependencies hinzugefügt wurden:
- [ ] Lizenz ist in der Whitelist
- [ ] SBOM wurde aktualisiert
- [ ] Keine GPL/AGPL-Abhängigkeiten eingeschleppt
- [ ] Bei Dual-License: MIT/Apache gewählt

202
.claude/rules/testing.md Normal file
View File

@@ -0,0 +1,202 @@
# Test-Regeln
## Automatische Test-Erweiterung
**WICHTIG:** Bei JEDER Code-Änderung müssen entsprechende Tests erstellt oder aktualisiert werden!
## Wann Tests schreiben?
### IMMER wenn du:
1. **Neue Funktionen** erstellst → Unit Test
2. **Neue API-Endpoints** hinzufügst → Handler Test
3. **Bugs fixst** → Regression Test (der Bug sollte nie wieder auftreten)
4. **Bestehenden Code änderst** → Bestehende Tests anpassen
## Test-Struktur
### Go Tests (Consent Service)
**Speicherort:** Im gleichen Verzeichnis wie der Code
```
internal/
├── services/
│ ├── auth_service.go
│ └── auth_service_test.go ← Test hier
├── handlers/
│ ├── handlers.go
│ └── handlers_test.go ← Test hier
└── middleware/
├── auth.go
└── middleware_test.go ← Test hier
```
**Test-Namenskonvention:**
```go
func TestFunctionName_Scenario_ExpectedResult(t *testing.T)
// Beispiele:
func TestHashPassword_ValidPassword_ReturnsHash(t *testing.T)
func TestLogin_InvalidCredentials_Returns401(t *testing.T)
func TestCreateDocument_MissingTitle_ReturnsError(t *testing.T)
```
**Test-Template:**
```go
func TestFunctionName(t *testing.T) {
// Arrange
service := &MyService{}
input := "test-input"
// Act
result, err := service.DoSomething(input)
// Assert
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if result != expected {
t.Errorf("Expected %v, got %v", expected, result)
}
}
```
**Table-Driven Tests bevorzugen:**
```go
func TestValidateEmail(t *testing.T) {
tests := []struct {
name string
email string
expected bool
}{
{"valid email", "test@example.com", true},
{"missing @", "testexample.com", false},
{"empty", "", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := ValidateEmail(tt.email)
if result != tt.expected {
t.Errorf("Expected %v, got %v", tt.expected, result)
}
})
}
}
```
### Python Tests (Backend)
**Speicherort:** `/backend/tests/`
```
backend/
├── consent_client.py
├── gdpr_api.py
└── tests/
├── __init__.py
├── test_consent_client.py ← Tests für consent_client.py
└── test_gdpr_api.py ← Tests für gdpr_api.py
```
**Test-Namenskonvention:**
```python
class TestClassName:
def test_method_scenario_expected_result(self):
pass
# Beispiele:
class TestConsentClient:
def test_check_consent_valid_token_returns_status(self):
pass
def test_check_consent_expired_token_raises_error(self):
pass
```
**Test-Template:**
```python
import pytest
from unittest.mock import AsyncMock, patch, MagicMock
class TestMyFeature:
def test_sync_function(self):
# Arrange
input_data = "test"
# Act
result = my_function(input_data)
# Assert
assert result == expected
@pytest.mark.asyncio
async def test_async_function(self):
# Arrange
client = MyClient()
# Act
with patch("httpx.AsyncClient") as mock:
mock_instance = AsyncMock()
mock.return_value = mock_instance
result = await client.fetch_data()
# Assert
assert result is not None
```
## Test-Kategorien
### 1. Unit Tests (Höchste Priorität)
- Testen einzelne Funktionen/Methoden
- Keine externen Abhängigkeiten (Mocks verwenden)
- Schnell ausführbar
### 2. Integration Tests
- Testen Zusammenspiel mehrerer Komponenten
- Können echte DB verwenden (Test-DB)
### 3. Security Tests
- Auth/JWT Validierung
- Passwort-Hashing
- Berechtigungsprüfung
## Checkliste vor Abschluss
Vor dem Abschluss einer Aufgabe:
- [ ] Gibt es Tests für alle neuen Funktionen?
- [ ] Gibt es Tests für alle Edge Cases?
- [ ] Gibt es Tests für Fehlerfälle?
- [ ] Laufen alle bestehenden Tests noch? (`go test ./...` / `pytest`)
- [ ] Ist die Test-Coverage angemessen?
## Tests ausführen
```bash
# Go - Alle Tests
cd consent-service && go test -v ./...
# Go - Mit Coverage
cd consent-service && go test -cover ./...
# Python - Alle Tests
cd backend && source venv/bin/activate && pytest -v
# Python - Mit Coverage
cd backend && pytest --cov=. --cov-report=html
```
## Beispiel: Vollständiger Test-Workflow
Wenn du z.B. eine neue `GetUserStats()` Funktion im Go Service hinzufügst:
1. **Funktion schreiben** in `internal/services/stats_service.go`
2. **Test erstellen** in `internal/services/stats_service_test.go`:
```go
func TestGetUserStats_ValidUser_ReturnsStats(t *testing.T) {...}
func TestGetUserStats_InvalidUser_ReturnsError(t *testing.T) {...}
func TestGetUserStats_NoConsents_ReturnsEmptyStats(t *testing.T) {...}
```
3. **Tests ausführen**: `go test -v ./internal/services/...`
4. **Dokumentation aktualisieren** (siehe documentation.md)

View File

@@ -681,6 +681,7 @@ services:
GITEA__service__REQUIRE_SIGNIN_VIEW: "true"
GITEA__repository__DEFAULT_BRANCH: main
GITEA__log__LEVEL: Warn
GITEA__security__INSTALL_LOCK: "true"
GITEA__webhook__ALLOWED_HOST_LIST: "*"
extra_hosts:
- "macmini:192.168.178.100"
@@ -810,9 +811,9 @@ services:
profiles: [docs]
platform: linux/arm64
ports:
- "8009:8009"
- "8009:80"
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:8009/"]
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:80/"]
interval: 30s
timeout: 10s
retries: 3

View File

@@ -1,321 +0,0 @@
-- AI Compliance SDK - RBAC Schema
-- Migration 001: Multi-Tenant RBAC with Namespace Isolation
-- Enable UUID extension
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- ============================================================================
-- Tenants (Mandanten)
-- ============================================================================
CREATE TABLE IF NOT EXISTS compliance_tenants (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
slug VARCHAR(100) NOT NULL UNIQUE,
settings JSONB DEFAULT '{}',
max_users INT DEFAULT 100,
llm_quota_monthly INT DEFAULT 10000,
status VARCHAR(50) DEFAULT 'active',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_compliance_tenants_slug ON compliance_tenants(slug);
CREATE INDEX idx_compliance_tenants_status ON compliance_tenants(status);
-- ============================================================================
-- Namespaces (Abteilungen - z.B. CFO Use-Case)
-- ============================================================================
CREATE TABLE IF NOT EXISTS compliance_namespaces (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
slug VARCHAR(100) NOT NULL,
parent_namespace_id UUID REFERENCES compliance_namespaces(id) ON DELETE SET NULL,
isolation_level VARCHAR(50) DEFAULT 'strict', -- 'strict', 'shared', 'public'
data_classification VARCHAR(50) DEFAULT 'internal', -- 'public', 'internal', 'confidential', 'restricted'
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(tenant_id, slug)
);
CREATE INDEX idx_compliance_namespaces_tenant ON compliance_namespaces(tenant_id);
CREATE INDEX idx_compliance_namespaces_parent ON compliance_namespaces(parent_namespace_id);
-- ============================================================================
-- Roles with Permissions
-- ============================================================================
CREATE TABLE IF NOT EXISTS compliance_roles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES compliance_tenants(id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
description TEXT,
permissions TEXT[] NOT NULL DEFAULT '{}',
is_system_role BOOLEAN DEFAULT FALSE,
hierarchy_level INT DEFAULT 100,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(tenant_id, name)
);
CREATE INDEX idx_compliance_roles_tenant ON compliance_roles(tenant_id);
CREATE INDEX idx_compliance_roles_system ON compliance_roles(is_system_role);
-- ============================================================================
-- System Roles (Pre-defined)
-- ============================================================================
INSERT INTO compliance_roles (name, description, permissions, is_system_role, hierarchy_level) VALUES
('compliance_executive', 'Executive mit Lesezugriff auf Compliance-Daten und LLM-Queries',
ARRAY['compliance:*:read', 'llm:query:execute', 'audit:own:read'], TRUE, 10),
('compliance_officer', 'Compliance-Verantwortlicher mit vollem Zugriff',
ARRAY['compliance:*', 'audit:*', 'llm:*', 'namespace:read'], TRUE, 20),
('data_protection_officer', 'Datenschutzbeauftragter',
ARRAY['compliance:privacy:*', 'consent:*', 'dsr:*', 'audit:read', 'llm:query:execute'], TRUE, 25),
('namespace_admin', 'Administrator fuer einen Namespace',
ARRAY['namespace:own:admin', 'compliance:own:*', 'llm:own:query', 'audit:own:read'], TRUE, 50),
('auditor', 'Auditor mit Lesezugriff',
ARRAY['compliance:read', 'audit:log:read', 'evidence:read'], TRUE, 60),
('compliance_user', 'Standardbenutzer mit eingeschraenktem Zugriff',
ARRAY['compliance:own:read', 'llm:own:query'], TRUE, 100)
ON CONFLICT (tenant_id, name) DO NOTHING;
-- ============================================================================
-- User-Role Assignments with Namespace Scope
-- ============================================================================
CREATE TABLE IF NOT EXISTS compliance_user_roles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL,
role_id UUID NOT NULL REFERENCES compliance_roles(id) ON DELETE CASCADE,
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
namespace_id UUID REFERENCES compliance_namespaces(id) ON DELETE CASCADE,
granted_by UUID NOT NULL,
expires_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(user_id, role_id, tenant_id, namespace_id)
);
CREATE INDEX idx_compliance_user_roles_user ON compliance_user_roles(user_id);
CREATE INDEX idx_compliance_user_roles_tenant ON compliance_user_roles(tenant_id);
CREATE INDEX idx_compliance_user_roles_namespace ON compliance_user_roles(namespace_id);
CREATE INDEX idx_compliance_user_roles_expires ON compliance_user_roles(expires_at) WHERE expires_at IS NOT NULL;
-- ============================================================================
-- LLM Access Policies
-- ============================================================================
CREATE TABLE IF NOT EXISTS compliance_llm_policies (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
namespace_id UUID REFERENCES compliance_namespaces(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
allowed_data_categories TEXT[] DEFAULT '{}', -- 'salary', 'health', 'personal', 'financial'
blocked_data_categories TEXT[] DEFAULT '{}',
require_pii_redaction BOOLEAN DEFAULT TRUE,
pii_redaction_level VARCHAR(50) DEFAULT 'strict', -- 'strict', 'moderate', 'minimal', 'none'
allowed_models TEXT[] DEFAULT '{}', -- 'qwen2.5:7b', 'claude-3-sonnet'
max_tokens_per_request INT DEFAULT 4000,
max_requests_per_day INT DEFAULT 1000,
max_requests_per_hour INT DEFAULT 100,
is_active BOOLEAN DEFAULT TRUE,
priority INT DEFAULT 100, -- Lower = higher priority
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_compliance_llm_policies_tenant ON compliance_llm_policies(tenant_id);
CREATE INDEX idx_compliance_llm_policies_namespace ON compliance_llm_policies(namespace_id);
CREATE INDEX idx_compliance_llm_policies_active ON compliance_llm_policies(is_active, priority);
-- ============================================================================
-- LLM Audit Log (Immutable)
-- ============================================================================
CREATE TABLE IF NOT EXISTS compliance_llm_audit_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id),
namespace_id UUID REFERENCES compliance_namespaces(id),
user_id UUID NOT NULL,
session_id VARCHAR(100),
operation VARCHAR(100) NOT NULL, -- 'query', 'completion', 'embedding', 'analysis'
model_used VARCHAR(100) NOT NULL,
provider VARCHAR(50) NOT NULL, -- 'ollama', 'anthropic', 'openai'
prompt_hash VARCHAR(64) NOT NULL, -- SHA-256 of prompt (no raw PII stored)
prompt_length INT NOT NULL,
response_length INT,
tokens_used INT NOT NULL,
duration_ms INT NOT NULL,
pii_detected BOOLEAN DEFAULT FALSE,
pii_types_detected TEXT[] DEFAULT '{}',
pii_redacted BOOLEAN DEFAULT FALSE,
policy_id UUID REFERENCES compliance_llm_policies(id),
policy_violations TEXT[] DEFAULT '{}',
data_categories_accessed TEXT[] DEFAULT '{}',
error_message TEXT,
request_metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Partitioning-ready indexes for large audit tables
CREATE INDEX idx_llm_audit_tenant_date ON compliance_llm_audit_log(tenant_id, created_at DESC);
CREATE INDEX idx_llm_audit_user ON compliance_llm_audit_log(user_id, created_at DESC);
CREATE INDEX idx_llm_audit_namespace ON compliance_llm_audit_log(namespace_id, created_at DESC);
CREATE INDEX idx_llm_audit_operation ON compliance_llm_audit_log(operation, created_at DESC);
CREATE INDEX idx_llm_audit_pii ON compliance_llm_audit_log(pii_detected, created_at DESC) WHERE pii_detected = TRUE;
CREATE INDEX idx_llm_audit_violations ON compliance_llm_audit_log(created_at DESC) WHERE array_length(policy_violations, 1) > 0;
-- ============================================================================
-- General Audit Trail
-- ============================================================================
CREATE TABLE IF NOT EXISTS compliance_audit_trail (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id),
namespace_id UUID REFERENCES compliance_namespaces(id),
user_id UUID NOT NULL,
action VARCHAR(100) NOT NULL, -- 'create', 'update', 'delete', 'access', 'export'
resource_type VARCHAR(100) NOT NULL, -- 'role', 'namespace', 'policy', 'evidence'
resource_id UUID,
old_values JSONB,
new_values JSONB,
ip_address INET,
user_agent TEXT,
reason TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_audit_trail_tenant_date ON compliance_audit_trail(tenant_id, created_at DESC);
CREATE INDEX idx_audit_trail_user ON compliance_audit_trail(user_id, created_at DESC);
CREATE INDEX idx_audit_trail_resource ON compliance_audit_trail(resource_type, resource_id);
-- ============================================================================
-- API Keys for SDK Access
-- ============================================================================
CREATE TABLE IF NOT EXISTS compliance_api_keys (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
key_hash VARCHAR(64) NOT NULL UNIQUE, -- SHA-256 of API key
key_prefix VARCHAR(8) NOT NULL, -- First 8 chars for identification
permissions TEXT[] DEFAULT '{}',
namespace_restrictions UUID[] DEFAULT '{}', -- Empty = all namespaces
rate_limit_per_hour INT DEFAULT 1000,
expires_at TIMESTAMPTZ,
last_used_at TIMESTAMPTZ,
is_active BOOLEAN DEFAULT TRUE,
created_by UUID NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_api_keys_tenant ON compliance_api_keys(tenant_id);
CREATE INDEX idx_api_keys_prefix ON compliance_api_keys(key_prefix);
CREATE INDEX idx_api_keys_active ON compliance_api_keys(is_active, expires_at);
-- ============================================================================
-- LLM Usage Statistics (Aggregated)
-- ============================================================================
CREATE TABLE IF NOT EXISTS compliance_llm_usage_stats (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id),
namespace_id UUID REFERENCES compliance_namespaces(id),
user_id UUID,
period_start DATE NOT NULL,
period_type VARCHAR(20) NOT NULL, -- 'daily', 'weekly', 'monthly'
total_requests INT DEFAULT 0,
total_tokens INT DEFAULT 0,
total_duration_ms BIGINT DEFAULT 0,
requests_with_pii INT DEFAULT 0,
policy_violations INT DEFAULT 0,
models_used JSONB DEFAULT '{}', -- {"qwen2.5:7b": 100, "claude-3-sonnet": 50}
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(tenant_id, namespace_id, user_id, period_start, period_type)
);
CREATE INDEX idx_llm_usage_tenant_period ON compliance_llm_usage_stats(tenant_id, period_start DESC);
-- ============================================================================
-- Helper Functions
-- ============================================================================
-- Function to check if user has permission in namespace
CREATE OR REPLACE FUNCTION check_namespace_permission(
p_user_id UUID,
p_tenant_id UUID,
p_namespace_id UUID,
p_permission TEXT
) RETURNS BOOLEAN AS $$
DECLARE
has_permission BOOLEAN := FALSE;
BEGIN
SELECT EXISTS (
SELECT 1
FROM compliance_user_roles ur
JOIN compliance_roles r ON ur.role_id = r.id
WHERE ur.user_id = p_user_id
AND ur.tenant_id = p_tenant_id
AND (ur.namespace_id = p_namespace_id OR ur.namespace_id IS NULL)
AND (ur.expires_at IS NULL OR ur.expires_at > NOW())
AND (
p_permission = ANY(r.permissions)
OR EXISTS (
SELECT 1 FROM unnest(r.permissions) perm
WHERE perm LIKE '%:*' AND p_permission LIKE replace(perm, ':*', '') || ':%'
)
)
) INTO has_permission;
RETURN has_permission;
END;
$$ LANGUAGE plpgsql;
-- Function to get effective permissions for user in namespace
CREATE OR REPLACE FUNCTION get_effective_permissions(
p_user_id UUID,
p_tenant_id UUID,
p_namespace_id UUID
) RETURNS TEXT[] AS $$
DECLARE
permissions TEXT[];
BEGIN
SELECT array_agg(DISTINCT perm)
INTO permissions
FROM (
SELECT unnest(r.permissions) as perm
FROM compliance_user_roles ur
JOIN compliance_roles r ON ur.role_id = r.id
WHERE ur.user_id = p_user_id
AND ur.tenant_id = p_tenant_id
AND (ur.namespace_id = p_namespace_id OR ur.namespace_id IS NULL)
AND (ur.expires_at IS NULL OR ur.expires_at > NOW())
) sub;
RETURN COALESCE(permissions, '{}');
END;
$$ LANGUAGE plpgsql;
-- ============================================================================
-- Default Tenant for Breakpilot (Self-Hosting)
-- ============================================================================
INSERT INTO compliance_tenants (name, slug, settings, max_users, llm_quota_monthly)
VALUES (
'Breakpilot',
'breakpilot',
'{"deployment": "self-hosted", "hybrid_mode": true}',
1000,
100000
) ON CONFLICT (slug) DO NOTHING;
-- Default namespaces
INSERT INTO compliance_namespaces (tenant_id, name, slug, data_classification)
SELECT
t.id,
ns.name,
ns.slug,
ns.classification
FROM compliance_tenants t
CROSS JOIN (VALUES
('Allgemein', 'general', 'internal'),
('Finanzen', 'finance', 'restricted'),
('Personal', 'hr', 'confidential'),
('IT', 'it', 'internal'),
('Compliance', 'compliance', 'confidential')
) AS ns(name, slug, classification)
WHERE t.slug = 'breakpilot'
ON CONFLICT (tenant_id, slug) DO NOTHING;

View File

@@ -1,215 +0,0 @@
-- DSGVO Schema Migration
-- AI Compliance SDK - Phase 4: DSGVO Integration
-- ============================================================================
-- VVT - Verarbeitungsverzeichnis (Art. 30 DSGVO)
-- ============================================================================
CREATE TABLE IF NOT EXISTS dsgvo_processing_activities (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
namespace_id UUID REFERENCES compliance_namespaces(id) ON DELETE SET NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
purpose TEXT NOT NULL,
legal_basis VARCHAR(50) NOT NULL, -- consent, contract, legal_obligation, vital_interests, public_interest, legitimate_interests
legal_basis_details TEXT,
data_categories JSONB DEFAULT '[]',
data_subject_categories JSONB DEFAULT '[]',
recipients JSONB DEFAULT '[]',
third_country_transfer BOOLEAN DEFAULT FALSE,
transfer_safeguards TEXT,
retention_period VARCHAR(255),
retention_policy_id UUID,
tom_reference JSONB DEFAULT '[]',
dsfa_required BOOLEAN DEFAULT FALSE,
dsfa_id UUID,
responsible_person VARCHAR(255),
responsible_department VARCHAR(255),
systems JSONB DEFAULT '[]',
status VARCHAR(50) DEFAULT 'draft', -- draft, active, under_review, archived
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
created_by UUID NOT NULL,
last_reviewed_at TIMESTAMPTZ,
next_review_at TIMESTAMPTZ
);
CREATE INDEX IF NOT EXISTS idx_dsgvo_pa_tenant ON dsgvo_processing_activities(tenant_id);
CREATE INDEX IF NOT EXISTS idx_dsgvo_pa_status ON dsgvo_processing_activities(tenant_id, status);
CREATE INDEX IF NOT EXISTS idx_dsgvo_pa_namespace ON dsgvo_processing_activities(namespace_id) WHERE namespace_id IS NOT NULL;
-- ============================================================================
-- DSFA - Datenschutz-Folgenabschätzung (Art. 35 DSGVO)
-- ============================================================================
CREATE TABLE IF NOT EXISTS dsgvo_dsfa (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
namespace_id UUID REFERENCES compliance_namespaces(id) ON DELETE SET NULL,
processing_activity_id UUID REFERENCES dsgvo_processing_activities(id) ON DELETE SET NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
processing_description TEXT,
necessity_assessment TEXT,
proportionality_assessment TEXT,
risks JSONB DEFAULT '[]',
mitigations JSONB DEFAULT '[]',
dpo_consulted BOOLEAN DEFAULT FALSE,
dpo_opinion TEXT,
authority_consulted BOOLEAN DEFAULT FALSE,
authority_reference VARCHAR(255),
status VARCHAR(50) DEFAULT 'draft', -- draft, in_progress, completed, approved, rejected
overall_risk_level VARCHAR(20), -- low, medium, high, very_high
conclusion TEXT,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
created_by UUID NOT NULL,
approved_by UUID,
approved_at TIMESTAMPTZ
);
CREATE INDEX IF NOT EXISTS idx_dsgvo_dsfa_tenant ON dsgvo_dsfa(tenant_id);
CREATE INDEX IF NOT EXISTS idx_dsgvo_dsfa_status ON dsgvo_dsfa(tenant_id, status);
CREATE INDEX IF NOT EXISTS idx_dsgvo_dsfa_pa ON dsgvo_dsfa(processing_activity_id) WHERE processing_activity_id IS NOT NULL;
-- ============================================================================
-- TOM - Technische und Organisatorische Maßnahmen (Art. 32 DSGVO)
-- ============================================================================
CREATE TABLE IF NOT EXISTS dsgvo_tom (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
namespace_id UUID REFERENCES compliance_namespaces(id) ON DELETE SET NULL,
category VARCHAR(50) NOT NULL, -- access_control, encryption, pseudonymization, etc.
subcategory VARCHAR(100),
name VARCHAR(255) NOT NULL,
description TEXT,
type VARCHAR(20) NOT NULL, -- technical, organizational
implementation_status VARCHAR(50) DEFAULT 'planned', -- planned, in_progress, implemented, verified, not_applicable
implemented_at TIMESTAMPTZ,
verified_at TIMESTAMPTZ,
verified_by UUID,
effectiveness_rating VARCHAR(20), -- low, medium, high
documentation TEXT,
responsible_person VARCHAR(255),
responsible_department VARCHAR(255),
review_frequency VARCHAR(50), -- monthly, quarterly, annually
last_review_at TIMESTAMPTZ,
next_review_at TIMESTAMPTZ,
related_controls JSONB DEFAULT '[]',
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
created_by UUID NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_dsgvo_tom_tenant ON dsgvo_tom(tenant_id);
CREATE INDEX IF NOT EXISTS idx_dsgvo_tom_category ON dsgvo_tom(tenant_id, category);
CREATE INDEX IF NOT EXISTS idx_dsgvo_tom_status ON dsgvo_tom(tenant_id, implementation_status);
-- ============================================================================
-- DSR - Data Subject Requests / Betroffenenrechte (Art. 15-22 DSGVO)
-- ============================================================================
CREATE TABLE IF NOT EXISTS dsgvo_dsr (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
namespace_id UUID REFERENCES compliance_namespaces(id) ON DELETE SET NULL,
request_type VARCHAR(50) NOT NULL, -- access, rectification, erasure, restriction, portability, objection
status VARCHAR(50) DEFAULT 'received', -- received, verified, in_progress, completed, rejected, extended
subject_name VARCHAR(255) NOT NULL,
subject_email VARCHAR(255) NOT NULL,
subject_identifier VARCHAR(255),
request_description TEXT,
request_channel VARCHAR(50), -- email, form, phone, letter
received_at TIMESTAMPTZ NOT NULL,
verified_at TIMESTAMPTZ,
verification_method VARCHAR(100),
deadline_at TIMESTAMPTZ NOT NULL,
extended_deadline_at TIMESTAMPTZ,
extension_reason TEXT,
completed_at TIMESTAMPTZ,
response_sent BOOLEAN DEFAULT FALSE,
response_sent_at TIMESTAMPTZ,
response_method VARCHAR(50),
rejection_reason TEXT,
notes TEXT,
affected_systems JSONB DEFAULT '[]',
assigned_to UUID,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
created_by UUID NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_dsgvo_dsr_tenant ON dsgvo_dsr(tenant_id);
CREATE INDEX IF NOT EXISTS idx_dsgvo_dsr_status ON dsgvo_dsr(tenant_id, status);
CREATE INDEX IF NOT EXISTS idx_dsgvo_dsr_deadline ON dsgvo_dsr(tenant_id, deadline_at) WHERE status NOT IN ('completed', 'rejected');
CREATE INDEX IF NOT EXISTS idx_dsgvo_dsr_type ON dsgvo_dsr(tenant_id, request_type);
-- ============================================================================
-- Retention Policies - Löschfristen (Art. 17 DSGVO)
-- ============================================================================
CREATE TABLE IF NOT EXISTS dsgvo_retention_policies (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
namespace_id UUID REFERENCES compliance_namespaces(id) ON DELETE SET NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
data_category VARCHAR(100) NOT NULL,
retention_period_days INT NOT NULL,
retention_period_text VARCHAR(255), -- Human readable
legal_basis VARCHAR(100),
legal_reference VARCHAR(255), -- § 147 AO, § 257 HGB, etc.
deletion_method VARCHAR(50), -- automatic, manual, anonymization
deletion_procedure TEXT,
exception_criteria TEXT,
applicable_systems JSONB DEFAULT '[]',
responsible_person VARCHAR(255),
responsible_department VARCHAR(255),
status VARCHAR(50) DEFAULT 'draft', -- draft, active, archived
last_review_at TIMESTAMPTZ,
next_review_at TIMESTAMPTZ,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
created_by UUID NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_dsgvo_retention_tenant ON dsgvo_retention_policies(tenant_id);
CREATE INDEX IF NOT EXISTS idx_dsgvo_retention_status ON dsgvo_retention_policies(tenant_id, status);
CREATE INDEX IF NOT EXISTS idx_dsgvo_retention_category ON dsgvo_retention_policies(tenant_id, data_category);
-- ============================================================================
-- Insert default TOM categories as reference data
-- ============================================================================
-- This is optional - the categories are also defined in code
-- But having them in the database allows for easier UI population
CREATE TABLE IF NOT EXISTS dsgvo_tom_categories (
id VARCHAR(50) PRIMARY KEY,
name_de VARCHAR(255) NOT NULL,
name_en VARCHAR(255) NOT NULL,
description_de TEXT,
article_reference VARCHAR(50)
);
INSERT INTO dsgvo_tom_categories (id, name_de, name_en, description_de, article_reference) VALUES
('access_control', 'Zutrittskontrolle', 'Physical Access Control', 'Maßnahmen zur Verhinderung des unbefugten Zutritts zu Datenverarbeitungsanlagen', 'Art. 32 Abs. 1 lit. b'),
('admission_control', 'Zugangskontrolle', 'Logical Access Control', 'Maßnahmen zur Verhinderung der unbefugten Nutzung von DV-Systemen', 'Art. 32 Abs. 1 lit. b'),
('access_management', 'Zugriffskontrolle', 'Access Management', 'Maßnahmen zur Gewährleistung, dass nur befugte Personen Zugriff auf Daten haben', 'Art. 32 Abs. 1 lit. b'),
('transfer_control', 'Weitergabekontrolle', 'Transfer Control', 'Maßnahmen zur Verhinderung des unbefugten Lesens, Kopierens oder Entfernens bei der Übertragung', 'Art. 32 Abs. 1 lit. b'),
('input_control', 'Eingabekontrolle', 'Input Control', 'Maßnahmen zur Nachvollziehbarkeit von Eingabe, Änderung und Löschung von Daten', 'Art. 32 Abs. 1 lit. b'),
('availability_control', 'Verfügbarkeitskontrolle', 'Availability Control', 'Maßnahmen zum Schutz gegen zufällige oder mutwillige Zerstörung oder Verlust', 'Art. 32 Abs. 1 lit. b, c'),
('separation_control', 'Trennungskontrolle', 'Separation Control', 'Maßnahmen zur getrennten Verarbeitung von Daten, die zu unterschiedlichen Zwecken erhoben wurden', 'Art. 32 Abs. 1 lit. b'),
('encryption', 'Verschlüsselung', 'Encryption', 'Verschlüsselung personenbezogener Daten', 'Art. 32 Abs. 1 lit. a'),
('pseudonymization', 'Pseudonymisierung', 'Pseudonymization', 'Verarbeitung in einer Weise, dass die Daten ohne zusätzliche Informationen nicht mehr zugeordnet werden können', 'Art. 32 Abs. 1 lit. a'),
('resilience', 'Belastbarkeit', 'Resilience', 'Fähigkeit, die Verfügbarkeit und den Zugang bei einem Zwischenfall rasch wiederherzustellen', 'Art. 32 Abs. 1 lit. b, c'),
('recovery', 'Wiederherstellung', 'Recovery', 'Verfahren zur Wiederherstellung der Verfügbarkeit und des Zugangs', 'Art. 32 Abs. 1 lit. c'),
('testing', 'Regelmäßige Überprüfung', 'Regular Testing', 'Verfahren zur regelmäßigen Überprüfung, Bewertung und Evaluierung der Wirksamkeit', 'Art. 32 Abs. 1 lit. d')
ON CONFLICT (id) DO NOTHING;

View File

@@ -1,96 +0,0 @@
-- Migration 003: UCCA (Use-Case Compliance & Feasibility Advisor) Schema
-- Creates table for storing AI use-case assessments
-- ============================================================================
-- UCCA Assessments Table
-- ============================================================================
CREATE TABLE IF NOT EXISTS ucca_assessments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
namespace_id UUID REFERENCES compliance_namespaces(id) ON DELETE SET NULL,
-- Metadata
title VARCHAR(500),
policy_version VARCHAR(50) NOT NULL DEFAULT '1.0.0',
status VARCHAR(50) DEFAULT 'completed',
-- Input
intake JSONB NOT NULL, -- Full UseCaseIntake
use_case_text_stored BOOLEAN DEFAULT FALSE, -- Opt-in for raw text storage
use_case_text_hash VARCHAR(64), -- SHA-256 hash (always stored)
-- Results - Main verdict
feasibility VARCHAR(20) NOT NULL, -- YES/CONDITIONAL/NO
risk_level VARCHAR(20) NOT NULL, -- MINIMAL/LOW/MEDIUM/HIGH/UNACCEPTABLE
complexity VARCHAR(10) NOT NULL, -- LOW/MEDIUM/HIGH
risk_score INT NOT NULL DEFAULT 0, -- 0-100
-- Results - Details (JSONB for flexibility)
triggered_rules JSONB DEFAULT '[]', -- Array of TriggeredRule
required_controls JSONB DEFAULT '[]', -- Array of RequiredControl
recommended_architecture JSONB DEFAULT '[]', -- Array of PatternRecommendation
forbidden_patterns JSONB DEFAULT '[]', -- Array of ForbiddenPattern
example_matches JSONB DEFAULT '[]', -- Array of ExampleMatch
-- Results - Flags
dsfa_recommended BOOLEAN DEFAULT FALSE,
art22_risk BOOLEAN DEFAULT FALSE, -- Art. 22 GDPR automated decision risk
training_allowed VARCHAR(50), -- YES/CONDITIONAL/NO
-- LLM Explanation (optional)
explanation_text TEXT,
explanation_generated_at TIMESTAMPTZ,
explanation_model VARCHAR(100),
-- Domain classification
domain VARCHAR(50),
-- Audit trail
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
created_by UUID NOT NULL
);
-- ============================================================================
-- Indexes for Performance
-- ============================================================================
-- Primary lookup by tenant
CREATE INDEX idx_ucca_tenant ON ucca_assessments(tenant_id);
-- List view with sorting by date
CREATE INDEX idx_ucca_tenant_created ON ucca_assessments(tenant_id, created_at DESC);
-- Filter by feasibility
CREATE INDEX idx_ucca_tenant_feasibility ON ucca_assessments(tenant_id, feasibility);
-- Filter by domain
CREATE INDEX idx_ucca_tenant_domain ON ucca_assessments(tenant_id, domain);
-- Filter by risk level
CREATE INDEX idx_ucca_tenant_risk ON ucca_assessments(tenant_id, risk_level);
-- JSONB index for searching within triggered_rules
CREATE INDEX idx_ucca_triggered_rules ON ucca_assessments USING GIN (triggered_rules);
-- ============================================================================
-- Comments for Documentation
-- ============================================================================
COMMENT ON TABLE ucca_assessments IS 'UCCA (Use-Case Compliance & Feasibility Advisor) assessments - stores evaluated AI use cases with GDPR compliance verdicts';
COMMENT ON COLUMN ucca_assessments.intake IS 'Full UseCaseIntake JSON including data types, purpose, automation level, hosting, etc.';
COMMENT ON COLUMN ucca_assessments.use_case_text_stored IS 'Whether the raw use case description text is stored (opt-in)';
COMMENT ON COLUMN ucca_assessments.use_case_text_hash IS 'SHA-256 hash of use case text for deduplication without storing raw text';
COMMENT ON COLUMN ucca_assessments.feasibility IS 'Overall verdict: YES (low risk), CONDITIONAL (needs controls), NO (not allowed)';
COMMENT ON COLUMN ucca_assessments.risk_score IS 'Numeric risk score 0-100 calculated from triggered rules';
COMMENT ON COLUMN ucca_assessments.triggered_rules IS 'Array of rules that were triggered during evaluation';
COMMENT ON COLUMN ucca_assessments.required_controls IS 'Array of controls/mitigations that must be implemented';
COMMENT ON COLUMN ucca_assessments.recommended_architecture IS 'Array of recommended architecture patterns';
COMMENT ON COLUMN ucca_assessments.forbidden_patterns IS 'Array of patterns that must NOT be used';
COMMENT ON COLUMN ucca_assessments.example_matches IS 'Array of matching didactic examples';
COMMENT ON COLUMN ucca_assessments.dsfa_recommended IS 'Whether a Data Protection Impact Assessment is recommended';
COMMENT ON COLUMN ucca_assessments.art22_risk IS 'Whether there is risk under Art. 22 GDPR (automated individual decisions)';
COMMENT ON COLUMN ucca_assessments.training_allowed IS 'Whether model training with the data is allowed';
COMMENT ON COLUMN ucca_assessments.explanation_text IS 'LLM-generated explanation in German (optional)';

View File

@@ -1,168 +0,0 @@
-- Migration 004: UCCA Escalation Workflow
-- Implements E0-E3 escalation levels with DSB routing
-- ============================================================================
-- Escalation Levels (Reference)
-- ============================================================================
-- E0: Auto-Approve - Only INFO rules triggered, Risk < 20
-- E1: Team-Lead Review - WARN rules OR Risk 20-40
-- E2: DSB Consultation - Art. 9 data OR Risk 40-60 OR DSFA recommended
-- E3: DSB + Legal - BLOCK rules OR Risk > 60 OR Art. 22 risk
-- ============================================================================
-- Escalation Queue Table
-- ============================================================================
CREATE TABLE IF NOT EXISTS ucca_escalations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
assessment_id UUID NOT NULL REFERENCES ucca_assessments(id) ON DELETE CASCADE,
-- Escalation Level
escalation_level VARCHAR(10) NOT NULL CHECK (escalation_level IN ('E0', 'E1', 'E2', 'E3')),
escalation_reason TEXT NOT NULL,
-- Routing
assigned_to UUID, -- User ID of assignee (DSB, Team Lead, etc.)
assigned_role VARCHAR(50), -- Role for assignment (dsb, team_lead, legal)
assigned_at TIMESTAMPTZ,
-- Status
status VARCHAR(30) NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending', 'assigned', 'in_review', 'approved', 'rejected', 'returned')),
-- Review
reviewer_id UUID,
reviewer_notes TEXT,
reviewed_at TIMESTAMPTZ,
-- Decision
decision VARCHAR(20) CHECK (decision IN ('approve', 'reject', 'modify', 'escalate')),
decision_notes TEXT,
decision_at TIMESTAMPTZ,
-- Conditions for approval
conditions JSONB DEFAULT '[]', -- Array of conditions that must be met
-- Timestamps
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
due_date TIMESTAMPTZ, -- SLA deadline
-- Notifications sent
notification_sent BOOLEAN DEFAULT FALSE,
notification_sent_at TIMESTAMPTZ
);
-- ============================================================================
-- Escalation History (Audit Trail)
-- ============================================================================
CREATE TABLE IF NOT EXISTS ucca_escalation_history (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
escalation_id UUID NOT NULL REFERENCES ucca_escalations(id) ON DELETE CASCADE,
-- What changed
action VARCHAR(50) NOT NULL, -- created, assigned, reviewed, decided, escalated, etc.
old_status VARCHAR(30),
new_status VARCHAR(30),
old_level VARCHAR(10),
new_level VARCHAR(10),
-- Who and when
actor_id UUID NOT NULL,
actor_role VARCHAR(50),
notes TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- ============================================================================
-- DSB Assignment Pool
-- ============================================================================
CREATE TABLE IF NOT EXISTS ucca_dsb_pool (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
user_id UUID NOT NULL,
user_name VARCHAR(255) NOT NULL,
user_email VARCHAR(255) NOT NULL,
role VARCHAR(50) NOT NULL DEFAULT 'dsb', -- dsb, deputy_dsb, legal
is_active BOOLEAN DEFAULT TRUE,
max_concurrent_reviews INT DEFAULT 10,
current_reviews INT DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(tenant_id, user_id)
);
-- ============================================================================
-- SLA Configuration per Escalation Level
-- ============================================================================
CREATE TABLE IF NOT EXISTS ucca_escalation_sla (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
escalation_level VARCHAR(10) NOT NULL CHECK (escalation_level IN ('E0', 'E1', 'E2', 'E3')),
-- SLA settings
response_hours INT NOT NULL DEFAULT 24, -- Hours to first response
resolution_hours INT NOT NULL DEFAULT 72, -- Hours to resolution
-- Notification settings
notify_on_creation BOOLEAN DEFAULT TRUE,
notify_on_approaching_sla BOOLEAN DEFAULT TRUE,
notify_on_sla_breach BOOLEAN DEFAULT TRUE,
approaching_sla_hours INT DEFAULT 8, -- Notify X hours before SLA breach
-- Auto-escalation
auto_escalate_on_breach BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(tenant_id, escalation_level)
);
-- ============================================================================
-- Indexes
-- ============================================================================
-- Fast lookup by tenant and status
CREATE INDEX idx_ucca_escalations_tenant_status ON ucca_escalations(tenant_id, status);
-- Fast lookup by assignee
CREATE INDEX idx_ucca_escalations_assigned ON ucca_escalations(assigned_to, status);
-- Fast lookup by assessment
CREATE INDEX idx_ucca_escalations_assessment ON ucca_escalations(assessment_id);
-- SLA monitoring (find escalations approaching or past due date)
CREATE INDEX idx_ucca_escalations_due ON ucca_escalations(due_date) WHERE status NOT IN ('approved', 'rejected');
-- History lookup
CREATE INDEX idx_ucca_escalation_history_escalation ON ucca_escalation_history(escalation_id);
-- DSB pool lookup
CREATE INDEX idx_ucca_dsb_pool_tenant ON ucca_dsb_pool(tenant_id, is_active);
-- ============================================================================
-- Default SLA Values (inserted on first use)
-- ============================================================================
-- Note: These will be inserted per-tenant when needed via application logic
-- E0: Auto-approve, no SLA
-- E1: 24h response, 72h resolution
-- E2: 8h response, 48h resolution
-- E3: 4h response, 24h resolution (urgent)
-- ============================================================================
-- Comments
-- ============================================================================
COMMENT ON TABLE ucca_escalations IS 'UCCA escalation queue for assessments requiring review';
COMMENT ON COLUMN ucca_escalations.escalation_level IS 'E0=Auto, E1=Team, E2=DSB, E3=DSB+Legal';
COMMENT ON COLUMN ucca_escalations.conditions IS 'JSON array of conditions required for approval';
COMMENT ON TABLE ucca_escalation_history IS 'Audit trail of all escalation state changes';
COMMENT ON TABLE ucca_dsb_pool IS 'Pool of DSB/Legal reviewers for assignment';
COMMENT ON TABLE ucca_escalation_sla IS 'SLA configuration per escalation level per tenant';

View File

@@ -1,193 +0,0 @@
-- ============================================================================
-- Migration 005: Roadmap Schema
-- Compliance Roadmap Management with Import Support
-- ============================================================================
-- Roadmaps table
CREATE TABLE IF NOT EXISTS roadmaps (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
namespace_id UUID REFERENCES namespaces(id) ON DELETE SET NULL,
title VARCHAR(255) NOT NULL,
description TEXT,
version VARCHAR(50) DEFAULT '1.0',
-- Links to other entities
assessment_id UUID REFERENCES ucca_assessments(id) ON DELETE SET NULL,
portfolio_id UUID, -- Will reference portfolio table when created
-- Status tracking
status VARCHAR(50) DEFAULT 'draft', -- draft, active, completed, archived
total_items INT DEFAULT 0,
completed_items INT DEFAULT 0,
progress INT DEFAULT 0, -- Percentage 0-100
-- Timeline
start_date DATE,
target_date DATE,
-- Audit
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID NOT NULL
);
-- Roadmap items table
CREATE TABLE IF NOT EXISTS roadmap_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
roadmap_id UUID NOT NULL REFERENCES roadmaps(id) ON DELETE CASCADE,
-- Core fields
title VARCHAR(500) NOT NULL,
description TEXT,
category VARCHAR(50) DEFAULT 'TECHNICAL', -- TECHNICAL, ORGANIZATIONAL, PROCESSUAL, DOCUMENTATION, TRAINING
priority VARCHAR(50) DEFAULT 'MEDIUM', -- CRITICAL, HIGH, MEDIUM, LOW
status VARCHAR(50) DEFAULT 'PLANNED', -- PLANNED, IN_PROGRESS, BLOCKED, COMPLETED, DEFERRED
-- Compliance mapping
control_id VARCHAR(100), -- e.g., "CTRL-AVV"
regulation_ref VARCHAR(255), -- e.g., "DSGVO Art. 28"
gap_id VARCHAR(100), -- e.g., "GAP_AVV_MISSING"
-- Effort estimation
effort_days INT,
effort_hours INT,
estimated_cost INT, -- EUR
-- Assignment
assignee_id UUID,
assignee_name VARCHAR(255),
department VARCHAR(255),
-- Timeline
planned_start DATE,
planned_end DATE,
actual_start DATE,
actual_end DATE,
-- Dependencies (JSONB arrays of UUIDs)
depends_on JSONB DEFAULT '[]',
blocked_by JSONB DEFAULT '[]',
-- Evidence
evidence_required JSONB DEFAULT '[]', -- Array of strings
evidence_provided JSONB DEFAULT '[]', -- Array of strings
-- Notes
notes TEXT,
risk_notes TEXT,
-- Import metadata
source_row INT,
source_file VARCHAR(500),
-- Ordering
sort_order INT DEFAULT 0,
-- Audit
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Import jobs table
CREATE TABLE IF NOT EXISTS roadmap_import_jobs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
roadmap_id UUID REFERENCES roadmaps(id) ON DELETE SET NULL,
-- File info
filename VARCHAR(500) NOT NULL,
format VARCHAR(50) NOT NULL, -- EXCEL, CSV, JSON
file_size BIGINT,
content_type VARCHAR(255),
-- Status
status VARCHAR(50) DEFAULT 'pending', -- pending, parsing, parsed, validating, completed, failed
error_message TEXT,
-- Parsing results
total_rows INT DEFAULT 0,
valid_rows INT DEFAULT 0,
invalid_rows INT DEFAULT 0,
imported_items INT DEFAULT 0,
-- Parsed items (before confirmation)
parsed_items JSONB DEFAULT '[]',
-- Audit
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
completed_at TIMESTAMPTZ,
created_by UUID NOT NULL
);
-- ============================================================================
-- Indexes
-- ============================================================================
-- Roadmaps indexes
CREATE INDEX IF NOT EXISTS idx_roadmaps_tenant ON roadmaps(tenant_id);
CREATE INDEX IF NOT EXISTS idx_roadmaps_status ON roadmaps(tenant_id, status);
CREATE INDEX IF NOT EXISTS idx_roadmaps_assessment ON roadmaps(assessment_id) WHERE assessment_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_roadmaps_portfolio ON roadmaps(portfolio_id) WHERE portfolio_id IS NOT NULL;
-- Roadmap items indexes
CREATE INDEX IF NOT EXISTS idx_roadmap_items_roadmap ON roadmap_items(roadmap_id);
CREATE INDEX IF NOT EXISTS idx_roadmap_items_status ON roadmap_items(roadmap_id, status);
CREATE INDEX IF NOT EXISTS idx_roadmap_items_priority ON roadmap_items(roadmap_id, priority);
CREATE INDEX IF NOT EXISTS idx_roadmap_items_category ON roadmap_items(roadmap_id, category);
CREATE INDEX IF NOT EXISTS idx_roadmap_items_assignee ON roadmap_items(assignee_id) WHERE assignee_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_roadmap_items_control ON roadmap_items(control_id) WHERE control_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_roadmap_items_deadline ON roadmap_items(planned_end) WHERE planned_end IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_roadmap_items_sort ON roadmap_items(roadmap_id, sort_order);
-- Import jobs indexes
CREATE INDEX IF NOT EXISTS idx_import_jobs_tenant ON roadmap_import_jobs(tenant_id);
CREATE INDEX IF NOT EXISTS idx_import_jobs_status ON roadmap_import_jobs(tenant_id, status);
CREATE INDEX IF NOT EXISTS idx_import_jobs_roadmap ON roadmap_import_jobs(roadmap_id) WHERE roadmap_id IS NOT NULL;
-- ============================================================================
-- Triggers for updated_at
-- ============================================================================
-- Trigger function (reuse if exists)
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ language 'plpgsql';
-- Roadmaps trigger
DROP TRIGGER IF EXISTS update_roadmaps_updated_at ON roadmaps;
CREATE TRIGGER update_roadmaps_updated_at
BEFORE UPDATE ON roadmaps
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Roadmap items trigger
DROP TRIGGER IF EXISTS update_roadmap_items_updated_at ON roadmap_items;
CREATE TRIGGER update_roadmap_items_updated_at
BEFORE UPDATE ON roadmap_items
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Import jobs trigger
DROP TRIGGER IF EXISTS update_roadmap_import_jobs_updated_at ON roadmap_import_jobs;
CREATE TRIGGER update_roadmap_import_jobs_updated_at
BEFORE UPDATE ON roadmap_import_jobs
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================================
-- Comments
-- ============================================================================
COMMENT ON TABLE roadmaps IS 'Compliance implementation roadmaps';
COMMENT ON TABLE roadmap_items IS 'Individual items/tasks in a compliance roadmap';
COMMENT ON TABLE roadmap_import_jobs IS 'Track file imports for roadmap items';
COMMENT ON COLUMN roadmap_items.control_id IS 'Reference to controls catalog (e.g., CTRL-AVV)';
COMMENT ON COLUMN roadmap_items.regulation_ref IS 'Reference to regulation article (e.g., DSGVO Art. 28)';
COMMENT ON COLUMN roadmap_items.gap_id IS 'Reference to gap mapping (e.g., GAP_AVV_MISSING)';
COMMENT ON COLUMN roadmap_items.depends_on IS 'Array of item IDs this item depends on';
COMMENT ON COLUMN roadmap_items.blocked_by IS 'Array of item IDs currently blocking this item';

View File

@@ -1,207 +0,0 @@
-- ============================================================================
-- Migration 006: Workshop Session Schema
-- Collaborative Compliance Workshop Sessions
-- ============================================================================
-- Workshop sessions table
CREATE TABLE IF NOT EXISTS workshop_sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
namespace_id UUID REFERENCES namespaces(id) ON DELETE SET NULL,
-- Session info
title VARCHAR(255) NOT NULL,
description TEXT,
session_type VARCHAR(50) NOT NULL, -- 'ucca', 'dsfa', 'custom'
status VARCHAR(50) DEFAULT 'DRAFT', -- DRAFT, SCHEDULED, ACTIVE, PAUSED, COMPLETED, CANCELLED
-- Wizard configuration
wizard_schema VARCHAR(100), -- Reference to wizard schema version
current_step INT DEFAULT 1,
total_steps INT DEFAULT 10,
-- Links to other entities
assessment_id UUID REFERENCES ucca_assessments(id) ON DELETE SET NULL,
roadmap_id UUID REFERENCES roadmaps(id) ON DELETE SET NULL,
portfolio_id UUID, -- Will reference portfolio table when created
-- Scheduling
scheduled_start TIMESTAMPTZ,
scheduled_end TIMESTAMPTZ,
actual_start TIMESTAMPTZ,
actual_end TIMESTAMPTZ,
-- Access control
join_code VARCHAR(10) NOT NULL UNIQUE,
require_auth BOOLEAN DEFAULT FALSE,
allow_anonymous BOOLEAN DEFAULT TRUE,
-- Settings (JSONB)
settings JSONB DEFAULT '{}',
-- Audit
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID NOT NULL
);
-- Workshop participants table
CREATE TABLE IF NOT EXISTS workshop_participants (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
session_id UUID NOT NULL REFERENCES workshop_sessions(id) ON DELETE CASCADE,
user_id UUID, -- Null for anonymous participants
-- Info
name VARCHAR(255) NOT NULL,
email VARCHAR(255),
role VARCHAR(50) DEFAULT 'STAKEHOLDER', -- FACILITATOR, EXPERT, STAKEHOLDER, OBSERVER
department VARCHAR(255),
-- Status
is_active BOOLEAN DEFAULT TRUE,
last_active_at TIMESTAMPTZ,
joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
left_at TIMESTAMPTZ,
-- Permissions
can_edit BOOLEAN DEFAULT TRUE,
can_comment BOOLEAN DEFAULT TRUE,
can_approve BOOLEAN DEFAULT FALSE
);
-- Workshop step progress table
CREATE TABLE IF NOT EXISTS workshop_step_progress (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
session_id UUID NOT NULL REFERENCES workshop_sessions(id) ON DELETE CASCADE,
step_number INT NOT NULL,
-- Status
status VARCHAR(50) DEFAULT 'pending', -- pending, in_progress, completed, skipped
progress INT DEFAULT 0, -- 0-100
-- Timestamps
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
-- Notes
notes TEXT,
UNIQUE(session_id, step_number)
);
-- Workshop responses table
CREATE TABLE IF NOT EXISTS workshop_responses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
session_id UUID NOT NULL REFERENCES workshop_sessions(id) ON DELETE CASCADE,
participant_id UUID NOT NULL REFERENCES workshop_participants(id) ON DELETE CASCADE,
-- Question reference
step_number INT NOT NULL,
field_id VARCHAR(100) NOT NULL,
-- Response data
value JSONB, -- Can be any JSON type
value_type VARCHAR(50), -- string, boolean, array, number, object
-- Status
status VARCHAR(50) DEFAULT 'SUBMITTED', -- PENDING, DRAFT, SUBMITTED, REVIEWED
-- Review
reviewed_by UUID,
reviewed_at TIMESTAMPTZ,
review_notes TEXT,
-- Audit
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- Unique constraint per participant per field
UNIQUE(session_id, participant_id, field_id)
);
-- Workshop comments table
CREATE TABLE IF NOT EXISTS workshop_comments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
session_id UUID NOT NULL REFERENCES workshop_sessions(id) ON DELETE CASCADE,
participant_id UUID NOT NULL REFERENCES workshop_participants(id) ON DELETE CASCADE,
-- Target (one of these should be set)
step_number INT,
field_id VARCHAR(100),
response_id UUID REFERENCES workshop_responses(id) ON DELETE CASCADE,
-- Content
text TEXT NOT NULL,
is_resolved BOOLEAN DEFAULT FALSE,
-- Audit
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ============================================================================
-- Indexes
-- ============================================================================
-- Session indexes
CREATE INDEX IF NOT EXISTS idx_workshop_sessions_tenant ON workshop_sessions(tenant_id);
CREATE INDEX IF NOT EXISTS idx_workshop_sessions_status ON workshop_sessions(tenant_id, status);
CREATE INDEX IF NOT EXISTS idx_workshop_sessions_join_code ON workshop_sessions(join_code);
CREATE INDEX IF NOT EXISTS idx_workshop_sessions_assessment ON workshop_sessions(assessment_id) WHERE assessment_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_workshop_sessions_created_by ON workshop_sessions(created_by);
-- Participant indexes
CREATE INDEX IF NOT EXISTS idx_workshop_participants_session ON workshop_participants(session_id);
CREATE INDEX IF NOT EXISTS idx_workshop_participants_user ON workshop_participants(user_id) WHERE user_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_workshop_participants_active ON workshop_participants(session_id, is_active);
-- Step progress indexes
CREATE INDEX IF NOT EXISTS idx_workshop_step_progress_session ON workshop_step_progress(session_id);
-- Response indexes
CREATE INDEX IF NOT EXISTS idx_workshop_responses_session ON workshop_responses(session_id);
CREATE INDEX IF NOT EXISTS idx_workshop_responses_participant ON workshop_responses(participant_id);
CREATE INDEX IF NOT EXISTS idx_workshop_responses_step ON workshop_responses(session_id, step_number);
CREATE INDEX IF NOT EXISTS idx_workshop_responses_field ON workshop_responses(session_id, field_id);
-- Comment indexes
CREATE INDEX IF NOT EXISTS idx_workshop_comments_session ON workshop_comments(session_id);
CREATE INDEX IF NOT EXISTS idx_workshop_comments_response ON workshop_comments(response_id) WHERE response_id IS NOT NULL;
-- ============================================================================
-- Triggers
-- ============================================================================
-- Reuse existing update_updated_at_column function
-- Sessions trigger
DROP TRIGGER IF EXISTS update_workshop_sessions_updated_at ON workshop_sessions;
CREATE TRIGGER update_workshop_sessions_updated_at
BEFORE UPDATE ON workshop_sessions
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Responses trigger
DROP TRIGGER IF EXISTS update_workshop_responses_updated_at ON workshop_responses;
CREATE TRIGGER update_workshop_responses_updated_at
BEFORE UPDATE ON workshop_responses
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Comments trigger
DROP TRIGGER IF EXISTS update_workshop_comments_updated_at ON workshop_comments;
CREATE TRIGGER update_workshop_comments_updated_at
BEFORE UPDATE ON workshop_comments
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================================
-- Comments
-- ============================================================================
COMMENT ON TABLE workshop_sessions IS 'Collaborative compliance workshop sessions';
COMMENT ON TABLE workshop_participants IS 'Participants in workshop sessions';
COMMENT ON TABLE workshop_step_progress IS 'Progress tracking for each wizard step';
COMMENT ON TABLE workshop_responses IS 'Participant responses to wizard questions';
COMMENT ON TABLE workshop_comments IS 'Comments and discussions on responses';
COMMENT ON COLUMN workshop_sessions.join_code IS 'Code for participants to join the session';
COMMENT ON COLUMN workshop_sessions.settings IS 'JSON settings (allow_back_navigation, require_all_responses, etc.)';
COMMENT ON COLUMN workshop_responses.value IS 'JSON response value (can be any type)';

View File

@@ -1,267 +0,0 @@
-- ============================================================================
-- Migration 007: Portfolio Schema
-- AI Use Case Portfolio Management with Merge Support
-- ============================================================================
-- Portfolios table
CREATE TABLE IF NOT EXISTS portfolios (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
namespace_id UUID REFERENCES namespaces(id) ON DELETE SET NULL,
-- Info
name VARCHAR(255) NOT NULL,
description TEXT,
status VARCHAR(50) DEFAULT 'DRAFT', -- DRAFT, ACTIVE, REVIEW, APPROVED, ARCHIVED
-- Organization
department VARCHAR(255),
business_unit VARCHAR(255),
owner VARCHAR(255),
owner_email VARCHAR(255),
-- Aggregated metrics (computed)
total_assessments INT DEFAULT 0,
total_roadmaps INT DEFAULT 0,
total_workshops INT DEFAULT 0,
avg_risk_score DECIMAL(5,2) DEFAULT 0,
high_risk_count INT DEFAULT 0,
conditional_count INT DEFAULT 0,
approved_count INT DEFAULT 0,
compliance_score DECIMAL(5,2) DEFAULT 0, -- 0-100
-- Settings (JSONB)
settings JSONB DEFAULT '{}',
-- Audit
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID NOT NULL,
approved_at TIMESTAMPTZ,
approved_by UUID
);
-- Portfolio items table (links portfolios to assessments, roadmaps, workshops)
CREATE TABLE IF NOT EXISTS portfolio_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
portfolio_id UUID NOT NULL REFERENCES portfolios(id) ON DELETE CASCADE,
item_type VARCHAR(50) NOT NULL, -- ASSESSMENT, ROADMAP, WORKSHOP, DOCUMENT
item_id UUID NOT NULL,
-- Cached info from the linked item
title VARCHAR(500),
status VARCHAR(50),
risk_level VARCHAR(20),
risk_score INT DEFAULT 0,
feasibility VARCHAR(20),
-- Ordering and categorization
sort_order INT DEFAULT 0,
tags JSONB DEFAULT '[]',
notes TEXT,
-- Audit
added_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
added_by UUID NOT NULL,
-- Unique constraint: item can only be in portfolio once
UNIQUE(portfolio_id, item_id)
);
-- Portfolio activity log table
CREATE TABLE IF NOT EXISTS portfolio_activity (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
portfolio_id UUID NOT NULL REFERENCES portfolios(id) ON DELETE CASCADE,
-- Activity info
timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
action VARCHAR(50) NOT NULL, -- added, removed, updated, merged, approved, submitted
item_type VARCHAR(50),
item_id UUID,
item_title VARCHAR(500),
user_id UUID NOT NULL,
-- Additional details
details JSONB
);
-- ============================================================================
-- Indexes
-- ============================================================================
-- Portfolio indexes
CREATE INDEX IF NOT EXISTS idx_portfolios_tenant ON portfolios(tenant_id);
CREATE INDEX IF NOT EXISTS idx_portfolios_tenant_status ON portfolios(tenant_id, status);
CREATE INDEX IF NOT EXISTS idx_portfolios_department ON portfolios(tenant_id, department) WHERE department IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_portfolios_business_unit ON portfolios(tenant_id, business_unit) WHERE business_unit IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_portfolios_owner ON portfolios(owner) WHERE owner IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_portfolios_created_by ON portfolios(created_by);
CREATE INDEX IF NOT EXISTS idx_portfolios_risk_score ON portfolios(avg_risk_score);
-- Portfolio item indexes
CREATE INDEX IF NOT EXISTS idx_portfolio_items_portfolio ON portfolio_items(portfolio_id);
CREATE INDEX IF NOT EXISTS idx_portfolio_items_type ON portfolio_items(portfolio_id, item_type);
CREATE INDEX IF NOT EXISTS idx_portfolio_items_item ON portfolio_items(item_id);
CREATE INDEX IF NOT EXISTS idx_portfolio_items_risk ON portfolio_items(portfolio_id, risk_level);
CREATE INDEX IF NOT EXISTS idx_portfolio_items_feasibility ON portfolio_items(portfolio_id, feasibility);
CREATE INDEX IF NOT EXISTS idx_portfolio_items_sort ON portfolio_items(portfolio_id, sort_order);
-- Activity indexes
CREATE INDEX IF NOT EXISTS idx_portfolio_activity_portfolio ON portfolio_activity(portfolio_id);
CREATE INDEX IF NOT EXISTS idx_portfolio_activity_timestamp ON portfolio_activity(portfolio_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS idx_portfolio_activity_user ON portfolio_activity(user_id);
-- ============================================================================
-- Triggers
-- ============================================================================
-- Reuse existing update_updated_at_column function
-- Portfolios trigger
DROP TRIGGER IF EXISTS update_portfolios_updated_at ON portfolios;
CREATE TRIGGER update_portfolios_updated_at
BEFORE UPDATE ON portfolios
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================================
-- Functions for Metrics Calculation
-- ============================================================================
-- Function to recalculate portfolio metrics
CREATE OR REPLACE FUNCTION recalculate_portfolio_metrics(p_portfolio_id UUID)
RETURNS VOID AS $$
DECLARE
v_total_assessments INT;
v_total_roadmaps INT;
v_total_workshops INT;
v_avg_risk DECIMAL(5,2);
v_high_risk INT;
v_conditional INT;
v_approved INT;
v_compliance DECIMAL(5,2);
BEGIN
-- Count by type
SELECT COUNT(*) INTO v_total_assessments
FROM portfolio_items
WHERE portfolio_id = p_portfolio_id AND item_type = 'ASSESSMENT';
SELECT COUNT(*) INTO v_total_roadmaps
FROM portfolio_items
WHERE portfolio_id = p_portfolio_id AND item_type = 'ROADMAP';
SELECT COUNT(*) INTO v_total_workshops
FROM portfolio_items
WHERE portfolio_id = p_portfolio_id AND item_type = 'WORKSHOP';
-- Calculate risk metrics
SELECT COALESCE(AVG(risk_score), 0) INTO v_avg_risk
FROM portfolio_items
WHERE portfolio_id = p_portfolio_id AND item_type = 'ASSESSMENT';
SELECT COUNT(*) INTO v_high_risk
FROM portfolio_items
WHERE portfolio_id = p_portfolio_id
AND item_type = 'ASSESSMENT'
AND risk_level IN ('HIGH', 'UNACCEPTABLE');
SELECT COUNT(*) INTO v_conditional
FROM portfolio_items
WHERE portfolio_id = p_portfolio_id
AND item_type = 'ASSESSMENT'
AND feasibility = 'CONDITIONAL';
SELECT COUNT(*) INTO v_approved
FROM portfolio_items
WHERE portfolio_id = p_portfolio_id
AND item_type = 'ASSESSMENT'
AND feasibility = 'YES';
-- Calculate compliance score
IF v_total_assessments > 0 THEN
v_compliance := (v_approved::DECIMAL / v_total_assessments) * 100;
ELSE
v_compliance := 0;
END IF;
-- Update portfolio
UPDATE portfolios SET
total_assessments = v_total_assessments,
total_roadmaps = v_total_roadmaps,
total_workshops = v_total_workshops,
avg_risk_score = v_avg_risk,
high_risk_count = v_high_risk,
conditional_count = v_conditional,
approved_count = v_approved,
compliance_score = v_compliance,
updated_at = NOW()
WHERE id = p_portfolio_id;
END;
$$ LANGUAGE plpgsql;
-- Trigger function to auto-update metrics on item changes
CREATE OR REPLACE FUNCTION portfolio_items_metrics_trigger()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'DELETE' THEN
PERFORM recalculate_portfolio_metrics(OLD.portfolio_id);
RETURN OLD;
ELSE
PERFORM recalculate_portfolio_metrics(NEW.portfolio_id);
RETURN NEW;
END IF;
END;
$$ LANGUAGE plpgsql;
-- Trigger for auto metrics update
DROP TRIGGER IF EXISTS trg_portfolio_items_metrics ON portfolio_items;
CREATE TRIGGER trg_portfolio_items_metrics
AFTER INSERT OR UPDATE OR DELETE ON portfolio_items
FOR EACH ROW EXECUTE FUNCTION portfolio_items_metrics_trigger();
-- ============================================================================
-- Views
-- ============================================================================
-- View for portfolio summary with counts
CREATE OR REPLACE VIEW portfolio_summary_view AS
SELECT
p.id,
p.tenant_id,
p.name,
p.description,
p.status,
p.department,
p.business_unit,
p.owner,
p.total_assessments,
p.total_roadmaps,
p.total_workshops,
p.avg_risk_score,
p.high_risk_count,
p.conditional_count,
p.approved_count,
p.compliance_score,
p.created_at,
p.updated_at,
(p.total_assessments + p.total_roadmaps + p.total_workshops) as total_items,
CASE
WHEN p.high_risk_count > 0 THEN 'CRITICAL'
WHEN p.conditional_count > p.approved_count THEN 'WARNING'
ELSE 'GOOD'
END as health_status
FROM portfolios p;
-- ============================================================================
-- Comments
-- ============================================================================
COMMENT ON TABLE portfolios IS 'AI use case portfolios for grouping and managing multiple assessments';
COMMENT ON TABLE portfolio_items IS 'Items linked to portfolios (assessments, roadmaps, workshops)';
COMMENT ON TABLE portfolio_activity IS 'Activity log for portfolio changes';
COMMENT ON COLUMN portfolios.compliance_score IS 'Percentage of assessments with YES feasibility (0-100)';
COMMENT ON COLUMN portfolios.avg_risk_score IS 'Average risk score across all assessments in portfolio';
COMMENT ON COLUMN portfolio_items.item_type IS 'Type of linked item: ASSESSMENT, ROADMAP, WORKSHOP, DOCUMENT';
COMMENT ON COLUMN portfolio_items.sort_order IS 'Custom ordering within the portfolio';
COMMENT ON FUNCTION recalculate_portfolio_metrics(UUID) IS 'Recalculates aggregated metrics for a portfolio';

View File

@@ -1,11 +0,0 @@
"""
Models Package
Data Models for AI Content Generator
"""
from .generation_job import GenerationJob, JobStatus
__all__ = [
"GenerationJob",
"JobStatus"
]

View File

@@ -1,101 +0,0 @@
"""
Generation Job Model
Tracking für Content-Generierungs-Jobs
"""
from enum import Enum
from datetime import datetime
from typing import Optional, Dict, Any
import uuid
class JobStatus(str, Enum):
"""Job Status"""
PENDING = "pending"
PROCESSING = "processing"
COMPLETED = "completed"
FAILED = "failed"
class GenerationJob:
"""Content-Generierungs-Job"""
def __init__(
self,
topic: str,
description: Optional[str] = None,
target_grade: Optional[str] = None,
material_count: int = 0
):
self.job_id = str(uuid.uuid4())
self.topic = topic
self.description = description
self.target_grade = target_grade
self.material_count = material_count
self.status = JobStatus.PENDING
self.progress = 0
self.message = "Job created"
self.result: Optional[Dict[str, Any]] = None
self.error: Optional[str] = None
self.created_at = datetime.utcnow()
self.updated_at = datetime.utcnow()
def update_progress(self, progress: int, message: str):
"""Update Job Progress"""
self.progress = progress
self.message = message
self.status = JobStatus.PROCESSING
self.updated_at = datetime.utcnow()
def complete(self, result: Dict[str, Any]):
"""Mark Job as Completed"""
self.status = JobStatus.COMPLETED
self.progress = 100
self.message = "Content generation completed"
self.result = result
self.updated_at = datetime.utcnow()
def fail(self, error: str):
"""Mark Job as Failed"""
self.status = JobStatus.FAILED
self.message = "Content generation failed"
self.error = error
self.updated_at = datetime.utcnow()
def to_dict(self) -> Dict[str, Any]:
"""Convert to dict"""
return {
"job_id": self.job_id,
"topic": self.topic,
"description": self.description,
"target_grade": self.target_grade,
"material_count": self.material_count,
"status": self.status.value,
"progress": self.progress,
"message": self.message,
"result": self.result,
"error": self.error,
"created_at": self.created_at.isoformat(),
"updated_at": self.updated_at.isoformat()
}
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "GenerationJob":
"""Create from dict"""
job = cls(
topic=data["topic"],
description=data.get("description"),
target_grade=data.get("target_grade"),
material_count=data.get("material_count", 0)
)
job.job_id = data["job_id"]
job.status = JobStatus(data["status"])
job.progress = data["progress"]
job.message = data["message"]
job.result = data.get("result")
job.error = data.get("error")
job.created_at = datetime.fromisoformat(data["created_at"])
job.updated_at = datetime.fromisoformat(data["updated_at"])
return job

View File

@@ -1,12 +0,0 @@
"""Alert Agent Models."""
from .alert_item import AlertItem, AlertSource, AlertStatus
from .relevance_profile import RelevanceProfile, PriorityItem
__all__ = [
"AlertItem",
"AlertSource",
"AlertStatus",
"RelevanceProfile",
"PriorityItem",
]

View File

@@ -1,174 +0,0 @@
"""
AlertItem Model.
Repräsentiert einen einzelnen Alert aus Google Alerts (RSS oder Email).
"""
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Optional
import hashlib
import uuid
class AlertSource(str, Enum):
"""Quelle des Alerts."""
GOOGLE_ALERTS_RSS = "google_alerts_rss"
GOOGLE_ALERTS_EMAIL = "google_alerts_email"
MANUAL = "manual"
class AlertStatus(str, Enum):
"""Verarbeitungsstatus des Alerts."""
NEW = "new"
PROCESSED = "processed"
DUPLICATE = "duplicate"
SCORED = "scored"
REVIEWED = "reviewed"
ARCHIVED = "archived"
@dataclass
class AlertItem:
"""Ein einzelner Alert-Eintrag."""
# Identifikation
id: str = field(default_factory=lambda: str(uuid.uuid4()))
# Quelle
source: AlertSource = AlertSource.GOOGLE_ALERTS_RSS
topic_label: str = "" # z.B. "Schulrecht Bayern"
feed_url: Optional[str] = None
# Content
title: str = ""
url: str = ""
snippet: str = ""
article_text: Optional[str] = None
# Metadaten
lang: str = "de"
published_at: Optional[datetime] = None
fetched_at: datetime = field(default_factory=datetime.utcnow)
# Deduplication
canonical_url: Optional[str] = None
url_hash: Optional[str] = None
content_hash: Optional[str] = None # SimHash für fuzzy matching
# Verarbeitung
status: AlertStatus = AlertStatus.NEW
cluster_id: Optional[str] = None
# Relevanz (nach Scoring)
relevance_score: Optional[float] = None # 0.0 - 1.0
relevance_decision: Optional[str] = None # KEEP, DROP, REVIEW
relevance_reasons: list = field(default_factory=list)
relevance_summary: Optional[str] = None
def __post_init__(self):
"""Berechne Hashes nach Initialisierung."""
if not self.url_hash and self.url:
self.url_hash = self._compute_url_hash()
if not self.canonical_url and self.url:
self.canonical_url = self._normalize_url(self.url)
def _compute_url_hash(self) -> str:
"""Berechne SHA256 Hash der URL."""
normalized = self._normalize_url(self.url)
return hashlib.sha256(normalized.encode()).hexdigest()[:16]
def _normalize_url(self, url: str) -> str:
"""Normalisiere URL für Deduplizierung."""
# Entferne Tracking-Parameter
import urllib.parse
parsed = urllib.parse.urlparse(url)
# Google News Redirect auflösen
if "news.google.com" in parsed.netloc and "/articles/" in parsed.path:
# news.google.com URLs enthalten die echte URL base64-kodiert
# Hier nur Basic-Handling - echte Auflösung komplexer
pass
# Tracking-Parameter entfernen
tracking_params = {
"utm_source", "utm_medium", "utm_campaign", "utm_content", "utm_term",
"fbclid", "gclid", "ref", "source"
}
query_params = urllib.parse.parse_qs(parsed.query)
cleaned_params = {k: v for k, v in query_params.items()
if k.lower() not in tracking_params}
cleaned_query = urllib.parse.urlencode(cleaned_params, doseq=True)
# Rekonstruiere URL ohne Fragment
normalized = urllib.parse.urlunparse((
parsed.scheme,
parsed.netloc.lower(),
parsed.path.rstrip("/"),
parsed.params,
cleaned_query,
"" # No fragment
))
return normalized
def compute_content_hash(self, text: Optional[str] = None) -> str:
"""
Berechne SimHash des Inhalts für Fuzzy-Matching.
SimHash erlaubt es, ähnliche Texte zu erkennen, auch wenn sie
sich leicht unterscheiden (z.B. verschiedene Quellen zum selben Thema).
"""
from ..processing.dedup import compute_simhash
content = text or self.article_text or self.snippet or self.title
if content:
self.content_hash = compute_simhash(content)
return self.content_hash or ""
def to_dict(self) -> dict:
"""Konvertiere zu Dictionary für JSON/DB."""
return {
"id": self.id,
"source": self.source.value,
"topic_label": self.topic_label,
"feed_url": self.feed_url,
"title": self.title,
"url": self.url,
"snippet": self.snippet,
"article_text": self.article_text,
"lang": self.lang,
"published_at": self.published_at.isoformat() if self.published_at else None,
"fetched_at": self.fetched_at.isoformat() if self.fetched_at else None,
"canonical_url": self.canonical_url,
"url_hash": self.url_hash,
"content_hash": self.content_hash,
"status": self.status.value,
"cluster_id": self.cluster_id,
"relevance_score": self.relevance_score,
"relevance_decision": self.relevance_decision,
"relevance_reasons": self.relevance_reasons,
"relevance_summary": self.relevance_summary,
}
@classmethod
def from_dict(cls, data: dict) -> "AlertItem":
"""Erstelle AlertItem aus Dictionary."""
# Parse Enums
if "source" in data and isinstance(data["source"], str):
data["source"] = AlertSource(data["source"])
if "status" in data and isinstance(data["status"], str):
data["status"] = AlertStatus(data["status"])
# Parse Timestamps
for field_name in ["published_at", "fetched_at"]:
if field_name in data and isinstance(data[field_name], str):
data[field_name] = datetime.fromisoformat(data[field_name])
return cls(**data)
def __repr__(self) -> str:
return f"AlertItem(id={self.id[:8]}, title='{self.title[:50]}...', status={self.status.value})"

View File

@@ -1,288 +0,0 @@
"""
RelevanceProfile Model.
Definiert das Relevanzprofil eines Nutzers für die Alerts-Filterung.
Lernt über Zeit durch Feedback.
"""
from dataclasses import dataclass, field
from datetime import datetime
from typing import Optional
import uuid
@dataclass
class PriorityItem:
"""Ein Prioritäts-Thema im Profil."""
label: str # z.B. "Inklusion", "Datenschutz Schule"
weight: float = 0.5 # 0.0 - 1.0, höher = wichtiger
keywords: list = field(default_factory=list) # Zusätzliche Keywords
description: Optional[str] = None # Kontext für LLM
def to_dict(self) -> dict:
return {
"label": self.label,
"weight": self.weight,
"keywords": self.keywords,
"description": self.description,
}
@classmethod
def from_dict(cls, data: dict) -> "PriorityItem":
return cls(**data)
@dataclass
class RelevanceProfile:
"""
Nutzerprofil für Relevanz-Scoring.
Das Profil wird verwendet, um Alerts auf Relevanz zu prüfen.
Es enthält:
- Prioritäten: Themen die wichtig sind (mit Gewichtung)
- Ausschlüsse: Themen die ignoriert werden sollen
- Positive Beispiele: URLs/Titel die relevant waren
- Negative Beispiele: URLs/Titel die irrelevant waren
- Policies: Zusätzliche Regeln (z.B. nur deutsche Quellen)
"""
# Identifikation
id: str = field(default_factory=lambda: str(uuid.uuid4()))
user_id: Optional[str] = None # Falls benutzerspezifisch
# Relevanz-Kriterien
priorities: list = field(default_factory=list) # List[PriorityItem]
exclusions: list = field(default_factory=list) # Keywords zum Ausschließen
# Beispiele für Few-Shot Learning
positive_examples: list = field(default_factory=list) # Relevante Alerts
negative_examples: list = field(default_factory=list) # Irrelevante Alerts
# Policies
policies: dict = field(default_factory=dict)
# Metadaten
created_at: datetime = field(default_factory=datetime.utcnow)
updated_at: datetime = field(default_factory=datetime.utcnow)
# Statistiken
total_scored: int = 0
total_kept: int = 0
total_dropped: int = 0
accuracy_estimate: Optional[float] = None # Geschätzte Genauigkeit
def add_priority(self, label: str, weight: float = 0.5, **kwargs) -> None:
"""Füge ein Prioritäts-Thema hinzu."""
self.priorities.append(PriorityItem(
label=label,
weight=weight,
**kwargs
))
self.updated_at = datetime.utcnow()
def add_exclusion(self, keyword: str) -> None:
"""Füge ein Ausschluss-Keyword hinzu."""
if keyword not in self.exclusions:
self.exclusions.append(keyword)
self.updated_at = datetime.utcnow()
def add_positive_example(self, title: str, url: str, reason: str = "") -> None:
"""Füge ein positives Beispiel hinzu (für Few-Shot Learning)."""
self.positive_examples.append({
"title": title,
"url": url,
"reason": reason,
"added_at": datetime.utcnow().isoformat(),
})
# Begrenze auf letzte 20 Beispiele
self.positive_examples = self.positive_examples[-20:]
self.updated_at = datetime.utcnow()
def add_negative_example(self, title: str, url: str, reason: str = "") -> None:
"""Füge ein negatives Beispiel hinzu."""
self.negative_examples.append({
"title": title,
"url": url,
"reason": reason,
"added_at": datetime.utcnow().isoformat(),
})
# Begrenze auf letzte 20 Beispiele
self.negative_examples = self.negative_examples[-20:]
self.updated_at = datetime.utcnow()
def update_from_feedback(self, alert_title: str, alert_url: str,
is_relevant: bool, reason: str = "") -> None:
"""
Aktualisiere Profil basierend auf Nutzer-Feedback.
Args:
alert_title: Titel des Alerts
alert_url: URL des Alerts
is_relevant: True wenn der Nutzer den Alert als relevant markiert hat
reason: Optional - Grund für die Entscheidung
"""
if is_relevant:
self.add_positive_example(alert_title, alert_url, reason)
self.total_kept += 1
else:
self.add_negative_example(alert_title, alert_url, reason)
self.total_dropped += 1
self.total_scored += 1
# Aktualisiere Accuracy-Schätzung (vereinfacht)
if self.total_scored > 10:
# Hier könnte eine komplexere Berechnung erfolgen
# basierend auf Vergleich von Vorhersage vs. tatsächlichem Feedback
pass
def get_prompt_context(self) -> str:
"""
Generiere Kontext für LLM-Prompt.
Dieser Text wird in den System-Prompt des Relevanz-Scorers eingefügt.
"""
lines = ["## Relevanzprofil des Nutzers\n"]
# Prioritäten
if self.priorities:
lines.append("### Prioritäten (Themen von Interesse):")
for p in self.priorities:
if isinstance(p, dict):
p = PriorityItem.from_dict(p)
weight_label = "Sehr wichtig" if p.weight > 0.7 else "Wichtig" if p.weight > 0.4 else "Interessant"
lines.append(f"- **{p.label}** ({weight_label})")
if p.description:
lines.append(f" {p.description}")
if p.keywords:
lines.append(f" Keywords: {', '.join(p.keywords)}")
lines.append("")
# Ausschlüsse
if self.exclusions:
lines.append("### Ausschlüsse (ignorieren):")
lines.append(f"Themen mit diesen Keywords: {', '.join(self.exclusions)}")
lines.append("")
# Positive Beispiele
if self.positive_examples:
lines.append("### Beispiele für relevante Alerts:")
for ex in self.positive_examples[-5:]: # Letzte 5
lines.append(f"- \"{ex['title']}\"")
if ex.get("reason"):
lines.append(f" Grund: {ex['reason']}")
lines.append("")
# Negative Beispiele
if self.negative_examples:
lines.append("### Beispiele für irrelevante Alerts:")
for ex in self.negative_examples[-5:]: # Letzte 5
lines.append(f"- \"{ex['title']}\"")
if ex.get("reason"):
lines.append(f" Grund: {ex['reason']}")
lines.append("")
# Policies
if self.policies:
lines.append("### Zusätzliche Regeln:")
for key, value in self.policies.items():
lines.append(f"- {key}: {value}")
return "\n".join(lines)
def to_dict(self) -> dict:
"""Konvertiere zu Dictionary."""
return {
"id": self.id,
"user_id": self.user_id,
"priorities": [p.to_dict() if isinstance(p, PriorityItem) else p
for p in self.priorities],
"exclusions": self.exclusions,
"positive_examples": self.positive_examples,
"negative_examples": self.negative_examples,
"policies": self.policies,
"created_at": self.created_at.isoformat(),
"updated_at": self.updated_at.isoformat(),
"total_scored": self.total_scored,
"total_kept": self.total_kept,
"total_dropped": self.total_dropped,
"accuracy_estimate": self.accuracy_estimate,
}
@classmethod
def from_dict(cls, data: dict) -> "RelevanceProfile":
"""Erstelle RelevanceProfile aus Dictionary."""
# Parse Timestamps
for field_name in ["created_at", "updated_at"]:
if field_name in data and isinstance(data[field_name], str):
data[field_name] = datetime.fromisoformat(data[field_name])
# Parse Priorities
if "priorities" in data:
data["priorities"] = [
PriorityItem.from_dict(p) if isinstance(p, dict) else p
for p in data["priorities"]
]
return cls(**data)
@classmethod
def create_default_education_profile(cls) -> "RelevanceProfile":
"""
Erstelle ein Standard-Profil für Bildungsthemen.
Dieses Profil ist für Lehrkräfte/Schulpersonal optimiert.
"""
profile = cls()
# Bildungs-relevante Prioritäten
profile.add_priority(
"Inklusion",
weight=0.9,
keywords=["inklusiv", "Förderbedarf", "Behinderung", "Barrierefreiheit"],
description="Inklusive Bildung, Förderschulen, Nachteilsausgleich"
)
profile.add_priority(
"Datenschutz Schule",
weight=0.85,
keywords=["DSGVO", "Schülerfotos", "Einwilligung", "personenbezogene Daten"],
description="DSGVO in Schulen, Datenschutz bei Klassenfotos"
)
profile.add_priority(
"Schulrecht Bayern",
weight=0.8,
keywords=["BayEUG", "Schulordnung", "Kultusministerium", "Bayern"],
description="Bayerisches Schulrecht, Verordnungen"
)
profile.add_priority(
"Digitalisierung Schule",
weight=0.7,
keywords=["DigitalPakt", "Tablet-Klasse", "Lernplattform"],
description="Digitale Medien im Unterricht"
)
profile.add_priority(
"Elternarbeit",
weight=0.6,
keywords=["Elternbeirat", "Elternabend", "Kommunikation"],
description="Zusammenarbeit mit Eltern"
)
# Standard-Ausschlüsse
profile.exclusions = [
"Stellenanzeige",
"Praktikum gesucht",
"Werbung",
"Pressemitteilung", # Oft generisch
]
# Policies
profile.policies = {
"prefer_german_sources": True,
"max_age_days": 30, # Ältere Alerts ignorieren
"min_content_length": 100, # Sehr kurze Snippets ignorieren
}
return profile
def __repr__(self) -> str:
return f"RelevanceProfile(id={self.id[:8]}, priorities={len(self.priorities)}, examples={len(self.positive_examples) + len(self.negative_examples)})"

View File

@@ -1,31 +0,0 @@
"""
Pydantic Models für OpenAI-kompatible API.
"""
from .chat import (
ChatMessage,
ChatCompletionRequest,
ChatCompletionResponse,
ChatCompletionChunk,
ChatChoice,
ChatChoiceDelta,
Usage,
ToolCall,
FunctionCall,
Tool,
ToolFunction,
)
__all__ = [
"ChatMessage",
"ChatCompletionRequest",
"ChatCompletionResponse",
"ChatCompletionChunk",
"ChatChoice",
"ChatChoiceDelta",
"Usage",
"ToolCall",
"FunctionCall",
"Tool",
"ToolFunction",
]

View File

@@ -1,135 +0,0 @@
"""
OpenAI-kompatible Chat Completion Models.
Basiert auf OpenAI API Spezifikation:
https://platform.openai.com/docs/api-reference/chat/create
"""
from __future__ import annotations
from typing import Optional, Literal, Any, Union, List, Dict
from pydantic import BaseModel, Field
import time
import uuid
class FunctionCall(BaseModel):
"""Function call in einer Tool-Anfrage."""
name: str
arguments: str # JSON string
class ToolCall(BaseModel):
"""Tool Call vom Modell."""
id: str = Field(default_factory=lambda: f"call_{uuid.uuid4().hex[:12]}")
type: Literal["function"] = "function"
function: FunctionCall
class ChatMessage(BaseModel):
"""Eine Nachricht im Chat."""
role: Literal["system", "user", "assistant", "tool"]
content: Optional[str] = None
name: Optional[str] = None
tool_call_id: Optional[str] = None
tool_calls: Optional[list[ToolCall]] = None
class ToolFunction(BaseModel):
"""Definition einer Tool-Funktion."""
name: str
description: Optional[str] = None
parameters: dict[str, Any] = Field(default_factory=dict)
class Tool(BaseModel):
"""Tool-Definition für Function Calling."""
type: Literal["function"] = "function"
function: ToolFunction
class RequestMetadata(BaseModel):
"""Zusätzliche Metadaten für die Anfrage."""
playbook_id: Optional[str] = None
tenant_id: Optional[str] = None
user_id: Optional[str] = None
class ChatCompletionRequest(BaseModel):
"""Request für Chat Completions."""
model: str
messages: list[ChatMessage]
stream: bool = False
temperature: Optional[float] = Field(default=0.7, ge=0, le=2)
top_p: Optional[float] = Field(default=1.0, ge=0, le=1)
max_tokens: Optional[int] = Field(default=None, ge=1)
stop: Optional[Union[List[str], str]] = None
presence_penalty: Optional[float] = Field(default=0, ge=-2, le=2)
frequency_penalty: Optional[float] = Field(default=0, ge=-2, le=2)
user: Optional[str] = None
tools: Optional[list[Tool]] = None
tool_choice: Optional[Union[str, Dict[str, Any]]] = None
metadata: Optional[RequestMetadata] = None
class ChatChoice(BaseModel):
"""Ein Choice in der Response."""
index: int = 0
message: ChatMessage
finish_reason: Optional[Literal["stop", "length", "tool_calls", "content_filter"]] = None
class ChatChoiceDelta(BaseModel):
"""Delta für Streaming Response."""
role: Optional[str] = None
content: Optional[str] = None
tool_calls: Optional[list[ToolCall]] = None
class StreamChoice(BaseModel):
"""Choice in Streaming Response."""
index: int = 0
delta: ChatChoiceDelta
finish_reason: Optional[Literal["stop", "length", "tool_calls", "content_filter"]] = None
class Usage(BaseModel):
"""Token Usage Statistiken."""
prompt_tokens: int = 0
completion_tokens: int = 0
total_tokens: int = 0
class ChatCompletionResponse(BaseModel):
"""Response für Chat Completions (non-streaming)."""
id: str = Field(default_factory=lambda: f"chatcmpl-{uuid.uuid4().hex[:12]}")
object: Literal["chat.completion"] = "chat.completion"
created: int = Field(default_factory=lambda: int(time.time()))
model: str
choices: list[ChatChoice]
usage: Optional[Usage] = None
class ChatCompletionChunk(BaseModel):
"""Chunk für Streaming Response."""
id: str = Field(default_factory=lambda: f"chatcmpl-{uuid.uuid4().hex[:12]}")
object: Literal["chat.completion.chunk"] = "chat.completion.chunk"
created: int = Field(default_factory=lambda: int(time.time()))
model: str
choices: list[StreamChoice]
# Model Info
class ModelInfo(BaseModel):
"""Information über ein verfügbares Modell."""
id: str
object: Literal["model"] = "model"
created: int = Field(default_factory=lambda: int(time.time()))
owned_by: str = "breakpilot"
description: Optional[str] = None
context_length: int = 8192
class ModelListResponse(BaseModel):
"""Response für /v1/models."""
object: Literal["list"] = "list"
data: list[ModelInfo]

View File

@@ -1,155 +0,0 @@
-- ============================================================================
-- Abitur Documents Migration
-- ============================================================================
-- Creates tables for storing Abitur documents (NiBiS, etc.) persistently.
-- Run with: psql -h localhost -U breakpilot -d breakpilot_dev -f add_abitur_docs_tables.sql
--
-- Tables created:
-- 1. abitur_dokumente - Main document metadata
-- 2. abitur_dokumente_chunks - Text chunks for RAG (optional, Qdrant primary)
-- ============================================================================
-- ============================================================================
-- ENUMS
-- ============================================================================
DO $$ BEGIN
CREATE TYPE bundesland_enum AS ENUM (
'niedersachsen', 'bayern', 'baden_wuerttemberg', 'nordrhein_westfalen',
'hessen', 'sachsen', 'thueringen', 'berlin', 'hamburg',
'schleswig_holstein', 'bremen', 'brandenburg', 'mecklenburg_vorpommern',
'sachsen_anhalt', 'rheinland_pfalz', 'saarland'
);
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
CREATE TYPE abitur_fach_enum AS ENUM (
'deutsch', 'englisch', 'mathematik', 'biologie', 'chemie', 'physik',
'geschichte', 'erdkunde', 'politik_wirtschaft', 'franzoesisch', 'spanisch',
'latein', 'griechisch', 'kunst', 'musik', 'sport', 'informatik',
'ev_religion', 'kath_religion', 'werte_normen', 'brc', 'bvw',
'ernaehrung', 'mechatronik', 'gesundheit_pflege', 'paedagogik_psychologie'
);
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
CREATE TYPE abitur_niveau_enum AS ENUM ('eA', 'gA');
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
CREATE TYPE abitur_dok_typ_enum AS ENUM (
'aufgabe', 'erwartungshorizont', 'deckblatt', 'material',
'hoerverstehen', 'sprachmittlung', 'bewertungsbogen'
);
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
CREATE TYPE abitur_status_enum AS ENUM (
'pending', 'processing', 'recognized', 'confirmed', 'indexed', 'error'
);
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
-- ============================================================================
-- MAIN TABLE: abitur_dokumente
-- ============================================================================
CREATE TABLE IF NOT EXISTS abitur_dokumente (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- File info
dateiname VARCHAR(500) NOT NULL,
original_dateiname VARCHAR(500) NOT NULL,
file_path VARCHAR(1000), -- MinIO path or local path
file_size BIGINT DEFAULT 0,
file_hash VARCHAR(64), -- SHA-256 for deduplication
-- Metadata
bundesland bundesland_enum NOT NULL DEFAULT 'niedersachsen',
fach abitur_fach_enum NOT NULL,
jahr INTEGER NOT NULL CHECK (jahr >= 2000 AND jahr <= 2100),
niveau abitur_niveau_enum NOT NULL DEFAULT 'eA',
typ abitur_dok_typ_enum NOT NULL DEFAULT 'aufgabe',
aufgaben_nummer VARCHAR(20), -- I, II, III, 1, 2, etc.
variante VARCHAR(50), -- BG, Tech, Wirt, etc.
-- Processing status
status abitur_status_enum NOT NULL DEFAULT 'pending',
confidence REAL DEFAULT 0.0,
-- Vector store integration
indexed BOOLEAN DEFAULT FALSE,
vector_ids TEXT[], -- Qdrant vector IDs
qdrant_collection VARCHAR(100) DEFAULT 'bp_nibis_eh',
-- Source tracking
source_dir VARCHAR(500), -- Original source directory
import_batch_id UUID, -- For batch imports
-- Timestamps
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
indexed_at TIMESTAMPTZ
);
-- Indexes for common queries
CREATE INDEX IF NOT EXISTS idx_abitur_dok_bundesland ON abitur_dokumente(bundesland);
CREATE INDEX IF NOT EXISTS idx_abitur_dok_fach ON abitur_dokumente(fach);
CREATE INDEX IF NOT EXISTS idx_abitur_dok_jahr ON abitur_dokumente(jahr);
CREATE INDEX IF NOT EXISTS idx_abitur_dok_niveau ON abitur_dokumente(niveau);
CREATE INDEX IF NOT EXISTS idx_abitur_dok_typ ON abitur_dokumente(typ);
CREATE INDEX IF NOT EXISTS idx_abitur_dok_status ON abitur_dokumente(status);
CREATE INDEX IF NOT EXISTS idx_abitur_dok_indexed ON abitur_dokumente(indexed);
CREATE INDEX IF NOT EXISTS idx_abitur_dok_file_hash ON abitur_dokumente(file_hash);
-- Composite index for typical searches
CREATE INDEX IF NOT EXISTS idx_abitur_dok_search
ON abitur_dokumente(bundesland, fach, jahr, niveau);
-- ============================================================================
-- TRIGGER: Auto-update updated_at
-- ============================================================================
CREATE OR REPLACE FUNCTION update_abitur_dok_timestamp()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS trigger_abitur_dok_updated ON abitur_dokumente;
CREATE TRIGGER trigger_abitur_dok_updated
BEFORE UPDATE ON abitur_dokumente
FOR EACH ROW
EXECUTE FUNCTION update_abitur_dok_timestamp();
-- ============================================================================
-- HELPER VIEWS
-- ============================================================================
CREATE OR REPLACE VIEW v_abitur_dok_stats AS
SELECT
bundesland,
fach,
jahr,
COUNT(*) as total,
COUNT(*) FILTER (WHERE indexed = TRUE) as indexed_count,
COUNT(*) FILTER (WHERE typ = 'aufgabe') as aufgaben_count,
COUNT(*) FILTER (WHERE typ = 'erwartungshorizont') as ewh_count
FROM abitur_dokumente
GROUP BY bundesland, fach, jahr
ORDER BY jahr DESC, fach;
-- ============================================================================
-- SAMPLE DATA CHECK
-- ============================================================================
-- Show table structure
-- \d abitur_dokumente
SELECT 'Migration completed: abitur_dokumente table created' AS status;

View File

@@ -1,293 +0,0 @@
-- Migration: Add Multi-Agent Architecture Tables
-- Date: 2025-01-15
-- Description: Creates tables for agent sessions, memory store, and message audit
-- Enable required extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- ============================================================================
-- 1. Agent Sessions Table
-- ============================================================================
-- Stores agent session data with state, context, and checkpoints
CREATE TABLE IF NOT EXISTS agent_sessions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
agent_type VARCHAR(50) NOT NULL,
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
state VARCHAR(20) NOT NULL DEFAULT 'active'
CHECK (state IN ('active', 'paused', 'completed', 'failed', 'deleted')),
context JSONB DEFAULT '{}'::jsonb,
checkpoints JSONB DEFAULT '[]'::jsonb,
metadata JSONB DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
last_heartbeat TIMESTAMPTZ DEFAULT NOW()
);
-- Indexes for common queries
CREATE INDEX IF NOT EXISTS idx_agent_sessions_user
ON agent_sessions(user_id);
CREATE INDEX IF NOT EXISTS idx_agent_sessions_state
ON agent_sessions(state) WHERE state = 'active';
CREATE INDEX IF NOT EXISTS idx_agent_sessions_agent_type
ON agent_sessions(agent_type);
CREATE INDEX IF NOT EXISTS idx_agent_sessions_heartbeat
ON agent_sessions(last_heartbeat);
CREATE INDEX IF NOT EXISTS idx_agent_sessions_created
ON agent_sessions(created_at DESC);
-- GIN index for JSONB context queries
CREATE INDEX IF NOT EXISTS idx_agent_sessions_context
ON agent_sessions USING GIN (context jsonb_path_ops);
-- Comments for documentation
COMMENT ON TABLE agent_sessions IS 'Stores agent session state and checkpoints for recovery';
COMMENT ON COLUMN agent_sessions.agent_type IS 'Type: tutor-agent, grader-agent, quality-judge, alert-agent, orchestrator';
COMMENT ON COLUMN agent_sessions.state IS 'Session state: active, paused, completed, failed, deleted';
COMMENT ON COLUMN agent_sessions.context IS 'Session context data (entities, conversation state)';
COMMENT ON COLUMN agent_sessions.checkpoints IS 'Recovery checkpoints as JSON array';
COMMENT ON COLUMN agent_sessions.last_heartbeat IS 'Last heartbeat timestamp for liveness detection';
-- ============================================================================
-- 2. Agent Memory Table
-- ============================================================================
-- Long-term memory store for agents with TTL support
CREATE TABLE IF NOT EXISTS agent_memory (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
namespace VARCHAR(100) NOT NULL DEFAULT 'breakpilot',
key VARCHAR(500) NOT NULL,
value JSONB NOT NULL,
agent_id VARCHAR(50) NOT NULL,
access_count INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
last_accessed TIMESTAMPTZ,
expires_at TIMESTAMPTZ,
metadata JSONB DEFAULT '{}'::jsonb,
-- Unique constraint per namespace
CONSTRAINT agent_memory_namespace_key_unique UNIQUE (namespace, key)
);
-- Indexes for efficient queries
CREATE INDEX IF NOT EXISTS idx_agent_memory_namespace
ON agent_memory(namespace);
CREATE INDEX IF NOT EXISTS idx_agent_memory_agent
ON agent_memory(agent_id);
CREATE INDEX IF NOT EXISTS idx_agent_memory_expires
ON agent_memory(expires_at) WHERE expires_at IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_agent_memory_key_pattern
ON agent_memory(key varchar_pattern_ops);
CREATE INDEX IF NOT EXISTS idx_agent_memory_access_count
ON agent_memory(access_count DESC);
-- GIN index for value queries
CREATE INDEX IF NOT EXISTS idx_agent_memory_value
ON agent_memory USING GIN (value jsonb_path_ops);
-- Comments
COMMENT ON TABLE agent_memory IS 'Long-term memory store for agents with TTL';
COMMENT ON COLUMN agent_memory.namespace IS 'Namespace for isolation (default: breakpilot)';
COMMENT ON COLUMN agent_memory.key IS 'Memory key (e.g., evaluation:math:student123)';
COMMENT ON COLUMN agent_memory.value IS 'Stored value as JSONB';
COMMENT ON COLUMN agent_memory.access_count IS 'Number of times this memory was accessed';
COMMENT ON COLUMN agent_memory.expires_at IS 'When this memory expires (NULL = never)';
-- ============================================================================
-- 3. Agent Messages Table (Audit Trail)
-- ============================================================================
-- Stores all inter-agent messages for audit and debugging
CREATE TABLE IF NOT EXISTS agent_messages (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
sender VARCHAR(50) NOT NULL,
receiver VARCHAR(50) NOT NULL,
message_type VARCHAR(50) NOT NULL,
payload JSONB NOT NULL,
priority INTEGER DEFAULT 1 CHECK (priority BETWEEN 0 AND 3),
correlation_id UUID,
reply_to VARCHAR(50),
created_at TIMESTAMPTZ DEFAULT NOW(),
-- Partition hint for future partitioning
created_date DATE GENERATED ALWAYS AS (DATE(created_at)) STORED
);
-- Indexes for message queries
CREATE INDEX IF NOT EXISTS idx_agent_messages_correlation
ON agent_messages(correlation_id) WHERE correlation_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_agent_messages_sender
ON agent_messages(sender);
CREATE INDEX IF NOT EXISTS idx_agent_messages_receiver
ON agent_messages(receiver);
CREATE INDEX IF NOT EXISTS idx_agent_messages_type
ON agent_messages(message_type);
CREATE INDEX IF NOT EXISTS idx_agent_messages_created
ON agent_messages(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_agent_messages_date
ON agent_messages(created_date);
-- Comments
COMMENT ON TABLE agent_messages IS 'Audit trail for inter-agent communication';
COMMENT ON COLUMN agent_messages.priority IS '0=LOW, 1=NORMAL, 2=HIGH, 3=CRITICAL';
COMMENT ON COLUMN agent_messages.correlation_id IS 'Links request/response pairs';
COMMENT ON COLUMN agent_messages.created_date IS 'Partition column for future table partitioning';
-- ============================================================================
-- 4. Helper Functions
-- ============================================================================
-- Function to clean up expired memories
CREATE OR REPLACE FUNCTION cleanup_expired_agent_memory()
RETURNS INTEGER AS $$
DECLARE
deleted_count INTEGER;
BEGIN
DELETE FROM agent_memory
WHERE expires_at IS NOT NULL AND expires_at < NOW();
GET DIAGNOSTICS deleted_count = ROW_COUNT;
RETURN deleted_count;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION cleanup_expired_agent_memory() IS 'Removes expired memory entries, returns count';
-- Function to clean up stale sessions
CREATE OR REPLACE FUNCTION cleanup_stale_agent_sessions(max_age_hours INTEGER DEFAULT 48)
RETURNS INTEGER AS $$
DECLARE
updated_count INTEGER;
BEGIN
UPDATE agent_sessions
SET state = 'failed',
updated_at = NOW(),
context = context || '{"failure_reason": "heartbeat_timeout"}'::jsonb
WHERE state = 'active'
AND last_heartbeat < NOW() - (max_age_hours || ' hours')::INTERVAL;
GET DIAGNOSTICS updated_count = ROW_COUNT;
RETURN updated_count;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION cleanup_stale_agent_sessions(INTEGER) IS 'Marks stale sessions as failed, returns count';
-- Function to update session heartbeat
CREATE OR REPLACE FUNCTION update_session_heartbeat(session_uuid UUID)
RETURNS BOOLEAN AS $$
BEGIN
UPDATE agent_sessions
SET last_heartbeat = NOW(), updated_at = NOW()
WHERE id = session_uuid AND state = 'active';
RETURN FOUND;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION update_session_heartbeat(UUID) IS 'Updates session heartbeat, returns true if found';
-- ============================================================================
-- 5. Triggers
-- ============================================================================
-- Auto-update updated_at on agent_sessions
CREATE OR REPLACE FUNCTION trigger_set_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS agent_sessions_updated_at ON agent_sessions;
CREATE TRIGGER agent_sessions_updated_at
BEFORE UPDATE ON agent_sessions
FOR EACH ROW
EXECUTE FUNCTION trigger_set_updated_at();
DROP TRIGGER IF EXISTS agent_memory_updated_at ON agent_memory;
CREATE TRIGGER agent_memory_updated_at
BEFORE UPDATE ON agent_memory
FOR EACH ROW
EXECUTE FUNCTION trigger_set_updated_at();
-- ============================================================================
-- 6. DSGVO Compliance: Audit Views
-- ============================================================================
-- View for session audit without PII
CREATE OR REPLACE VIEW v_agent_sessions_audit AS
SELECT
id,
agent_type,
-- Hash user_id for privacy
CASE
WHEN user_id IS NOT NULL
THEN encode(digest(user_id::text, 'sha256'), 'hex')
ELSE NULL
END AS user_id_hash,
state,
-- Only expose non-sensitive context keys
jsonb_build_object(
'message_count', COALESCE((context->>'message_count')::int, 0),
'intent_count', COALESCE(jsonb_array_length(context->'intent_history'), 0)
) AS context_summary,
jsonb_array_length(checkpoints) AS checkpoint_count,
created_at,
updated_at,
last_heartbeat,
EXTRACT(EPOCH FROM (updated_at - created_at)) AS session_duration_seconds
FROM agent_sessions;
COMMENT ON VIEW v_agent_sessions_audit IS 'Privacy-safe view of agent sessions for auditing';
-- View for message audit
CREATE OR REPLACE VIEW v_agent_messages_daily_stats AS
SELECT
created_date,
sender,
receiver,
message_type,
COUNT(*) AS message_count,
AVG(priority) AS avg_priority
FROM agent_messages
GROUP BY created_date, sender, receiver, message_type;
COMMENT ON VIEW v_agent_messages_daily_stats IS 'Daily statistics for inter-agent messages';
-- ============================================================================
-- 7. Sample Data for Testing (Optional - Comment out in production)
-- ============================================================================
/*
-- Uncomment to insert sample data for testing
INSERT INTO agent_sessions (agent_type, state, context)
VALUES
('tutor-agent', 'active', '{"subject": "math", "grade": 10}'::jsonb),
('grader-agent', 'active', '{"exam_type": "vorabitur"}'::jsonb);
INSERT INTO agent_memory (namespace, key, value, agent_id, expires_at)
VALUES
('breakpilot', 'test:memory:1', '{"test": true}'::jsonb, 'tutor-agent', NOW() + INTERVAL '30 days');
*/
-- ============================================================================
-- 8. Grants (Adjust based on your user/role setup)
-- ============================================================================
-- Uncomment and adjust for your environment
/*
GRANT SELECT, INSERT, UPDATE ON agent_sessions TO breakpilot_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON agent_memory TO breakpilot_app;
GRANT SELECT, INSERT ON agent_messages TO breakpilot_app;
GRANT EXECUTE ON FUNCTION cleanup_expired_agent_memory() TO breakpilot_app;
GRANT EXECUTE ON FUNCTION cleanup_stale_agent_sessions(INTEGER) TO breakpilot_app;
GRANT EXECUTE ON FUNCTION update_session_heartbeat(UUID) TO breakpilot_app;
*/
-- ============================================================================
-- Migration Complete
-- ============================================================================
-- To verify migration:
-- \dt agent_*
-- \df cleanup_*
-- \dv v_agent_*

View File

@@ -1,807 +0,0 @@
-- ============================================================================
-- Compliance & Audit Framework Migration
-- ============================================================================
-- This migration creates all tables required for the Compliance module.
-- Run with: psql -h localhost -U breakpilot -d breakpilot -f add_compliance_tables.sql
--
-- Tables created:
-- 1. Core Compliance Framework (7 tables)
-- 2. Service Module Registry (3 tables)
-- 3. Audit Sessions & Sign-Off (2 tables)
-- 4. ISO 27001 ISMS Models (11 tables)
-- ============================================================================
-- ============================================================================
-- ENUMS (PostgreSQL ENUM types)
-- ============================================================================
DO $$ BEGIN
CREATE TYPE regulation_type AS ENUM (
'eu_regulation', 'eu_directive', 'de_law', 'bsi_standard', 'industry_standard'
);
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
CREATE TYPE control_type AS ENUM ('preventive', 'detective', 'corrective');
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
CREATE TYPE control_domain AS ENUM (
'gov', 'priv', 'iam', 'crypto', 'sdlc', 'ops', 'ai', 'cra', 'aud'
);
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
CREATE TYPE control_status AS ENUM ('pass', 'partial', 'fail', 'n/a', 'planned');
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
CREATE TYPE risk_level AS ENUM ('low', 'medium', 'high', 'critical');
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
CREATE TYPE evidence_status AS ENUM ('valid', 'expired', 'pending', 'failed');
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
CREATE TYPE export_status AS ENUM ('pending', 'generating', 'completed', 'failed');
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
CREATE TYPE service_type AS ENUM (
'backend', 'database', 'ai', 'communication', 'storage',
'infrastructure', 'monitoring', 'security'
);
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
CREATE TYPE relevance_level AS ENUM ('critical', 'high', 'medium', 'low');
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
CREATE TYPE audit_result AS ENUM (
'compliant', 'compliant_notes', 'non_compliant', 'not_applicable', 'pending'
);
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
CREATE TYPE audit_session_status AS ENUM ('draft', 'in_progress', 'completed', 'archived');
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
CREATE TYPE approval_status AS ENUM ('draft', 'under_review', 'approved', 'superseded');
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
CREATE TYPE finding_type AS ENUM ('major', 'minor', 'ofi', 'positive');
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
CREATE TYPE finding_status AS ENUM (
'open', 'in_progress', 'capa_pending', 'verification_pending', 'verified', 'closed'
);
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
CREATE TYPE capa_type AS ENUM ('corrective', 'preventive', 'both');
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
-- ============================================================================
-- CORE COMPLIANCE TABLES
-- ============================================================================
-- Table 1: compliance_regulations
CREATE TABLE IF NOT EXISTS compliance_regulations (
id VARCHAR(36) PRIMARY KEY,
code VARCHAR(20) UNIQUE NOT NULL,
name VARCHAR(200) NOT NULL,
full_name TEXT,
regulation_type regulation_type NOT NULL,
source_url VARCHAR(500),
local_pdf_path VARCHAR(500),
effective_date DATE,
description TEXT,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS ix_regulations_code ON compliance_regulations(code);
-- Table 2: compliance_requirements
CREATE TABLE IF NOT EXISTS compliance_requirements (
id VARCHAR(36) PRIMARY KEY,
regulation_id VARCHAR(36) NOT NULL REFERENCES compliance_regulations(id),
article VARCHAR(50) NOT NULL,
paragraph VARCHAR(20),
requirement_id_external VARCHAR(50),
title VARCHAR(300) NOT NULL,
description TEXT,
requirement_text TEXT,
breakpilot_interpretation TEXT,
implementation_status VARCHAR(30) DEFAULT 'not_started',
implementation_details TEXT,
code_references JSONB,
documentation_links JSONB,
evidence_description TEXT,
evidence_artifacts JSONB,
auditor_notes TEXT,
audit_status VARCHAR(30) DEFAULT 'pending',
last_audit_date TIMESTAMP,
last_auditor VARCHAR(100),
is_applicable BOOLEAN DEFAULT TRUE,
applicability_reason TEXT,
priority INTEGER DEFAULT 2,
source_page INTEGER,
source_section VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS ix_requirement_regulation_article ON compliance_requirements(regulation_id, article);
CREATE INDEX IF NOT EXISTS ix_requirement_audit_status ON compliance_requirements(audit_status);
CREATE INDEX IF NOT EXISTS ix_requirement_impl_status ON compliance_requirements(implementation_status);
-- Table 3: compliance_controls
CREATE TABLE IF NOT EXISTS compliance_controls (
id VARCHAR(36) PRIMARY KEY,
control_id VARCHAR(20) UNIQUE NOT NULL,
domain control_domain NOT NULL,
control_type control_type NOT NULL,
title VARCHAR(300) NOT NULL,
description TEXT,
pass_criteria TEXT NOT NULL,
implementation_guidance TEXT,
code_reference VARCHAR(500),
documentation_url VARCHAR(500),
is_automated BOOLEAN DEFAULT FALSE,
automation_tool VARCHAR(100),
automation_config JSONB,
status control_status DEFAULT 'planned',
status_notes TEXT,
owner VARCHAR(100),
review_frequency_days INTEGER DEFAULT 90,
last_reviewed_at TIMESTAMP,
next_review_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS ix_control_id ON compliance_controls(control_id);
CREATE INDEX IF NOT EXISTS ix_control_domain_status ON compliance_controls(domain, status);
-- Table 4: compliance_control_mappings
CREATE TABLE IF NOT EXISTS compliance_control_mappings (
id VARCHAR(36) PRIMARY KEY,
requirement_id VARCHAR(36) NOT NULL REFERENCES compliance_requirements(id),
control_id VARCHAR(36) NOT NULL REFERENCES compliance_controls(id),
coverage_level VARCHAR(20) DEFAULT 'full',
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE UNIQUE INDEX IF NOT EXISTS ix_mapping_req_ctrl ON compliance_control_mappings(requirement_id, control_id);
-- Table 5: compliance_evidence
CREATE TABLE IF NOT EXISTS compliance_evidence (
id VARCHAR(36) PRIMARY KEY,
control_id VARCHAR(36) NOT NULL REFERENCES compliance_controls(id),
evidence_type VARCHAR(50) NOT NULL,
title VARCHAR(300) NOT NULL,
description TEXT,
artifact_path VARCHAR(500),
artifact_url VARCHAR(500),
artifact_hash VARCHAR(64),
file_size_bytes INTEGER,
mime_type VARCHAR(100),
valid_from TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
valid_until TIMESTAMP,
status evidence_status DEFAULT 'valid',
source VARCHAR(100),
ci_job_id VARCHAR(100),
uploaded_by VARCHAR(100),
collected_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS ix_evidence_control_type ON compliance_evidence(control_id, evidence_type);
CREATE INDEX IF NOT EXISTS ix_evidence_status ON compliance_evidence(status);
-- Table 6: compliance_risks
CREATE TABLE IF NOT EXISTS compliance_risks (
id VARCHAR(36) PRIMARY KEY,
risk_id VARCHAR(20) UNIQUE NOT NULL,
title VARCHAR(300) NOT NULL,
description TEXT,
category VARCHAR(50) NOT NULL,
likelihood INTEGER NOT NULL,
impact INTEGER NOT NULL,
inherent_risk risk_level NOT NULL,
mitigating_controls JSONB,
residual_likelihood INTEGER,
residual_impact INTEGER,
residual_risk risk_level,
owner VARCHAR(100),
status VARCHAR(20) DEFAULT 'open',
treatment_plan TEXT,
identified_date DATE DEFAULT CURRENT_DATE,
review_date DATE,
last_assessed_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS ix_risk_id ON compliance_risks(risk_id);
CREATE INDEX IF NOT EXISTS ix_risk_category_status ON compliance_risks(category, status);
CREATE INDEX IF NOT EXISTS ix_risk_inherent ON compliance_risks(inherent_risk);
-- Table 7: compliance_audit_exports
CREATE TABLE IF NOT EXISTS compliance_audit_exports (
id VARCHAR(36) PRIMARY KEY,
export_type VARCHAR(50) NOT NULL,
export_name VARCHAR(200),
included_regulations JSONB,
included_domains JSONB,
date_range_start DATE,
date_range_end DATE,
requested_by VARCHAR(100) NOT NULL,
requested_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
completed_at TIMESTAMP,
file_path VARCHAR(500),
file_hash VARCHAR(64),
file_size_bytes INTEGER,
status export_status DEFAULT 'pending',
error_message TEXT,
total_controls INTEGER,
total_evidence INTEGER,
compliance_score FLOAT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- ============================================================================
-- SERVICE MODULE REGISTRY TABLES
-- ============================================================================
-- Table 8: compliance_service_modules
CREATE TABLE IF NOT EXISTS compliance_service_modules (
id VARCHAR(36) PRIMARY KEY,
name VARCHAR(100) UNIQUE NOT NULL,
display_name VARCHAR(200) NOT NULL,
description TEXT,
service_type service_type NOT NULL,
port INTEGER,
technology_stack JSONB,
repository_path VARCHAR(500),
docker_image VARCHAR(200),
data_categories JSONB,
processes_pii BOOLEAN DEFAULT FALSE,
processes_health_data BOOLEAN DEFAULT FALSE,
ai_components BOOLEAN DEFAULT FALSE,
is_active BOOLEAN DEFAULT TRUE,
criticality VARCHAR(20) DEFAULT 'medium',
compliance_score FLOAT,
last_compliance_check TIMESTAMP,
owner_team VARCHAR(100),
owner_contact VARCHAR(200),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS ix_module_name ON compliance_service_modules(name);
CREATE INDEX IF NOT EXISTS ix_module_type_active ON compliance_service_modules(service_type, is_active);
-- Table 9: compliance_module_regulations
CREATE TABLE IF NOT EXISTS compliance_module_regulations (
id VARCHAR(36) PRIMARY KEY,
module_id VARCHAR(36) NOT NULL REFERENCES compliance_service_modules(id),
regulation_id VARCHAR(36) NOT NULL REFERENCES compliance_regulations(id),
relevance_level relevance_level DEFAULT 'medium',
notes TEXT,
applicable_articles JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE UNIQUE INDEX IF NOT EXISTS ix_module_regulation ON compliance_module_regulations(module_id, regulation_id);
-- Table 10: compliance_module_risks
CREATE TABLE IF NOT EXISTS compliance_module_risks (
id VARCHAR(36) PRIMARY KEY,
module_id VARCHAR(36) NOT NULL REFERENCES compliance_service_modules(id),
risk_id VARCHAR(36) NOT NULL REFERENCES compliance_risks(id),
module_likelihood INTEGER,
module_impact INTEGER,
module_risk_level risk_level,
assessment_notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE UNIQUE INDEX IF NOT EXISTS ix_module_risk ON compliance_module_risks(module_id, risk_id);
-- ============================================================================
-- AUDIT SESSION & SIGN-OFF TABLES
-- ============================================================================
-- Table 11: compliance_audit_sessions
CREATE TABLE IF NOT EXISTS compliance_audit_sessions (
id VARCHAR(36) PRIMARY KEY,
name VARCHAR(200) NOT NULL,
description TEXT,
auditor_name VARCHAR(100) NOT NULL,
auditor_email VARCHAR(200),
auditor_organization VARCHAR(200),
status audit_session_status DEFAULT 'draft',
regulation_ids JSONB,
total_items INTEGER DEFAULT 0,
completed_items INTEGER DEFAULT 0,
compliant_count INTEGER DEFAULT 0,
non_compliant_count INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
started_at TIMESTAMP,
completed_at TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS ix_audit_session_status ON compliance_audit_sessions(status);
CREATE INDEX IF NOT EXISTS ix_audit_session_auditor ON compliance_audit_sessions(auditor_name);
-- Table 12: compliance_audit_signoffs
CREATE TABLE IF NOT EXISTS compliance_audit_signoffs (
id VARCHAR(36) PRIMARY KEY,
session_id VARCHAR(36) NOT NULL REFERENCES compliance_audit_sessions(id),
requirement_id VARCHAR(36) NOT NULL REFERENCES compliance_requirements(id),
result audit_result DEFAULT 'pending',
notes TEXT,
evidence_ids JSONB,
signature_hash VARCHAR(64),
signed_at TIMESTAMP,
signed_by VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE UNIQUE INDEX IF NOT EXISTS ix_signoff_session_requirement ON compliance_audit_signoffs(session_id, requirement_id);
CREATE INDEX IF NOT EXISTS ix_signoff_result ON compliance_audit_signoffs(result);
-- ============================================================================
-- ISO 27001 ISMS TABLES
-- ============================================================================
-- Table 13: compliance_isms_scope
CREATE TABLE IF NOT EXISTS compliance_isms_scope (
id VARCHAR(36) PRIMARY KEY,
version VARCHAR(20) NOT NULL DEFAULT '1.0',
scope_statement TEXT NOT NULL,
included_locations JSONB,
included_processes JSONB,
included_services JSONB,
excluded_items JSONB,
exclusion_justification TEXT,
organizational_boundary TEXT,
physical_boundary TEXT,
technical_boundary TEXT,
status approval_status DEFAULT 'draft',
approved_by VARCHAR(100),
approved_at TIMESTAMP,
approval_signature VARCHAR(64),
effective_date DATE,
review_date DATE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by VARCHAR(100),
updated_by VARCHAR(100)
);
CREATE INDEX IF NOT EXISTS ix_isms_scope_status ON compliance_isms_scope(status);
-- Table 14: compliance_isms_context
CREATE TABLE IF NOT EXISTS compliance_isms_context (
id VARCHAR(36) PRIMARY KEY,
version VARCHAR(20) NOT NULL DEFAULT '1.0',
internal_issues JSONB,
external_issues JSONB,
interested_parties JSONB,
regulatory_requirements JSONB,
contractual_requirements JSONB,
swot_strengths JSONB,
swot_weaknesses JSONB,
swot_opportunities JSONB,
swot_threats JSONB,
status approval_status DEFAULT 'draft',
approved_by VARCHAR(100),
approved_at TIMESTAMP,
last_reviewed_at TIMESTAMP,
next_review_date DATE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Table 15: compliance_isms_policies
CREATE TABLE IF NOT EXISTS compliance_isms_policies (
id VARCHAR(36) PRIMARY KEY,
policy_id VARCHAR(30) UNIQUE NOT NULL,
title VARCHAR(200) NOT NULL,
policy_type VARCHAR(50) NOT NULL,
description TEXT,
policy_text TEXT NOT NULL,
applies_to JSONB,
version VARCHAR(20) NOT NULL DEFAULT '1.0',
status approval_status DEFAULT 'draft',
authored_by VARCHAR(100),
reviewed_by VARCHAR(100),
approved_by VARCHAR(100),
approved_at TIMESTAMP,
approval_signature VARCHAR(64),
effective_date DATE,
review_frequency_months INTEGER DEFAULT 12,
next_review_date DATE,
parent_policy_id VARCHAR(36) REFERENCES compliance_isms_policies(id),
related_controls JSONB,
document_path VARCHAR(500),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS ix_policy_id ON compliance_isms_policies(policy_id);
CREATE INDEX IF NOT EXISTS ix_policy_type_status ON compliance_isms_policies(policy_type, status);
-- Table 16: compliance_security_objectives
CREATE TABLE IF NOT EXISTS compliance_security_objectives (
id VARCHAR(36) PRIMARY KEY,
objective_id VARCHAR(30) UNIQUE NOT NULL,
title VARCHAR(200) NOT NULL,
description TEXT,
category VARCHAR(50),
specific TEXT,
measurable TEXT,
achievable TEXT,
relevant TEXT,
time_bound TEXT,
kpi_name VARCHAR(100),
kpi_target VARCHAR(100),
kpi_current VARCHAR(100),
kpi_unit VARCHAR(50),
measurement_frequency VARCHAR(50),
owner VARCHAR(100),
accountable VARCHAR(100),
status VARCHAR(30) DEFAULT 'active',
progress_percentage INTEGER DEFAULT 0,
target_date DATE,
achieved_date DATE,
related_controls JSONB,
related_risks JSONB,
approved_by VARCHAR(100),
approved_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS ix_objective_id ON compliance_security_objectives(objective_id);
CREATE INDEX IF NOT EXISTS ix_objective_status ON compliance_security_objectives(status);
CREATE INDEX IF NOT EXISTS ix_objective_category ON compliance_security_objectives(category);
-- Table 17: compliance_soa (Statement of Applicability)
CREATE TABLE IF NOT EXISTS compliance_soa (
id VARCHAR(36) PRIMARY KEY,
annex_a_control VARCHAR(20) NOT NULL,
annex_a_title VARCHAR(300) NOT NULL,
annex_a_category VARCHAR(100),
is_applicable BOOLEAN NOT NULL,
applicability_justification TEXT NOT NULL,
implementation_status VARCHAR(30) DEFAULT 'planned',
implementation_notes TEXT,
breakpilot_control_ids JSONB,
coverage_level VARCHAR(20) DEFAULT 'full',
evidence_description TEXT,
evidence_ids JSONB,
risk_assessment_notes TEXT,
compensating_controls TEXT,
reviewed_by VARCHAR(100),
reviewed_at TIMESTAMP,
approved_by VARCHAR(100),
approved_at TIMESTAMP,
version VARCHAR(20) DEFAULT '1.0',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE UNIQUE INDEX IF NOT EXISTS ix_soa_annex_control ON compliance_soa(annex_a_control);
CREATE INDEX IF NOT EXISTS ix_soa_applicable ON compliance_soa(is_applicable);
CREATE INDEX IF NOT EXISTS ix_soa_status ON compliance_soa(implementation_status);
-- Table 18: compliance_internal_audits (MUST be before audit_findings due to FK)
CREATE TABLE IF NOT EXISTS compliance_internal_audits (
id VARCHAR(36) PRIMARY KEY,
audit_id VARCHAR(30) UNIQUE NOT NULL,
title VARCHAR(200) NOT NULL,
audit_type VARCHAR(50) NOT NULL,
scope_description TEXT NOT NULL,
iso_chapters_covered JSONB,
annex_a_controls_covered JSONB,
processes_covered JSONB,
departments_covered JSONB,
criteria TEXT,
planned_date DATE NOT NULL,
actual_start_date DATE,
actual_end_date DATE,
lead_auditor VARCHAR(100) NOT NULL,
audit_team JSONB,
auditee_representatives JSONB,
status VARCHAR(30) DEFAULT 'planned',
total_findings INTEGER DEFAULT 0,
major_findings INTEGER DEFAULT 0,
minor_findings INTEGER DEFAULT 0,
ofi_count INTEGER DEFAULT 0,
positive_observations INTEGER DEFAULT 0,
audit_conclusion TEXT,
overall_assessment VARCHAR(30),
report_date DATE,
report_document_path VARCHAR(500),
report_approved_by VARCHAR(100),
report_approved_at TIMESTAMP,
follow_up_audit_required BOOLEAN DEFAULT FALSE,
follow_up_audit_id VARCHAR(36),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS ix_internal_audit_id ON compliance_internal_audits(audit_id);
CREATE INDEX IF NOT EXISTS ix_internal_audit_date ON compliance_internal_audits(planned_date);
CREATE INDEX IF NOT EXISTS ix_internal_audit_status ON compliance_internal_audits(status);
-- Table 19: compliance_audit_findings
CREATE TABLE IF NOT EXISTS compliance_audit_findings (
id VARCHAR(36) PRIMARY KEY,
finding_id VARCHAR(30) UNIQUE NOT NULL,
audit_session_id VARCHAR(36) REFERENCES compliance_audit_sessions(id),
internal_audit_id VARCHAR(36) REFERENCES compliance_internal_audits(id),
finding_type finding_type NOT NULL,
iso_chapter VARCHAR(20),
annex_a_control VARCHAR(20),
title VARCHAR(300) NOT NULL,
description TEXT NOT NULL,
objective_evidence TEXT NOT NULL,
root_cause TEXT,
root_cause_method VARCHAR(50),
impact_description TEXT,
affected_processes JSONB,
affected_assets JSONB,
status finding_status DEFAULT 'open',
owner VARCHAR(100),
auditor VARCHAR(100),
identified_date DATE NOT NULL DEFAULT CURRENT_DATE,
due_date DATE,
closed_date DATE,
verification_method TEXT,
verified_by VARCHAR(100),
verified_at TIMESTAMP,
verification_evidence TEXT,
closure_notes TEXT,
closed_by VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS ix_finding_id ON compliance_audit_findings(finding_id);
CREATE INDEX IF NOT EXISTS ix_finding_type_status ON compliance_audit_findings(finding_type, status);
CREATE INDEX IF NOT EXISTS ix_finding_due_date ON compliance_audit_findings(due_date);
-- Table 20: compliance_corrective_actions
CREATE TABLE IF NOT EXISTS compliance_corrective_actions (
id VARCHAR(36) PRIMARY KEY,
capa_id VARCHAR(30) UNIQUE NOT NULL,
finding_id VARCHAR(36) NOT NULL REFERENCES compliance_audit_findings(id),
capa_type capa_type NOT NULL,
title VARCHAR(300) NOT NULL,
description TEXT NOT NULL,
expected_outcome TEXT,
assigned_to VARCHAR(100) NOT NULL,
approved_by VARCHAR(100),
planned_start DATE,
planned_completion DATE NOT NULL,
actual_completion DATE,
status VARCHAR(30) DEFAULT 'planned',
progress_percentage INTEGER DEFAULT 0,
estimated_effort_hours INTEGER,
actual_effort_hours INTEGER,
resources_required TEXT,
implementation_evidence TEXT,
evidence_ids JSONB,
effectiveness_criteria TEXT,
effectiveness_verified BOOLEAN DEFAULT FALSE,
effectiveness_verification_date DATE,
effectiveness_notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS ix_capa_id ON compliance_corrective_actions(capa_id);
CREATE INDEX IF NOT EXISTS ix_capa_status ON compliance_corrective_actions(status);
CREATE INDEX IF NOT EXISTS ix_capa_due ON compliance_corrective_actions(planned_completion);
-- Table 21: compliance_management_reviews
CREATE TABLE IF NOT EXISTS compliance_management_reviews (
id VARCHAR(36) PRIMARY KEY,
review_id VARCHAR(30) UNIQUE NOT NULL,
title VARCHAR(200) NOT NULL,
review_date DATE NOT NULL,
review_period_start DATE,
review_period_end DATE,
chairperson VARCHAR(100) NOT NULL,
attendees JSONB,
input_previous_actions TEXT,
input_isms_changes TEXT,
input_security_performance TEXT,
input_interested_party_feedback TEXT,
input_risk_assessment_results TEXT,
input_improvement_opportunities TEXT,
input_policy_effectiveness TEXT,
input_objective_achievement TEXT,
input_resource_adequacy TEXT,
output_improvement_decisions TEXT,
output_isms_changes TEXT,
output_resource_needs TEXT,
action_items JSONB,
isms_effectiveness_rating VARCHAR(20),
key_decisions TEXT,
status VARCHAR(30) DEFAULT 'draft',
approved_by VARCHAR(100),
approved_at TIMESTAMP,
minutes_document_path VARCHAR(500),
next_review_date DATE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS ix_mgmt_review_id ON compliance_management_reviews(review_id);
CREATE INDEX IF NOT EXISTS ix_mgmt_review_date ON compliance_management_reviews(review_date);
CREATE INDEX IF NOT EXISTS ix_mgmt_review_status ON compliance_management_reviews(status);
-- Table 22: compliance_audit_trail
CREATE TABLE IF NOT EXISTS compliance_audit_trail (
id VARCHAR(36) PRIMARY KEY,
entity_type VARCHAR(50) NOT NULL,
entity_id VARCHAR(36) NOT NULL,
entity_name VARCHAR(200),
action VARCHAR(20) NOT NULL,
field_changed VARCHAR(100),
old_value TEXT,
new_value TEXT,
change_summary TEXT,
performed_by VARCHAR(100) NOT NULL,
performed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
ip_address VARCHAR(45),
user_agent VARCHAR(500),
session_id VARCHAR(100),
checksum VARCHAR(64),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS ix_audit_trail_entity ON compliance_audit_trail(entity_type, entity_id);
CREATE INDEX IF NOT EXISTS ix_audit_trail_time ON compliance_audit_trail(performed_at);
CREATE INDEX IF NOT EXISTS ix_audit_trail_user ON compliance_audit_trail(performed_by);
-- Table 23: compliance_isms_readiness
CREATE TABLE IF NOT EXISTS compliance_isms_readiness (
id VARCHAR(36) PRIMARY KEY,
check_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
triggered_by VARCHAR(100),
overall_status VARCHAR(20) NOT NULL,
certification_possible BOOLEAN NOT NULL,
chapter_4_status VARCHAR(20),
chapter_5_status VARCHAR(20),
chapter_6_status VARCHAR(20),
chapter_7_status VARCHAR(20),
chapter_8_status VARCHAR(20),
chapter_9_status VARCHAR(20),
chapter_10_status VARCHAR(20),
potential_majors JSONB,
potential_minors JSONB,
improvement_opportunities JSONB,
readiness_score FLOAT,
documentation_score FLOAT,
implementation_score FLOAT,
evidence_score FLOAT,
priority_actions JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS ix_readiness_date ON compliance_isms_readiness(check_date);
CREATE INDEX IF NOT EXISTS ix_readiness_status ON compliance_isms_readiness(overall_status);
-- ============================================================================
-- UPDATE TIMESTAMPS TRIGGER
-- ============================================================================
CREATE OR REPLACE FUNCTION update_compliance_timestamp()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Apply trigger to all compliance tables with updated_at
DO $$
DECLARE
t TEXT;
BEGIN
FOR t IN
SELECT table_name
FROM information_schema.columns
WHERE table_schema = 'public'
AND column_name = 'updated_at'
AND table_name LIKE 'compliance_%'
LOOP
EXECUTE format('
DROP TRIGGER IF EXISTS trigger_%I_updated_at ON %I;
CREATE TRIGGER trigger_%I_updated_at
BEFORE UPDATE ON %I
FOR EACH ROW EXECUTE FUNCTION update_compliance_timestamp();
', t, t, t, t);
END LOOP;
END $$;
-- ============================================================================
-- CLEANUP FUNCTIONS
-- ============================================================================
-- Function to cleanup expired evidence
CREATE OR REPLACE FUNCTION cleanup_expired_compliance_evidence()
RETURNS INTEGER AS $$
DECLARE
deleted_count INTEGER;
BEGIN
UPDATE compliance_evidence
SET status = 'expired'
WHERE valid_until < CURRENT_TIMESTAMP
AND status = 'valid';
GET DIAGNOSTICS deleted_count = ROW_COUNT;
RETURN deleted_count;
END;
$$ LANGUAGE plpgsql;
-- ============================================================================
-- GRANT PERMISSIONS
-- ============================================================================
-- Grant permissions to the application user (adjust username as needed)
-- GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO breakpilot;
-- GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO breakpilot;
-- ============================================================================
-- MIGRATION COMPLETE
-- ============================================================================
SELECT 'Compliance migration completed. ' ||
(SELECT COUNT(*) FROM information_schema.tables
WHERE table_schema = 'public' AND table_name LIKE 'compliance_%') ||
' tables created.' AS status;

View File

@@ -1,241 +0,0 @@
-- ==============================================
-- Breakpilot Drive - Game Tables Migration
-- ==============================================
-- Run this migration to add game-related tables to PostgreSQL.
--
-- Execute with:
-- psql -h localhost -U breakpilot -d breakpilot -f add_game_tables.sql
-- Enable UUID extension if not already enabled
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- ==============================================
-- Student Learning State
-- ==============================================
-- Tracks the learning progress of each student across subjects.
-- This is the core table for adaptive difficulty.
CREATE TABLE IF NOT EXISTS student_learning_state (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
student_id UUID NOT NULL,
overall_level INTEGER DEFAULT 3 CHECK (overall_level >= 1 AND overall_level <= 5),
math_level DECIMAL(3,2) DEFAULT 3.0 CHECK (math_level >= 1.0 AND math_level <= 5.0),
german_level DECIMAL(3,2) DEFAULT 3.0 CHECK (german_level >= 1.0 AND german_level <= 5.0),
english_level DECIMAL(3,2) DEFAULT 3.0 CHECK (english_level >= 1.0 AND english_level <= 5.0),
total_play_time_minutes INTEGER DEFAULT 0,
total_sessions INTEGER DEFAULT 0,
questions_answered INTEGER DEFAULT 0,
questions_correct INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT unique_student_learning UNIQUE(student_id)
);
-- Index for fast lookups by student_id
CREATE INDEX IF NOT EXISTS idx_learning_state_student ON student_learning_state(student_id);
-- Comment on table
COMMENT ON TABLE student_learning_state IS 'Tracks learning progress for Breakpilot Drive game';
COMMENT ON COLUMN student_learning_state.overall_level IS 'Overall difficulty level 1-5 (1=Beginner/Grade 2-3, 5=Expert/Grade 6+)';
COMMENT ON COLUMN student_learning_state.math_level IS 'Math subject proficiency level';
COMMENT ON COLUMN student_learning_state.german_level IS 'German subject proficiency level';
COMMENT ON COLUMN student_learning_state.english_level IS 'English subject proficiency level';
-- ==============================================
-- Game Sessions
-- ==============================================
-- Records each game session played by a student.
CREATE TABLE IF NOT EXISTS game_sessions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
student_id UUID NOT NULL,
game_mode VARCHAR(20) NOT NULL CHECK (game_mode IN ('video', 'audio')),
duration_seconds INTEGER NOT NULL CHECK (duration_seconds >= 0),
distance_traveled DECIMAL(10,2),
score INTEGER NOT NULL DEFAULT 0,
questions_answered INTEGER DEFAULT 0,
questions_correct INTEGER DEFAULT 0,
difficulty_level INTEGER NOT NULL CHECK (difficulty_level >= 1 AND difficulty_level <= 5),
started_at TIMESTAMPTZ NOT NULL,
ended_at TIMESTAMPTZ DEFAULT NOW(),
metadata JSONB,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Indexes for common queries
CREATE INDEX IF NOT EXISTS idx_game_sessions_student ON game_sessions(student_id);
CREATE INDEX IF NOT EXISTS idx_game_sessions_date ON game_sessions(ended_at);
CREATE INDEX IF NOT EXISTS idx_game_sessions_score ON game_sessions(score DESC);
-- Comment on table
COMMENT ON TABLE game_sessions IS 'Records individual game sessions for Breakpilot Drive';
COMMENT ON COLUMN game_sessions.game_mode IS 'Game mode: video (visual) or audio (voice-guided)';
COMMENT ON COLUMN game_sessions.distance_traveled IS 'Distance traveled in game units';
COMMENT ON COLUMN game_sessions.metadata IS 'Additional session data in JSON format';
-- ==============================================
-- Game Quiz Answers
-- ==============================================
-- Tracks individual quiz answers for detailed analytics.
CREATE TABLE IF NOT EXISTS game_quiz_answers (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
session_id UUID REFERENCES game_sessions(id) ON DELETE CASCADE,
question_id VARCHAR(100) NOT NULL,
subject VARCHAR(50) NOT NULL CHECK (subject IN ('math', 'german', 'english', 'general')),
difficulty INTEGER NOT NULL CHECK (difficulty >= 1 AND difficulty <= 5),
is_correct BOOLEAN NOT NULL,
answer_time_ms INTEGER CHECK (answer_time_ms >= 0),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Indexes for analytics queries
CREATE INDEX IF NOT EXISTS idx_quiz_answers_session ON game_quiz_answers(session_id);
CREATE INDEX IF NOT EXISTS idx_quiz_answers_subject ON game_quiz_answers(subject);
CREATE INDEX IF NOT EXISTS idx_quiz_answers_correct ON game_quiz_answers(is_correct);
-- Comment on table
COMMENT ON TABLE game_quiz_answers IS 'Individual quiz answer records for learning analytics';
-- ==============================================
-- Trigger: Update updated_at timestamp
-- ==============================================
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ language 'plpgsql';
-- Apply trigger to student_learning_state
DROP TRIGGER IF EXISTS update_student_learning_state_updated_at ON student_learning_state;
CREATE TRIGGER update_student_learning_state_updated_at
BEFORE UPDATE ON student_learning_state
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
-- ==============================================
-- View: Student Summary Statistics
-- ==============================================
CREATE OR REPLACE VIEW game_student_summary AS
SELECT
sls.student_id,
sls.overall_level,
sls.math_level,
sls.german_level,
sls.english_level,
sls.total_play_time_minutes,
sls.total_sessions,
sls.questions_answered,
sls.questions_correct,
CASE WHEN sls.questions_answered > 0
THEN ROUND((sls.questions_correct::DECIMAL / sls.questions_answered) * 100, 1)
ELSE 0 END as accuracy_percent,
COALESCE(recent.recent_score, 0) as recent_score,
COALESCE(recent.recent_sessions, 0) as sessions_last_7_days
FROM student_learning_state sls
LEFT JOIN (
SELECT
student_id,
SUM(score) as recent_score,
COUNT(*) as recent_sessions
FROM game_sessions
WHERE ended_at > NOW() - INTERVAL '7 days'
GROUP BY student_id
) recent ON sls.student_id = recent.student_id;
COMMENT ON VIEW game_student_summary IS 'Summary statistics for each student including recent activity';
-- ==============================================
-- View: Daily Leaderboard
-- ==============================================
CREATE OR REPLACE VIEW game_daily_leaderboard AS
SELECT
student_id,
SUM(score) as total_score,
COUNT(*) as session_count,
SUM(questions_correct) as total_correct,
SUM(questions_answered) as total_questions,
RANK() OVER (ORDER BY SUM(score) DESC) as rank
FROM game_sessions
WHERE ended_at > NOW() - INTERVAL '1 day'
GROUP BY student_id
ORDER BY total_score DESC;
COMMENT ON VIEW game_daily_leaderboard IS 'Daily leaderboard for Breakpilot Drive';
-- ==============================================
-- Function: Calculate Level Adjustment
-- ==============================================
-- Returns the recommended level adjustment based on recent performance.
-- Returns: -1 (decrease), 0 (keep), 1 (increase)
CREATE OR REPLACE FUNCTION calculate_level_adjustment(p_student_id UUID)
RETURNS INTEGER AS $$
DECLARE
v_recent_accuracy DECIMAL;
v_recent_questions INTEGER;
BEGIN
-- Get accuracy from last 10 questions
SELECT
CASE WHEN COUNT(*) > 0
THEN SUM(CASE WHEN is_correct THEN 1 ELSE 0 END)::DECIMAL / COUNT(*)
ELSE 0 END,
COUNT(*)
INTO v_recent_accuracy, v_recent_questions
FROM (
SELECT is_correct
FROM game_quiz_answers qa
JOIN game_sessions gs ON qa.session_id = gs.id
WHERE gs.student_id = p_student_id
ORDER BY qa.created_at DESC
LIMIT 10
) recent;
-- Need at least 5 questions for adjustment
IF v_recent_questions < 5 THEN
RETURN 0;
END IF;
-- High accuracy (>=80%) -> increase level
IF v_recent_accuracy >= 0.8 THEN
RETURN 1;
END IF;
-- Low accuracy (<40%) -> decrease level
IF v_recent_accuracy < 0.4 THEN
RETURN -1;
END IF;
-- Keep current level
RETURN 0;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION calculate_level_adjustment IS 'Calculates recommended difficulty adjustment based on recent performance';
-- ==============================================
-- Grant Permissions (adjust user as needed)
-- ==============================================
-- GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO breakpilot;
-- GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO breakpilot;
-- GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO breakpilot;
-- ==============================================
-- Migration Complete Message
-- ==============================================
DO $$
BEGIN
RAISE NOTICE 'Breakpilot Drive game tables created successfully!';
RAISE NOTICE 'Tables: student_learning_state, game_sessions, game_quiz_answers';
RAISE NOTICE 'Views: game_student_summary, game_daily_leaderboard';
END $$;

View File

@@ -1,409 +0,0 @@
-- ==============================================
-- Jitsi Recordings & Transcription Tables Migration
-- ==============================================
-- Run this migration to add recording and transcription tables.
--
-- Execute with:
-- psql -h localhost -U breakpilot -d breakpilot_db -f add_recording_transcription_tables.sql
-- Enable UUID extension if not already enabled
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- ==============================================
-- Meeting Recording Consents (DSGVO)
-- ==============================================
-- Tracks consent for meeting recordings.
-- All participants must consent before recording starts.
CREATE TABLE IF NOT EXISTS meeting_recording_consents (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
meeting_id VARCHAR(255) NOT NULL,
user_id UUID,
consent_type VARCHAR(50) NOT NULL CHECK (consent_type IN ('opt_in', 'announced', 'implicit')),
all_participants_consented BOOLEAN DEFAULT FALSE,
participant_count INTEGER DEFAULT 0,
consented_count INTEGER DEFAULT 0,
consented_at TIMESTAMPTZ,
withdrawn_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Indexes
CREATE INDEX IF NOT EXISTS idx_recording_consents_meeting ON meeting_recording_consents(meeting_id);
CREATE INDEX IF NOT EXISTS idx_recording_consents_user ON meeting_recording_consents(user_id);
-- Comments
COMMENT ON TABLE meeting_recording_consents IS 'DSGVO-compliant consent tracking for meeting recordings';
COMMENT ON COLUMN meeting_recording_consents.consent_type IS 'Type: opt_in (explicit), announced (verbally announced), implicit (policy-based)';
COMMENT ON COLUMN meeting_recording_consents.withdrawn_at IS 'Set when consent is withdrawn (soft delete)';
-- ==============================================
-- Recordings
-- ==============================================
-- Stores metadata for recorded meetings.
CREATE TABLE IF NOT EXISTS recordings (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
meeting_id VARCHAR(255) NOT NULL,
jibri_session_id VARCHAR(255),
title VARCHAR(500),
storage_path VARCHAR(1000) NOT NULL,
audio_path VARCHAR(1000),
file_size_bytes BIGINT,
duration_seconds INTEGER,
participant_count INTEGER DEFAULT 0,
status VARCHAR(50) NOT NULL DEFAULT 'uploaded' CHECK (status IN ('uploaded', 'processing', 'ready', 'failed', 'deleted')),
created_by UUID,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
retention_days INTEGER DEFAULT 365,
deleted_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Indexes
CREATE INDEX IF NOT EXISTS idx_recordings_meeting ON recordings(meeting_id);
CREATE INDEX IF NOT EXISTS idx_recordings_status ON recordings(status);
CREATE INDEX IF NOT EXISTS idx_recordings_created_by ON recordings(created_by);
CREATE INDEX IF NOT EXISTS idx_recordings_recorded_at ON recordings(recorded_at);
-- Comments
COMMENT ON TABLE recordings IS 'Jitsi meeting recordings stored in MinIO';
COMMENT ON COLUMN recordings.storage_path IS 'Path in MinIO bucket: recordings/{recording_name}/video.mp4';
COMMENT ON COLUMN recordings.audio_path IS 'Extracted audio for transcription: recordings/{recording_name}/audio.wav';
COMMENT ON COLUMN recordings.retention_days IS 'Days until automatic deletion (DSGVO compliance)';
COMMENT ON COLUMN recordings.deleted_at IS 'Soft delete timestamp for DSGVO audit trail';
-- ==============================================
-- Transcriptions
-- ==============================================
-- Stores transcription metadata and full text.
CREATE TABLE IF NOT EXISTS transcriptions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
recording_id UUID NOT NULL REFERENCES recordings(id) ON DELETE CASCADE,
language VARCHAR(10) NOT NULL DEFAULT 'de',
model VARCHAR(100) NOT NULL DEFAULT 'large-v3',
status VARCHAR(50) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'queued', 'processing', 'completed', 'failed')),
full_text TEXT,
word_count INTEGER DEFAULT 0,
confidence_score FLOAT,
vtt_path VARCHAR(1000),
srt_path VARCHAR(1000),
json_path VARCHAR(1000),
error_message TEXT,
processing_started_at TIMESTAMPTZ,
processing_completed_at TIMESTAMPTZ,
processing_duration_seconds INTEGER,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Indexes
CREATE INDEX IF NOT EXISTS idx_transcriptions_recording ON transcriptions(recording_id);
CREATE INDEX IF NOT EXISTS idx_transcriptions_status ON transcriptions(status);
CREATE INDEX IF NOT EXISTS idx_transcriptions_language ON transcriptions(language);
-- Full-text search index for transcription content
CREATE INDEX IF NOT EXISTS idx_transcriptions_fulltext ON transcriptions USING gin(to_tsvector('german', COALESCE(full_text, '')));
-- Comments
COMMENT ON TABLE transcriptions IS 'Whisper transcriptions with speaker diarization';
COMMENT ON COLUMN transcriptions.model IS 'Whisper model used: tiny, base, small, medium, large-v3';
COMMENT ON COLUMN transcriptions.vtt_path IS 'WebVTT subtitle file path in MinIO';
COMMENT ON COLUMN transcriptions.srt_path IS 'SRT subtitle file path in MinIO';
COMMENT ON COLUMN transcriptions.json_path IS 'Full JSON with segments and speakers in MinIO';
-- ==============================================
-- Transcription Segments
-- ==============================================
-- Individual speech segments with speaker identification.
CREATE TABLE IF NOT EXISTS transcription_segments (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
transcription_id UUID NOT NULL REFERENCES transcriptions(id) ON DELETE CASCADE,
segment_index INTEGER NOT NULL,
start_time_ms INTEGER NOT NULL,
end_time_ms INTEGER NOT NULL,
text TEXT NOT NULL,
speaker_id VARCHAR(50),
speaker_name VARCHAR(255),
confidence FLOAT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Indexes
CREATE INDEX IF NOT EXISTS idx_segments_transcription ON transcription_segments(transcription_id);
CREATE INDEX IF NOT EXISTS idx_segments_speaker ON transcription_segments(speaker_id);
CREATE INDEX IF NOT EXISTS idx_segments_time ON transcription_segments(start_time_ms, end_time_ms);
-- Full-text search on segments
CREATE INDEX IF NOT EXISTS idx_segments_fulltext ON transcription_segments USING gin(to_tsvector('german', text));
-- Comments
COMMENT ON TABLE transcription_segments IS 'Individual speech segments with timestamps and speaker IDs';
COMMENT ON COLUMN transcription_segments.speaker_id IS 'pyannote speaker ID: SPEAKER_00, SPEAKER_01, etc.';
COMMENT ON COLUMN transcription_segments.speaker_name IS 'Optionally mapped to actual participant name';
-- ==============================================
-- Recording Audit Log (DSGVO)
-- ==============================================
-- Tracks all access and modifications for compliance.
CREATE TABLE IF NOT EXISTS recording_audit_log (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
recording_id UUID,
transcription_id UUID,
user_id UUID,
action VARCHAR(100) NOT NULL CHECK (action IN (
'created', 'viewed', 'downloaded', 'shared',
'transcription_started', 'transcription_completed',
'deleted', 'retention_expired', 'consent_withdrawn'
)),
ip_address INET,
user_agent TEXT,
metadata JSONB,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Indexes
CREATE INDEX IF NOT EXISTS idx_audit_recording ON recording_audit_log(recording_id);
CREATE INDEX IF NOT EXISTS idx_audit_transcription ON recording_audit_log(transcription_id);
CREATE INDEX IF NOT EXISTS idx_audit_user ON recording_audit_log(user_id);
CREATE INDEX IF NOT EXISTS idx_audit_action ON recording_audit_log(action);
CREATE INDEX IF NOT EXISTS idx_audit_created ON recording_audit_log(created_at);
-- Comments
COMMENT ON TABLE recording_audit_log IS 'DSGVO audit trail for all recording access';
COMMENT ON COLUMN recording_audit_log.action IS 'Type of action performed';
COMMENT ON COLUMN recording_audit_log.metadata IS 'Additional context (e.g., reason for deletion)';
-- ==============================================
-- Transcription Queue (RQ Job Tracking)
-- ==============================================
-- Tracks pending and completed transcription jobs.
CREATE TABLE IF NOT EXISTS transcription_queue (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
transcription_id UUID NOT NULL REFERENCES transcriptions(id) ON DELETE CASCADE,
job_id VARCHAR(255),
priority INTEGER DEFAULT 0,
status VARCHAR(50) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'queued', 'processing', 'completed', 'failed', 'cancelled')),
worker_id VARCHAR(255),
attempts INTEGER DEFAULT 0,
max_attempts INTEGER DEFAULT 3,
error_message TEXT,
queued_at TIMESTAMPTZ,
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Indexes
CREATE INDEX IF NOT EXISTS idx_queue_status ON transcription_queue(status);
CREATE INDEX IF NOT EXISTS idx_queue_priority ON transcription_queue(priority DESC, created_at ASC);
CREATE INDEX IF NOT EXISTS idx_queue_job ON transcription_queue(job_id);
-- Comments
COMMENT ON TABLE transcription_queue IS 'RQ job queue tracking for transcription workers';
-- ==============================================
-- Trigger: Update updated_at timestamp
-- ==============================================
CREATE OR REPLACE FUNCTION update_recording_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE 'plpgsql';
-- Apply triggers
DROP TRIGGER IF EXISTS update_recordings_updated_at ON recordings;
CREATE TRIGGER update_recordings_updated_at
BEFORE UPDATE ON recordings
FOR EACH ROW
EXECUTE FUNCTION update_recording_updated_at();
DROP TRIGGER IF EXISTS update_transcriptions_updated_at ON transcriptions;
CREATE TRIGGER update_transcriptions_updated_at
BEFORE UPDATE ON transcriptions
FOR EACH ROW
EXECUTE FUNCTION update_recording_updated_at();
DROP TRIGGER IF EXISTS update_consents_updated_at ON meeting_recording_consents;
CREATE TRIGGER update_consents_updated_at
BEFORE UPDATE ON meeting_recording_consents
FOR EACH ROW
EXECUTE FUNCTION update_recording_updated_at();
DROP TRIGGER IF EXISTS update_queue_updated_at ON transcription_queue;
CREATE TRIGGER update_queue_updated_at
BEFORE UPDATE ON transcription_queue
FOR EACH ROW
EXECUTE FUNCTION update_recording_updated_at();
-- ==============================================
-- View: Recording Overview
-- ==============================================
CREATE OR REPLACE VIEW recording_overview AS
SELECT
r.id,
r.meeting_id,
r.title,
r.status as recording_status,
r.duration_seconds,
r.participant_count,
r.recorded_at,
r.retention_days,
r.recorded_at + (r.retention_days || ' days')::INTERVAL as retention_expires_at,
t.id as transcription_id,
t.status as transcription_status,
t.language,
t.word_count,
t.confidence_score,
c.all_participants_consented,
c.consent_type
FROM recordings r
LEFT JOIN transcriptions t ON t.recording_id = r.id
LEFT JOIN meeting_recording_consents c ON c.meeting_id = r.meeting_id
WHERE r.deleted_at IS NULL;
COMMENT ON VIEW recording_overview IS 'Combined view of recordings with transcription and consent status';
-- ==============================================
-- View: Pending Transcriptions
-- ==============================================
CREATE OR REPLACE VIEW pending_transcriptions AS
SELECT
t.id,
t.recording_id,
r.storage_path,
r.audio_path,
t.language,
t.model,
q.priority,
q.attempts,
q.max_attempts,
q.created_at as queued_at
FROM transcriptions t
JOIN recordings r ON r.id = t.recording_id
LEFT JOIN transcription_queue q ON q.transcription_id = t.id
WHERE t.status IN ('pending', 'queued')
AND r.status = 'uploaded'
AND r.deleted_at IS NULL
ORDER BY q.priority DESC, q.created_at ASC;
COMMENT ON VIEW pending_transcriptions IS 'Queue of transcriptions waiting to be processed';
-- ==============================================
-- Function: Search Transcripts
-- ==============================================
CREATE OR REPLACE FUNCTION search_transcripts(
p_query TEXT,
p_language VARCHAR(10) DEFAULT 'de',
p_limit INTEGER DEFAULT 20
)
RETURNS TABLE (
transcription_id UUID,
recording_id UUID,
meeting_id VARCHAR(255),
title VARCHAR(500),
segment_id UUID,
segment_text TEXT,
start_time_ms INTEGER,
end_time_ms INTEGER,
speaker_id VARCHAR(50),
relevance FLOAT
) AS $$
BEGIN
RETURN QUERY
SELECT
t.id as transcription_id,
r.id as recording_id,
r.meeting_id,
r.title,
s.id as segment_id,
s.text as segment_text,
s.start_time_ms,
s.end_time_ms,
s.speaker_id,
ts_rank(to_tsvector('german', s.text), plainto_tsquery('german', p_query))::FLOAT as relevance
FROM transcription_segments s
JOIN transcriptions t ON t.id = s.transcription_id
JOIN recordings r ON r.id = t.recording_id
WHERE t.language = p_language
AND t.status = 'completed'
AND r.deleted_at IS NULL
AND to_tsvector('german', s.text) @@ plainto_tsquery('german', p_query)
ORDER BY relevance DESC
LIMIT p_limit;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION search_transcripts IS 'Full-text search across all transcription segments';
-- ==============================================
-- Function: Cleanup Expired Recordings
-- ==============================================
-- Call periodically to soft-delete recordings past retention.
CREATE OR REPLACE FUNCTION cleanup_expired_recordings()
RETURNS INTEGER AS $$
DECLARE
v_count INTEGER;
BEGIN
WITH expired AS (
UPDATE recordings
SET status = 'deleted',
deleted_at = NOW()
WHERE deleted_at IS NULL
AND status != 'deleted'
AND recorded_at + (retention_days || ' days')::INTERVAL < NOW()
RETURNING id
)
SELECT COUNT(*) INTO v_count FROM expired;
-- Log the cleanup action
IF v_count > 0 THEN
INSERT INTO recording_audit_log (action, metadata)
VALUES ('retention_expired', jsonb_build_object('count', v_count, 'timestamp', NOW()));
END IF;
RETURN v_count;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION cleanup_expired_recordings IS 'Soft-deletes recordings past their retention period';
-- ==============================================
-- Grant Permissions
-- ==============================================
-- GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO breakpilot;
-- GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO breakpilot;
-- GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO breakpilot;
-- ==============================================
-- Migration Complete Message
-- ==============================================
DO $$
BEGIN
RAISE NOTICE 'Recording & Transcription tables created successfully!';
RAISE NOTICE 'Tables: meeting_recording_consents, recordings, transcriptions, transcription_segments, recording_audit_log, transcription_queue';
RAISE NOTICE 'Views: recording_overview, pending_transcriptions';
RAISE NOTICE 'Functions: search_transcripts, cleanup_expired_recordings';
END $$;

View File

@@ -1,410 +0,0 @@
-- ==============================================
-- Breakpilot Drive - Educational Unit Tables Migration
-- ==============================================
-- Adds tables for the contextual learning unit system.
-- Supports FlightPath and StationLoop templates.
--
-- Execute with:
-- psql -h localhost -U breakpilot -d breakpilot -f add_unit_tables.sql
-- Enable UUID extension if not already enabled
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- ==============================================
-- Unit Definitions
-- ==============================================
-- Stores the configuration for each learning unit.
-- JSON definition contains stops, interactions, vocab, etc.
CREATE TABLE IF NOT EXISTS unit_definitions (
unit_id VARCHAR(100) PRIMARY KEY,
template VARCHAR(50) NOT NULL CHECK (template IN ('flight_path', 'station_loop')),
version VARCHAR(20) NOT NULL,
locale VARCHAR(10)[] DEFAULT ARRAY['de-DE'],
grade_band VARCHAR(10)[] DEFAULT ARRAY['5', '6'],
duration_minutes INTEGER NOT NULL CHECK (duration_minutes >= 3 AND duration_minutes <= 20),
difficulty VARCHAR(20) DEFAULT 'base' CHECK (difficulty IN ('base', 'advanced')),
definition JSONB NOT NULL,
is_published BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Index for filtering
CREATE INDEX IF NOT EXISTS idx_unit_definitions_template ON unit_definitions(template);
CREATE INDEX IF NOT EXISTS idx_unit_definitions_published ON unit_definitions(is_published);
CREATE INDEX IF NOT EXISTS idx_unit_definitions_locale ON unit_definitions USING GIN(locale);
CREATE INDEX IF NOT EXISTS idx_unit_definitions_grade ON unit_definitions USING GIN(grade_band);
-- Comments
COMMENT ON TABLE unit_definitions IS 'Stores unit configurations for contextual learning experiences';
COMMENT ON COLUMN unit_definitions.template IS 'Unit template type: flight_path (linear) or station_loop (hub-based)';
COMMENT ON COLUMN unit_definitions.definition IS 'Complete JSON definition including stops, interactions, vocab, etc.';
COMMENT ON COLUMN unit_definitions.grade_band IS 'Target grade levels (e.g., ["5", "6", "7"])';
-- ==============================================
-- Unit Sessions
-- ==============================================
-- Records each unit session played by a student.
CREATE TABLE IF NOT EXISTS unit_sessions (
session_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
unit_id VARCHAR(100) NOT NULL REFERENCES unit_definitions(unit_id),
student_id UUID NOT NULL,
locale VARCHAR(10) DEFAULT 'de-DE',
difficulty VARCHAR(20) DEFAULT 'base',
started_at TIMESTAMPTZ DEFAULT NOW(),
completed_at TIMESTAMPTZ,
aborted_at TIMESTAMPTZ,
duration_seconds INTEGER,
completion_rate DECIMAL(3,2) CHECK (completion_rate >= 0 AND completion_rate <= 1),
precheck_score DECIMAL(3,2) CHECK (precheck_score >= 0 AND precheck_score <= 1),
postcheck_score DECIMAL(3,2) CHECK (postcheck_score >= 0 AND postcheck_score <= 1),
stops_completed INTEGER DEFAULT 0,
total_stops INTEGER DEFAULT 0,
session_token VARCHAR(500),
metadata JSONB,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Indexes for common queries
CREATE INDEX IF NOT EXISTS idx_unit_sessions_student ON unit_sessions(student_id);
CREATE INDEX IF NOT EXISTS idx_unit_sessions_unit ON unit_sessions(unit_id);
CREATE INDEX IF NOT EXISTS idx_unit_sessions_completed ON unit_sessions(completed_at);
CREATE INDEX IF NOT EXISTS idx_unit_sessions_started ON unit_sessions(started_at DESC);
-- Comments
COMMENT ON TABLE unit_sessions IS 'Records individual unit playthrough sessions';
COMMENT ON COLUMN unit_sessions.completion_rate IS 'Percentage of stops completed (0.0 to 1.0)';
COMMENT ON COLUMN unit_sessions.precheck_score IS 'Score from pre-unit diagnostic quiz (0.0 to 1.0)';
COMMENT ON COLUMN unit_sessions.postcheck_score IS 'Score from post-unit diagnostic quiz (0.0 to 1.0)';
-- ==============================================
-- Unit Telemetry Events
-- ==============================================
-- Stores detailed telemetry events from unit sessions.
CREATE TABLE IF NOT EXISTS unit_telemetry (
id BIGSERIAL PRIMARY KEY,
session_id UUID NOT NULL REFERENCES unit_sessions(session_id) ON DELETE CASCADE,
event_type VARCHAR(50) NOT NULL,
stop_id VARCHAR(100),
event_timestamp TIMESTAMPTZ DEFAULT NOW(),
metrics JSONB,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Indexes for analytics
CREATE INDEX IF NOT EXISTS idx_unit_telemetry_session ON unit_telemetry(session_id);
CREATE INDEX IF NOT EXISTS idx_unit_telemetry_type ON unit_telemetry(event_type);
CREATE INDEX IF NOT EXISTS idx_unit_telemetry_stop ON unit_telemetry(stop_id);
CREATE INDEX IF NOT EXISTS idx_unit_telemetry_timestamp ON unit_telemetry(event_timestamp);
-- Partitioning hint (for production with high volume)
-- Consider partitioning by created_at for older data cleanup
-- Comments
COMMENT ON TABLE unit_telemetry IS 'Detailed telemetry events from unit sessions';
COMMENT ON COLUMN unit_telemetry.event_type IS 'Event type: stop_completed, hint_used, state_change, etc.';
COMMENT ON COLUMN unit_telemetry.metrics IS 'Event-specific metrics in JSON format';
-- ==============================================
-- Unit Stop Metrics
-- ==============================================
-- Aggregated metrics per stop per session.
CREATE TABLE IF NOT EXISTS unit_stop_metrics (
id BIGSERIAL PRIMARY KEY,
session_id UUID NOT NULL REFERENCES unit_sessions(session_id) ON DELETE CASCADE,
stop_id VARCHAR(100) NOT NULL,
completed BOOLEAN DEFAULT false,
success BOOLEAN,
attempts INTEGER DEFAULT 0,
time_seconds DECIMAL(10,2),
hints_used TEXT[],
completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT unique_session_stop UNIQUE(session_id, stop_id)
);
-- Indexes
CREATE INDEX IF NOT EXISTS idx_stop_metrics_session ON unit_stop_metrics(session_id);
CREATE INDEX IF NOT EXISTS idx_stop_metrics_stop ON unit_stop_metrics(stop_id);
CREATE INDEX IF NOT EXISTS idx_stop_metrics_success ON unit_stop_metrics(success);
-- Comments
COMMENT ON TABLE unit_stop_metrics IS 'Aggregated metrics for each stop in a unit session';
-- ==============================================
-- Unit Misconceptions
-- ==============================================
-- Tracks detected misconceptions per student.
CREATE TABLE IF NOT EXISTS unit_misconceptions (
id BIGSERIAL PRIMARY KEY,
student_id UUID NOT NULL,
unit_id VARCHAR(100) NOT NULL REFERENCES unit_definitions(unit_id),
misconception_id VARCHAR(100) NOT NULL,
stop_id VARCHAR(100),
detected_at TIMESTAMPTZ DEFAULT NOW(),
addressed BOOLEAN DEFAULT false,
addressed_at TIMESTAMPTZ,
session_id UUID REFERENCES unit_sessions(session_id),
CONSTRAINT unique_student_misconception UNIQUE(student_id, unit_id, misconception_id)
);
-- Indexes
CREATE INDEX IF NOT EXISTS idx_misconceptions_student ON unit_misconceptions(student_id);
CREATE INDEX IF NOT EXISTS idx_misconceptions_unit ON unit_misconceptions(unit_id);
CREATE INDEX IF NOT EXISTS idx_misconceptions_addressed ON unit_misconceptions(addressed);
-- Comments
COMMENT ON TABLE unit_misconceptions IS 'Tracks detected misconceptions for targeted remediation';
-- ==============================================
-- Trigger: Update updated_at timestamp
-- ==============================================
-- Reuse existing function if available, otherwise create
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'update_updated_at_column') THEN
CREATE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $func$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$func$ LANGUAGE plpgsql;
END IF;
END $$;
-- Apply triggers
DROP TRIGGER IF EXISTS update_unit_definitions_updated_at ON unit_definitions;
CREATE TRIGGER update_unit_definitions_updated_at
BEFORE UPDATE ON unit_definitions
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
DROP TRIGGER IF EXISTS update_unit_sessions_updated_at ON unit_sessions;
CREATE TRIGGER update_unit_sessions_updated_at
BEFORE UPDATE ON unit_sessions
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
-- ==============================================
-- View: Unit Session Summary
-- ==============================================
CREATE OR REPLACE VIEW unit_session_summary AS
SELECT
us.session_id,
us.unit_id,
ud.template,
us.student_id,
us.started_at,
us.completed_at,
us.duration_seconds,
us.completion_rate,
us.precheck_score,
us.postcheck_score,
CASE
WHEN us.precheck_score IS NOT NULL AND us.postcheck_score IS NOT NULL
THEN us.postcheck_score - us.precheck_score
ELSE NULL
END as learning_gain,
us.stops_completed,
us.total_stops,
CASE WHEN us.completed_at IS NOT NULL THEN true ELSE false END as is_completed
FROM unit_sessions us
JOIN unit_definitions ud ON us.unit_id = ud.unit_id;
COMMENT ON VIEW unit_session_summary IS 'Summary view of unit sessions with learning gain calculation';
-- ==============================================
-- View: Unit Analytics by Student
-- ==============================================
CREATE OR REPLACE VIEW unit_student_analytics AS
SELECT
student_id,
COUNT(DISTINCT unit_id) as units_attempted,
COUNT(*) as total_sessions,
COUNT(*) FILTER (WHERE completed_at IS NOT NULL) as completed_sessions,
AVG(completion_rate) as avg_completion_rate,
AVG(precheck_score) as avg_precheck_score,
AVG(postcheck_score) as avg_postcheck_score,
AVG(CASE
WHEN precheck_score IS NOT NULL AND postcheck_score IS NOT NULL
THEN postcheck_score - precheck_score
ELSE NULL
END) as avg_learning_gain,
SUM(duration_seconds) / 60 as total_minutes_played,
MAX(completed_at) as last_completed_at
FROM unit_sessions
GROUP BY student_id;
COMMENT ON VIEW unit_student_analytics IS 'Aggregated analytics per student across all units';
-- ==============================================
-- View: Unit Performance
-- ==============================================
CREATE OR REPLACE VIEW unit_performance AS
SELECT
ud.unit_id,
ud.template,
ud.difficulty,
COUNT(us.session_id) as total_sessions,
COUNT(*) FILTER (WHERE us.completed_at IS NOT NULL) as completed_sessions,
ROUND(
COUNT(*) FILTER (WHERE us.completed_at IS NOT NULL)::DECIMAL /
NULLIF(COUNT(us.session_id), 0) * 100,
1
) as completion_percent,
AVG(us.duration_seconds) / 60 as avg_duration_minutes,
AVG(us.completion_rate) as avg_completion_rate,
AVG(CASE
WHEN us.precheck_score IS NOT NULL AND us.postcheck_score IS NOT NULL
THEN us.postcheck_score - us.precheck_score
ELSE NULL
END) as avg_learning_gain
FROM unit_definitions ud
LEFT JOIN unit_sessions us ON ud.unit_id = us.unit_id
GROUP BY ud.unit_id, ud.template, ud.difficulty;
COMMENT ON VIEW unit_performance IS 'Performance metrics per unit for content optimization';
-- ==============================================
-- Function: Get Recommended Units for Student
-- ==============================================
CREATE OR REPLACE FUNCTION get_recommended_units(
p_student_id UUID,
p_grade VARCHAR(10) DEFAULT NULL,
p_locale VARCHAR(10) DEFAULT 'de-DE',
p_limit INTEGER DEFAULT 5
)
RETURNS TABLE (
unit_id VARCHAR(100),
template VARCHAR(50),
difficulty VARCHAR(20),
reason TEXT
) AS $$
BEGIN
RETURN QUERY
SELECT
ud.unit_id,
ud.template,
ud.difficulty,
CASE
-- Never played
WHEN NOT EXISTS (
SELECT 1 FROM unit_sessions us
WHERE us.student_id = p_student_id AND us.unit_id = ud.unit_id
) THEN 'Neu: Noch nicht gespielt'
-- Played but not completed
WHEN NOT EXISTS (
SELECT 1 FROM unit_sessions us
WHERE us.student_id = p_student_id
AND us.unit_id = ud.unit_id
AND us.completed_at IS NOT NULL
) THEN 'Fortsetzen: Noch nicht abgeschlossen'
-- Completed with low postcheck score
WHEN EXISTS (
SELECT 1 FROM unit_sessions us
WHERE us.student_id = p_student_id
AND us.unit_id = ud.unit_id
AND us.postcheck_score < 0.6
) THEN 'Wiederholen: Verständnis vertiefen'
ELSE 'Abgeschlossen'
END as reason
FROM unit_definitions ud
WHERE ud.is_published = true
AND (p_locale = ANY(ud.locale) OR p_locale IS NULL)
AND (p_grade = ANY(ud.grade_band) OR p_grade IS NULL)
ORDER BY
-- Prioritize: new > incomplete > low score > completed
CASE
WHEN NOT EXISTS (
SELECT 1 FROM unit_sessions us
WHERE us.student_id = p_student_id AND us.unit_id = ud.unit_id
) THEN 1
WHEN NOT EXISTS (
SELECT 1 FROM unit_sessions us
WHERE us.student_id = p_student_id
AND us.unit_id = ud.unit_id
AND us.completed_at IS NOT NULL
) THEN 2
WHEN EXISTS (
SELECT 1 FROM unit_sessions us
WHERE us.student_id = p_student_id
AND us.unit_id = ud.unit_id
AND us.postcheck_score < 0.6
) THEN 3
ELSE 4
END,
ud.unit_id
LIMIT p_limit;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION get_recommended_units IS 'Returns recommended units for a student based on completion status';
-- ==============================================
-- Sample Data: Demo Unit Definition
-- ==============================================
-- Insert a demo unit for testing (will be replaced by real content)
INSERT INTO unit_definitions (unit_id, template, version, locale, grade_band, duration_minutes, difficulty, definition, is_published)
VALUES (
'demo_unit_v1',
'flight_path',
'1.0.0',
ARRAY['de-DE'],
ARRAY['5', '6', '7'],
5,
'base',
'{
"unit_id": "demo_unit_v1",
"template": "flight_path",
"version": "1.0.0",
"learning_objectives": ["Demo: Grundfunktion testen", "Demo: Navigation verstehen"],
"stops": [
{"stop_id": "stop_1", "label": {"de-DE": "Start"}, "interaction": {"type": "aim_and_pass"}},
{"stop_id": "stop_2", "label": {"de-DE": "Mitte"}, "interaction": {"type": "aim_and_pass"}},
{"stop_id": "stop_3", "label": {"de-DE": "Ende"}, "interaction": {"type": "aim_and_pass"}}
],
"teacher_controls": {"allow_skip": true, "allow_replay": true}
}'::jsonb,
true
)
ON CONFLICT (unit_id) DO UPDATE SET
definition = EXCLUDED.definition,
updated_at = NOW();
-- ==============================================
-- Grant Permissions (adjust user as needed)
-- ==============================================
-- GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO breakpilot;
-- GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO breakpilot;
-- GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO breakpilot;
-- ==============================================
-- Migration Complete Message
-- ==============================================
DO $$
BEGIN
RAISE NOTICE 'Breakpilot Drive unit tables created successfully!';
RAISE NOTICE 'Tables: unit_definitions, unit_sessions, unit_telemetry, unit_stop_metrics, unit_misconceptions';
RAISE NOTICE 'Views: unit_session_summary, unit_student_analytics, unit_performance';
RAISE NOTICE 'Functions: get_recommended_units';
END $$;

View File

@@ -1,204 +0,0 @@
-- BreakPilot Compliance SDK - Database Initialization
-- Mac Mini Deployment
-- Create extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- Schema: SDK State
CREATE TABLE IF NOT EXISTS sdk_state (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id VARCHAR(255) NOT NULL UNIQUE,
state JSONB NOT NULL DEFAULT '{}',
version INTEGER NOT NULL DEFAULT 1,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- Schema: Consents
CREATE TABLE IF NOT EXISTS consents (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id VARCHAR(255) NOT NULL,
user_id VARCHAR(255) NOT NULL,
purpose VARCHAR(50) NOT NULL,
granted BOOLEAN NOT NULL DEFAULT false,
source VARCHAR(100),
ip_address VARCHAR(45),
user_agent TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
revoked_at TIMESTAMP WITH TIME ZONE,
INDEX idx_consents_tenant (tenant_id),
INDEX idx_consents_user (tenant_id, user_id)
);
-- Schema: DSR Requests
CREATE TABLE IF NOT EXISTS dsr_requests (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id VARCHAR(255) NOT NULL,
request_type VARCHAR(50) NOT NULL,
email VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
notes TEXT,
submitted_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
deadline TIMESTAMP WITH TIME ZONE,
completed_at TIMESTAMP WITH TIME ZONE,
INDEX idx_dsr_tenant (tenant_id),
INDEX idx_dsr_status (status)
);
-- Schema: Processing Activities (VVT)
CREATE TABLE IF NOT EXISTS processing_activities (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
purpose TEXT,
legal_basis VARCHAR(100),
data_categories TEXT[],
data_subjects TEXT[],
recipients TEXT[],
retention_period VARCHAR(100),
security_measures TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
INDEX idx_activities_tenant (tenant_id)
);
-- Schema: TOMs
CREATE TABLE IF NOT EXISTS toms (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id VARCHAR(255) NOT NULL,
category VARCHAR(50) NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT,
implementation_status VARCHAR(50) DEFAULT 'PLANNED',
responsible VARCHAR(255),
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
INDEX idx_toms_tenant (tenant_id)
);
-- Schema: Controls
CREATE TABLE IF NOT EXISTS controls (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id VARCHAR(255) NOT NULL,
control_id VARCHAR(50) NOT NULL,
name VARCHAR(255) NOT NULL,
domain VARCHAR(50),
description TEXT,
implementation_status VARCHAR(50) DEFAULT 'NOT_IMPLEMENTED',
responsible VARCHAR(255),
evidence_ids UUID[],
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
INDEX idx_controls_tenant (tenant_id),
UNIQUE (tenant_id, control_id)
);
-- Schema: Evidence
CREATE TABLE IF NOT EXISTS evidence (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id VARCHAR(255) NOT NULL,
title VARCHAR(255) NOT NULL,
type VARCHAR(50) NOT NULL,
file_path VARCHAR(500),
description TEXT,
valid_from TIMESTAMP WITH TIME ZONE,
valid_until TIMESTAMP WITH TIME ZONE,
uploaded_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
INDEX idx_evidence_tenant (tenant_id)
);
-- Schema: Risks
CREATE TABLE IF NOT EXISTS risks (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id VARCHAR(255) NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT,
likelihood INTEGER CHECK (likelihood BETWEEN 1 AND 5),
impact INTEGER CHECK (impact BETWEEN 1 AND 5),
severity VARCHAR(20),
status VARCHAR(50) DEFAULT 'IDENTIFIED',
mitigation TEXT,
control_ids UUID[],
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
INDEX idx_risks_tenant (tenant_id)
);
-- Schema: Security Findings
CREATE TABLE IF NOT EXISTS security_findings (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id VARCHAR(255) NOT NULL,
tool VARCHAR(50) NOT NULL,
severity VARCHAR(20) NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT,
file_path VARCHAR(500),
line_number INTEGER,
recommendation TEXT,
status VARCHAR(50) DEFAULT 'OPEN',
cve VARCHAR(50),
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
INDEX idx_findings_tenant (tenant_id),
INDEX idx_findings_severity (severity)
);
-- Schema: Audit Log
CREATE TABLE IF NOT EXISTS audit_log (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id VARCHAR(255) NOT NULL,
user_id VARCHAR(255),
action VARCHAR(100) NOT NULL,
resource_type VARCHAR(100),
resource_id VARCHAR(255),
details JSONB,
ip_address VARCHAR(45),
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
INDEX idx_audit_tenant (tenant_id),
INDEX idx_audit_created (created_at)
);
-- Function: Update timestamp
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Triggers for updated_at
CREATE TRIGGER trg_sdk_state_updated
BEFORE UPDATE ON sdk_state
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER trg_activities_updated
BEFORE UPDATE ON processing_activities
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER trg_toms_updated
BEFORE UPDATE ON toms
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER trg_controls_updated
BEFORE UPDATE ON controls
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER trg_risks_updated
BEFORE UPDATE ON risks
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
-- Initial data
INSERT INTO sdk_state (tenant_id, state)
VALUES ('default', '{"completedSteps": [], "currentStep": "overview"}')
ON CONFLICT (tenant_id) DO NOTHING;

View File

@@ -1,2 +0,0 @@
# Placeholder fuer Unity WebGL Build
# Dieser Ordner wird vom Unity Build Process befuellt

View File

@@ -1,390 +0,0 @@
/**
* Consent SDK Types
*
* DSGVO/TTDSG-konforme Typdefinitionen für das Consent Management System.
*/
/**
* Standard-Consent-Kategorien nach IAB TCF 2.2
*/
type ConsentCategory = 'essential' | 'functional' | 'analytics' | 'marketing' | 'social';
/**
* Consent-Status pro Kategorie
*/
type ConsentCategories = Record<ConsentCategory, boolean>;
/**
* Consent-Status pro Vendor
*/
type ConsentVendors = Record<string, boolean>;
/**
* Aktueller Consent-Zustand
*/
interface ConsentState {
/** Consent pro Kategorie */
categories: ConsentCategories;
/** Consent pro Vendor (optional, für granulare Kontrolle) */
vendors: ConsentVendors;
/** Zeitstempel der letzten Aenderung */
timestamp: string;
/** SDK-Version bei Erstellung */
version: string;
/** Eindeutige Consent-ID vom Backend */
consentId?: string;
/** Ablaufdatum */
expiresAt?: string;
/** IAB TCF String (falls aktiviert) */
tcfString?: string;
}
/**
* UI-Position des Banners
*/
type BannerPosition = 'bottom' | 'top' | 'center';
/**
* Banner-Layout
*/
type BannerLayout = 'bar' | 'modal' | 'floating';
/**
* Farbschema
*/
type BannerTheme = 'light' | 'dark' | 'auto';
/**
* UI-Konfiguration
*/
interface ConsentUIConfig {
/** Position des Banners */
position?: BannerPosition;
/** Layout-Typ */
layout?: BannerLayout;
/** Farbschema */
theme?: BannerTheme;
/** Pfad zu Custom CSS */
customCss?: string;
/** z-index fuer Banner */
zIndex?: number;
/** Scroll blockieren bei Modal */
blockScrollOnModal?: boolean;
/** Custom Container-ID */
containerId?: string;
}
/**
* Consent-Verhaltens-Konfiguration
*/
interface ConsentBehaviorConfig {
/** Muss Nutzer interagieren? */
required?: boolean;
/** "Alle ablehnen" Button sichtbar */
rejectAllVisible?: boolean;
/** "Alle akzeptieren" Button sichtbar */
acceptAllVisible?: boolean;
/** Einzelne Kategorien waehlbar */
granularControl?: boolean;
/** Einzelne Vendors waehlbar */
vendorControl?: boolean;
/** Auswahl speichern */
rememberChoice?: boolean;
/** Speicherdauer in Tagen */
rememberDays?: number;
/** Nur in EU anzeigen (Geo-Targeting) */
geoTargeting?: boolean;
/** Erneut nachfragen nach X Tagen */
recheckAfterDays?: number;
}
/**
* TCF 2.2 Konfiguration
*/
interface TCFConfig {
/** TCF aktivieren */
enabled?: boolean;
/** CMP ID */
cmpId?: number;
/** CMP Version */
cmpVersion?: number;
}
/**
* PWA-spezifische Konfiguration
*/
interface PWAConfig {
/** Offline-Unterstuetzung aktivieren */
offlineSupport?: boolean;
/** Bei Reconnect synchronisieren */
syncOnReconnect?: boolean;
/** Cache-Strategie */
cacheStrategy?: 'stale-while-revalidate' | 'network-first' | 'cache-first';
}
/**
* Haupt-Konfiguration fuer ConsentManager
*/
interface ConsentConfig {
/** API-Endpunkt fuer Consent-Backend */
apiEndpoint: string;
/** Site-ID */
siteId: string;
/** Sprache (ISO 639-1) */
language?: string;
/** Fallback-Sprache */
fallbackLanguage?: string;
/** UI-Konfiguration */
ui?: ConsentUIConfig;
/** Consent-Verhaltens-Konfiguration */
consent?: ConsentBehaviorConfig;
/** Aktive Kategorien */
categories?: ConsentCategory[];
/** TCF 2.2 Konfiguration */
tcf?: TCFConfig;
/** PWA-Konfiguration */
pwa?: PWAConfig;
/** Callback bei Consent-Aenderung */
onConsentChange?: (consent: ConsentState) => void;
/** Callback wenn Banner angezeigt wird */
onBannerShow?: () => void;
/** Callback wenn Banner geschlossen wird */
onBannerHide?: () => void;
/** Callback bei Fehler */
onError?: (error: Error) => void;
/** Debug-Modus aktivieren */
debug?: boolean;
}
/**
* Angular Integration fuer @breakpilot/consent-sdk
*
* @example
* ```typescript
* // app.module.ts
* import { ConsentModule } from '@breakpilot/consent-sdk/angular';
*
* @NgModule({
* imports: [
* ConsentModule.forRoot({
* apiEndpoint: 'https://consent.example.com/api/v1',
* siteId: 'site_abc123',
* }),
* ],
* })
* export class AppModule {}
* ```
*/
/**
* ConsentService Interface fuer Angular DI
*
* @example
* ```typescript
* @Component({...})
* export class MyComponent {
* constructor(private consent: ConsentService) {
* if (this.consent.hasConsent('analytics')) {
* // Analytics laden
* }
* }
* }
* ```
*/
interface IConsentService {
/** Initialisiert? */
readonly isInitialized: boolean;
/** Laedt noch? */
readonly isLoading: boolean;
/** Banner sichtbar? */
readonly isBannerVisible: boolean;
/** Aktueller Consent-Zustand */
readonly consent: ConsentState | null;
/** Muss Consent eingeholt werden? */
readonly needsConsent: boolean;
/** Prueft Consent fuer Kategorie */
hasConsent(category: ConsentCategory): boolean;
/** Alle akzeptieren */
acceptAll(): Promise<void>;
/** Alle ablehnen */
rejectAll(): Promise<void>;
/** Auswahl speichern */
saveSelection(categories: Partial<ConsentCategories>): Promise<void>;
/** Banner anzeigen */
showBanner(): void;
/** Banner ausblenden */
hideBanner(): void;
/** Einstellungen oeffnen */
showSettings(): void;
}
/**
* ConsentService - Angular Service Wrapper
*
* Diese Klasse kann als Angular Service registriert werden:
*
* @example
* ```typescript
* // consent.service.ts
* import { Injectable } from '@angular/core';
* import { ConsentServiceBase } from '@breakpilot/consent-sdk/angular';
*
* @Injectable({ providedIn: 'root' })
* export class ConsentService extends ConsentServiceBase {
* constructor() {
* super({
* apiEndpoint: environment.consentApiEndpoint,
* siteId: environment.siteId,
* });
* }
* }
* ```
*/
declare class ConsentServiceBase implements IConsentService {
private manager;
private _consent;
private _isInitialized;
private _isLoading;
private _isBannerVisible;
private changeCallbacks;
private bannerShowCallbacks;
private bannerHideCallbacks;
constructor(config: ConsentConfig);
get isInitialized(): boolean;
get isLoading(): boolean;
get isBannerVisible(): boolean;
get consent(): ConsentState | null;
get needsConsent(): boolean;
hasConsent(category: ConsentCategory): boolean;
acceptAll(): Promise<void>;
rejectAll(): Promise<void>;
saveSelection(categories: Partial<ConsentCategories>): Promise<void>;
showBanner(): void;
hideBanner(): void;
showSettings(): void;
/**
* Registriert Callback fuer Consent-Aenderungen
* (fuer Angular Change Detection)
*/
onConsentChange(callback: (consent: ConsentState) => void): () => void;
/**
* Registriert Callback wenn Banner angezeigt wird
*/
onBannerShow(callback: () => void): () => void;
/**
* Registriert Callback wenn Banner ausgeblendet wird
*/
onBannerHide(callback: () => void): () => void;
private setupEventListeners;
private initialize;
}
/**
* Konfiguration fuer ConsentModule.forRoot()
*/
interface ConsentModuleConfig extends ConsentConfig {
}
/**
* Token fuer Dependency Injection
* Verwendung mit Angular @Inject():
*
* @example
* ```typescript
* constructor(@Inject(CONSENT_CONFIG) private config: ConsentConfig) {}
* ```
*/
declare const CONSENT_CONFIG = "CONSENT_CONFIG";
declare const CONSENT_SERVICE = "CONSENT_SERVICE";
/**
* Factory fuer ConsentService
*
* @example
* ```typescript
* // app.module.ts
* providers: [
* { provide: CONSENT_CONFIG, useValue: { apiEndpoint: '...', siteId: '...' } },
* { provide: CONSENT_SERVICE, useFactory: consentServiceFactory, deps: [CONSENT_CONFIG] },
* ]
* ```
*/
declare function consentServiceFactory(config: ConsentConfig): ConsentServiceBase;
/**
* ConsentModule - Angular Module
*
* Dies ist eine Template-Definition. Fuer echte Angular-Nutzung
* muss ein separates Angular Library Package erstellt werden.
*
* @example
* ```typescript
* // In einem Angular Library Package:
* @NgModule({
* declarations: [ConsentBannerComponent, ConsentGateDirective],
* exports: [ConsentBannerComponent, ConsentGateDirective],
* })
* export class ConsentModule {
* static forRoot(config: ConsentModuleConfig): ModuleWithProviders<ConsentModule> {
* return {
* ngModule: ConsentModule,
* providers: [
* { provide: CONSENT_CONFIG, useValue: config },
* { provide: CONSENT_SERVICE, useFactory: consentServiceFactory, deps: [CONSENT_CONFIG] },
* ],
* };
* }
* }
* ```
*/
declare const ConsentModuleDefinition: {
/**
* Providers fuer Root-Module
*/
forRoot: (config: ConsentModuleConfig) => {
provide: string;
useValue: ConsentModuleConfig;
};
};
/**
* ConsentBannerComponent Template
*
* Fuer Angular Library Implementation:
*
* @example
* ```typescript
* @Component({
* selector: 'bp-consent-banner',
* template: CONSENT_BANNER_TEMPLATE,
* styles: [CONSENT_BANNER_STYLES],
* })
* export class ConsentBannerComponent {
* constructor(public consent: ConsentService) {}
* }
* ```
*/
declare const CONSENT_BANNER_TEMPLATE = "\n<div\n *ngIf=\"consent.isBannerVisible\"\n class=\"bp-consent-banner\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Cookie-Einstellungen\"\n>\n <div class=\"bp-consent-banner-content\">\n <h2>Datenschutzeinstellungen</h2>\n <p>\n Wir nutzen Cookies und \u00E4hnliche Technologien, um Ihnen ein optimales\n Nutzererlebnis zu bieten.\n </p>\n <div class=\"bp-consent-banner-actions\">\n <button\n type=\"button\"\n class=\"bp-consent-btn bp-consent-btn-reject\"\n (click)=\"consent.rejectAll()\"\n >\n Alle ablehnen\n </button>\n <button\n type=\"button\"\n class=\"bp-consent-btn bp-consent-btn-settings\"\n (click)=\"consent.showSettings()\"\n >\n Einstellungen\n </button>\n <button\n type=\"button\"\n class=\"bp-consent-btn bp-consent-btn-accept\"\n (click)=\"consent.acceptAll()\"\n >\n Alle akzeptieren\n </button>\n </div>\n </div>\n</div>\n";
/**
* ConsentGateDirective Template
*
* @example
* ```typescript
* @Directive({
* selector: '[bpConsentGate]',
* })
* export class ConsentGateDirective implements OnInit, OnDestroy {
* @Input('bpConsentGate') category!: ConsentCategory;
*
* private unsubscribe?: () => void;
*
* constructor(
* private templateRef: TemplateRef<any>,
* private viewContainer: ViewContainerRef,
* private consent: ConsentService
* ) {}
*
* ngOnInit() {
* this.updateView();
* this.unsubscribe = this.consent.onConsentChange(() => this.updateView());
* }
*
* ngOnDestroy() {
* this.unsubscribe?.();
* }
*
* private updateView() {
* if (this.consent.hasConsent(this.category)) {
* this.viewContainer.createEmbeddedView(this.templateRef);
* } else {
* this.viewContainer.clear();
* }
* }
* }
* ```
*/
declare const CONSENT_GATE_USAGE = "\n<!-- Verwendung in Templates -->\n<div *bpConsentGate=\"'analytics'\">\n <analytics-component></analytics-component>\n</div>\n\n<!-- Mit else Template -->\n<ng-container *bpConsentGate=\"'marketing'; else placeholder\">\n <marketing-component></marketing-component>\n</ng-container>\n<ng-template #placeholder>\n <p>Bitte akzeptieren Sie Marketing-Cookies.</p>\n</ng-template>\n";
export { CONSENT_BANNER_TEMPLATE, CONSENT_CONFIG, CONSENT_GATE_USAGE, CONSENT_SERVICE, type ConsentCategories, type ConsentCategory, type ConsentConfig, type ConsentModuleConfig, ConsentModuleDefinition, ConsentServiceBase, type ConsentState, type IConsentService, consentServiceFactory };

View File

@@ -1,390 +0,0 @@
/**
* Consent SDK Types
*
* DSGVO/TTDSG-konforme Typdefinitionen für das Consent Management System.
*/
/**
* Standard-Consent-Kategorien nach IAB TCF 2.2
*/
type ConsentCategory = 'essential' | 'functional' | 'analytics' | 'marketing' | 'social';
/**
* Consent-Status pro Kategorie
*/
type ConsentCategories = Record<ConsentCategory, boolean>;
/**
* Consent-Status pro Vendor
*/
type ConsentVendors = Record<string, boolean>;
/**
* Aktueller Consent-Zustand
*/
interface ConsentState {
/** Consent pro Kategorie */
categories: ConsentCategories;
/** Consent pro Vendor (optional, für granulare Kontrolle) */
vendors: ConsentVendors;
/** Zeitstempel der letzten Aenderung */
timestamp: string;
/** SDK-Version bei Erstellung */
version: string;
/** Eindeutige Consent-ID vom Backend */
consentId?: string;
/** Ablaufdatum */
expiresAt?: string;
/** IAB TCF String (falls aktiviert) */
tcfString?: string;
}
/**
* UI-Position des Banners
*/
type BannerPosition = 'bottom' | 'top' | 'center';
/**
* Banner-Layout
*/
type BannerLayout = 'bar' | 'modal' | 'floating';
/**
* Farbschema
*/
type BannerTheme = 'light' | 'dark' | 'auto';
/**
* UI-Konfiguration
*/
interface ConsentUIConfig {
/** Position des Banners */
position?: BannerPosition;
/** Layout-Typ */
layout?: BannerLayout;
/** Farbschema */
theme?: BannerTheme;
/** Pfad zu Custom CSS */
customCss?: string;
/** z-index fuer Banner */
zIndex?: number;
/** Scroll blockieren bei Modal */
blockScrollOnModal?: boolean;
/** Custom Container-ID */
containerId?: string;
}
/**
* Consent-Verhaltens-Konfiguration
*/
interface ConsentBehaviorConfig {
/** Muss Nutzer interagieren? */
required?: boolean;
/** "Alle ablehnen" Button sichtbar */
rejectAllVisible?: boolean;
/** "Alle akzeptieren" Button sichtbar */
acceptAllVisible?: boolean;
/** Einzelne Kategorien waehlbar */
granularControl?: boolean;
/** Einzelne Vendors waehlbar */
vendorControl?: boolean;
/** Auswahl speichern */
rememberChoice?: boolean;
/** Speicherdauer in Tagen */
rememberDays?: number;
/** Nur in EU anzeigen (Geo-Targeting) */
geoTargeting?: boolean;
/** Erneut nachfragen nach X Tagen */
recheckAfterDays?: number;
}
/**
* TCF 2.2 Konfiguration
*/
interface TCFConfig {
/** TCF aktivieren */
enabled?: boolean;
/** CMP ID */
cmpId?: number;
/** CMP Version */
cmpVersion?: number;
}
/**
* PWA-spezifische Konfiguration
*/
interface PWAConfig {
/** Offline-Unterstuetzung aktivieren */
offlineSupport?: boolean;
/** Bei Reconnect synchronisieren */
syncOnReconnect?: boolean;
/** Cache-Strategie */
cacheStrategy?: 'stale-while-revalidate' | 'network-first' | 'cache-first';
}
/**
* Haupt-Konfiguration fuer ConsentManager
*/
interface ConsentConfig {
/** API-Endpunkt fuer Consent-Backend */
apiEndpoint: string;
/** Site-ID */
siteId: string;
/** Sprache (ISO 639-1) */
language?: string;
/** Fallback-Sprache */
fallbackLanguage?: string;
/** UI-Konfiguration */
ui?: ConsentUIConfig;
/** Consent-Verhaltens-Konfiguration */
consent?: ConsentBehaviorConfig;
/** Aktive Kategorien */
categories?: ConsentCategory[];
/** TCF 2.2 Konfiguration */
tcf?: TCFConfig;
/** PWA-Konfiguration */
pwa?: PWAConfig;
/** Callback bei Consent-Aenderung */
onConsentChange?: (consent: ConsentState) => void;
/** Callback wenn Banner angezeigt wird */
onBannerShow?: () => void;
/** Callback wenn Banner geschlossen wird */
onBannerHide?: () => void;
/** Callback bei Fehler */
onError?: (error: Error) => void;
/** Debug-Modus aktivieren */
debug?: boolean;
}
/**
* Angular Integration fuer @breakpilot/consent-sdk
*
* @example
* ```typescript
* // app.module.ts
* import { ConsentModule } from '@breakpilot/consent-sdk/angular';
*
* @NgModule({
* imports: [
* ConsentModule.forRoot({
* apiEndpoint: 'https://consent.example.com/api/v1',
* siteId: 'site_abc123',
* }),
* ],
* })
* export class AppModule {}
* ```
*/
/**
* ConsentService Interface fuer Angular DI
*
* @example
* ```typescript
* @Component({...})
* export class MyComponent {
* constructor(private consent: ConsentService) {
* if (this.consent.hasConsent('analytics')) {
* // Analytics laden
* }
* }
* }
* ```
*/
interface IConsentService {
/** Initialisiert? */
readonly isInitialized: boolean;
/** Laedt noch? */
readonly isLoading: boolean;
/** Banner sichtbar? */
readonly isBannerVisible: boolean;
/** Aktueller Consent-Zustand */
readonly consent: ConsentState | null;
/** Muss Consent eingeholt werden? */
readonly needsConsent: boolean;
/** Prueft Consent fuer Kategorie */
hasConsent(category: ConsentCategory): boolean;
/** Alle akzeptieren */
acceptAll(): Promise<void>;
/** Alle ablehnen */
rejectAll(): Promise<void>;
/** Auswahl speichern */
saveSelection(categories: Partial<ConsentCategories>): Promise<void>;
/** Banner anzeigen */
showBanner(): void;
/** Banner ausblenden */
hideBanner(): void;
/** Einstellungen oeffnen */
showSettings(): void;
}
/**
* ConsentService - Angular Service Wrapper
*
* Diese Klasse kann als Angular Service registriert werden:
*
* @example
* ```typescript
* // consent.service.ts
* import { Injectable } from '@angular/core';
* import { ConsentServiceBase } from '@breakpilot/consent-sdk/angular';
*
* @Injectable({ providedIn: 'root' })
* export class ConsentService extends ConsentServiceBase {
* constructor() {
* super({
* apiEndpoint: environment.consentApiEndpoint,
* siteId: environment.siteId,
* });
* }
* }
* ```
*/
declare class ConsentServiceBase implements IConsentService {
private manager;
private _consent;
private _isInitialized;
private _isLoading;
private _isBannerVisible;
private changeCallbacks;
private bannerShowCallbacks;
private bannerHideCallbacks;
constructor(config: ConsentConfig);
get isInitialized(): boolean;
get isLoading(): boolean;
get isBannerVisible(): boolean;
get consent(): ConsentState | null;
get needsConsent(): boolean;
hasConsent(category: ConsentCategory): boolean;
acceptAll(): Promise<void>;
rejectAll(): Promise<void>;
saveSelection(categories: Partial<ConsentCategories>): Promise<void>;
showBanner(): void;
hideBanner(): void;
showSettings(): void;
/**
* Registriert Callback fuer Consent-Aenderungen
* (fuer Angular Change Detection)
*/
onConsentChange(callback: (consent: ConsentState) => void): () => void;
/**
* Registriert Callback wenn Banner angezeigt wird
*/
onBannerShow(callback: () => void): () => void;
/**
* Registriert Callback wenn Banner ausgeblendet wird
*/
onBannerHide(callback: () => void): () => void;
private setupEventListeners;
private initialize;
}
/**
* Konfiguration fuer ConsentModule.forRoot()
*/
interface ConsentModuleConfig extends ConsentConfig {
}
/**
* Token fuer Dependency Injection
* Verwendung mit Angular @Inject():
*
* @example
* ```typescript
* constructor(@Inject(CONSENT_CONFIG) private config: ConsentConfig) {}
* ```
*/
declare const CONSENT_CONFIG = "CONSENT_CONFIG";
declare const CONSENT_SERVICE = "CONSENT_SERVICE";
/**
* Factory fuer ConsentService
*
* @example
* ```typescript
* // app.module.ts
* providers: [
* { provide: CONSENT_CONFIG, useValue: { apiEndpoint: '...', siteId: '...' } },
* { provide: CONSENT_SERVICE, useFactory: consentServiceFactory, deps: [CONSENT_CONFIG] },
* ]
* ```
*/
declare function consentServiceFactory(config: ConsentConfig): ConsentServiceBase;
/**
* ConsentModule - Angular Module
*
* Dies ist eine Template-Definition. Fuer echte Angular-Nutzung
* muss ein separates Angular Library Package erstellt werden.
*
* @example
* ```typescript
* // In einem Angular Library Package:
* @NgModule({
* declarations: [ConsentBannerComponent, ConsentGateDirective],
* exports: [ConsentBannerComponent, ConsentGateDirective],
* })
* export class ConsentModule {
* static forRoot(config: ConsentModuleConfig): ModuleWithProviders<ConsentModule> {
* return {
* ngModule: ConsentModule,
* providers: [
* { provide: CONSENT_CONFIG, useValue: config },
* { provide: CONSENT_SERVICE, useFactory: consentServiceFactory, deps: [CONSENT_CONFIG] },
* ],
* };
* }
* }
* ```
*/
declare const ConsentModuleDefinition: {
/**
* Providers fuer Root-Module
*/
forRoot: (config: ConsentModuleConfig) => {
provide: string;
useValue: ConsentModuleConfig;
};
};
/**
* ConsentBannerComponent Template
*
* Fuer Angular Library Implementation:
*
* @example
* ```typescript
* @Component({
* selector: 'bp-consent-banner',
* template: CONSENT_BANNER_TEMPLATE,
* styles: [CONSENT_BANNER_STYLES],
* })
* export class ConsentBannerComponent {
* constructor(public consent: ConsentService) {}
* }
* ```
*/
declare const CONSENT_BANNER_TEMPLATE = "\n<div\n *ngIf=\"consent.isBannerVisible\"\n class=\"bp-consent-banner\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Cookie-Einstellungen\"\n>\n <div class=\"bp-consent-banner-content\">\n <h2>Datenschutzeinstellungen</h2>\n <p>\n Wir nutzen Cookies und \u00E4hnliche Technologien, um Ihnen ein optimales\n Nutzererlebnis zu bieten.\n </p>\n <div class=\"bp-consent-banner-actions\">\n <button\n type=\"button\"\n class=\"bp-consent-btn bp-consent-btn-reject\"\n (click)=\"consent.rejectAll()\"\n >\n Alle ablehnen\n </button>\n <button\n type=\"button\"\n class=\"bp-consent-btn bp-consent-btn-settings\"\n (click)=\"consent.showSettings()\"\n >\n Einstellungen\n </button>\n <button\n type=\"button\"\n class=\"bp-consent-btn bp-consent-btn-accept\"\n (click)=\"consent.acceptAll()\"\n >\n Alle akzeptieren\n </button>\n </div>\n </div>\n</div>\n";
/**
* ConsentGateDirective Template
*
* @example
* ```typescript
* @Directive({
* selector: '[bpConsentGate]',
* })
* export class ConsentGateDirective implements OnInit, OnDestroy {
* @Input('bpConsentGate') category!: ConsentCategory;
*
* private unsubscribe?: () => void;
*
* constructor(
* private templateRef: TemplateRef<any>,
* private viewContainer: ViewContainerRef,
* private consent: ConsentService
* ) {}
*
* ngOnInit() {
* this.updateView();
* this.unsubscribe = this.consent.onConsentChange(() => this.updateView());
* }
*
* ngOnDestroy() {
* this.unsubscribe?.();
* }
*
* private updateView() {
* if (this.consent.hasConsent(this.category)) {
* this.viewContainer.createEmbeddedView(this.templateRef);
* } else {
* this.viewContainer.clear();
* }
* }
* }
* ```
*/
declare const CONSENT_GATE_USAGE = "\n<!-- Verwendung in Templates -->\n<div *bpConsentGate=\"'analytics'\">\n <analytics-component></analytics-component>\n</div>\n\n<!-- Mit else Template -->\n<ng-container *bpConsentGate=\"'marketing'; else placeholder\">\n <marketing-component></marketing-component>\n</ng-container>\n<ng-template #placeholder>\n <p>Bitte akzeptieren Sie Marketing-Cookies.</p>\n</ng-template>\n";
export { CONSENT_BANNER_TEMPLATE, CONSENT_CONFIG, CONSENT_GATE_USAGE, CONSENT_SERVICE, type ConsentCategories, type ConsentCategory, type ConsentConfig, type ConsentModuleConfig, ConsentModuleDefinition, ConsentServiceBase, type ConsentState, type IConsentService, consentServiceFactory };

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,707 +0,0 @@
/**
* Consent SDK Types
*
* DSGVO/TTDSG-konforme Typdefinitionen für das Consent Management System.
*/
/**
* Standard-Consent-Kategorien nach IAB TCF 2.2
*/
type ConsentCategory = 'essential' | 'functional' | 'analytics' | 'marketing' | 'social';
/**
* Consent-Status pro Kategorie
*/
type ConsentCategories = Record<ConsentCategory, boolean>;
/**
* Consent-Status pro Vendor
*/
type ConsentVendors = Record<string, boolean>;
/**
* Aktueller Consent-Zustand
*/
interface ConsentState {
/** Consent pro Kategorie */
categories: ConsentCategories;
/** Consent pro Vendor (optional, für granulare Kontrolle) */
vendors: ConsentVendors;
/** Zeitstempel der letzten Aenderung */
timestamp: string;
/** SDK-Version bei Erstellung */
version: string;
/** Eindeutige Consent-ID vom Backend */
consentId?: string;
/** Ablaufdatum */
expiresAt?: string;
/** IAB TCF String (falls aktiviert) */
tcfString?: string;
}
/**
* Minimaler Consent-Input fuer setConsent()
*/
type ConsentInput = Partial<ConsentCategories> | {
categories?: Partial<ConsentCategories>;
vendors?: ConsentVendors;
};
/**
* UI-Position des Banners
*/
type BannerPosition = 'bottom' | 'top' | 'center';
/**
* Banner-Layout
*/
type BannerLayout = 'bar' | 'modal' | 'floating';
/**
* Farbschema
*/
type BannerTheme = 'light' | 'dark' | 'auto';
/**
* UI-Konfiguration
*/
interface ConsentUIConfig {
/** Position des Banners */
position?: BannerPosition;
/** Layout-Typ */
layout?: BannerLayout;
/** Farbschema */
theme?: BannerTheme;
/** Pfad zu Custom CSS */
customCss?: string;
/** z-index fuer Banner */
zIndex?: number;
/** Scroll blockieren bei Modal */
blockScrollOnModal?: boolean;
/** Custom Container-ID */
containerId?: string;
}
/**
* Consent-Verhaltens-Konfiguration
*/
interface ConsentBehaviorConfig {
/** Muss Nutzer interagieren? */
required?: boolean;
/** "Alle ablehnen" Button sichtbar */
rejectAllVisible?: boolean;
/** "Alle akzeptieren" Button sichtbar */
acceptAllVisible?: boolean;
/** Einzelne Kategorien waehlbar */
granularControl?: boolean;
/** Einzelne Vendors waehlbar */
vendorControl?: boolean;
/** Auswahl speichern */
rememberChoice?: boolean;
/** Speicherdauer in Tagen */
rememberDays?: number;
/** Nur in EU anzeigen (Geo-Targeting) */
geoTargeting?: boolean;
/** Erneut nachfragen nach X Tagen */
recheckAfterDays?: number;
}
/**
* TCF 2.2 Konfiguration
*/
interface TCFConfig {
/** TCF aktivieren */
enabled?: boolean;
/** CMP ID */
cmpId?: number;
/** CMP Version */
cmpVersion?: number;
}
/**
* PWA-spezifische Konfiguration
*/
interface PWAConfig {
/** Offline-Unterstuetzung aktivieren */
offlineSupport?: boolean;
/** Bei Reconnect synchronisieren */
syncOnReconnect?: boolean;
/** Cache-Strategie */
cacheStrategy?: 'stale-while-revalidate' | 'network-first' | 'cache-first';
}
/**
* Haupt-Konfiguration fuer ConsentManager
*/
interface ConsentConfig {
/** API-Endpunkt fuer Consent-Backend */
apiEndpoint: string;
/** Site-ID */
siteId: string;
/** Sprache (ISO 639-1) */
language?: string;
/** Fallback-Sprache */
fallbackLanguage?: string;
/** UI-Konfiguration */
ui?: ConsentUIConfig;
/** Consent-Verhaltens-Konfiguration */
consent?: ConsentBehaviorConfig;
/** Aktive Kategorien */
categories?: ConsentCategory[];
/** TCF 2.2 Konfiguration */
tcf?: TCFConfig;
/** PWA-Konfiguration */
pwa?: PWAConfig;
/** Callback bei Consent-Aenderung */
onConsentChange?: (consent: ConsentState) => void;
/** Callback wenn Banner angezeigt wird */
onBannerShow?: () => void;
/** Callback wenn Banner geschlossen wird */
onBannerHide?: () => void;
/** Callback bei Fehler */
onError?: (error: Error) => void;
/** Debug-Modus aktivieren */
debug?: boolean;
}
/**
* Cookie-Information
*/
interface CookieInfo {
/** Cookie-Name */
name: string;
/** Cookie-Domain */
domain: string;
/** Ablaufzeit (z.B. "2 Jahre", "Session") */
expiration: string;
/** Speichertyp */
type: 'http' | 'localStorage' | 'sessionStorage' | 'indexedDB';
/** Beschreibung */
description: string;
}
/**
* Vendor-Definition
*/
interface ConsentVendor {
/** Eindeutige Vendor-ID */
id: string;
/** Anzeigename */
name: string;
/** Kategorie */
category: ConsentCategory;
/** IAB TCF Purposes (falls relevant) */
purposes?: number[];
/** Legitimate Interests */
legitimateInterests?: number[];
/** Cookie-Liste */
cookies: CookieInfo[];
/** Link zur Datenschutzerklaerung */
privacyPolicyUrl: string;
/** Datenaufbewahrung */
dataRetention?: string;
/** Datentransfer (z.B. "USA (EU-US DPF)", "EU") */
dataTransfer?: string;
}
/**
* API-Antwort fuer Consent-Erstellung
*/
interface ConsentAPIResponse {
consentId: string;
timestamp: string;
expiresAt: string;
version: string;
}
/**
* API-Antwort fuer Site-Konfiguration
*/
interface SiteConfigResponse {
siteId: string;
siteName: string;
categories: CategoryConfig[];
ui: ConsentUIConfig;
legal: LegalConfig;
tcf?: TCFConfig;
}
/**
* Kategorie-Konfiguration vom Server
*/
interface CategoryConfig {
id: ConsentCategory;
name: Record<string, string>;
description: Record<string, string>;
required: boolean;
vendors: ConsentVendor[];
}
/**
* Rechtliche Konfiguration
*/
interface LegalConfig {
privacyPolicyUrl: string;
imprintUrl: string;
dpo?: {
name: string;
email: string;
};
}
/**
* Event-Typen
*/
type ConsentEventType = 'init' | 'change' | 'accept_all' | 'reject_all' | 'save_selection' | 'banner_show' | 'banner_hide' | 'settings_open' | 'settings_close' | 'vendor_enable' | 'vendor_disable' | 'error';
/**
* Event-Listener Callback
*/
type ConsentEventCallback<T = unknown> = (data: T) => void;
/**
* Event-Daten fuer verschiedene Events
*/
type ConsentEventData = {
init: ConsentState | null;
change: ConsentState;
accept_all: ConsentState;
reject_all: ConsentState;
save_selection: ConsentState;
banner_show: undefined;
banner_hide: undefined;
settings_open: undefined;
settings_close: undefined;
vendor_enable: string;
vendor_disable: string;
error: Error;
};
/**
* Storage-Adapter Interface
*/
interface ConsentStorageAdapter {
/** Consent laden */
get(): ConsentState | null;
/** Consent speichern */
set(consent: ConsentState): void;
/** Consent loeschen */
clear(): void;
/** Pruefen ob Consent existiert */
exists(): boolean;
}
/**
* Uebersetzungsstruktur
*/
interface ConsentTranslations {
title: string;
description: string;
acceptAll: string;
rejectAll: string;
settings: string;
saveSelection: string;
close: string;
categories: {
[K in ConsentCategory]: {
name: string;
description: string;
};
};
footer: {
privacyPolicy: string;
imprint: string;
cookieDetails: string;
};
accessibility: {
closeButton: string;
categoryToggle: string;
requiredCategory: string;
};
}
/**
* Alle unterstuetzten Sprachen
*/
type SupportedLanguage = 'de' | 'en' | 'fr' | 'es' | 'it' | 'nl' | 'pl' | 'pt' | 'cs' | 'da' | 'el' | 'fi' | 'hu' | 'ro' | 'sk' | 'sl' | 'sv';
/**
* ConsentManager - Hauptklasse fuer das Consent Management
*
* DSGVO/TTDSG-konformes Consent Management fuer Web, PWA und Mobile.
*/
/**
* ConsentManager - Zentrale Klasse fuer Consent-Verwaltung
*/
declare class ConsentManager {
private config;
private storage;
private scriptBlocker;
private api;
private events;
private currentConsent;
private initialized;
private bannerVisible;
private deviceFingerprint;
constructor(config: ConsentConfig);
/**
* SDK initialisieren
*/
init(): Promise<void>;
/**
* Pruefen ob Consent fuer Kategorie vorhanden
*/
hasConsent(category: ConsentCategory): boolean;
/**
* Pruefen ob Consent fuer Vendor vorhanden
*/
hasVendorConsent(vendorId: string): boolean;
/**
* Aktuellen Consent-State abrufen
*/
getConsent(): ConsentState | null;
/**
* Consent setzen
*/
setConsent(input: ConsentInput): Promise<void>;
/**
* Alle Kategorien akzeptieren
*/
acceptAll(): Promise<void>;
/**
* Alle nicht-essentiellen Kategorien ablehnen
*/
rejectAll(): Promise<void>;
/**
* Alle Einwilligungen widerrufen
*/
revokeAll(): Promise<void>;
/**
* Consent-Daten exportieren (DSGVO Art. 20)
*/
exportConsent(): Promise<string>;
/**
* Pruefen ob Consent-Abfrage noetig
*/
needsConsent(): boolean;
/**
* Banner anzeigen
*/
showBanner(): void;
/**
* Banner verstecken
*/
hideBanner(): void;
/**
* Einstellungs-Modal oeffnen
*/
showSettings(): void;
/**
* Pruefen ob Banner sichtbar
*/
isBannerVisible(): boolean;
/**
* Event-Listener registrieren
*/
on<T extends ConsentEventType>(event: T, callback: ConsentEventCallback<ConsentEventData[T]>): () => void;
/**
* Event-Listener entfernen
*/
off<T extends ConsentEventType>(event: T, callback: ConsentEventCallback<ConsentEventData[T]>): void;
/**
* Konfiguration zusammenfuehren
*/
private mergeConfig;
/**
* Consent-Input normalisieren
*/
private normalizeConsentInput;
/**
* Consent anwenden (Skripte aktivieren/blockieren)
*/
private applyConsent;
/**
* Google Consent Mode v2 aktualisieren
*/
private updateGoogleConsentMode;
/**
* Pruefen ob Consent abgelaufen
*/
private isConsentExpired;
/**
* Event emittieren
*/
private emit;
/**
* Fehler behandeln
*/
private handleError;
/**
* Debug-Logging
*/
private log;
/**
* SDK-Version abrufen
*/
static getVersion(): string;
}
/**
* ConsentStorage - Lokale Speicherung des Consent-Status
*
* Speichert Consent-Daten im localStorage mit HMAC-Signatur
* zur Manipulationserkennung.
*/
/**
* ConsentStorage - Persistente Speicherung
*/
declare class ConsentStorage {
private config;
private storageKey;
constructor(config: ConsentConfig);
/**
* Consent laden
*/
get(): ConsentState | null;
/**
* Consent speichern
*/
set(consent: ConsentState): void;
/**
* Consent loeschen
*/
clear(): void;
/**
* Pruefen ob Consent existiert
*/
exists(): boolean;
/**
* Consent als Cookie setzen
*/
private setCookie;
/**
* Cookie loeschen
*/
private clearCookie;
/**
* Signatur generieren
*/
private generateSignature;
/**
* Signatur verifizieren
*/
private verifySignature;
/**
* Einfache Hash-Funktion (djb2)
*/
private simpleHash;
/**
* Debug-Logging
*/
private log;
}
/**
* ScriptBlocker - Blockiert Skripte bis Consent erteilt wird
*
* Verwendet das data-consent Attribut zur Identifikation von
* Skripten, die erst nach Consent geladen werden duerfen.
*
* Beispiel:
* <script data-consent="analytics" data-src="..." type="text/plain"></script>
*/
/**
* ScriptBlocker - Verwaltet Script-Blocking
*/
declare class ScriptBlocker {
private config;
private observer;
private enabledCategories;
private processedElements;
constructor(config: ConsentConfig);
/**
* Initialisieren und Observer starten
*/
init(): void;
/**
* Kategorie aktivieren
*/
enableCategory(category: ConsentCategory): void;
/**
* Kategorie deaktivieren
*/
disableCategory(category: ConsentCategory): void;
/**
* Alle Kategorien blockieren (ausser Essential)
*/
blockAll(): void;
/**
* Pruefen ob Kategorie aktiviert
*/
isCategoryEnabled(category: ConsentCategory): boolean;
/**
* Observer stoppen
*/
destroy(): void;
/**
* Bestehende Elemente verarbeiten
*/
private processExistingElements;
/**
* Element verarbeiten
*/
private processElement;
/**
* Script-Element verarbeiten
*/
private processScript;
/**
* iFrame-Element verarbeiten
*/
private processIframe;
/**
* Script aktivieren
*/
private activateScript;
/**
* iFrame aktivieren
*/
private activateIframe;
/**
* Placeholder fuer blockierten iFrame anzeigen
*/
private showPlaceholder;
/**
* Alle Elemente einer Kategorie aktivieren
*/
private activateCategory;
/**
* Kategorie-Name fuer UI
*/
private getCategoryName;
/**
* Debug-Logging
*/
private log;
}
/**
* ConsentAPI - Kommunikation mit dem Consent-Backend
*
* Sendet Consent-Entscheidungen an das Backend zur
* revisionssicheren Speicherung.
*/
/**
* Request-Payload fuer Consent-Speicherung
*/
interface SaveConsentRequest {
siteId: string;
userId?: string;
deviceFingerprint: string;
consent: ConsentState;
metadata?: {
userAgent?: string;
language?: string;
screenResolution?: string;
platform?: string;
appVersion?: string;
};
}
/**
* ConsentAPI - Backend-Kommunikation
*/
declare class ConsentAPI {
private config;
private baseUrl;
constructor(config: ConsentConfig);
/**
* Consent speichern
*/
saveConsent(request: SaveConsentRequest): Promise<ConsentAPIResponse>;
/**
* Consent abrufen
*/
getConsent(siteId: string, deviceFingerprint: string): Promise<ConsentState | null>;
/**
* Consent widerrufen
*/
revokeConsent(consentId: string): Promise<void>;
/**
* Site-Konfiguration abrufen
*/
getSiteConfig(siteId: string): Promise<SiteConfigResponse>;
/**
* Consent-Historie exportieren (DSGVO Art. 20)
*/
exportConsent(userId: string): Promise<unknown>;
/**
* Fetch mit Standard-Headers
*/
private fetch;
/**
* Signatur-Headers generieren (HMAC)
*/
private getSignatureHeaders;
/**
* Einfache Hash-Funktion (djb2)
*/
private simpleHash;
/**
* Debug-Logging
*/
private log;
}
/**
* EventEmitter - Typsicherer Event-Handler
*/
type EventCallback<T = unknown> = (data: T) => void;
declare class EventEmitter<Events extends Record<string, any> = Record<string, unknown>> {
private listeners;
/**
* Event-Listener registrieren
* @returns Unsubscribe-Funktion
*/
on<K extends keyof Events>(event: K, callback: EventCallback<Events[K]>): () => void;
/**
* Event-Listener entfernen
*/
off<K extends keyof Events>(event: K, callback: EventCallback<Events[K]>): void;
/**
* Event emittieren
*/
emit<K extends keyof Events>(event: K, data: Events[K]): void;
/**
* Einmaligen Listener registrieren
*/
once<K extends keyof Events>(event: K, callback: EventCallback<Events[K]>): () => void;
/**
* Alle Listener entfernen
*/
clear(): void;
/**
* Alle Listener fuer ein Event entfernen
*/
clearEvent<K extends keyof Events>(event: K): void;
/**
* Anzahl Listener fuer ein Event
*/
listenerCount<K extends keyof Events>(event: K): number;
}
/**
* Device Fingerprinting - Datenschutzkonform
*
* Generiert einen anonymen Fingerprint OHNE:
* - Canvas Fingerprinting
* - WebGL Fingerprinting
* - Audio Fingerprinting
* - Hardware-spezifische IDs
*
* Verwendet nur:
* - User Agent
* - Sprache
* - Bildschirmaufloesung
* - Zeitzone
* - Platform
*/
/**
* Datenschutzkonformen Fingerprint generieren
*
* Der Fingerprint ist:
* - Nicht eindeutig (viele Nutzer teilen sich denselben)
* - Nicht persistent (aendert sich bei Browser-Updates)
* - Nicht invasiv (keine Canvas/WebGL/Audio)
* - Anonymisiert (SHA-256 Hash)
*/
declare function generateFingerprint(): Promise<string>;
/**
* Synchrone Version (mit einfachem Hash)
*/
declare function generateFingerprintSync(): string;
/**
* SDK Version
*/
declare const SDK_VERSION = "1.0.0";
export { type BannerLayout, type BannerPosition, type BannerTheme, type CategoryConfig, ConsentAPI, type ConsentAPIResponse, type ConsentBehaviorConfig, type ConsentCategories, type ConsentCategory, type ConsentConfig, type ConsentEventCallback, type ConsentEventData, type ConsentEventType, type ConsentInput, ConsentManager, type ConsentState, ConsentStorage, type ConsentStorageAdapter, type ConsentTranslations, type ConsentUIConfig, type ConsentVendor, type ConsentVendors, type CookieInfo, EventEmitter, type LegalConfig, type PWAConfig, SDK_VERSION, ScriptBlocker, type SiteConfigResponse, type SupportedLanguage, type TCFConfig, ConsentManager as default, generateFingerprint, generateFingerprintSync };

View File

@@ -1,707 +0,0 @@
/**
* Consent SDK Types
*
* DSGVO/TTDSG-konforme Typdefinitionen für das Consent Management System.
*/
/**
* Standard-Consent-Kategorien nach IAB TCF 2.2
*/
type ConsentCategory = 'essential' | 'functional' | 'analytics' | 'marketing' | 'social';
/**
* Consent-Status pro Kategorie
*/
type ConsentCategories = Record<ConsentCategory, boolean>;
/**
* Consent-Status pro Vendor
*/
type ConsentVendors = Record<string, boolean>;
/**
* Aktueller Consent-Zustand
*/
interface ConsentState {
/** Consent pro Kategorie */
categories: ConsentCategories;
/** Consent pro Vendor (optional, für granulare Kontrolle) */
vendors: ConsentVendors;
/** Zeitstempel der letzten Aenderung */
timestamp: string;
/** SDK-Version bei Erstellung */
version: string;
/** Eindeutige Consent-ID vom Backend */
consentId?: string;
/** Ablaufdatum */
expiresAt?: string;
/** IAB TCF String (falls aktiviert) */
tcfString?: string;
}
/**
* Minimaler Consent-Input fuer setConsent()
*/
type ConsentInput = Partial<ConsentCategories> | {
categories?: Partial<ConsentCategories>;
vendors?: ConsentVendors;
};
/**
* UI-Position des Banners
*/
type BannerPosition = 'bottom' | 'top' | 'center';
/**
* Banner-Layout
*/
type BannerLayout = 'bar' | 'modal' | 'floating';
/**
* Farbschema
*/
type BannerTheme = 'light' | 'dark' | 'auto';
/**
* UI-Konfiguration
*/
interface ConsentUIConfig {
/** Position des Banners */
position?: BannerPosition;
/** Layout-Typ */
layout?: BannerLayout;
/** Farbschema */
theme?: BannerTheme;
/** Pfad zu Custom CSS */
customCss?: string;
/** z-index fuer Banner */
zIndex?: number;
/** Scroll blockieren bei Modal */
blockScrollOnModal?: boolean;
/** Custom Container-ID */
containerId?: string;
}
/**
* Consent-Verhaltens-Konfiguration
*/
interface ConsentBehaviorConfig {
/** Muss Nutzer interagieren? */
required?: boolean;
/** "Alle ablehnen" Button sichtbar */
rejectAllVisible?: boolean;
/** "Alle akzeptieren" Button sichtbar */
acceptAllVisible?: boolean;
/** Einzelne Kategorien waehlbar */
granularControl?: boolean;
/** Einzelne Vendors waehlbar */
vendorControl?: boolean;
/** Auswahl speichern */
rememberChoice?: boolean;
/** Speicherdauer in Tagen */
rememberDays?: number;
/** Nur in EU anzeigen (Geo-Targeting) */
geoTargeting?: boolean;
/** Erneut nachfragen nach X Tagen */
recheckAfterDays?: number;
}
/**
* TCF 2.2 Konfiguration
*/
interface TCFConfig {
/** TCF aktivieren */
enabled?: boolean;
/** CMP ID */
cmpId?: number;
/** CMP Version */
cmpVersion?: number;
}
/**
* PWA-spezifische Konfiguration
*/
interface PWAConfig {
/** Offline-Unterstuetzung aktivieren */
offlineSupport?: boolean;
/** Bei Reconnect synchronisieren */
syncOnReconnect?: boolean;
/** Cache-Strategie */
cacheStrategy?: 'stale-while-revalidate' | 'network-first' | 'cache-first';
}
/**
* Haupt-Konfiguration fuer ConsentManager
*/
interface ConsentConfig {
/** API-Endpunkt fuer Consent-Backend */
apiEndpoint: string;
/** Site-ID */
siteId: string;
/** Sprache (ISO 639-1) */
language?: string;
/** Fallback-Sprache */
fallbackLanguage?: string;
/** UI-Konfiguration */
ui?: ConsentUIConfig;
/** Consent-Verhaltens-Konfiguration */
consent?: ConsentBehaviorConfig;
/** Aktive Kategorien */
categories?: ConsentCategory[];
/** TCF 2.2 Konfiguration */
tcf?: TCFConfig;
/** PWA-Konfiguration */
pwa?: PWAConfig;
/** Callback bei Consent-Aenderung */
onConsentChange?: (consent: ConsentState) => void;
/** Callback wenn Banner angezeigt wird */
onBannerShow?: () => void;
/** Callback wenn Banner geschlossen wird */
onBannerHide?: () => void;
/** Callback bei Fehler */
onError?: (error: Error) => void;
/** Debug-Modus aktivieren */
debug?: boolean;
}
/**
* Cookie-Information
*/
interface CookieInfo {
/** Cookie-Name */
name: string;
/** Cookie-Domain */
domain: string;
/** Ablaufzeit (z.B. "2 Jahre", "Session") */
expiration: string;
/** Speichertyp */
type: 'http' | 'localStorage' | 'sessionStorage' | 'indexedDB';
/** Beschreibung */
description: string;
}
/**
* Vendor-Definition
*/
interface ConsentVendor {
/** Eindeutige Vendor-ID */
id: string;
/** Anzeigename */
name: string;
/** Kategorie */
category: ConsentCategory;
/** IAB TCF Purposes (falls relevant) */
purposes?: number[];
/** Legitimate Interests */
legitimateInterests?: number[];
/** Cookie-Liste */
cookies: CookieInfo[];
/** Link zur Datenschutzerklaerung */
privacyPolicyUrl: string;
/** Datenaufbewahrung */
dataRetention?: string;
/** Datentransfer (z.B. "USA (EU-US DPF)", "EU") */
dataTransfer?: string;
}
/**
* API-Antwort fuer Consent-Erstellung
*/
interface ConsentAPIResponse {
consentId: string;
timestamp: string;
expiresAt: string;
version: string;
}
/**
* API-Antwort fuer Site-Konfiguration
*/
interface SiteConfigResponse {
siteId: string;
siteName: string;
categories: CategoryConfig[];
ui: ConsentUIConfig;
legal: LegalConfig;
tcf?: TCFConfig;
}
/**
* Kategorie-Konfiguration vom Server
*/
interface CategoryConfig {
id: ConsentCategory;
name: Record<string, string>;
description: Record<string, string>;
required: boolean;
vendors: ConsentVendor[];
}
/**
* Rechtliche Konfiguration
*/
interface LegalConfig {
privacyPolicyUrl: string;
imprintUrl: string;
dpo?: {
name: string;
email: string;
};
}
/**
* Event-Typen
*/
type ConsentEventType = 'init' | 'change' | 'accept_all' | 'reject_all' | 'save_selection' | 'banner_show' | 'banner_hide' | 'settings_open' | 'settings_close' | 'vendor_enable' | 'vendor_disable' | 'error';
/**
* Event-Listener Callback
*/
type ConsentEventCallback<T = unknown> = (data: T) => void;
/**
* Event-Daten fuer verschiedene Events
*/
type ConsentEventData = {
init: ConsentState | null;
change: ConsentState;
accept_all: ConsentState;
reject_all: ConsentState;
save_selection: ConsentState;
banner_show: undefined;
banner_hide: undefined;
settings_open: undefined;
settings_close: undefined;
vendor_enable: string;
vendor_disable: string;
error: Error;
};
/**
* Storage-Adapter Interface
*/
interface ConsentStorageAdapter {
/** Consent laden */
get(): ConsentState | null;
/** Consent speichern */
set(consent: ConsentState): void;
/** Consent loeschen */
clear(): void;
/** Pruefen ob Consent existiert */
exists(): boolean;
}
/**
* Uebersetzungsstruktur
*/
interface ConsentTranslations {
title: string;
description: string;
acceptAll: string;
rejectAll: string;
settings: string;
saveSelection: string;
close: string;
categories: {
[K in ConsentCategory]: {
name: string;
description: string;
};
};
footer: {
privacyPolicy: string;
imprint: string;
cookieDetails: string;
};
accessibility: {
closeButton: string;
categoryToggle: string;
requiredCategory: string;
};
}
/**
* Alle unterstuetzten Sprachen
*/
type SupportedLanguage = 'de' | 'en' | 'fr' | 'es' | 'it' | 'nl' | 'pl' | 'pt' | 'cs' | 'da' | 'el' | 'fi' | 'hu' | 'ro' | 'sk' | 'sl' | 'sv';
/**
* ConsentManager - Hauptklasse fuer das Consent Management
*
* DSGVO/TTDSG-konformes Consent Management fuer Web, PWA und Mobile.
*/
/**
* ConsentManager - Zentrale Klasse fuer Consent-Verwaltung
*/
declare class ConsentManager {
private config;
private storage;
private scriptBlocker;
private api;
private events;
private currentConsent;
private initialized;
private bannerVisible;
private deviceFingerprint;
constructor(config: ConsentConfig);
/**
* SDK initialisieren
*/
init(): Promise<void>;
/**
* Pruefen ob Consent fuer Kategorie vorhanden
*/
hasConsent(category: ConsentCategory): boolean;
/**
* Pruefen ob Consent fuer Vendor vorhanden
*/
hasVendorConsent(vendorId: string): boolean;
/**
* Aktuellen Consent-State abrufen
*/
getConsent(): ConsentState | null;
/**
* Consent setzen
*/
setConsent(input: ConsentInput): Promise<void>;
/**
* Alle Kategorien akzeptieren
*/
acceptAll(): Promise<void>;
/**
* Alle nicht-essentiellen Kategorien ablehnen
*/
rejectAll(): Promise<void>;
/**
* Alle Einwilligungen widerrufen
*/
revokeAll(): Promise<void>;
/**
* Consent-Daten exportieren (DSGVO Art. 20)
*/
exportConsent(): Promise<string>;
/**
* Pruefen ob Consent-Abfrage noetig
*/
needsConsent(): boolean;
/**
* Banner anzeigen
*/
showBanner(): void;
/**
* Banner verstecken
*/
hideBanner(): void;
/**
* Einstellungs-Modal oeffnen
*/
showSettings(): void;
/**
* Pruefen ob Banner sichtbar
*/
isBannerVisible(): boolean;
/**
* Event-Listener registrieren
*/
on<T extends ConsentEventType>(event: T, callback: ConsentEventCallback<ConsentEventData[T]>): () => void;
/**
* Event-Listener entfernen
*/
off<T extends ConsentEventType>(event: T, callback: ConsentEventCallback<ConsentEventData[T]>): void;
/**
* Konfiguration zusammenfuehren
*/
private mergeConfig;
/**
* Consent-Input normalisieren
*/
private normalizeConsentInput;
/**
* Consent anwenden (Skripte aktivieren/blockieren)
*/
private applyConsent;
/**
* Google Consent Mode v2 aktualisieren
*/
private updateGoogleConsentMode;
/**
* Pruefen ob Consent abgelaufen
*/
private isConsentExpired;
/**
* Event emittieren
*/
private emit;
/**
* Fehler behandeln
*/
private handleError;
/**
* Debug-Logging
*/
private log;
/**
* SDK-Version abrufen
*/
static getVersion(): string;
}
/**
* ConsentStorage - Lokale Speicherung des Consent-Status
*
* Speichert Consent-Daten im localStorage mit HMAC-Signatur
* zur Manipulationserkennung.
*/
/**
* ConsentStorage - Persistente Speicherung
*/
declare class ConsentStorage {
private config;
private storageKey;
constructor(config: ConsentConfig);
/**
* Consent laden
*/
get(): ConsentState | null;
/**
* Consent speichern
*/
set(consent: ConsentState): void;
/**
* Consent loeschen
*/
clear(): void;
/**
* Pruefen ob Consent existiert
*/
exists(): boolean;
/**
* Consent als Cookie setzen
*/
private setCookie;
/**
* Cookie loeschen
*/
private clearCookie;
/**
* Signatur generieren
*/
private generateSignature;
/**
* Signatur verifizieren
*/
private verifySignature;
/**
* Einfache Hash-Funktion (djb2)
*/
private simpleHash;
/**
* Debug-Logging
*/
private log;
}
/**
* ScriptBlocker - Blockiert Skripte bis Consent erteilt wird
*
* Verwendet das data-consent Attribut zur Identifikation von
* Skripten, die erst nach Consent geladen werden duerfen.
*
* Beispiel:
* <script data-consent="analytics" data-src="..." type="text/plain"></script>
*/
/**
* ScriptBlocker - Verwaltet Script-Blocking
*/
declare class ScriptBlocker {
private config;
private observer;
private enabledCategories;
private processedElements;
constructor(config: ConsentConfig);
/**
* Initialisieren und Observer starten
*/
init(): void;
/**
* Kategorie aktivieren
*/
enableCategory(category: ConsentCategory): void;
/**
* Kategorie deaktivieren
*/
disableCategory(category: ConsentCategory): void;
/**
* Alle Kategorien blockieren (ausser Essential)
*/
blockAll(): void;
/**
* Pruefen ob Kategorie aktiviert
*/
isCategoryEnabled(category: ConsentCategory): boolean;
/**
* Observer stoppen
*/
destroy(): void;
/**
* Bestehende Elemente verarbeiten
*/
private processExistingElements;
/**
* Element verarbeiten
*/
private processElement;
/**
* Script-Element verarbeiten
*/
private processScript;
/**
* iFrame-Element verarbeiten
*/
private processIframe;
/**
* Script aktivieren
*/
private activateScript;
/**
* iFrame aktivieren
*/
private activateIframe;
/**
* Placeholder fuer blockierten iFrame anzeigen
*/
private showPlaceholder;
/**
* Alle Elemente einer Kategorie aktivieren
*/
private activateCategory;
/**
* Kategorie-Name fuer UI
*/
private getCategoryName;
/**
* Debug-Logging
*/
private log;
}
/**
* ConsentAPI - Kommunikation mit dem Consent-Backend
*
* Sendet Consent-Entscheidungen an das Backend zur
* revisionssicheren Speicherung.
*/
/**
* Request-Payload fuer Consent-Speicherung
*/
interface SaveConsentRequest {
siteId: string;
userId?: string;
deviceFingerprint: string;
consent: ConsentState;
metadata?: {
userAgent?: string;
language?: string;
screenResolution?: string;
platform?: string;
appVersion?: string;
};
}
/**
* ConsentAPI - Backend-Kommunikation
*/
declare class ConsentAPI {
private config;
private baseUrl;
constructor(config: ConsentConfig);
/**
* Consent speichern
*/
saveConsent(request: SaveConsentRequest): Promise<ConsentAPIResponse>;
/**
* Consent abrufen
*/
getConsent(siteId: string, deviceFingerprint: string): Promise<ConsentState | null>;
/**
* Consent widerrufen
*/
revokeConsent(consentId: string): Promise<void>;
/**
* Site-Konfiguration abrufen
*/
getSiteConfig(siteId: string): Promise<SiteConfigResponse>;
/**
* Consent-Historie exportieren (DSGVO Art. 20)
*/
exportConsent(userId: string): Promise<unknown>;
/**
* Fetch mit Standard-Headers
*/
private fetch;
/**
* Signatur-Headers generieren (HMAC)
*/
private getSignatureHeaders;
/**
* Einfache Hash-Funktion (djb2)
*/
private simpleHash;
/**
* Debug-Logging
*/
private log;
}
/**
* EventEmitter - Typsicherer Event-Handler
*/
type EventCallback<T = unknown> = (data: T) => void;
declare class EventEmitter<Events extends Record<string, any> = Record<string, unknown>> {
private listeners;
/**
* Event-Listener registrieren
* @returns Unsubscribe-Funktion
*/
on<K extends keyof Events>(event: K, callback: EventCallback<Events[K]>): () => void;
/**
* Event-Listener entfernen
*/
off<K extends keyof Events>(event: K, callback: EventCallback<Events[K]>): void;
/**
* Event emittieren
*/
emit<K extends keyof Events>(event: K, data: Events[K]): void;
/**
* Einmaligen Listener registrieren
*/
once<K extends keyof Events>(event: K, callback: EventCallback<Events[K]>): () => void;
/**
* Alle Listener entfernen
*/
clear(): void;
/**
* Alle Listener fuer ein Event entfernen
*/
clearEvent<K extends keyof Events>(event: K): void;
/**
* Anzahl Listener fuer ein Event
*/
listenerCount<K extends keyof Events>(event: K): number;
}
/**
* Device Fingerprinting - Datenschutzkonform
*
* Generiert einen anonymen Fingerprint OHNE:
* - Canvas Fingerprinting
* - WebGL Fingerprinting
* - Audio Fingerprinting
* - Hardware-spezifische IDs
*
* Verwendet nur:
* - User Agent
* - Sprache
* - Bildschirmaufloesung
* - Zeitzone
* - Platform
*/
/**
* Datenschutzkonformen Fingerprint generieren
*
* Der Fingerprint ist:
* - Nicht eindeutig (viele Nutzer teilen sich denselben)
* - Nicht persistent (aendert sich bei Browser-Updates)
* - Nicht invasiv (keine Canvas/WebGL/Audio)
* - Anonymisiert (SHA-256 Hash)
*/
declare function generateFingerprint(): Promise<string>;
/**
* Synchrone Version (mit einfachem Hash)
*/
declare function generateFingerprintSync(): string;
/**
* SDK Version
*/
declare const SDK_VERSION = "1.0.0";
export { type BannerLayout, type BannerPosition, type BannerTheme, type CategoryConfig, ConsentAPI, type ConsentAPIResponse, type ConsentBehaviorConfig, type ConsentCategories, type ConsentCategory, type ConsentConfig, type ConsentEventCallback, type ConsentEventData, type ConsentEventType, type ConsentInput, ConsentManager, type ConsentState, ConsentStorage, type ConsentStorageAdapter, type ConsentTranslations, type ConsentUIConfig, type ConsentVendor, type ConsentVendors, type CookieInfo, EventEmitter, type LegalConfig, type PWAConfig, SDK_VERSION, ScriptBlocker, type SiteConfigResponse, type SupportedLanguage, type TCFConfig, ConsentManager as default, generateFingerprint, generateFingerprintSync };

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,450 +0,0 @@
import * as react from 'react';
import { FC, ReactNode } from 'react';
/**
* Consent SDK Types
*
* DSGVO/TTDSG-konforme Typdefinitionen für das Consent Management System.
*/
/**
* Standard-Consent-Kategorien nach IAB TCF 2.2
*/
type ConsentCategory = 'essential' | 'functional' | 'analytics' | 'marketing' | 'social';
/**
* Consent-Status pro Kategorie
*/
type ConsentCategories = Record<ConsentCategory, boolean>;
/**
* Consent-Status pro Vendor
*/
type ConsentVendors = Record<string, boolean>;
/**
* Aktueller Consent-Zustand
*/
interface ConsentState {
/** Consent pro Kategorie */
categories: ConsentCategories;
/** Consent pro Vendor (optional, für granulare Kontrolle) */
vendors: ConsentVendors;
/** Zeitstempel der letzten Aenderung */
timestamp: string;
/** SDK-Version bei Erstellung */
version: string;
/** Eindeutige Consent-ID vom Backend */
consentId?: string;
/** Ablaufdatum */
expiresAt?: string;
/** IAB TCF String (falls aktiviert) */
tcfString?: string;
}
/**
* Minimaler Consent-Input fuer setConsent()
*/
type ConsentInput = Partial<ConsentCategories> | {
categories?: Partial<ConsentCategories>;
vendors?: ConsentVendors;
};
/**
* UI-Position des Banners
*/
type BannerPosition = 'bottom' | 'top' | 'center';
/**
* Banner-Layout
*/
type BannerLayout = 'bar' | 'modal' | 'floating';
/**
* Farbschema
*/
type BannerTheme = 'light' | 'dark' | 'auto';
/**
* UI-Konfiguration
*/
interface ConsentUIConfig {
/** Position des Banners */
position?: BannerPosition;
/** Layout-Typ */
layout?: BannerLayout;
/** Farbschema */
theme?: BannerTheme;
/** Pfad zu Custom CSS */
customCss?: string;
/** z-index fuer Banner */
zIndex?: number;
/** Scroll blockieren bei Modal */
blockScrollOnModal?: boolean;
/** Custom Container-ID */
containerId?: string;
}
/**
* Consent-Verhaltens-Konfiguration
*/
interface ConsentBehaviorConfig {
/** Muss Nutzer interagieren? */
required?: boolean;
/** "Alle ablehnen" Button sichtbar */
rejectAllVisible?: boolean;
/** "Alle akzeptieren" Button sichtbar */
acceptAllVisible?: boolean;
/** Einzelne Kategorien waehlbar */
granularControl?: boolean;
/** Einzelne Vendors waehlbar */
vendorControl?: boolean;
/** Auswahl speichern */
rememberChoice?: boolean;
/** Speicherdauer in Tagen */
rememberDays?: number;
/** Nur in EU anzeigen (Geo-Targeting) */
geoTargeting?: boolean;
/** Erneut nachfragen nach X Tagen */
recheckAfterDays?: number;
}
/**
* TCF 2.2 Konfiguration
*/
interface TCFConfig {
/** TCF aktivieren */
enabled?: boolean;
/** CMP ID */
cmpId?: number;
/** CMP Version */
cmpVersion?: number;
}
/**
* PWA-spezifische Konfiguration
*/
interface PWAConfig {
/** Offline-Unterstuetzung aktivieren */
offlineSupport?: boolean;
/** Bei Reconnect synchronisieren */
syncOnReconnect?: boolean;
/** Cache-Strategie */
cacheStrategy?: 'stale-while-revalidate' | 'network-first' | 'cache-first';
}
/**
* Haupt-Konfiguration fuer ConsentManager
*/
interface ConsentConfig {
/** API-Endpunkt fuer Consent-Backend */
apiEndpoint: string;
/** Site-ID */
siteId: string;
/** Sprache (ISO 639-1) */
language?: string;
/** Fallback-Sprache */
fallbackLanguage?: string;
/** UI-Konfiguration */
ui?: ConsentUIConfig;
/** Consent-Verhaltens-Konfiguration */
consent?: ConsentBehaviorConfig;
/** Aktive Kategorien */
categories?: ConsentCategory[];
/** TCF 2.2 Konfiguration */
tcf?: TCFConfig;
/** PWA-Konfiguration */
pwa?: PWAConfig;
/** Callback bei Consent-Aenderung */
onConsentChange?: (consent: ConsentState) => void;
/** Callback wenn Banner angezeigt wird */
onBannerShow?: () => void;
/** Callback wenn Banner geschlossen wird */
onBannerHide?: () => void;
/** Callback bei Fehler */
onError?: (error: Error) => void;
/** Debug-Modus aktivieren */
debug?: boolean;
}
/**
* Event-Typen
*/
type ConsentEventType = 'init' | 'change' | 'accept_all' | 'reject_all' | 'save_selection' | 'banner_show' | 'banner_hide' | 'settings_open' | 'settings_close' | 'vendor_enable' | 'vendor_disable' | 'error';
/**
* Event-Listener Callback
*/
type ConsentEventCallback<T = unknown> = (data: T) => void;
/**
* Event-Daten fuer verschiedene Events
*/
type ConsentEventData = {
init: ConsentState | null;
change: ConsentState;
accept_all: ConsentState;
reject_all: ConsentState;
save_selection: ConsentState;
banner_show: undefined;
banner_hide: undefined;
settings_open: undefined;
settings_close: undefined;
vendor_enable: string;
vendor_disable: string;
error: Error;
};
/**
* ConsentManager - Hauptklasse fuer das Consent Management
*
* DSGVO/TTDSG-konformes Consent Management fuer Web, PWA und Mobile.
*/
/**
* ConsentManager - Zentrale Klasse fuer Consent-Verwaltung
*/
declare class ConsentManager {
private config;
private storage;
private scriptBlocker;
private api;
private events;
private currentConsent;
private initialized;
private bannerVisible;
private deviceFingerprint;
constructor(config: ConsentConfig);
/**
* SDK initialisieren
*/
init(): Promise<void>;
/**
* Pruefen ob Consent fuer Kategorie vorhanden
*/
hasConsent(category: ConsentCategory): boolean;
/**
* Pruefen ob Consent fuer Vendor vorhanden
*/
hasVendorConsent(vendorId: string): boolean;
/**
* Aktuellen Consent-State abrufen
*/
getConsent(): ConsentState | null;
/**
* Consent setzen
*/
setConsent(input: ConsentInput): Promise<void>;
/**
* Alle Kategorien akzeptieren
*/
acceptAll(): Promise<void>;
/**
* Alle nicht-essentiellen Kategorien ablehnen
*/
rejectAll(): Promise<void>;
/**
* Alle Einwilligungen widerrufen
*/
revokeAll(): Promise<void>;
/**
* Consent-Daten exportieren (DSGVO Art. 20)
*/
exportConsent(): Promise<string>;
/**
* Pruefen ob Consent-Abfrage noetig
*/
needsConsent(): boolean;
/**
* Banner anzeigen
*/
showBanner(): void;
/**
* Banner verstecken
*/
hideBanner(): void;
/**
* Einstellungs-Modal oeffnen
*/
showSettings(): void;
/**
* Pruefen ob Banner sichtbar
*/
isBannerVisible(): boolean;
/**
* Event-Listener registrieren
*/
on<T extends ConsentEventType>(event: T, callback: ConsentEventCallback<ConsentEventData[T]>): () => void;
/**
* Event-Listener entfernen
*/
off<T extends ConsentEventType>(event: T, callback: ConsentEventCallback<ConsentEventData[T]>): void;
/**
* Konfiguration zusammenfuehren
*/
private mergeConfig;
/**
* Consent-Input normalisieren
*/
private normalizeConsentInput;
/**
* Consent anwenden (Skripte aktivieren/blockieren)
*/
private applyConsent;
/**
* Google Consent Mode v2 aktualisieren
*/
private updateGoogleConsentMode;
/**
* Pruefen ob Consent abgelaufen
*/
private isConsentExpired;
/**
* Event emittieren
*/
private emit;
/**
* Fehler behandeln
*/
private handleError;
/**
* Debug-Logging
*/
private log;
/**
* SDK-Version abrufen
*/
static getVersion(): string;
}
interface ConsentContextValue {
/** ConsentManager Instanz */
manager: ConsentManager | null;
/** Aktueller Consent-State */
consent: ConsentState | null;
/** Ist SDK initialisiert? */
isInitialized: boolean;
/** Wird geladen? */
isLoading: boolean;
/** Ist Banner sichtbar? */
isBannerVisible: boolean;
/** Wird Consent benoetigt? */
needsConsent: boolean;
/** Consent fuer Kategorie pruefen */
hasConsent: (category: ConsentCategory) => boolean;
/** Alle akzeptieren */
acceptAll: () => Promise<void>;
/** Alle ablehnen */
rejectAll: () => Promise<void>;
/** Auswahl speichern */
saveSelection: (categories: Partial<ConsentCategories>) => Promise<void>;
/** Banner anzeigen */
showBanner: () => void;
/** Banner verstecken */
hideBanner: () => void;
/** Einstellungen oeffnen */
showSettings: () => void;
}
declare const ConsentContext: react.Context<ConsentContextValue | null>;
interface ConsentProviderProps {
/** SDK-Konfiguration */
config: ConsentConfig;
/** Kinder-Komponenten */
children: ReactNode;
}
/**
* ConsentProvider - Stellt Consent-Kontext bereit
*/
declare const ConsentProvider: FC<ConsentProviderProps>;
/**
* useConsent - Hook fuer Consent-Zugriff
*
* @example
* ```tsx
* const { hasConsent, acceptAll, rejectAll } = useConsent();
*
* if (hasConsent('analytics')) {
* // Analytics laden
* }
* ```
*/
declare function useConsent(): ConsentContextValue;
declare function useConsent(category: ConsentCategory): ConsentContextValue & {
allowed: boolean;
};
/**
* useConsentManager - Direkter Zugriff auf ConsentManager
*/
declare function useConsentManager(): ConsentManager | null;
interface ConsentGateProps {
/** Erforderliche Kategorie */
category: ConsentCategory;
/** Inhalt bei Consent */
children: ReactNode;
/** Inhalt ohne Consent */
placeholder?: ReactNode;
/** Fallback waehrend Laden */
fallback?: ReactNode;
}
/**
* ConsentGate - Zeigt Inhalt nur bei Consent
*
* @example
* ```tsx
* <ConsentGate
* category="analytics"
* placeholder={<ConsentPlaceholder category="analytics" />}
* >
* <GoogleAnalytics />
* </ConsentGate>
* ```
*/
declare const ConsentGate: FC<ConsentGateProps>;
interface ConsentPlaceholderProps {
/** Kategorie */
category: ConsentCategory;
/** Custom Nachricht */
message?: string;
/** Custom Button-Text */
buttonText?: string;
/** Custom Styling */
className?: string;
}
/**
* ConsentPlaceholder - Placeholder fuer blockierten Inhalt
*/
declare const ConsentPlaceholder: FC<ConsentPlaceholderProps>;
interface ConsentBannerRenderProps {
/** Ist Banner sichtbar? */
isVisible: boolean;
/** Aktueller Consent */
consent: ConsentState | null;
/** Wird Consent benoetigt? */
needsConsent: boolean;
/** Alle akzeptieren */
onAcceptAll: () => void;
/** Alle ablehnen */
onRejectAll: () => void;
/** Auswahl speichern */
onSaveSelection: (categories: Partial<ConsentCategories>) => void;
/** Einstellungen oeffnen */
onShowSettings: () => void;
/** Banner schliessen */
onClose: () => void;
}
interface ConsentBannerProps {
/** Render-Funktion fuer Custom UI */
render?: (props: ConsentBannerRenderProps) => ReactNode;
/** Custom Styling */
className?: string;
}
/**
* ConsentBanner - Headless Banner-Komponente
*
* Kann mit eigener UI gerendert werden oder nutzt Default-UI.
*
* @example
* ```tsx
* // Mit eigener UI
* <ConsentBanner
* render={({ isVisible, onAcceptAll, onRejectAll }) => (
* isVisible && (
* <div className="my-banner">
* <button onClick={onAcceptAll}>Accept</button>
* <button onClick={onRejectAll}>Reject</button>
* </div>
* )
* )}
* />
*
* // Mit Default-UI
* <ConsentBanner />
* ```
*/
declare const ConsentBanner: FC<ConsentBannerProps>;
export { ConsentBanner, type ConsentBannerRenderProps, ConsentContext, type ConsentContextValue, ConsentGate, ConsentPlaceholder, ConsentProvider, useConsent, useConsentManager };

View File

@@ -1,450 +0,0 @@
import * as react from 'react';
import { FC, ReactNode } from 'react';
/**
* Consent SDK Types
*
* DSGVO/TTDSG-konforme Typdefinitionen für das Consent Management System.
*/
/**
* Standard-Consent-Kategorien nach IAB TCF 2.2
*/
type ConsentCategory = 'essential' | 'functional' | 'analytics' | 'marketing' | 'social';
/**
* Consent-Status pro Kategorie
*/
type ConsentCategories = Record<ConsentCategory, boolean>;
/**
* Consent-Status pro Vendor
*/
type ConsentVendors = Record<string, boolean>;
/**
* Aktueller Consent-Zustand
*/
interface ConsentState {
/** Consent pro Kategorie */
categories: ConsentCategories;
/** Consent pro Vendor (optional, für granulare Kontrolle) */
vendors: ConsentVendors;
/** Zeitstempel der letzten Aenderung */
timestamp: string;
/** SDK-Version bei Erstellung */
version: string;
/** Eindeutige Consent-ID vom Backend */
consentId?: string;
/** Ablaufdatum */
expiresAt?: string;
/** IAB TCF String (falls aktiviert) */
tcfString?: string;
}
/**
* Minimaler Consent-Input fuer setConsent()
*/
type ConsentInput = Partial<ConsentCategories> | {
categories?: Partial<ConsentCategories>;
vendors?: ConsentVendors;
};
/**
* UI-Position des Banners
*/
type BannerPosition = 'bottom' | 'top' | 'center';
/**
* Banner-Layout
*/
type BannerLayout = 'bar' | 'modal' | 'floating';
/**
* Farbschema
*/
type BannerTheme = 'light' | 'dark' | 'auto';
/**
* UI-Konfiguration
*/
interface ConsentUIConfig {
/** Position des Banners */
position?: BannerPosition;
/** Layout-Typ */
layout?: BannerLayout;
/** Farbschema */
theme?: BannerTheme;
/** Pfad zu Custom CSS */
customCss?: string;
/** z-index fuer Banner */
zIndex?: number;
/** Scroll blockieren bei Modal */
blockScrollOnModal?: boolean;
/** Custom Container-ID */
containerId?: string;
}
/**
* Consent-Verhaltens-Konfiguration
*/
interface ConsentBehaviorConfig {
/** Muss Nutzer interagieren? */
required?: boolean;
/** "Alle ablehnen" Button sichtbar */
rejectAllVisible?: boolean;
/** "Alle akzeptieren" Button sichtbar */
acceptAllVisible?: boolean;
/** Einzelne Kategorien waehlbar */
granularControl?: boolean;
/** Einzelne Vendors waehlbar */
vendorControl?: boolean;
/** Auswahl speichern */
rememberChoice?: boolean;
/** Speicherdauer in Tagen */
rememberDays?: number;
/** Nur in EU anzeigen (Geo-Targeting) */
geoTargeting?: boolean;
/** Erneut nachfragen nach X Tagen */
recheckAfterDays?: number;
}
/**
* TCF 2.2 Konfiguration
*/
interface TCFConfig {
/** TCF aktivieren */
enabled?: boolean;
/** CMP ID */
cmpId?: number;
/** CMP Version */
cmpVersion?: number;
}
/**
* PWA-spezifische Konfiguration
*/
interface PWAConfig {
/** Offline-Unterstuetzung aktivieren */
offlineSupport?: boolean;
/** Bei Reconnect synchronisieren */
syncOnReconnect?: boolean;
/** Cache-Strategie */
cacheStrategy?: 'stale-while-revalidate' | 'network-first' | 'cache-first';
}
/**
* Haupt-Konfiguration fuer ConsentManager
*/
interface ConsentConfig {
/** API-Endpunkt fuer Consent-Backend */
apiEndpoint: string;
/** Site-ID */
siteId: string;
/** Sprache (ISO 639-1) */
language?: string;
/** Fallback-Sprache */
fallbackLanguage?: string;
/** UI-Konfiguration */
ui?: ConsentUIConfig;
/** Consent-Verhaltens-Konfiguration */
consent?: ConsentBehaviorConfig;
/** Aktive Kategorien */
categories?: ConsentCategory[];
/** TCF 2.2 Konfiguration */
tcf?: TCFConfig;
/** PWA-Konfiguration */
pwa?: PWAConfig;
/** Callback bei Consent-Aenderung */
onConsentChange?: (consent: ConsentState) => void;
/** Callback wenn Banner angezeigt wird */
onBannerShow?: () => void;
/** Callback wenn Banner geschlossen wird */
onBannerHide?: () => void;
/** Callback bei Fehler */
onError?: (error: Error) => void;
/** Debug-Modus aktivieren */
debug?: boolean;
}
/**
* Event-Typen
*/
type ConsentEventType = 'init' | 'change' | 'accept_all' | 'reject_all' | 'save_selection' | 'banner_show' | 'banner_hide' | 'settings_open' | 'settings_close' | 'vendor_enable' | 'vendor_disable' | 'error';
/**
* Event-Listener Callback
*/
type ConsentEventCallback<T = unknown> = (data: T) => void;
/**
* Event-Daten fuer verschiedene Events
*/
type ConsentEventData = {
init: ConsentState | null;
change: ConsentState;
accept_all: ConsentState;
reject_all: ConsentState;
save_selection: ConsentState;
banner_show: undefined;
banner_hide: undefined;
settings_open: undefined;
settings_close: undefined;
vendor_enable: string;
vendor_disable: string;
error: Error;
};
/**
* ConsentManager - Hauptklasse fuer das Consent Management
*
* DSGVO/TTDSG-konformes Consent Management fuer Web, PWA und Mobile.
*/
/**
* ConsentManager - Zentrale Klasse fuer Consent-Verwaltung
*/
declare class ConsentManager {
private config;
private storage;
private scriptBlocker;
private api;
private events;
private currentConsent;
private initialized;
private bannerVisible;
private deviceFingerprint;
constructor(config: ConsentConfig);
/**
* SDK initialisieren
*/
init(): Promise<void>;
/**
* Pruefen ob Consent fuer Kategorie vorhanden
*/
hasConsent(category: ConsentCategory): boolean;
/**
* Pruefen ob Consent fuer Vendor vorhanden
*/
hasVendorConsent(vendorId: string): boolean;
/**
* Aktuellen Consent-State abrufen
*/
getConsent(): ConsentState | null;
/**
* Consent setzen
*/
setConsent(input: ConsentInput): Promise<void>;
/**
* Alle Kategorien akzeptieren
*/
acceptAll(): Promise<void>;
/**
* Alle nicht-essentiellen Kategorien ablehnen
*/
rejectAll(): Promise<void>;
/**
* Alle Einwilligungen widerrufen
*/
revokeAll(): Promise<void>;
/**
* Consent-Daten exportieren (DSGVO Art. 20)
*/
exportConsent(): Promise<string>;
/**
* Pruefen ob Consent-Abfrage noetig
*/
needsConsent(): boolean;
/**
* Banner anzeigen
*/
showBanner(): void;
/**
* Banner verstecken
*/
hideBanner(): void;
/**
* Einstellungs-Modal oeffnen
*/
showSettings(): void;
/**
* Pruefen ob Banner sichtbar
*/
isBannerVisible(): boolean;
/**
* Event-Listener registrieren
*/
on<T extends ConsentEventType>(event: T, callback: ConsentEventCallback<ConsentEventData[T]>): () => void;
/**
* Event-Listener entfernen
*/
off<T extends ConsentEventType>(event: T, callback: ConsentEventCallback<ConsentEventData[T]>): void;
/**
* Konfiguration zusammenfuehren
*/
private mergeConfig;
/**
* Consent-Input normalisieren
*/
private normalizeConsentInput;
/**
* Consent anwenden (Skripte aktivieren/blockieren)
*/
private applyConsent;
/**
* Google Consent Mode v2 aktualisieren
*/
private updateGoogleConsentMode;
/**
* Pruefen ob Consent abgelaufen
*/
private isConsentExpired;
/**
* Event emittieren
*/
private emit;
/**
* Fehler behandeln
*/
private handleError;
/**
* Debug-Logging
*/
private log;
/**
* SDK-Version abrufen
*/
static getVersion(): string;
}
interface ConsentContextValue {
/** ConsentManager Instanz */
manager: ConsentManager | null;
/** Aktueller Consent-State */
consent: ConsentState | null;
/** Ist SDK initialisiert? */
isInitialized: boolean;
/** Wird geladen? */
isLoading: boolean;
/** Ist Banner sichtbar? */
isBannerVisible: boolean;
/** Wird Consent benoetigt? */
needsConsent: boolean;
/** Consent fuer Kategorie pruefen */
hasConsent: (category: ConsentCategory) => boolean;
/** Alle akzeptieren */
acceptAll: () => Promise<void>;
/** Alle ablehnen */
rejectAll: () => Promise<void>;
/** Auswahl speichern */
saveSelection: (categories: Partial<ConsentCategories>) => Promise<void>;
/** Banner anzeigen */
showBanner: () => void;
/** Banner verstecken */
hideBanner: () => void;
/** Einstellungen oeffnen */
showSettings: () => void;
}
declare const ConsentContext: react.Context<ConsentContextValue | null>;
interface ConsentProviderProps {
/** SDK-Konfiguration */
config: ConsentConfig;
/** Kinder-Komponenten */
children: ReactNode;
}
/**
* ConsentProvider - Stellt Consent-Kontext bereit
*/
declare const ConsentProvider: FC<ConsentProviderProps>;
/**
* useConsent - Hook fuer Consent-Zugriff
*
* @example
* ```tsx
* const { hasConsent, acceptAll, rejectAll } = useConsent();
*
* if (hasConsent('analytics')) {
* // Analytics laden
* }
* ```
*/
declare function useConsent(): ConsentContextValue;
declare function useConsent(category: ConsentCategory): ConsentContextValue & {
allowed: boolean;
};
/**
* useConsentManager - Direkter Zugriff auf ConsentManager
*/
declare function useConsentManager(): ConsentManager | null;
interface ConsentGateProps {
/** Erforderliche Kategorie */
category: ConsentCategory;
/** Inhalt bei Consent */
children: ReactNode;
/** Inhalt ohne Consent */
placeholder?: ReactNode;
/** Fallback waehrend Laden */
fallback?: ReactNode;
}
/**
* ConsentGate - Zeigt Inhalt nur bei Consent
*
* @example
* ```tsx
* <ConsentGate
* category="analytics"
* placeholder={<ConsentPlaceholder category="analytics" />}
* >
* <GoogleAnalytics />
* </ConsentGate>
* ```
*/
declare const ConsentGate: FC<ConsentGateProps>;
interface ConsentPlaceholderProps {
/** Kategorie */
category: ConsentCategory;
/** Custom Nachricht */
message?: string;
/** Custom Button-Text */
buttonText?: string;
/** Custom Styling */
className?: string;
}
/**
* ConsentPlaceholder - Placeholder fuer blockierten Inhalt
*/
declare const ConsentPlaceholder: FC<ConsentPlaceholderProps>;
interface ConsentBannerRenderProps {
/** Ist Banner sichtbar? */
isVisible: boolean;
/** Aktueller Consent */
consent: ConsentState | null;
/** Wird Consent benoetigt? */
needsConsent: boolean;
/** Alle akzeptieren */
onAcceptAll: () => void;
/** Alle ablehnen */
onRejectAll: () => void;
/** Auswahl speichern */
onSaveSelection: (categories: Partial<ConsentCategories>) => void;
/** Einstellungen oeffnen */
onShowSettings: () => void;
/** Banner schliessen */
onClose: () => void;
}
interface ConsentBannerProps {
/** Render-Funktion fuer Custom UI */
render?: (props: ConsentBannerRenderProps) => ReactNode;
/** Custom Styling */
className?: string;
}
/**
* ConsentBanner - Headless Banner-Komponente
*
* Kann mit eigener UI gerendert werden oder nutzt Default-UI.
*
* @example
* ```tsx
* // Mit eigener UI
* <ConsentBanner
* render={({ isVisible, onAcceptAll, onRejectAll }) => (
* isVisible && (
* <div className="my-banner">
* <button onClick={onAcceptAll}>Accept</button>
* <button onClick={onRejectAll}>Reject</button>
* </div>
* )
* )}
* />
*
* // Mit Default-UI
* <ConsentBanner />
* ```
*/
declare const ConsentBanner: FC<ConsentBannerProps>;
export { ConsentBanner, type ConsentBannerRenderProps, ConsentContext, type ConsentContextValue, ConsentGate, ConsentPlaceholder, ConsentProvider, useConsent, useConsentManager };

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,483 +0,0 @@
import * as vue from 'vue';
import { InjectionKey, Ref, PropType } from 'vue';
/**
* Consent SDK Types
*
* DSGVO/TTDSG-konforme Typdefinitionen für das Consent Management System.
*/
/**
* Standard-Consent-Kategorien nach IAB TCF 2.2
*/
type ConsentCategory = 'essential' | 'functional' | 'analytics' | 'marketing' | 'social';
/**
* Consent-Status pro Kategorie
*/
type ConsentCategories = Record<ConsentCategory, boolean>;
/**
* Consent-Status pro Vendor
*/
type ConsentVendors = Record<string, boolean>;
/**
* Aktueller Consent-Zustand
*/
interface ConsentState {
/** Consent pro Kategorie */
categories: ConsentCategories;
/** Consent pro Vendor (optional, für granulare Kontrolle) */
vendors: ConsentVendors;
/** Zeitstempel der letzten Aenderung */
timestamp: string;
/** SDK-Version bei Erstellung */
version: string;
/** Eindeutige Consent-ID vom Backend */
consentId?: string;
/** Ablaufdatum */
expiresAt?: string;
/** IAB TCF String (falls aktiviert) */
tcfString?: string;
}
/**
* Minimaler Consent-Input fuer setConsent()
*/
type ConsentInput = Partial<ConsentCategories> | {
categories?: Partial<ConsentCategories>;
vendors?: ConsentVendors;
};
/**
* UI-Position des Banners
*/
type BannerPosition = 'bottom' | 'top' | 'center';
/**
* Banner-Layout
*/
type BannerLayout = 'bar' | 'modal' | 'floating';
/**
* Farbschema
*/
type BannerTheme = 'light' | 'dark' | 'auto';
/**
* UI-Konfiguration
*/
interface ConsentUIConfig {
/** Position des Banners */
position?: BannerPosition;
/** Layout-Typ */
layout?: BannerLayout;
/** Farbschema */
theme?: BannerTheme;
/** Pfad zu Custom CSS */
customCss?: string;
/** z-index fuer Banner */
zIndex?: number;
/** Scroll blockieren bei Modal */
blockScrollOnModal?: boolean;
/** Custom Container-ID */
containerId?: string;
}
/**
* Consent-Verhaltens-Konfiguration
*/
interface ConsentBehaviorConfig {
/** Muss Nutzer interagieren? */
required?: boolean;
/** "Alle ablehnen" Button sichtbar */
rejectAllVisible?: boolean;
/** "Alle akzeptieren" Button sichtbar */
acceptAllVisible?: boolean;
/** Einzelne Kategorien waehlbar */
granularControl?: boolean;
/** Einzelne Vendors waehlbar */
vendorControl?: boolean;
/** Auswahl speichern */
rememberChoice?: boolean;
/** Speicherdauer in Tagen */
rememberDays?: number;
/** Nur in EU anzeigen (Geo-Targeting) */
geoTargeting?: boolean;
/** Erneut nachfragen nach X Tagen */
recheckAfterDays?: number;
}
/**
* TCF 2.2 Konfiguration
*/
interface TCFConfig {
/** TCF aktivieren */
enabled?: boolean;
/** CMP ID */
cmpId?: number;
/** CMP Version */
cmpVersion?: number;
}
/**
* PWA-spezifische Konfiguration
*/
interface PWAConfig {
/** Offline-Unterstuetzung aktivieren */
offlineSupport?: boolean;
/** Bei Reconnect synchronisieren */
syncOnReconnect?: boolean;
/** Cache-Strategie */
cacheStrategy?: 'stale-while-revalidate' | 'network-first' | 'cache-first';
}
/**
* Haupt-Konfiguration fuer ConsentManager
*/
interface ConsentConfig {
/** API-Endpunkt fuer Consent-Backend */
apiEndpoint: string;
/** Site-ID */
siteId: string;
/** Sprache (ISO 639-1) */
language?: string;
/** Fallback-Sprache */
fallbackLanguage?: string;
/** UI-Konfiguration */
ui?: ConsentUIConfig;
/** Consent-Verhaltens-Konfiguration */
consent?: ConsentBehaviorConfig;
/** Aktive Kategorien */
categories?: ConsentCategory[];
/** TCF 2.2 Konfiguration */
tcf?: TCFConfig;
/** PWA-Konfiguration */
pwa?: PWAConfig;
/** Callback bei Consent-Aenderung */
onConsentChange?: (consent: ConsentState) => void;
/** Callback wenn Banner angezeigt wird */
onBannerShow?: () => void;
/** Callback wenn Banner geschlossen wird */
onBannerHide?: () => void;
/** Callback bei Fehler */
onError?: (error: Error) => void;
/** Debug-Modus aktivieren */
debug?: boolean;
}
/**
* Event-Typen
*/
type ConsentEventType = 'init' | 'change' | 'accept_all' | 'reject_all' | 'save_selection' | 'banner_show' | 'banner_hide' | 'settings_open' | 'settings_close' | 'vendor_enable' | 'vendor_disable' | 'error';
/**
* Event-Listener Callback
*/
type ConsentEventCallback<T = unknown> = (data: T) => void;
/**
* Event-Daten fuer verschiedene Events
*/
type ConsentEventData = {
init: ConsentState | null;
change: ConsentState;
accept_all: ConsentState;
reject_all: ConsentState;
save_selection: ConsentState;
banner_show: undefined;
banner_hide: undefined;
settings_open: undefined;
settings_close: undefined;
vendor_enable: string;
vendor_disable: string;
error: Error;
};
/**
* ConsentManager - Hauptklasse fuer das Consent Management
*
* DSGVO/TTDSG-konformes Consent Management fuer Web, PWA und Mobile.
*/
/**
* ConsentManager - Zentrale Klasse fuer Consent-Verwaltung
*/
declare class ConsentManager {
private config;
private storage;
private scriptBlocker;
private api;
private events;
private currentConsent;
private initialized;
private bannerVisible;
private deviceFingerprint;
constructor(config: ConsentConfig);
/**
* SDK initialisieren
*/
init(): Promise<void>;
/**
* Pruefen ob Consent fuer Kategorie vorhanden
*/
hasConsent(category: ConsentCategory): boolean;
/**
* Pruefen ob Consent fuer Vendor vorhanden
*/
hasVendorConsent(vendorId: string): boolean;
/**
* Aktuellen Consent-State abrufen
*/
getConsent(): ConsentState | null;
/**
* Consent setzen
*/
setConsent(input: ConsentInput): Promise<void>;
/**
* Alle Kategorien akzeptieren
*/
acceptAll(): Promise<void>;
/**
* Alle nicht-essentiellen Kategorien ablehnen
*/
rejectAll(): Promise<void>;
/**
* Alle Einwilligungen widerrufen
*/
revokeAll(): Promise<void>;
/**
* Consent-Daten exportieren (DSGVO Art. 20)
*/
exportConsent(): Promise<string>;
/**
* Pruefen ob Consent-Abfrage noetig
*/
needsConsent(): boolean;
/**
* Banner anzeigen
*/
showBanner(): void;
/**
* Banner verstecken
*/
hideBanner(): void;
/**
* Einstellungs-Modal oeffnen
*/
showSettings(): void;
/**
* Pruefen ob Banner sichtbar
*/
isBannerVisible(): boolean;
/**
* Event-Listener registrieren
*/
on<T extends ConsentEventType>(event: T, callback: ConsentEventCallback<ConsentEventData[T]>): () => void;
/**
* Event-Listener entfernen
*/
off<T extends ConsentEventType>(event: T, callback: ConsentEventCallback<ConsentEventData[T]>): void;
/**
* Konfiguration zusammenfuehren
*/
private mergeConfig;
/**
* Consent-Input normalisieren
*/
private normalizeConsentInput;
/**
* Consent anwenden (Skripte aktivieren/blockieren)
*/
private applyConsent;
/**
* Google Consent Mode v2 aktualisieren
*/
private updateGoogleConsentMode;
/**
* Pruefen ob Consent abgelaufen
*/
private isConsentExpired;
/**
* Event emittieren
*/
private emit;
/**
* Fehler behandeln
*/
private handleError;
/**
* Debug-Logging
*/
private log;
/**
* SDK-Version abrufen
*/
static getVersion(): string;
}
declare const CONSENT_KEY: InjectionKey<ConsentContext>;
interface ConsentContext {
manager: Ref<ConsentManager | null>;
consent: Ref<ConsentState | null>;
isInitialized: Ref<boolean>;
isLoading: Ref<boolean>;
isBannerVisible: Ref<boolean>;
needsConsent: Ref<boolean>;
hasConsent: (category: ConsentCategory) => boolean;
acceptAll: () => Promise<void>;
rejectAll: () => Promise<void>;
saveSelection: (categories: Partial<ConsentCategories>) => Promise<void>;
showBanner: () => void;
hideBanner: () => void;
showSettings: () => void;
}
/**
* Haupt-Composable fuer Consent-Zugriff
*
* @example
* ```vue
* <script setup>
* const { hasConsent, acceptAll } = useConsent();
*
* if (hasConsent('analytics')) {
* // Analytics laden
* }
* </script>
* ```
*/
declare function useConsent(): ConsentContext;
/**
* Consent-Provider einrichten (in App.vue aufrufen)
*
* @example
* ```vue
* <script setup>
* import { provideConsent } from '@breakpilot/consent-sdk/vue';
*
* provideConsent({
* apiEndpoint: 'https://consent.example.com/api/v1',
* siteId: 'site_abc123',
* });
* </script>
* ```
*/
declare function provideConsent(config: ConsentConfig): ConsentContext;
/**
* ConsentProvider - Wrapper-Komponente
*
* @example
* ```vue
* <ConsentProvider :config="config">
* <App />
* </ConsentProvider>
* ```
*/
declare const ConsentProvider: vue.DefineComponent<vue.ExtractPropTypes<{
config: {
type: PropType<ConsentConfig>;
required: true;
};
}>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
[key: string]: any;
}>[] | undefined, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
config: {
type: PropType<ConsentConfig>;
required: true;
};
}>> & Readonly<{}>, {}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
/**
* ConsentGate - Zeigt Inhalt nur bei Consent
*
* @example
* ```vue
* <ConsentGate category="analytics">
* <template #default>
* <AnalyticsComponent />
* </template>
* <template #placeholder>
* <p>Bitte akzeptieren Sie Statistik-Cookies.</p>
* </template>
* </ConsentGate>
* ```
*/
declare const ConsentGate: vue.DefineComponent<vue.ExtractPropTypes<{
category: {
type: PropType<ConsentCategory>;
required: true;
};
}>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
[key: string]: any;
}>[] | null | undefined, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
category: {
type: PropType<ConsentCategory>;
required: true;
};
}>> & Readonly<{}>, {}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
/**
* ConsentPlaceholder - Placeholder fuer blockierten Inhalt
*
* @example
* ```vue
* <ConsentPlaceholder category="marketing" />
* ```
*/
declare const ConsentPlaceholder: vue.DefineComponent<vue.ExtractPropTypes<{
category: {
type: PropType<ConsentCategory>;
required: true;
};
message: {
type: StringConstructor;
default: string;
};
buttonText: {
type: StringConstructor;
default: string;
};
}>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
[key: string]: any;
}>, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
category: {
type: PropType<ConsentCategory>;
required: true;
};
message: {
type: StringConstructor;
default: string;
};
buttonText: {
type: StringConstructor;
default: string;
};
}>> & Readonly<{}>, {
message: string;
buttonText: string;
}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
/**
* ConsentBanner - Cookie-Banner Komponente
*
* @example
* ```vue
* <ConsentBanner>
* <template #default="{ isVisible, onAcceptAll, onRejectAll, onShowSettings }">
* <div v-if="isVisible" class="my-banner">
* <button @click="onAcceptAll">Accept</button>
* <button @click="onRejectAll">Reject</button>
* </div>
* </template>
* </ConsentBanner>
* ```
*/
declare const ConsentBanner: vue.DefineComponent<{}, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
[key: string]: any;
}> | vue.VNode<vue.RendererNode, vue.RendererElement, {
[key: string]: any;
}>[] | null, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
/**
* Vue Plugin fuer globale Installation
*
* @example
* ```ts
* import { createApp } from 'vue';
* import { ConsentPlugin } from '@breakpilot/consent-sdk/vue';
*
* const app = createApp(App);
* app.use(ConsentPlugin, {
* apiEndpoint: 'https://consent.example.com/api/v1',
* siteId: 'site_abc123',
* });
* ```
*/
declare const ConsentPlugin: {
install(app: {
provide: (key: symbol | string, value: unknown) => void;
}, config: ConsentConfig): void;
};
export { CONSENT_KEY, ConsentBanner, type ConsentContext, ConsentGate, ConsentPlaceholder, ConsentPlugin, ConsentProvider, provideConsent, useConsent };

View File

@@ -1,483 +0,0 @@
import * as vue from 'vue';
import { InjectionKey, Ref, PropType } from 'vue';
/**
* Consent SDK Types
*
* DSGVO/TTDSG-konforme Typdefinitionen für das Consent Management System.
*/
/**
* Standard-Consent-Kategorien nach IAB TCF 2.2
*/
type ConsentCategory = 'essential' | 'functional' | 'analytics' | 'marketing' | 'social';
/**
* Consent-Status pro Kategorie
*/
type ConsentCategories = Record<ConsentCategory, boolean>;
/**
* Consent-Status pro Vendor
*/
type ConsentVendors = Record<string, boolean>;
/**
* Aktueller Consent-Zustand
*/
interface ConsentState {
/** Consent pro Kategorie */
categories: ConsentCategories;
/** Consent pro Vendor (optional, für granulare Kontrolle) */
vendors: ConsentVendors;
/** Zeitstempel der letzten Aenderung */
timestamp: string;
/** SDK-Version bei Erstellung */
version: string;
/** Eindeutige Consent-ID vom Backend */
consentId?: string;
/** Ablaufdatum */
expiresAt?: string;
/** IAB TCF String (falls aktiviert) */
tcfString?: string;
}
/**
* Minimaler Consent-Input fuer setConsent()
*/
type ConsentInput = Partial<ConsentCategories> | {
categories?: Partial<ConsentCategories>;
vendors?: ConsentVendors;
};
/**
* UI-Position des Banners
*/
type BannerPosition = 'bottom' | 'top' | 'center';
/**
* Banner-Layout
*/
type BannerLayout = 'bar' | 'modal' | 'floating';
/**
* Farbschema
*/
type BannerTheme = 'light' | 'dark' | 'auto';
/**
* UI-Konfiguration
*/
interface ConsentUIConfig {
/** Position des Banners */
position?: BannerPosition;
/** Layout-Typ */
layout?: BannerLayout;
/** Farbschema */
theme?: BannerTheme;
/** Pfad zu Custom CSS */
customCss?: string;
/** z-index fuer Banner */
zIndex?: number;
/** Scroll blockieren bei Modal */
blockScrollOnModal?: boolean;
/** Custom Container-ID */
containerId?: string;
}
/**
* Consent-Verhaltens-Konfiguration
*/
interface ConsentBehaviorConfig {
/** Muss Nutzer interagieren? */
required?: boolean;
/** "Alle ablehnen" Button sichtbar */
rejectAllVisible?: boolean;
/** "Alle akzeptieren" Button sichtbar */
acceptAllVisible?: boolean;
/** Einzelne Kategorien waehlbar */
granularControl?: boolean;
/** Einzelne Vendors waehlbar */
vendorControl?: boolean;
/** Auswahl speichern */
rememberChoice?: boolean;
/** Speicherdauer in Tagen */
rememberDays?: number;
/** Nur in EU anzeigen (Geo-Targeting) */
geoTargeting?: boolean;
/** Erneut nachfragen nach X Tagen */
recheckAfterDays?: number;
}
/**
* TCF 2.2 Konfiguration
*/
interface TCFConfig {
/** TCF aktivieren */
enabled?: boolean;
/** CMP ID */
cmpId?: number;
/** CMP Version */
cmpVersion?: number;
}
/**
* PWA-spezifische Konfiguration
*/
interface PWAConfig {
/** Offline-Unterstuetzung aktivieren */
offlineSupport?: boolean;
/** Bei Reconnect synchronisieren */
syncOnReconnect?: boolean;
/** Cache-Strategie */
cacheStrategy?: 'stale-while-revalidate' | 'network-first' | 'cache-first';
}
/**
* Haupt-Konfiguration fuer ConsentManager
*/
interface ConsentConfig {
/** API-Endpunkt fuer Consent-Backend */
apiEndpoint: string;
/** Site-ID */
siteId: string;
/** Sprache (ISO 639-1) */
language?: string;
/** Fallback-Sprache */
fallbackLanguage?: string;
/** UI-Konfiguration */
ui?: ConsentUIConfig;
/** Consent-Verhaltens-Konfiguration */
consent?: ConsentBehaviorConfig;
/** Aktive Kategorien */
categories?: ConsentCategory[];
/** TCF 2.2 Konfiguration */
tcf?: TCFConfig;
/** PWA-Konfiguration */
pwa?: PWAConfig;
/** Callback bei Consent-Aenderung */
onConsentChange?: (consent: ConsentState) => void;
/** Callback wenn Banner angezeigt wird */
onBannerShow?: () => void;
/** Callback wenn Banner geschlossen wird */
onBannerHide?: () => void;
/** Callback bei Fehler */
onError?: (error: Error) => void;
/** Debug-Modus aktivieren */
debug?: boolean;
}
/**
* Event-Typen
*/
type ConsentEventType = 'init' | 'change' | 'accept_all' | 'reject_all' | 'save_selection' | 'banner_show' | 'banner_hide' | 'settings_open' | 'settings_close' | 'vendor_enable' | 'vendor_disable' | 'error';
/**
* Event-Listener Callback
*/
type ConsentEventCallback<T = unknown> = (data: T) => void;
/**
* Event-Daten fuer verschiedene Events
*/
type ConsentEventData = {
init: ConsentState | null;
change: ConsentState;
accept_all: ConsentState;
reject_all: ConsentState;
save_selection: ConsentState;
banner_show: undefined;
banner_hide: undefined;
settings_open: undefined;
settings_close: undefined;
vendor_enable: string;
vendor_disable: string;
error: Error;
};
/**
* ConsentManager - Hauptklasse fuer das Consent Management
*
* DSGVO/TTDSG-konformes Consent Management fuer Web, PWA und Mobile.
*/
/**
* ConsentManager - Zentrale Klasse fuer Consent-Verwaltung
*/
declare class ConsentManager {
private config;
private storage;
private scriptBlocker;
private api;
private events;
private currentConsent;
private initialized;
private bannerVisible;
private deviceFingerprint;
constructor(config: ConsentConfig);
/**
* SDK initialisieren
*/
init(): Promise<void>;
/**
* Pruefen ob Consent fuer Kategorie vorhanden
*/
hasConsent(category: ConsentCategory): boolean;
/**
* Pruefen ob Consent fuer Vendor vorhanden
*/
hasVendorConsent(vendorId: string): boolean;
/**
* Aktuellen Consent-State abrufen
*/
getConsent(): ConsentState | null;
/**
* Consent setzen
*/
setConsent(input: ConsentInput): Promise<void>;
/**
* Alle Kategorien akzeptieren
*/
acceptAll(): Promise<void>;
/**
* Alle nicht-essentiellen Kategorien ablehnen
*/
rejectAll(): Promise<void>;
/**
* Alle Einwilligungen widerrufen
*/
revokeAll(): Promise<void>;
/**
* Consent-Daten exportieren (DSGVO Art. 20)
*/
exportConsent(): Promise<string>;
/**
* Pruefen ob Consent-Abfrage noetig
*/
needsConsent(): boolean;
/**
* Banner anzeigen
*/
showBanner(): void;
/**
* Banner verstecken
*/
hideBanner(): void;
/**
* Einstellungs-Modal oeffnen
*/
showSettings(): void;
/**
* Pruefen ob Banner sichtbar
*/
isBannerVisible(): boolean;
/**
* Event-Listener registrieren
*/
on<T extends ConsentEventType>(event: T, callback: ConsentEventCallback<ConsentEventData[T]>): () => void;
/**
* Event-Listener entfernen
*/
off<T extends ConsentEventType>(event: T, callback: ConsentEventCallback<ConsentEventData[T]>): void;
/**
* Konfiguration zusammenfuehren
*/
private mergeConfig;
/**
* Consent-Input normalisieren
*/
private normalizeConsentInput;
/**
* Consent anwenden (Skripte aktivieren/blockieren)
*/
private applyConsent;
/**
* Google Consent Mode v2 aktualisieren
*/
private updateGoogleConsentMode;
/**
* Pruefen ob Consent abgelaufen
*/
private isConsentExpired;
/**
* Event emittieren
*/
private emit;
/**
* Fehler behandeln
*/
private handleError;
/**
* Debug-Logging
*/
private log;
/**
* SDK-Version abrufen
*/
static getVersion(): string;
}
declare const CONSENT_KEY: InjectionKey<ConsentContext>;
interface ConsentContext {
manager: Ref<ConsentManager | null>;
consent: Ref<ConsentState | null>;
isInitialized: Ref<boolean>;
isLoading: Ref<boolean>;
isBannerVisible: Ref<boolean>;
needsConsent: Ref<boolean>;
hasConsent: (category: ConsentCategory) => boolean;
acceptAll: () => Promise<void>;
rejectAll: () => Promise<void>;
saveSelection: (categories: Partial<ConsentCategories>) => Promise<void>;
showBanner: () => void;
hideBanner: () => void;
showSettings: () => void;
}
/**
* Haupt-Composable fuer Consent-Zugriff
*
* @example
* ```vue
* <script setup>
* const { hasConsent, acceptAll } = useConsent();
*
* if (hasConsent('analytics')) {
* // Analytics laden
* }
* </script>
* ```
*/
declare function useConsent(): ConsentContext;
/**
* Consent-Provider einrichten (in App.vue aufrufen)
*
* @example
* ```vue
* <script setup>
* import { provideConsent } from '@breakpilot/consent-sdk/vue';
*
* provideConsent({
* apiEndpoint: 'https://consent.example.com/api/v1',
* siteId: 'site_abc123',
* });
* </script>
* ```
*/
declare function provideConsent(config: ConsentConfig): ConsentContext;
/**
* ConsentProvider - Wrapper-Komponente
*
* @example
* ```vue
* <ConsentProvider :config="config">
* <App />
* </ConsentProvider>
* ```
*/
declare const ConsentProvider: vue.DefineComponent<vue.ExtractPropTypes<{
config: {
type: PropType<ConsentConfig>;
required: true;
};
}>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
[key: string]: any;
}>[] | undefined, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
config: {
type: PropType<ConsentConfig>;
required: true;
};
}>> & Readonly<{}>, {}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
/**
* ConsentGate - Zeigt Inhalt nur bei Consent
*
* @example
* ```vue
* <ConsentGate category="analytics">
* <template #default>
* <AnalyticsComponent />
* </template>
* <template #placeholder>
* <p>Bitte akzeptieren Sie Statistik-Cookies.</p>
* </template>
* </ConsentGate>
* ```
*/
declare const ConsentGate: vue.DefineComponent<vue.ExtractPropTypes<{
category: {
type: PropType<ConsentCategory>;
required: true;
};
}>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
[key: string]: any;
}>[] | null | undefined, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
category: {
type: PropType<ConsentCategory>;
required: true;
};
}>> & Readonly<{}>, {}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
/**
* ConsentPlaceholder - Placeholder fuer blockierten Inhalt
*
* @example
* ```vue
* <ConsentPlaceholder category="marketing" />
* ```
*/
declare const ConsentPlaceholder: vue.DefineComponent<vue.ExtractPropTypes<{
category: {
type: PropType<ConsentCategory>;
required: true;
};
message: {
type: StringConstructor;
default: string;
};
buttonText: {
type: StringConstructor;
default: string;
};
}>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
[key: string]: any;
}>, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
category: {
type: PropType<ConsentCategory>;
required: true;
};
message: {
type: StringConstructor;
default: string;
};
buttonText: {
type: StringConstructor;
default: string;
};
}>> & Readonly<{}>, {
message: string;
buttonText: string;
}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
/**
* ConsentBanner - Cookie-Banner Komponente
*
* @example
* ```vue
* <ConsentBanner>
* <template #default="{ isVisible, onAcceptAll, onRejectAll, onShowSettings }">
* <div v-if="isVisible" class="my-banner">
* <button @click="onAcceptAll">Accept</button>
* <button @click="onRejectAll">Reject</button>
* </div>
* </template>
* </ConsentBanner>
* ```
*/
declare const ConsentBanner: vue.DefineComponent<{}, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
[key: string]: any;
}> | vue.VNode<vue.RendererNode, vue.RendererElement, {
[key: string]: any;
}>[] | null, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
/**
* Vue Plugin fuer globale Installation
*
* @example
* ```ts
* import { createApp } from 'vue';
* import { ConsentPlugin } from '@breakpilot/consent-sdk/vue';
*
* const app = createApp(App);
* app.use(ConsentPlugin, {
* apiEndpoint: 'https://consent.example.com/api/v1',
* siteId: 'site_abc123',
* });
* ```
*/
declare const ConsentPlugin: {
install(app: {
provide: (key: symbol | string, value: unknown) => void;
}, config: ConsentConfig): void;
};
export { CONSENT_KEY, ConsentBanner, type ConsentContext, ConsentGate, ConsentPlaceholder, ConsentPlugin, ConsentProvider, provideConsent, useConsent };

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
site_name: Breakpilot Dokumentation
site_url: https://macmini:8008
site_name: BreakPilot Core - Dokumentation
site_url: https://macmini:8009
docs_dir: docs-src
site_dir: docs-site
@@ -9,14 +9,16 @@ theme:
palette:
- scheme: default
primary: teal
accent: teal
toggle:
icon: material/brightness-7
name: Dark Mode aktivieren
name: Dark Mode
- scheme: slate
primary: teal
accent: teal
toggle:
icon: material/brightness-4
name: Light Mode aktivieren
name: Light Mode
features:
- search.highlight
- search.suggest
@@ -25,7 +27,6 @@ theme:
- navigation.expand
- navigation.top
- content.code.copy
- content.tabs.link
- toc.follow
plugins:
@@ -52,46 +53,18 @@ markdown_extensions:
- toc:
permalink: true
extra:
social:
- icon: fontawesome/brands/github
link: http://macmini:3003/breakpilot/breakpilot-pwa
nav:
- Start: index.md
- Erste Schritte:
- Umgebung einrichten: getting-started/environment-setup.md
- Einrichtung: getting-started/environment-setup.md
- Mac Mini Setup: getting-started/mac-mini-setup.md
- Architektur:
- Systemuebersicht: architecture/system-architecture.md
- System-Architektur: architecture/system-architecture.md
- Auth-System: architecture/auth-system.md
- Mail-RBAC: architecture/mail-rbac-architecture.md
- Multi-Agent: architecture/multi-agent.md
- Mail & RBAC: architecture/mail-rbac-architecture.md
- Secrets Management: architecture/secrets-management.md
- DevSecOps: architecture/devsecops.md
- Environments: architecture/environments.md
- Zeugnis-System: architecture/zeugnis-system.md
- Services:
- KI-Daten-Pipeline:
- Uebersicht: services/ki-daten-pipeline/index.md
- Architektur: services/ki-daten-pipeline/architecture.md
- Klausur-Service:
- Uebersicht: services/klausur-service/index.md
- BYOEH Architektur: services/klausur-service/BYOEH-Architecture.md
- BYOEH Developer Guide: services/klausur-service/BYOEH-Developer-Guide.md
- NiBiS Pipeline: services/klausur-service/NiBiS-Ingestion-Pipeline.md
- OCR Labeling: services/klausur-service/OCR-Labeling-Spec.md
- OCR Compare: services/klausur-service/OCR-Compare.md
- RAG Admin: services/klausur-service/RAG-Admin-Spec.md
- Worksheet Editor: services/klausur-service/Worksheet-Editor-Architecture.md
- Voice-Service: services/voice-service/index.md
- Agent-Core: services/agent-core/index.md
- AI-Compliance-SDK:
- Uebersicht: services/ai-compliance-sdk/index.md
- Architektur: services/ai-compliance-sdk/ARCHITECTURE.md
- Developer Guide: services/ai-compliance-sdk/DEVELOPER.md
- Auditor Dokumentation: services/ai-compliance-sdk/AUDITOR_DOCUMENTATION.md
- SBOM: services/ai-compliance-sdk/SBOM.md
- API:
- Backend API: api/backend-api.md
- Entwicklung:

View File

@@ -188,7 +188,7 @@ server {
# Docs proxy
location /docs/ {
set $upstream_docs bp-core-docs:8009;
set $upstream_docs bp-lehrer-docs:80;
rewrite ^/docs(/.*)$ $1 break;
proxy_pass http://$upstream_docs;
proxy_http_version 1.1;
@@ -242,7 +242,7 @@ server {
# Docs proxy
location /docs/ {
set $upstream_docs bp-core-docs:8009;
set $upstream_docs bp-compliance-docs:80;
rewrite ^/docs(/.*)$ $1 break;
proxy_pass http://$upstream_docs;
proxy_http_version 1.1;