Tables: courses, lessons, course_enrollments, lesson_progress, certificates, course_reviews with proper indexes and constraints. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
135 lines
5.7 KiB
SQL
135 lines
5.7 KiB
SQL
-- v0.12.3: Formation & Éducation (F276-F305)
|
|
-- Courses, lessons, enrollments, progress, certificates, reviews
|
|
|
|
-- Courses catalog
|
|
CREATE TABLE IF NOT EXISTS courses (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
creator_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
title VARCHAR(255) NOT NULL,
|
|
slug VARCHAR(255) NOT NULL UNIQUE,
|
|
description TEXT NOT NULL DEFAULT '',
|
|
category VARCHAR(100),
|
|
tags VARCHAR(50)[] DEFAULT '{}',
|
|
cover_image_url TEXT,
|
|
price_cents INTEGER NOT NULL DEFAULT 0,
|
|
currency VARCHAR(3) NOT NULL DEFAULT 'USD',
|
|
pricing_model VARCHAR(50) NOT NULL DEFAULT 'fixed',
|
|
minimum_price_cents INTEGER DEFAULT 0,
|
|
status VARCHAR(50) NOT NULL DEFAULT 'draft',
|
|
level VARCHAR(50) DEFAULT 'beginner',
|
|
language VARCHAR(5) DEFAULT 'en',
|
|
total_duration_seconds INTEGER NOT NULL DEFAULT 0,
|
|
lesson_count INTEGER NOT NULL DEFAULT 0,
|
|
enrollment_count INTEGER NOT NULL DEFAULT 0,
|
|
review_count INTEGER NOT NULL DEFAULT 0,
|
|
average_rating NUMERIC(3,2) DEFAULT 0,
|
|
published_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
deleted_at TIMESTAMPTZ
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_courses_creator ON courses(creator_id);
|
|
CREATE INDEX IF NOT EXISTS idx_courses_status ON courses(status, published_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_courses_category ON courses(category);
|
|
CREATE INDEX IF NOT EXISTS idx_courses_slug ON courses(slug);
|
|
|
|
-- Course lessons
|
|
CREATE TABLE IF NOT EXISTS lessons (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
course_id UUID NOT NULL REFERENCES courses(id) ON DELETE CASCADE,
|
|
order_index INTEGER NOT NULL,
|
|
title VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
video_file_path VARCHAR(512),
|
|
duration_seconds INTEGER NOT NULL DEFAULT 0,
|
|
is_preview_free BOOLEAN NOT NULL DEFAULT false,
|
|
transcoding_status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
|
hls_master_playlist_url TEXT,
|
|
thumbnail_url TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
CONSTRAINT uq_lesson_order UNIQUE (course_id, order_index),
|
|
CONSTRAINT chk_order_positive CHECK (order_index > 0)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_lessons_course_order ON lessons(course_id, order_index);
|
|
CREATE INDEX IF NOT EXISTS idx_lessons_transcoding ON lessons(transcoding_status);
|
|
|
|
-- Course enrollments (purchases)
|
|
CREATE TABLE IF NOT EXISTS course_enrollments (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
course_id UUID NOT NULL REFERENCES courses(id) ON DELETE CASCADE,
|
|
purchased_price_cents INTEGER NOT NULL DEFAULT 0,
|
|
currency VARCHAR(3) NOT NULL DEFAULT 'USD',
|
|
status VARCHAR(50) NOT NULL DEFAULT 'active',
|
|
access_expires_at TIMESTAMPTZ,
|
|
purchased_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
refunded_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
CONSTRAINT uq_enrollment_user_course UNIQUE (user_id, course_id)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_enrollments_user ON course_enrollments(user_id, purchased_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_enrollments_course ON course_enrollments(course_id, status);
|
|
|
|
-- Lesson progress per user
|
|
CREATE TABLE IF NOT EXISTS lesson_progress (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
lesson_id UUID NOT NULL REFERENCES lessons(id) ON DELETE CASCADE,
|
|
enrollment_id UUID NOT NULL REFERENCES course_enrollments(id) ON DELETE CASCADE,
|
|
watched_percentage INTEGER DEFAULT 0,
|
|
watched_duration_seconds INTEGER DEFAULT 0,
|
|
playback_position_seconds INTEGER DEFAULT 0,
|
|
is_completed BOOLEAN NOT NULL DEFAULT false,
|
|
completed_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
CONSTRAINT uq_progress_user_lesson UNIQUE (user_id, lesson_id),
|
|
CONSTRAINT chk_percentage CHECK (watched_percentage >= 0 AND watched_percentage <= 100)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_progress_enrollment ON lesson_progress(enrollment_id);
|
|
|
|
-- Completion certificates (declarative, not gamified)
|
|
CREATE TABLE IF NOT EXISTS certificates (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
course_id UUID NOT NULL REFERENCES courses(id) ON DELETE CASCADE,
|
|
enrollment_id UUID NOT NULL REFERENCES course_enrollments(id) ON DELETE CASCADE,
|
|
certificate_code VARCHAR(255) NOT NULL UNIQUE,
|
|
issue_date TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
status VARCHAR(50) NOT NULL DEFAULT 'active',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
CONSTRAINT uq_cert_user_course UNIQUE (user_id, course_id)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_certificates_code ON certificates(certificate_code);
|
|
|
|
-- Course reviews
|
|
CREATE TABLE IF NOT EXISTS course_reviews (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
course_id UUID NOT NULL REFERENCES courses(id) ON DELETE CASCADE,
|
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
enrollment_id UUID NOT NULL REFERENCES course_enrollments(id),
|
|
rating INTEGER NOT NULL,
|
|
title VARCHAR(255),
|
|
content TEXT NOT NULL,
|
|
status VARCHAR(50) NOT NULL DEFAULT 'approved',
|
|
helpful_count INTEGER NOT NULL DEFAULT 0,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
deleted_at TIMESTAMPTZ,
|
|
|
|
CONSTRAINT uq_review_user_course UNIQUE (user_id, course_id),
|
|
CONSTRAINT chk_rating CHECK (rating >= 1 AND rating <= 5)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_reviews_course ON course_reviews(course_id, rating DESC);
|