Yimaru-BackEnd/db/query/hierarchy.sql
Yared Yemane de95c4d0d2 feat: practice detail API, inactive purge tracking, and related plumbing
- Add GET /api/v1/course-management/practices/:practiceId/detail with full question items

- Add migration 000040 for sub-module content inactive purge tracking

- Hierarchy queries, sqlc gen, config/app purge job, swagger refresh

Made-with: Cursor
2026-04-20 08:24:59 -07:00

568 lines
13 KiB
SQL

-- name: GetCoursesWithHierarchy :many
SELECT
cc.id AS category_id,
cc.name AS category_name,
csc.id AS sub_category_id,
csc.name AS sub_category_name,
c.id AS course_id,
c.title AS course_title
FROM course_categories cc
LEFT JOIN course_sub_categories csc ON csc.category_id = cc.id AND csc.is_active = TRUE
LEFT JOIN courses c ON c.sub_category_id = csc.id AND c.is_active = TRUE
WHERE cc.is_active = TRUE
ORDER BY cc.id, csc.display_order, csc.id, c.id;
-- name: GetLevelsByCourseID :many
SELECT *
FROM levels
WHERE course_id = $1
AND is_active = TRUE
ORDER BY display_order ASC, id ASC;
-- name: GetAllLevels :many
SELECT
COUNT(*) OVER () AS total_count,
l.*
FROM levels l
ORDER BY l.display_order ASC, l.id ASC
LIMIT sqlc.narg('limit')::INT
OFFSET sqlc.narg('offset')::INT;
-- name: GetLevelByID :one
SELECT *
FROM levels
WHERE id = $1;
-- name: GetModulesByLevelID :many
SELECT *
FROM modules
WHERE level_id = $1
AND is_active = TRUE
ORDER BY display_order ASC, id ASC;
-- name: GetAllModules :many
SELECT
COUNT(*) OVER () AS total_count,
m.*
FROM modules m
ORDER BY m.display_order ASC, m.id ASC
LIMIT sqlc.narg('limit')::INT
OFFSET sqlc.narg('offset')::INT;
-- name: GetModuleByID :one
SELECT *
FROM modules
WHERE id = $1;
-- name: GetSubModulesByModuleID :many
SELECT *
FROM sub_modules
WHERE module_id = $1
AND is_active = TRUE
ORDER BY display_order ASC, id ASC;
-- name: GetAllSubModules :many
SELECT
COUNT(*) OVER () AS total_count,
sm.*
FROM sub_modules sm
ORDER BY sm.display_order ASC, sm.id ASC
LIMIT sqlc.narg('limit')::INT
OFFSET sqlc.narg('offset')::INT;
-- name: GetSubModuleByID :one
SELECT *
FROM sub_modules
WHERE id = $1;
-- name: GetSubModuleVideos :many
SELECT *
FROM sub_module_videos
WHERE sub_module_id = $1
AND status != 'ARCHIVED'
ORDER BY display_order ASC, id ASC;
-- name: GetSubModuleLessons :many
SELECT *
FROM sub_module_lessons
WHERE sub_module_id = $1
AND is_active = TRUE
ORDER BY display_order ASC, id ASC;
-- name: GetSubModuleLessonsAll :many
SELECT *
FROM sub_module_lessons
WHERE sub_module_id = $1
ORDER BY display_order ASC, id ASC;
-- name: GetSubModuleLessonByID :one
SELECT *
FROM sub_module_lessons
WHERE id = $1;
-- name: GetSubModulePractices :many
SELECT
smp.id,
smp.sub_module_id,
smp.title,
smp.description,
smp.thumbnail,
smp.intro_video_url,
smp.question_set_id,
smp.display_order,
smp.is_active,
smp.inactive_since,
qs.status,
qs.set_type,
(SELECT COUNT(*) FROM question_set_items qsi WHERE qsi.set_id = qs.id) AS question_count
FROM sub_module_practices smp
JOIN question_sets qs ON qs.id = smp.question_set_id
WHERE smp.sub_module_id = $1
AND smp.is_active = TRUE
AND qs.set_type = 'PRACTICE'
ORDER BY smp.display_order ASC, smp.id ASC;
-- name: GetSubModulePracticeByID :one
SELECT
smp.id,
smp.sub_module_id,
smp.title,
smp.description,
smp.thumbnail,
smp.intro_video_url,
smp.question_set_id,
smp.display_order,
smp.is_active,
smp.inactive_since,
qs.status,
qs.set_type,
(SELECT COUNT(*) FROM question_set_items qsi WHERE qsi.set_id = qs.id) AS question_count
FROM sub_module_practices smp
JOIN question_sets qs ON qs.id = smp.question_set_id
WHERE smp.id = $1
AND smp.is_active = TRUE
AND qs.set_type = 'PRACTICE';
-- name: GetSubModuleCapstones :many
SELECT
smc.id,
smc.sub_module_id,
smc.title,
smc.description,
smc.tips,
smc.thumbnail,
smc.question_set_id,
smc.display_order,
smc.is_active,
smc.inactive_since,
qs.status,
qs.set_type,
qs.time_limit_minutes,
qs.passing_score,
qs.shuffle_questions,
(SELECT COUNT(*) FROM question_set_items qsi WHERE qsi.set_id = qs.id) AS question_count
FROM sub_module_capstones smc
JOIN question_sets qs ON qs.id = smc.question_set_id
WHERE smc.sub_module_id = $1
AND smc.is_active = TRUE
AND qs.set_type = 'CAPSTONE'
ORDER BY smc.display_order ASC, smc.id ASC;
-- name: GetSubModuleCapstoneByID :one
SELECT
smc.id,
smc.sub_module_id,
smc.title,
smc.description,
smc.tips,
smc.thumbnail,
smc.question_set_id,
smc.display_order,
smc.is_active,
smc.inactive_since,
qs.status,
qs.set_type,
qs.time_limit_minutes,
qs.passing_score,
qs.shuffle_questions,
(SELECT COUNT(*) FROM question_set_items qsi WHERE qsi.set_id = qs.id) AS question_count
FROM sub_module_capstones smc
JOIN question_sets qs ON qs.id = smc.question_set_id
WHERE smc.id = $1
AND smc.is_active = TRUE
AND qs.set_type = 'CAPSTONE';
-- name: GetFullHierarchyByCourseID :many
SELECT
c.id AS course_id,
c.title AS course_title,
l.id AS level_id,
l.cefr_level,
l.title AS level_title,
l.description AS level_description,
l.thumbnail AS level_thumbnail,
m.id AS module_id,
m.title AS module_title,
m.icon_url AS module_icon_url,
sm.id AS sub_module_id,
sm.title AS sub_module_title,
sm.description AS sub_module_description,
sm.thumbnail AS sub_module_thumbnail,
sm.tips AS sub_module_tips,
sm.display_order AS sub_module_display_order
FROM courses c
LEFT JOIN levels l ON l.course_id = c.id AND l.is_active = TRUE
LEFT JOIN modules m ON m.level_id = l.id AND m.is_active = TRUE
LEFT JOIN sub_modules sm ON sm.module_id = m.id AND sm.is_active = TRUE
WHERE c.id = $1
ORDER BY l.display_order, l.id, m.display_order, m.id, sm.display_order, sm.id;
-- name: CreateCourseSubCategory :one
INSERT INTO course_sub_categories (
category_id,
name,
description,
display_order,
is_active
)
VALUES ($1, $2, $3, COALESCE($4, 0), COALESCE($5, TRUE))
RETURNING *;
-- name: GetCourseSubCategories :many
SELECT
COUNT(*) OVER () AS total_count,
csc.id,
csc.category_id,
cc.name AS category_name,
csc.name,
csc.description,
csc.display_order,
csc.is_active,
csc.created_at
FROM course_sub_categories csc
JOIN course_categories cc ON cc.id = csc.category_id
WHERE csc.is_active = TRUE
ORDER BY csc.display_order ASC, csc.id ASC
LIMIT sqlc.narg('limit')::INT
OFFSET sqlc.narg('offset')::INT;
-- name: GetHumanLanguageCourseSubCategories :many
SELECT
COUNT(*) OVER () AS total_count,
csc.id,
csc.category_id,
cc.name AS category_name,
csc.name,
csc.description,
csc.display_order,
csc.is_active,
csc.created_at
FROM course_sub_categories csc
JOIN course_categories cc ON cc.id = csc.category_id
WHERE csc.is_active = TRUE
AND cc.is_active = TRUE
AND lower(trim(cc.name)) = 'human language'
ORDER BY csc.display_order ASC, csc.id ASC
LIMIT sqlc.narg('limit')::INT
OFFSET sqlc.narg('offset')::INT;
-- name: CreateLevel :one
INSERT INTO levels (
course_id,
cefr_level,
title,
description,
thumbnail,
display_order,
is_active
)
VALUES ($1, $2, $3, $4, $5, COALESCE($6, 0), COALESCE($7, TRUE))
RETURNING *;
-- name: UpdateLevel :one
UPDATE levels
SET
title = $1,
description = $2,
thumbnail = $3,
display_order = $4,
is_active = $5
WHERE id = $6
RETURNING *;
-- name: CreateModule :one
INSERT INTO modules (
level_id,
title,
description,
icon_url,
display_order,
is_active
)
VALUES ($1, $2, $3, $4, COALESCE($5, 0), COALESCE($6, TRUE))
RETURNING *;
-- name: UpdateModule :one
UPDATE modules
SET
title = $1,
description = $2,
icon_url = $3,
display_order = $4,
is_active = $5
WHERE id = $6
RETURNING *;
-- name: CreateSubModule :one
INSERT INTO sub_modules (
module_id,
title,
description,
thumbnail,
tips,
display_order,
is_active
)
VALUES ($1, $2, $3, $4, $5, COALESCE($6, 0), COALESCE($7, TRUE))
RETURNING *;
-- name: UpdateSubModule :one
UPDATE sub_modules
SET
title = $1,
description = $2,
thumbnail = $3,
tips = $4,
display_order = $5,
is_active = $6
WHERE id = $7
RETURNING *;
-- name: CreateSubModuleVideo :one
INSERT INTO sub_module_videos (
sub_module_id,
title,
description,
video_url,
duration,
resolution,
is_published,
publish_date,
visibility,
instructor_id,
thumbnail,
display_order,
status,
vimeo_id,
vimeo_embed_url,
vimeo_player_html,
vimeo_status,
video_host_provider
)
VALUES (
$1, $2, $3, $4, $5, $6,
COALESCE($7, FALSE), $8, $9, $10, $11,
COALESCE($12, 0), COALESCE($13, 'DRAFT'),
$14, $15, $16, $17, COALESCE($18, 'DIRECT')
)
RETURNING *;
-- name: CreateSubModuleLesson :one
INSERT INTO sub_module_lessons (
sub_module_id,
title,
description,
thumbnail,
teaching_text,
teaching_image_url,
teaching_audio_url,
teaching_video_url,
display_order,
is_active,
inactive_since
)
VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7,
$8,
COALESCE($9, 0),
COALESCE($10, TRUE),
CASE WHEN COALESCE($10, TRUE) THEN NULL ELSE NOW() END
)
RETURNING *;
-- name: UpdateSubModuleLesson :one
UPDATE sub_module_lessons
SET
sub_module_id = $1,
title = $2,
description = $3,
thumbnail = $4,
teaching_text = $5,
teaching_image_url = $6,
teaching_audio_url = $7,
teaching_video_url = $8,
display_order = $9,
is_active = $10,
inactive_since = CASE
WHEN $10 THEN NULL
WHEN is_active THEN NOW()
ELSE inactive_since
END
WHERE id = $11
RETURNING *;
-- name: CreateSubModulePractice :one
INSERT INTO sub_module_practices (
sub_module_id,
title,
description,
thumbnail,
intro_video_url,
question_set_id,
display_order,
is_active,
inactive_since
)
VALUES ($1, $2, $3, $4, $5, $6, COALESCE($7, 0), COALESCE($8, TRUE), CASE WHEN COALESCE($8, TRUE) THEN NULL ELSE NOW() END)
RETURNING *;
-- name: CreateSubModuleCapstone :one
INSERT INTO sub_module_capstones (
sub_module_id,
title,
description,
tips,
thumbnail,
question_set_id,
display_order,
is_active,
inactive_since
)
VALUES ($1, $2, $3, $4, $5, $6, COALESCE($7, 0), COALESCE($8, TRUE), CASE WHEN COALESCE($8, TRUE) THEN NULL ELSE NOW() END)
RETURNING *;
-- name: UpdateSubModuleCapstone :one
UPDATE sub_module_capstones
SET
title = $1,
description = $2,
tips = $3,
thumbnail = $4,
display_order = $5,
is_active = $6,
inactive_since = CASE
WHEN $6 THEN NULL
WHEN is_active THEN NOW()
ELSE inactive_since
END
WHERE id = $7
RETURNING *;
-- name: PurgeInactiveSubModuleLessonsBefore :execrows
DELETE FROM sub_module_lessons
WHERE is_active = FALSE
AND inactive_since IS NOT NULL
AND inactive_since < $1;
-- name: PurgeInactiveSubModulePracticesBefore :execrows
DELETE FROM question_sets qs
USING (
SELECT question_set_id
FROM sub_module_practices
WHERE is_active = FALSE
AND inactive_since IS NOT NULL
AND inactive_since < $1
) doomed
WHERE qs.id = doomed.question_set_id;
-- name: PurgeInactiveSubModuleCapstonesBefore :execrows
DELETE FROM question_sets qs
USING (
SELECT question_set_id
FROM sub_module_capstones
WHERE is_active = FALSE
AND inactive_since IS NOT NULL
AND inactive_since < $1
) doomed
WHERE qs.id = doomed.question_set_id;
-- name: GetModuleCapstones :many
SELECT
mc.id,
mc.module_id,
mc.title,
mc.description,
mc.tips,
mc.thumbnail,
mc.question_set_id,
mc.display_order,
mc.is_active,
qs.status,
qs.set_type,
qs.time_limit_minutes,
qs.passing_score,
qs.shuffle_questions,
(SELECT COUNT(*) FROM question_set_items qsi WHERE qsi.set_id = qs.id) AS question_count
FROM module_capstones mc
JOIN question_sets qs ON qs.id = mc.question_set_id
WHERE mc.module_id = $1
AND mc.is_active = TRUE
AND qs.set_type = 'CAPSTONE'
ORDER BY mc.display_order ASC, mc.id ASC;
-- name: GetModuleCapstoneByID :one
SELECT
mc.id,
mc.module_id,
mc.title,
mc.description,
mc.tips,
mc.thumbnail,
mc.question_set_id,
mc.display_order,
mc.is_active,
qs.status,
qs.set_type,
qs.time_limit_minutes,
qs.passing_score,
qs.shuffle_questions,
(SELECT COUNT(*) FROM question_set_items qsi WHERE qsi.set_id = qs.id) AS question_count
FROM module_capstones mc
JOIN question_sets qs ON qs.id = mc.question_set_id
WHERE mc.id = $1
AND mc.is_active = TRUE
AND qs.set_type = 'CAPSTONE';
-- name: CreateModuleCapstone :one
INSERT INTO module_capstones (
module_id,
title,
description,
tips,
thumbnail,
question_set_id,
display_order,
is_active
)
VALUES ($1, $2, $3, $4, $5, $6, COALESCE($7, 0), COALESCE($8, TRUE))
RETURNING *;
-- name: UpdateModuleCapstone :one
UPDATE module_capstones
SET
title = $1,
description = $2,
tips = $3,
thumbnail = $4,
display_order = $5,
is_active = $6
WHERE id = $7
RETURNING *;