CREATE TABLE IF NOT EXISTS course_categories ( id BIGSERIAL PRIMARY KEY, name VARCHAR(150) NOT NULL, -- "Learning English", "Other Courses" is_active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS courses ( id BIGSERIAL PRIMARY KEY, category_id BIGINT NOT NULL REFERENCES course_categories(id) ON DELETE CASCADE, title VARCHAR(255) NOT NULL, description TEXT, is_active BOOLEAN NOT NULL DEFAULT TRUE ); CREATE TABLE IF NOT EXISTS programs ( id BIGSERIAL PRIMARY KEY, course_id BIGINT NOT NULL REFERENCES courses(id) ON DELETE CASCADE, title VARCHAR(255) NOT NULL, description TEXT, thumbnail TEXT, display_order INT NOT NULL DEFAULT 0, is_active BOOLEAN NOT NULL DEFAULT TRUE ); CREATE TABLE IF NOT EXISTS levels ( id BIGSERIAL PRIMARY KEY, program_id BIGINT NOT NULL REFERENCES programs(id) ON DELETE CASCADE, title VARCHAR(255) NOT NULL, description TEXT, level_index INT NOT NULL, -- 1,2,3... number_of_modules INT NOT NULL DEFAULT 0, number_of_practices INT NOT NULL DEFAULT 0, number_of_videos INT NOT NULL DEFAULT 0, is_active BOOLEAN NOT NULL DEFAULT TRUE ); CREATE TABLE IF NOT EXISTS modules ( id BIGSERIAL PRIMARY KEY, level_id BIGINT NOT NULL REFERENCES levels(id) ON DELETE CASCADE, title VARCHAR(255) NOT NULL, content TEXT, display_order INT NOT NULL DEFAULT 0, is_active BOOLEAN NOT NULL DEFAULT TRUE ); CREATE TABLE IF NOT EXISTS module_videos ( id BIGSERIAL PRIMARY KEY, module_id BIGINT NOT NULL REFERENCES modules(id) ON DELETE CASCADE, title VARCHAR(255) NOT NULL, description TEXT, video_url TEXT NOT NULL, duration INT NOT NULL, -- seconds resolution VARCHAR(20), -- "720p", "1080p" is_published BOOLEAN NOT NULL DEFAULT FALSE, publish_date TIMESTAMPTZ, visibility VARCHAR(50), -- public, private, unlisted instructor_id VARCHAR(100), thumbnail TEXT, is_active BOOLEAN NOT NULL DEFAULT TRUE ); CREATE TABLE IF NOT EXISTS practices ( id BIGSERIAL PRIMARY KEY, owner_type VARCHAR(50) NOT NULL, -- LEVEL | MODULE owner_id BIGINT NOT NULL, title VARCHAR(255) NOT NULL, description TEXT, banner_image TEXT, persona VARCHAR(100), is_active BOOLEAN NOT NULL DEFAULT TRUE, CHECK (owner_type IN ('LEVEL', 'MODULE')) ); CREATE TABLE IF NOT EXISTS practice_questions ( id BIGSERIAL PRIMARY KEY, practice_id BIGINT NOT NULL REFERENCES practices(id) ON DELETE CASCADE, question TEXT NOT NULL, question_voice_prompt TEXT, sample_answer_voice_prompt TEXT, sample_answer TEXT, tips TEXT, type VARCHAR(50) NOT NULL -- MCQ, TRUE_FALSE, SHORT ); CREATE INDEX IF NOT EXISTS idx_courses_category_id ON courses(category_id); CREATE INDEX IF NOT EXISTS idx_programs_course_id ON programs(course_id); CREATE INDEX IF NOT EXISTS idx_levels_program_id ON levels(program_id); CREATE INDEX IF NOT EXISTS idx_modules_level_id ON modules(level_id); CREATE INDEX IF NOT EXISTS idx_videos_module_id ON module_videos(module_id); CREATE INDEX IF NOT EXISTS idx_practices_owner ON practices(owner_type, owner_id); CREATE INDEX IF NOT EXISTS idx_practice_questions_practice_id ON practice_questions(practice_id);