Some checks failed
Test Suite / playwright-tests (push) Waiting to run
Deploy Development / deploy (push) Failing after 43s
Test Suite / pytest-backend (push) Failing after 31s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Has been cancelled
- Enhanced the deployment workflow to include error handling for the DEV API, ensuring logs are captured if the API is unreachable. - Updated the migration scripts to safely rename existing tables by checking for their existence, preventing potential conflicts during migrations. - Added exception handling in migration 079 to ensure the prerequisites are met before proceeding with the creation of the capabilities table.
226 lines
13 KiB
SQL
226 lines
13 KiB
SQL
-- Migration 079: Capability-Registry + Rollen-Grants (M3 / CAPABILITY_CATALOG.v1.md C1)
|
|
-- Account-Gates und Enforcement in Python (account_lifecycle.py, capabilities.py).
|
|
-- Voraussetzung: Migration 078 (features.id TEXT). Kein FK auf features — vermeidet
|
|
-- Startup-Abbruch wenn 078 noch aussteht oder features-Schema driftet (001 vs v9c).
|
|
|
|
DO $migration$
|
|
BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM information_schema.columns
|
|
WHERE table_schema = 'public' AND table_name = 'features' AND column_name = 'limit_type'
|
|
) THEN
|
|
RAISE EXCEPTION
|
|
'Migration 079: features-Tabelle nicht v9c (limit_type fehlt). Zuerst 078_club_features_and_plans anwenden.';
|
|
END IF;
|
|
END
|
|
$migration$;
|
|
|
|
CREATE TABLE IF NOT EXISTS capabilities (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
description TEXT,
|
|
domain TEXT NOT NULL,
|
|
min_account_state TEXT NOT NULL DEFAULT 'active_member'
|
|
CHECK (min_account_state IN (
|
|
'unverified', 'verified_pending_club', 'active_member', 'platform_admin'
|
|
)),
|
|
linked_feature_id TEXT,
|
|
active BOOLEAN NOT NULL DEFAULT true,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_capabilities_domain ON capabilities(domain) WHERE active = true;
|
|
|
|
CREATE TABLE IF NOT EXISTS club_role_capability_grants (
|
|
role_code TEXT NOT NULL,
|
|
capability_id TEXT NOT NULL REFERENCES capabilities(id) ON DELETE CASCADE,
|
|
PRIMARY KEY (role_code, capability_id)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_club_role_cap_grants_cap ON club_role_capability_grants(capability_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS portal_role_capability_grants (
|
|
portal_role TEXT NOT NULL,
|
|
capability_id TEXT NOT NULL REFERENCES capabilities(id) ON DELETE CASCADE,
|
|
PRIMARY KEY (portal_role, capability_id)
|
|
);
|
|
|
|
-- ── Seed: Capabilities (v1 Katalog §5) ───────────────────────────────────────
|
|
INSERT INTO capabilities (id, name, domain, min_account_state, linked_feature_id) VALUES
|
|
('account.settings.read', 'Einstellungen lesen', 'account', 'unverified', NULL),
|
|
('account.settings.update', 'Einstellungen ändern', 'account', 'unverified', NULL),
|
|
('account.password.change', 'Passwort ändern', 'account', 'unverified', NULL),
|
|
('account.resend_verification', 'Verifizierung erneut senden', 'account', 'unverified', NULL),
|
|
('club.directory.read', 'Vereinsverzeichnis', 'club', 'verified_pending_club', NULL),
|
|
('club.join_request.create', 'Vereinsbeitritt beantragen', 'club', 'verified_pending_club', NULL),
|
|
('club.join_request.withdraw', 'Beitrittsantrag zurückziehen', 'club', 'verified_pending_club', NULL),
|
|
('club.join_request.read_own', 'Eigene Beitrittsanträge', 'club', 'verified_pending_club', NULL),
|
|
('org.club.read', 'Vereine lesen', 'org', 'active_member', NULL),
|
|
('org.club.create', 'Verein anlegen', 'org', 'platform_admin', NULL),
|
|
('org.club.update', 'Verein bearbeiten', 'org', 'active_member', NULL),
|
|
('org.club.delete', 'Verein löschen', 'org', 'platform_admin', NULL),
|
|
('org.structure.manage', 'Vereinsstruktur verwalten', 'org', 'active_member', 'training_groups'),
|
|
('org.members.read', 'Mitgliederliste', 'org', 'active_member', NULL),
|
|
('org.members.manage', 'Mitglieder verwalten', 'org', 'active_member', 'active_members'),
|
|
('org.members.directory', 'Mitglieder-Verzeichnis', 'org', 'active_member', NULL),
|
|
('org.join_request.review', 'Beitrittsanträge prüfen', 'org', 'active_member', NULL),
|
|
('org.inbox.read', 'Posteingang', 'org', 'active_member', NULL),
|
|
('exercises.read', 'Übungen lesen', 'exercises', 'active_member', NULL),
|
|
('exercises.create', 'Übung anlegen', 'exercises', 'active_member', 'exercises'),
|
|
('exercises.update', 'Übung bearbeiten', 'exercises', 'active_member', NULL),
|
|
('exercises.delete', 'Übung löschen', 'exercises', 'active_member', NULL),
|
|
('exercises.bulk_metadata', 'Übungen Stapel-Metadaten', 'exercises', 'active_member', NULL),
|
|
('exercises.ai.suggest', 'KI-Vorschlag Übung', 'exercises', 'active_member', 'ai_calls'),
|
|
('exercises.ai.regenerate', 'KI neu generieren', 'exercises', 'active_member', 'ai_calls'),
|
|
('exercises.media.read', 'Übungsmedien lesen', 'exercises', 'active_member', NULL),
|
|
('exercises.media.upload', 'Übungsmedien hochladen', 'exercises', 'active_member', 'exercise_media'),
|
|
('exercises.variants.manage', 'Übungsvarianten', 'exercises', 'active_member', NULL),
|
|
('media.library.read', 'Medienbibliothek lesen', 'media', 'active_member', NULL),
|
|
('media.library.upload', 'Medienbibliothek Upload', 'media', 'active_member', 'exercise_media'),
|
|
('media.library.update', 'Medienbibliothek bearbeiten', 'media', 'active_member', NULL),
|
|
('media.library.lifecycle', 'Medien-Lifecycle', 'media', 'active_member', NULL),
|
|
('media.rights.declare', 'Medienrechte erklären', 'media', 'active_member', NULL),
|
|
('media.admin.rights_review', 'Medienrechte Review (Plattform)', 'media', 'platform_admin', NULL),
|
|
('modules.read', 'Trainingsmodule lesen', 'modules', 'active_member', NULL),
|
|
('modules.create', 'Trainingsmodul anlegen', 'modules', 'active_member', 'training_programs'),
|
|
('modules.update', 'Trainingsmodul bearbeiten', 'modules', 'active_member', NULL),
|
|
('modules.delete', 'Trainingsmodul löschen', 'modules', 'active_member', NULL),
|
|
('framework.read', 'Rahmenprogramme lesen', 'framework', 'active_member', NULL),
|
|
('framework.create', 'Rahmenprogramm anlegen', 'framework', 'active_member', 'training_programs'),
|
|
('framework.update', 'Rahmenprogramm bearbeiten', 'framework', 'active_member', NULL),
|
|
('framework.delete', 'Rahmenprogramm löschen', 'framework', 'active_member', NULL),
|
|
('plan_templates.read', 'Planungsvorlagen lesen', 'planning', 'active_member', NULL),
|
|
('plan_templates.manage', 'Planungsvorlagen verwalten', 'planning', 'active_member', NULL),
|
|
('progression.read', 'Progressionspfade lesen', 'progression', 'active_member', NULL),
|
|
('progression.manage', 'Progressionspfade verwalten', 'progression', 'active_member', NULL),
|
|
('planning.calendar.read', 'Planungskalender lesen', 'planning', 'active_member', NULL),
|
|
('planning.units.create', 'Trainingseinheit anlegen', 'planning', 'active_member', 'training_units'),
|
|
('planning.units.update', 'Trainingseinheit bearbeiten', 'planning', 'active_member', NULL),
|
|
('planning.units.delete', 'Trainingseinheit löschen', 'planning', 'active_member', NULL),
|
|
('planning.units.run', 'Training durchführen', 'planning', 'active_member', NULL),
|
|
('planning.coach.execute', 'Coach ausführen', 'planning', 'active_member', NULL),
|
|
('planning.ai.suggest', 'Planungs-KI Suggest', 'planning', 'active_member', 'ai_calls'),
|
|
('planning.ai.progression_path', 'Planungs-KI Progressionspfad', 'planning', 'active_member', 'ai_calls'),
|
|
('skills.catalog.read', 'Fähigkeitenkatalog', 'skills', 'active_member', NULL),
|
|
('skills.discovery.read', 'Fähigkeiten-Discovery', 'skills', 'active_member', NULL),
|
|
('skill_profiles.read', 'Skill-Profile lesen', 'skills', 'active_member', NULL),
|
|
('governance.content_report.create', 'Inhalt melden', 'governance', 'active_member', NULL),
|
|
('governance.content_report.review', 'Meldungen prüfen', 'governance', 'active_member', NULL),
|
|
('platform.admin.access', 'Plattform-Admin-Bereich', 'platform', 'platform_admin', NULL),
|
|
('platform.users.manage', 'Nutzer verwalten', 'platform', 'platform_admin', NULL),
|
|
('platform.catalogs.manage', 'Kataloge verwalten', 'platform', 'platform_admin', NULL),
|
|
('platform.maturity_models.manage', 'Reifegradmodelle', 'platform', 'platform_admin', NULL),
|
|
('platform.wiki_import.execute', 'Wiki-Import', 'platform', 'platform_admin', 'wiki_import'),
|
|
('platform.ai_prompts.manage', 'KI-Prompts verwalten', 'platform', 'platform_admin', NULL),
|
|
('platform.exercise_enrichment.execute', 'Übungs-Anreicherung KI', 'platform', 'platform_admin', 'ai_calls'),
|
|
('platform.user_content.moderate', 'Nutzer-Inhalte moderieren', 'platform', 'platform_admin', NULL),
|
|
('platform.legal_documents.manage', 'Rechtstexte verwalten', 'platform', 'platform_admin', NULL),
|
|
('platform.media_storage.manage', 'Medienspeicher verwalten', 'platform', 'platform_admin', NULL),
|
|
('platform.club_creation.approve', 'Vereinsgründung freigeben', 'platform', 'platform_admin', NULL)
|
|
ON CONFLICT (id) DO NOTHING;
|
|
|
|
-- ── Vereinsrollen-Grants (§6 — nur eingeschränkte Capabilities) ─────────────
|
|
-- Konvention: keine Grant-Zeile = alle aktiven Mitglieder (min_account_state reicht).
|
|
|
|
INSERT INTO club_role_capability_grants (role_code, capability_id)
|
|
SELECT r.role_code, c.cap_id
|
|
FROM (VALUES
|
|
('club_admin', 'org.structure.manage'),
|
|
('division_lead', 'org.structure.manage'),
|
|
('club_admin', 'org.members.manage'),
|
|
('club_admin', 'org.join_request.review'),
|
|
('club_admin', 'org.inbox.read'),
|
|
('club_admin', 'exercises.create'),
|
|
('trainer', 'exercises.create'),
|
|
('content_editor', 'exercises.create'),
|
|
('division_lead', 'exercises.create'),
|
|
('club_admin', 'exercises.update'),
|
|
('trainer', 'exercises.update'),
|
|
('content_editor', 'exercises.update'),
|
|
('division_lead', 'exercises.update'),
|
|
('club_admin', 'exercises.delete'),
|
|
('club_admin', 'exercises.bulk_metadata'),
|
|
('content_editor', 'exercises.bulk_metadata'),
|
|
('club_admin', 'exercises.ai.suggest'),
|
|
('trainer', 'exercises.ai.suggest'),
|
|
('content_editor', 'exercises.ai.suggest'),
|
|
('division_lead', 'exercises.ai.suggest'),
|
|
('club_admin', 'exercises.ai.regenerate'),
|
|
('trainer', 'exercises.ai.regenerate'),
|
|
('content_editor', 'exercises.ai.regenerate'),
|
|
('division_lead', 'exercises.ai.regenerate'),
|
|
('club_admin', 'exercises.media.upload'),
|
|
('trainer', 'exercises.media.upload'),
|
|
('content_editor', 'exercises.media.upload'),
|
|
('club_admin', 'exercises.variants.manage'),
|
|
('trainer', 'exercises.variants.manage'),
|
|
('content_editor', 'exercises.variants.manage'),
|
|
('club_admin', 'media.library.upload'),
|
|
('trainer', 'media.library.upload'),
|
|
('content_editor', 'media.library.upload'),
|
|
('club_admin', 'media.library.update'),
|
|
('trainer', 'media.library.update'),
|
|
('content_editor', 'media.library.update'),
|
|
('club_admin', 'media.library.lifecycle'),
|
|
('trainer', 'media.library.lifecycle'),
|
|
('club_admin', 'media.rights.declare'),
|
|
('trainer', 'media.rights.declare'),
|
|
('club_admin', 'modules.create'),
|
|
('trainer', 'modules.create'),
|
|
('content_editor', 'modules.create'),
|
|
('club_admin', 'modules.update'),
|
|
('trainer', 'modules.update'),
|
|
('content_editor', 'modules.update'),
|
|
('club_admin', 'modules.delete'),
|
|
('club_admin', 'framework.create'),
|
|
('trainer', 'framework.create'),
|
|
('club_admin', 'framework.update'),
|
|
('trainer', 'framework.update'),
|
|
('club_admin', 'framework.delete'),
|
|
('club_admin', 'plan_templates.manage'),
|
|
('trainer', 'plan_templates.manage'),
|
|
('club_admin', 'progression.manage'),
|
|
('trainer', 'progression.manage'),
|
|
('content_editor', 'progression.manage'),
|
|
('club_admin', 'planning.units.create'),
|
|
('trainer', 'planning.units.create'),
|
|
('division_lead', 'planning.units.create'),
|
|
('club_admin', 'planning.units.update'),
|
|
('trainer', 'planning.units.update'),
|
|
('division_lead', 'planning.units.update'),
|
|
('club_admin', 'planning.units.delete'),
|
|
('trainer', 'planning.units.delete'),
|
|
('club_admin', 'planning.units.run'),
|
|
('trainer', 'planning.units.run'),
|
|
('division_lead', 'planning.units.run'),
|
|
('club_admin', 'planning.coach.execute'),
|
|
('trainer', 'planning.coach.execute'),
|
|
('club_admin', 'planning.ai.suggest'),
|
|
('trainer', 'planning.ai.suggest'),
|
|
('division_lead', 'planning.ai.suggest'),
|
|
('club_admin', 'planning.ai.progression_path'),
|
|
('trainer', 'planning.ai.progression_path'),
|
|
('division_lead', 'planning.ai.progression_path'),
|
|
('club_admin', 'skills.discovery.read'),
|
|
('trainer', 'skills.discovery.read'),
|
|
('content_editor', 'skills.discovery.read'),
|
|
('club_admin', 'governance.content_report.review')
|
|
) AS r(role_code, cap_id)
|
|
JOIN capabilities c ON c.id = r.cap_id
|
|
ON CONFLICT DO NOTHING;
|
|
|
|
-- org.club.update: club_admin (zusätzlich zu platform_admin via Bypass)
|
|
INSERT INTO club_role_capability_grants (role_code, capability_id)
|
|
VALUES ('club_admin', 'org.club.update')
|
|
ON CONFLICT DO NOTHING;
|
|
|
|
-- ── Portal-Rollen ───────────────────────────────────────────────────────────
|
|
INSERT INTO portal_role_capability_grants (portal_role, capability_id)
|
|
SELECT 'admin', id FROM capabilities WHERE id = 'platform.admin.access'
|
|
ON CONFLICT DO NOTHING;
|
|
|
|
INSERT INTO portal_role_capability_grants (portal_role, capability_id)
|
|
SELECT 'superadmin', id FROM capabilities WHERE domain = 'platform'
|
|
ON CONFLICT DO NOTHING;
|