Files
breakpilot-compliance/ai-compliance-sdk/migrations/008_academy_schema.sql
Benjamin Boenisch 504dd3591b feat: Add Academy, Whistleblower, Incidents, Vendor, DSB, SSO, Reporting, Multi-Tenant and Industry backends
Go handlers, models, stores and migrations for all SDK modules.
Updates developer portal navigation and BYOEH page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 21:11:27 +01:00

160 lines
7.1 KiB
SQL

-- ============================================================================
-- Migration 008: Academy (E-Learning / Compliance Academy) Schema
-- Compliance training courses, enrollments, and certificate management
-- ============================================================================
-- Academy courses table
CREATE TABLE IF NOT EXISTS academy_courses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
-- Course info
title VARCHAR(255) NOT NULL,
description TEXT,
category VARCHAR(50) NOT NULL, -- 'dsgvo_basics', 'it_security', 'ai_literacy', 'whistleblower_protection', 'custom'
duration_minutes INT DEFAULT 0,
required_for_roles JSONB DEFAULT '[]', -- Array of role strings
-- Status
is_active BOOLEAN DEFAULT TRUE,
-- Audit
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Academy lessons table
CREATE TABLE IF NOT EXISTS academy_lessons (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
course_id UUID NOT NULL REFERENCES academy_courses(id) ON DELETE CASCADE,
-- Lesson info
title VARCHAR(255) NOT NULL,
description TEXT,
lesson_type VARCHAR(50) NOT NULL, -- 'video', 'text', 'quiz', 'interactive'
content_url TEXT,
duration_minutes INT DEFAULT 0,
order_index INT DEFAULT 0,
-- Quiz questions (only for lesson_type = 'quiz')
quiz_questions JSONB DEFAULT '[]', -- Array of {question, options, correct_index, explanation}
-- Audit
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Academy enrollments table
CREATE TABLE IF NOT EXISTS academy_enrollments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
course_id UUID NOT NULL REFERENCES academy_courses(id) ON DELETE CASCADE,
user_id UUID NOT NULL,
-- User info (denormalized for reporting)
user_name VARCHAR(255) NOT NULL,
user_email VARCHAR(255) NOT NULL,
-- Progress tracking
status VARCHAR(50) DEFAULT 'not_started', -- 'not_started', 'in_progress', 'completed', 'expired'
progress_percent INT DEFAULT 0, -- 0-100
current_lesson_index INT DEFAULT 0,
-- Timestamps
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
deadline TIMESTAMPTZ,
-- Audit
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Academy certificates table
CREATE TABLE IF NOT EXISTS academy_certificates (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
enrollment_id UUID NOT NULL UNIQUE REFERENCES academy_enrollments(id) ON DELETE CASCADE,
-- Certificate info
user_name VARCHAR(255) NOT NULL,
course_title VARCHAR(255) NOT NULL,
issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
valid_until TIMESTAMPTZ,
pdf_url TEXT,
-- Audit
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ============================================================================
-- Indexes
-- ============================================================================
-- Course indexes
CREATE INDEX IF NOT EXISTS idx_academy_courses_tenant ON academy_courses(tenant_id);
CREATE INDEX IF NOT EXISTS idx_academy_courses_category ON academy_courses(tenant_id, category);
CREATE INDEX IF NOT EXISTS idx_academy_courses_active ON academy_courses(tenant_id, is_active);
-- Lesson indexes
CREATE INDEX IF NOT EXISTS idx_academy_lessons_course ON academy_lessons(course_id);
CREATE INDEX IF NOT EXISTS idx_academy_lessons_order ON academy_lessons(course_id, order_index);
-- Enrollment indexes
CREATE INDEX IF NOT EXISTS idx_academy_enrollments_tenant ON academy_enrollments(tenant_id);
CREATE INDEX IF NOT EXISTS idx_academy_enrollments_course ON academy_enrollments(course_id);
CREATE INDEX IF NOT EXISTS idx_academy_enrollments_user ON academy_enrollments(user_id);
CREATE INDEX IF NOT EXISTS idx_academy_enrollments_status ON academy_enrollments(tenant_id, status);
CREATE INDEX IF NOT EXISTS idx_academy_enrollments_deadline ON academy_enrollments(deadline) WHERE deadline IS NOT NULL AND status NOT IN ('completed', 'expired');
CREATE INDEX IF NOT EXISTS idx_academy_enrollments_tenant_course ON academy_enrollments(tenant_id, course_id);
-- Certificate indexes
CREATE INDEX IF NOT EXISTS idx_academy_certificates_enrollment ON academy_certificates(enrollment_id);
CREATE INDEX IF NOT EXISTS idx_academy_certificates_valid_until ON academy_certificates(valid_until) WHERE valid_until IS NOT NULL;
-- ============================================================================
-- Triggers
-- ============================================================================
-- Reuse existing update_updated_at_column function
-- Courses trigger
DROP TRIGGER IF EXISTS update_academy_courses_updated_at ON academy_courses;
CREATE TRIGGER update_academy_courses_updated_at
BEFORE UPDATE ON academy_courses
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Lessons trigger
DROP TRIGGER IF EXISTS update_academy_lessons_updated_at ON academy_lessons;
CREATE TRIGGER update_academy_lessons_updated_at
BEFORE UPDATE ON academy_lessons
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Enrollments trigger
DROP TRIGGER IF EXISTS update_academy_enrollments_updated_at ON academy_enrollments;
CREATE TRIGGER update_academy_enrollments_updated_at
BEFORE UPDATE ON academy_enrollments
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Certificates trigger
DROP TRIGGER IF EXISTS update_academy_certificates_updated_at ON academy_certificates;
CREATE TRIGGER update_academy_certificates_updated_at
BEFORE UPDATE ON academy_certificates
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================================
-- Comments
-- ============================================================================
COMMENT ON TABLE academy_courses IS 'Compliance training courses (DSGVO, IT-Security, AI Literacy, Whistleblower)';
COMMENT ON TABLE academy_lessons IS 'Individual lessons within a course (video, text, quiz, interactive)';
COMMENT ON TABLE academy_enrollments IS 'User enrollments in courses with progress tracking';
COMMENT ON TABLE academy_certificates IS 'Completion certificates issued for finished enrollments';
COMMENT ON COLUMN academy_courses.category IS 'Course category: dsgvo_basics, it_security, ai_literacy, whistleblower_protection, custom';
COMMENT ON COLUMN academy_courses.required_for_roles IS 'JSON array of role names that are required to complete this course';
COMMENT ON COLUMN academy_lessons.quiz_questions IS 'JSON array of quiz questions: [{question, options, correct_index, explanation}]';
COMMENT ON COLUMN academy_enrollments.status IS 'Enrollment status: not_started, in_progress, completed, expired';
COMMENT ON COLUMN academy_enrollments.progress_percent IS 'Course completion percentage (0-100)';
COMMENT ON COLUMN academy_certificates.enrollment_id IS 'One-to-one relationship with enrollment (UNIQUE constraint)';