feat(v0.12.3): database migrations for education courses
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>
This commit is contained in:
parent
3c5f0e0cfc
commit
5a42ca4f52
2 changed files with 142 additions and 0 deletions
135
veza-backend-api/migrations/951_education_courses_v0123.sql
Normal file
135
veza-backend-api/migrations/951_education_courses_v0123.sql
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
-- 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);
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
-- v0.12.3: Rollback Formation & Éducation
|
||||
DROP TABLE IF EXISTS course_reviews;
|
||||
DROP TABLE IF EXISTS certificates;
|
||||
DROP TABLE IF EXISTS lesson_progress;
|
||||
DROP TABLE IF EXISTS course_enrollments;
|
||||
DROP TABLE IF EXISTS lessons;
|
||||
DROP TABLE IF EXISTS courses;
|
||||
Loading…
Reference in a new issue