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>
160 lines
7.1 KiB
SQL
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)';
|