This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/consent-service/migrations/005_banner_consent_tables.sql
Benjamin Admin bfdaf63ba9 fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

224 lines
7.3 KiB
PL/PgSQL

-- Migration: Banner Consent Tables
-- Für @breakpilot/consent-sdk
-- DSGVO/TTDSG-konforme Speicherung von Cookie-Einwilligungen
-- ========================================
-- Banner Consents (anonyme Einwilligungen)
-- ========================================
CREATE TABLE IF NOT EXISTS banner_consents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
site_id VARCHAR(50) NOT NULL,
device_fingerprint VARCHAR(64) NOT NULL,
user_id VARCHAR(100), -- Optional: Für eingeloggte Nutzer
-- Consent-Daten
categories JSONB NOT NULL DEFAULT '{}', -- { "analytics": true, "marketing": false }
vendors JSONB DEFAULT '{}', -- { "google-analytics": true }
tcf_string TEXT, -- IAB TCF String
-- Metadaten (anonymisiert)
ip_hash VARCHAR(64), -- Anonymisierte IP
user_agent TEXT,
language VARCHAR(10),
platform VARCHAR(20), -- web, ios, android
app_version VARCHAR(20),
-- Versionierung
version VARCHAR(20) DEFAULT '1.0.0',
-- Zeitstempel
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
expires_at TIMESTAMPTZ,
revoked_at TIMESTAMPTZ,
-- Constraints
CONSTRAINT unique_site_device UNIQUE (site_id, device_fingerprint)
);
-- Indizes für schnelle Abfragen
CREATE INDEX IF NOT EXISTS idx_banner_consents_site ON banner_consents(site_id);
CREATE INDEX IF NOT EXISTS idx_banner_consents_user ON banner_consents(user_id) WHERE user_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_banner_consents_device ON banner_consents(device_fingerprint);
CREATE INDEX IF NOT EXISTS idx_banner_consents_created ON banner_consents(created_at);
CREATE INDEX IF NOT EXISTS idx_banner_consents_expires ON banner_consents(expires_at) WHERE expires_at IS NOT NULL;
-- ========================================
-- Audit Log (unveränderbar)
-- ========================================
CREATE TABLE IF NOT EXISTS banner_consent_audit_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
consent_id UUID NOT NULL,
action VARCHAR(20) NOT NULL, -- created, updated, revoked
details JSONB,
ip_hash VARCHAR(64),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Kein UPDATE/DELETE auf Audit-Log
-- REVOKE UPDATE, DELETE ON banner_consent_audit_log FROM PUBLIC;
CREATE INDEX IF NOT EXISTS idx_banner_audit_consent ON banner_consent_audit_log(consent_id);
CREATE INDEX IF NOT EXISTS idx_banner_audit_created ON banner_consent_audit_log(created_at);
-- ========================================
-- Site-Konfigurationen
-- ========================================
CREATE TABLE IF NOT EXISTS banner_site_configs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
site_id VARCHAR(50) UNIQUE NOT NULL,
site_name VARCHAR(100) NOT NULL,
-- UI-Konfiguration
ui_theme VARCHAR(20) DEFAULT 'auto',
ui_position VARCHAR(20) DEFAULT 'bottom',
ui_layout VARCHAR(20) DEFAULT 'modal',
custom_css TEXT,
-- Rechtliche Links
privacy_policy_url VARCHAR(255),
imprint_url VARCHAR(255),
dpo_name VARCHAR(100),
dpo_email VARCHAR(100),
-- TCF 2.2
tcf_enabled BOOLEAN DEFAULT FALSE,
tcf_cmp_id INTEGER,
tcf_cmp_version INTEGER,
-- Zeitstempel
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- ========================================
-- Kategorie-Konfigurationen pro Site
-- ========================================
CREATE TABLE IF NOT EXISTS banner_category_configs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
site_id VARCHAR(50) NOT NULL,
category_id VARCHAR(50) NOT NULL,
-- Namen (mehrsprachig)
name_de VARCHAR(100) NOT NULL,
name_en VARCHAR(100),
-- Beschreibungen (mehrsprachig)
description_de TEXT,
description_en TEXT,
-- Einstellungen
is_required BOOLEAN DEFAULT FALSE,
sort_order INTEGER DEFAULT 0,
is_active BOOLEAN DEFAULT TRUE,
-- Zeitstempel
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT unique_site_category UNIQUE (site_id, category_id)
);
-- ========================================
-- Vendor-Konfigurationen
-- ========================================
CREATE TABLE IF NOT EXISTS banner_vendor_configs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
site_id VARCHAR(50) NOT NULL,
category_id VARCHAR(50) NOT NULL,
vendor_id VARCHAR(100) NOT NULL,
-- Vendor-Informationen
name VARCHAR(100) NOT NULL,
privacy_policy_url VARCHAR(255),
data_retention VARCHAR(50),
data_transfer VARCHAR(100),
-- TCF
tcf_vendor_id INTEGER,
tcf_purposes JSONB,
tcf_legitimate_interests JSONB,
-- Cookies
cookies JSONB DEFAULT '[]',
-- Zeitstempel
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT unique_site_vendor UNIQUE (site_id, vendor_id)
);
-- ========================================
-- Helper-Funktionen
-- ========================================
-- Abgelaufene Consents bereinigen
CREATE OR REPLACE FUNCTION cleanup_expired_banner_consents()
RETURNS INTEGER AS $$
DECLARE
deleted_count INTEGER;
BEGIN
-- Soft-Delete nach 30 Tagen nach Ablauf
WITH deleted AS (
DELETE FROM banner_consents
WHERE expires_at < NOW() - INTERVAL '30 days'
AND revoked_at IS NULL
RETURNING id
)
SELECT COUNT(*) INTO deleted_count FROM deleted;
RETURN deleted_count;
END;
$$ LANGUAGE plpgsql;
-- Statistik-View
CREATE OR REPLACE VIEW banner_consent_stats AS
SELECT
site_id,
DATE(created_at) as consent_date,
COUNT(*) as total_consents,
COUNT(*) FILTER (WHERE revoked_at IS NOT NULL) as revoked_consents,
COUNT(*) FILTER (WHERE (categories->>'analytics')::boolean = true) as analytics_accepted,
COUNT(*) FILTER (WHERE (categories->>'marketing')::boolean = true) as marketing_accepted,
COUNT(*) FILTER (WHERE (categories->>'functional')::boolean = true) as functional_accepted,
COUNT(*) FILTER (WHERE (categories->>'social')::boolean = true) as social_accepted
FROM banner_consents
GROUP BY site_id, DATE(created_at);
-- ========================================
-- Standard-Kategorien einfügen
-- ========================================
INSERT INTO banner_category_configs (site_id, category_id, name_de, name_en, description_de, description_en, is_required, sort_order)
VALUES
('default', 'essential', 'Essentiell', 'Essential',
'Notwendig für die Grundfunktionen der Website.',
'Required for basic website functionality.',
TRUE, 1),
('default', 'functional', 'Funktional', 'Functional',
'Ermöglicht Personalisierung und Komfortfunktionen.',
'Enables personalization and comfort features.',
FALSE, 2),
('default', 'analytics', 'Statistik', 'Analytics',
'Hilft uns, die Website zu verbessern.',
'Helps us improve the website.',
FALSE, 3),
('default', 'marketing', 'Marketing', 'Marketing',
'Ermöglicht personalisierte Werbung.',
'Enables personalized advertising.',
FALSE, 4),
('default', 'social', 'Soziale Medien', 'Social Media',
'Ermöglicht Inhalte von sozialen Netzwerken.',
'Enables content from social networks.',
FALSE, 5)
ON CONFLICT (site_id, category_id) DO NOTHING;
-- Fertig
SELECT 'Banner Consent Tables created successfully' as status;