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>
224 lines
7.3 KiB
PL/PgSQL
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;
|