feat: add hierarchy publish status and resolve question type definition IDs
Extend DRAFT/PUBLISHED to programs, courses, modules, and exam-prep hierarchy entities with learner visibility gating and progress exclusion. Resolve question_type_definition_id in question responses for legacy system types and unlinked dynamic questions. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
5c6cb1b8d3
commit
e56bea3abf
27
db/migrations/000078_hierarchy_publish_status.down.sql
Normal file
27
db/migrations/000078_hierarchy_publish_status.down.sql
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
ALTER TABLE programs DROP CONSTRAINT IF EXISTS chk_programs_publish_status;
|
||||
|
||||
ALTER TABLE programs DROP COLUMN IF EXISTS publish_status;
|
||||
|
||||
ALTER TABLE courses DROP CONSTRAINT IF EXISTS chk_courses_publish_status;
|
||||
|
||||
ALTER TABLE courses DROP COLUMN IF EXISTS publish_status;
|
||||
|
||||
ALTER TABLE modules DROP CONSTRAINT IF EXISTS chk_modules_publish_status;
|
||||
|
||||
ALTER TABLE modules DROP COLUMN IF EXISTS publish_status;
|
||||
|
||||
ALTER TABLE exam_prep.catalog_courses DROP CONSTRAINT IF EXISTS chk_exam_prep_catalog_courses_publish_status;
|
||||
|
||||
ALTER TABLE exam_prep.catalog_courses DROP COLUMN IF EXISTS publish_status;
|
||||
|
||||
ALTER TABLE exam_prep.units DROP CONSTRAINT IF EXISTS chk_exam_prep_units_publish_status;
|
||||
|
||||
ALTER TABLE exam_prep.units DROP COLUMN IF EXISTS publish_status;
|
||||
|
||||
ALTER TABLE exam_prep.unit_modules DROP CONSTRAINT IF EXISTS chk_exam_prep_unit_modules_publish_status;
|
||||
|
||||
ALTER TABLE exam_prep.unit_modules DROP COLUMN IF EXISTS publish_status;
|
||||
|
||||
ALTER TABLE exam_prep.unit_module_lessons DROP CONSTRAINT IF EXISTS chk_exam_prep_unit_module_lessons_publish_status;
|
||||
|
||||
ALTER TABLE exam_prep.unit_module_lessons DROP COLUMN IF EXISTS publish_status;
|
||||
54
db/migrations/000078_hierarchy_publish_status.up.sql
Normal file
54
db/migrations/000078_hierarchy_publish_status.up.sql
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
-- Draft vs published visibility for the remaining LMS and exam-prep hierarchy levels
|
||||
-- (mirrors lessons.publish_status from 000062 and practice publish_status from 000060).
|
||||
-- Existing rows stay PUBLISHED; new inserts default to DRAFT unless the API sends PUBLISHED.
|
||||
|
||||
-- LMS hierarchy
|
||||
ALTER TABLE programs
|
||||
ADD COLUMN publish_status VARCHAR(16) NOT NULL DEFAULT 'PUBLISHED'
|
||||
CONSTRAINT chk_programs_publish_status CHECK (publish_status IN ('DRAFT', 'PUBLISHED'));
|
||||
|
||||
ALTER TABLE programs
|
||||
ALTER COLUMN publish_status SET DEFAULT 'DRAFT';
|
||||
|
||||
ALTER TABLE courses
|
||||
ADD COLUMN publish_status VARCHAR(16) NOT NULL DEFAULT 'PUBLISHED'
|
||||
CONSTRAINT chk_courses_publish_status CHECK (publish_status IN ('DRAFT', 'PUBLISHED'));
|
||||
|
||||
ALTER TABLE courses
|
||||
ALTER COLUMN publish_status SET DEFAULT 'DRAFT';
|
||||
|
||||
ALTER TABLE modules
|
||||
ADD COLUMN publish_status VARCHAR(16) NOT NULL DEFAULT 'PUBLISHED'
|
||||
CONSTRAINT chk_modules_publish_status CHECK (publish_status IN ('DRAFT', 'PUBLISHED'));
|
||||
|
||||
ALTER TABLE modules
|
||||
ALTER COLUMN publish_status SET DEFAULT 'DRAFT';
|
||||
|
||||
-- Exam-prep hierarchy
|
||||
ALTER TABLE exam_prep.catalog_courses
|
||||
ADD COLUMN publish_status VARCHAR(16) NOT NULL DEFAULT 'PUBLISHED'
|
||||
CONSTRAINT chk_exam_prep_catalog_courses_publish_status CHECK (publish_status IN ('DRAFT', 'PUBLISHED'));
|
||||
|
||||
ALTER TABLE exam_prep.catalog_courses
|
||||
ALTER COLUMN publish_status SET DEFAULT 'DRAFT';
|
||||
|
||||
ALTER TABLE exam_prep.units
|
||||
ADD COLUMN publish_status VARCHAR(16) NOT NULL DEFAULT 'PUBLISHED'
|
||||
CONSTRAINT chk_exam_prep_units_publish_status CHECK (publish_status IN ('DRAFT', 'PUBLISHED'));
|
||||
|
||||
ALTER TABLE exam_prep.units
|
||||
ALTER COLUMN publish_status SET DEFAULT 'DRAFT';
|
||||
|
||||
ALTER TABLE exam_prep.unit_modules
|
||||
ADD COLUMN publish_status VARCHAR(16) NOT NULL DEFAULT 'PUBLISHED'
|
||||
CONSTRAINT chk_exam_prep_unit_modules_publish_status CHECK (publish_status IN ('DRAFT', 'PUBLISHED'));
|
||||
|
||||
ALTER TABLE exam_prep.unit_modules
|
||||
ALTER COLUMN publish_status SET DEFAULT 'DRAFT';
|
||||
|
||||
ALTER TABLE exam_prep.unit_module_lessons
|
||||
ADD COLUMN publish_status VARCHAR(16) NOT NULL DEFAULT 'PUBLISHED'
|
||||
CONSTRAINT chk_exam_prep_unit_module_lessons_publish_status CHECK (publish_status IN ('DRAFT', 'PUBLISHED'));
|
||||
|
||||
ALTER TABLE exam_prep.unit_module_lessons
|
||||
ALTER COLUMN publish_status SET DEFAULT 'DRAFT';
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
-- name: ExamPrepCreateCatalogCourse :one
|
||||
INSERT INTO exam_prep.catalog_courses (name, description, category, thumbnail, sort_order)
|
||||
INSERT INTO exam_prep.catalog_courses (name, description, category, thumbnail, sort_order, publish_status)
|
||||
SELECT
|
||||
$1,
|
||||
$2,
|
||||
|
|
@ -8,7 +8,8 @@ SELECT
|
|||
coalesce((
|
||||
SELECT
|
||||
max(c.sort_order)
|
||||
FROM exam_prep.catalog_courses AS c), 0) + 1
|
||||
FROM exam_prep.catalog_courses AS c), 0) + 1,
|
||||
$5
|
||||
RETURNING
|
||||
*;
|
||||
|
||||
|
|
@ -22,6 +23,10 @@ SELECT
|
|||
INNER JOIN exam_prep.unit_modules m ON m.id = l.unit_module_id
|
||||
INNER JOIN exam_prep.units u ON u.id = m.unit_id
|
||||
WHERE u.catalog_course_id = c.id
|
||||
AND p.publish_status = 'PUBLISHED'
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
AND u.publish_status = 'PUBLISHED'
|
||||
) AS has_practice
|
||||
FROM exam_prep.catalog_courses c
|
||||
WHERE c.id = $1;
|
||||
|
|
@ -35,7 +40,10 @@ WITH catalog_course_counts AS (
|
|||
COUNT(DISTINCT l.id)::BIGINT AS lessons_count
|
||||
FROM exam_prep.units u
|
||||
LEFT JOIN exam_prep.unit_modules m ON m.unit_id = u.id
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
LEFT JOIN exam_prep.unit_module_lessons l ON l.unit_module_id = m.id
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
WHERE u.publish_status = 'PUBLISHED'
|
||||
GROUP BY u.catalog_course_id
|
||||
)
|
||||
SELECT
|
||||
|
|
@ -46,6 +54,7 @@ SELECT
|
|||
c.category,
|
||||
c.thumbnail,
|
||||
c.sort_order,
|
||||
c.publish_status,
|
||||
COALESCE(cc.units_count, 0)::BIGINT AS units_count,
|
||||
COALESCE(cc.modules_count, 0)::BIGINT AS modules_count,
|
||||
COALESCE(cc.lessons_count, 0)::BIGINT AS lessons_count,
|
||||
|
|
@ -56,11 +65,19 @@ SELECT
|
|||
INNER JOIN exam_prep.unit_modules m ON m.id = l.unit_module_id
|
||||
INNER JOIN exam_prep.units u ON u.id = m.unit_id
|
||||
WHERE u.catalog_course_id = c.id
|
||||
AND p.publish_status = 'PUBLISHED'
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
AND u.publish_status = 'PUBLISHED'
|
||||
) AS has_practice,
|
||||
c.created_at,
|
||||
c.updated_at
|
||||
FROM exam_prep.catalog_courses c
|
||||
LEFT JOIN catalog_course_counts cc ON cc.catalog_course_id = c.id
|
||||
WHERE (
|
||||
sqlc.arg('published_only')::boolean = FALSE
|
||||
OR c.publish_status = 'PUBLISHED'::TEXT
|
||||
)
|
||||
ORDER BY c.sort_order ASC, c.id ASC
|
||||
LIMIT $1 OFFSET $2;
|
||||
|
||||
|
|
@ -78,6 +95,7 @@ SET
|
|||
category = coalesce(sqlc.narg('category')::varchar, category),
|
||||
thumbnail = coalesce(sqlc.narg('thumbnail')::text, thumbnail),
|
||||
sort_order = coalesce(sqlc.narg('sort_order')::int, sort_order),
|
||||
publish_status = coalesce(sqlc.narg('publish_status')::varchar, publish_status),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = sqlc.arg('id')
|
||||
RETURNING
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ FROM
|
|||
INNER JOIN question_sets qs ON qs.id = p.question_set_id
|
||||
WHERE
|
||||
l.unit_module_id = $1
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
AND qs.set_type = 'PRACTICE'
|
||||
AND qs.status = 'PUBLISHED'
|
||||
AND p.publish_status = 'PUBLISHED';
|
||||
|
|
@ -48,6 +49,7 @@ FROM
|
|||
INNER JOIN user_practice_progress upp ON upp.question_set_id = p.question_set_id
|
||||
WHERE
|
||||
l.unit_module_id = $1
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
AND upp.user_id = $2
|
||||
AND upp.completed_at IS NOT NULL
|
||||
AND qs.set_type = 'PRACTICE'
|
||||
|
|
@ -64,6 +66,8 @@ FROM
|
|||
INNER JOIN question_sets qs ON qs.id = p.question_set_id
|
||||
WHERE
|
||||
m.unit_id = $1
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
AND qs.set_type = 'PRACTICE'
|
||||
AND qs.status = 'PUBLISHED'
|
||||
AND p.publish_status = 'PUBLISHED';
|
||||
|
|
@ -79,6 +83,8 @@ FROM
|
|||
INNER JOIN user_practice_progress upp ON upp.question_set_id = p.question_set_id
|
||||
WHERE
|
||||
m.unit_id = $1
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
AND upp.user_id = $2
|
||||
AND upp.completed_at IS NOT NULL
|
||||
AND qs.set_type = 'PRACTICE'
|
||||
|
|
@ -96,6 +102,9 @@ FROM
|
|||
INNER JOIN question_sets qs ON qs.id = p.question_set_id
|
||||
WHERE
|
||||
u.catalog_course_id = $1
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
AND u.publish_status = 'PUBLISHED'
|
||||
AND qs.set_type = 'PRACTICE'
|
||||
AND qs.status = 'PUBLISHED'
|
||||
AND p.publish_status = 'PUBLISHED';
|
||||
|
|
@ -112,6 +121,9 @@ FROM
|
|||
INNER JOIN user_practice_progress upp ON upp.question_set_id = p.question_set_id
|
||||
WHERE
|
||||
u.catalog_course_id = $1
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
AND u.publish_status = 'PUBLISHED'
|
||||
AND upp.user_id = $2
|
||||
AND upp.completed_at IS NOT NULL
|
||||
AND qs.set_type = 'PRACTICE'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
-- name: ExamPrepCreateUnitModuleLesson :one
|
||||
INSERT INTO exam_prep.unit_module_lessons (unit_module_id, title, video_url, thumbnail, description, sort_order)
|
||||
INSERT INTO exam_prep.unit_module_lessons (unit_module_id, title, video_url, thumbnail, description, sort_order, publish_status)
|
||||
SELECT
|
||||
$1,
|
||||
$2,
|
||||
|
|
@ -11,7 +11,8 @@ SELECT
|
|||
max(l.sort_order)
|
||||
FROM exam_prep.unit_module_lessons l
|
||||
WHERE
|
||||
l.unit_module_id = $1), 0) + 1
|
||||
l.unit_module_id = $1), 0) + 1,
|
||||
$6
|
||||
RETURNING
|
||||
*;
|
||||
|
||||
|
|
@ -22,6 +23,7 @@ SELECT
|
|||
SELECT 1
|
||||
FROM exam_prep.lesson_practices p
|
||||
WHERE p.unit_module_lesson_id = l.id
|
||||
AND p.publish_status = 'PUBLISHED'
|
||||
) AS has_practice
|
||||
FROM exam_prep.unit_module_lessons l
|
||||
WHERE l.id = $1;
|
||||
|
|
@ -35,6 +37,17 @@ WHERE
|
|||
ORDER BY
|
||||
l.id;
|
||||
|
||||
-- Published lessons only, for learner-facing progress rollups.
|
||||
-- name: ExamPrepListPublishedUnitModuleLessonIDsByUnitModule :many
|
||||
SELECT
|
||||
l.id
|
||||
FROM exam_prep.unit_module_lessons l
|
||||
WHERE
|
||||
l.unit_module_id = $1
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
ORDER BY
|
||||
l.id;
|
||||
|
||||
-- name: ExamPrepListUnitModuleLessonsByUnitModuleID :many
|
||||
SELECT
|
||||
COUNT(*) OVER () AS total_count,
|
||||
|
|
@ -45,16 +58,22 @@ SELECT
|
|||
l.thumbnail,
|
||||
l.description,
|
||||
l.sort_order,
|
||||
l.publish_status,
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM exam_prep.lesson_practices p
|
||||
WHERE p.unit_module_lesson_id = l.id
|
||||
AND p.publish_status = 'PUBLISHED'
|
||||
) AS has_practice,
|
||||
l.created_at,
|
||||
l.updated_at
|
||||
FROM exam_prep.unit_module_lessons l
|
||||
WHERE
|
||||
l.unit_module_id = $1
|
||||
AND (
|
||||
sqlc.arg('published_only')::boolean = FALSE
|
||||
OR l.publish_status = 'PUBLISHED'::TEXT
|
||||
)
|
||||
ORDER BY
|
||||
l.sort_order ASC,
|
||||
l.id ASC
|
||||
|
|
@ -69,6 +88,7 @@ SET
|
|||
thumbnail = coalesce(sqlc.narg('thumbnail')::text, thumbnail),
|
||||
description = coalesce(sqlc.narg('description')::text, description),
|
||||
sort_order = coalesce(sqlc.narg('sort_order')::int, sort_order),
|
||||
publish_status = coalesce(sqlc.narg('publish_status')::varchar, publish_status),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = sqlc.arg('id')
|
||||
RETURNING
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
-- name: ExamPrepCreateUnitModule :one
|
||||
INSERT INTO exam_prep.unit_modules (unit_id, name, description, thumbnail, icon, sort_order)
|
||||
INSERT INTO exam_prep.unit_modules (unit_id, name, description, thumbnail, icon, sort_order, publish_status)
|
||||
SELECT
|
||||
$1,
|
||||
$2,
|
||||
|
|
@ -11,7 +11,8 @@ SELECT
|
|||
max(m.sort_order)
|
||||
FROM exam_prep.unit_modules m
|
||||
WHERE
|
||||
m.unit_id = $1), 0) + 1
|
||||
m.unit_id = $1), 0) + 1,
|
||||
$6
|
||||
RETURNING
|
||||
*;
|
||||
|
||||
|
|
@ -23,6 +24,8 @@ SELECT
|
|||
FROM exam_prep.lesson_practices p
|
||||
INNER JOIN exam_prep.unit_module_lessons l ON l.id = p.unit_module_lesson_id
|
||||
WHERE l.unit_module_id = m.id
|
||||
AND p.publish_status = 'PUBLISHED'
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
) AS has_practice
|
||||
FROM exam_prep.unit_modules m
|
||||
WHERE m.id = $1;
|
||||
|
|
@ -36,6 +39,17 @@ WHERE
|
|||
ORDER BY
|
||||
m.id;
|
||||
|
||||
-- Published modules only, for learner-facing progress rollups.
|
||||
-- name: ExamPrepListPublishedUnitModuleIDsByUnit :many
|
||||
SELECT
|
||||
m.id
|
||||
FROM exam_prep.unit_modules m
|
||||
WHERE
|
||||
m.unit_id = $1
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
ORDER BY
|
||||
m.id;
|
||||
|
||||
-- name: ExamPrepListUnitModulesByUnit :many
|
||||
WITH module_counts AS (
|
||||
SELECT
|
||||
|
|
@ -44,7 +58,9 @@ WITH module_counts AS (
|
|||
COUNT(DISTINCT p.id)::BIGINT AS practices_count
|
||||
FROM exam_prep.unit_modules m
|
||||
LEFT JOIN exam_prep.unit_module_lessons l ON l.unit_module_id = m.id
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
LEFT JOIN exam_prep.lesson_practices p ON p.unit_module_lesson_id = l.id
|
||||
AND p.publish_status = 'PUBLISHED'
|
||||
GROUP BY m.id
|
||||
)
|
||||
SELECT
|
||||
|
|
@ -56,6 +72,7 @@ SELECT
|
|||
m.thumbnail,
|
||||
m.icon,
|
||||
m.sort_order,
|
||||
m.publish_status,
|
||||
COALESCE(mc.lessons_count, 0)::BIGINT AS lessons_count,
|
||||
COALESCE(mc.practices_count, 0)::BIGINT AS practices_count,
|
||||
(COALESCE(mc.practices_count, 0)::BIGINT > 0) AS has_practice,
|
||||
|
|
@ -65,6 +82,10 @@ FROM exam_prep.unit_modules m
|
|||
LEFT JOIN module_counts mc ON mc.module_id = m.id
|
||||
WHERE
|
||||
m.unit_id = $1
|
||||
AND (
|
||||
sqlc.arg('published_only')::boolean = FALSE
|
||||
OR m.publish_status = 'PUBLISHED'::TEXT
|
||||
)
|
||||
ORDER BY
|
||||
m.sort_order ASC,
|
||||
m.id ASC
|
||||
|
|
@ -79,6 +100,7 @@ SET
|
|||
thumbnail = coalesce(sqlc.narg('thumbnail')::text, thumbnail),
|
||||
icon = coalesce(sqlc.narg('icon')::text, icon),
|
||||
sort_order = coalesce(sqlc.narg('sort_order')::int, sort_order),
|
||||
publish_status = coalesce(sqlc.narg('publish_status')::varchar, publish_status),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = sqlc.arg('id')
|
||||
RETURNING
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
-- name: ExamPrepCreateUnit :one
|
||||
INSERT INTO exam_prep.units (catalog_course_id, name, description, thumbnail, sort_order)
|
||||
INSERT INTO exam_prep.units (catalog_course_id, name, description, thumbnail, sort_order, publish_status)
|
||||
SELECT
|
||||
sqlc.arg('catalog_course_id'),
|
||||
sqlc.arg('name'),
|
||||
|
|
@ -11,7 +11,8 @@ SELECT
|
|||
max(u.sort_order)
|
||||
FROM exam_prep.units u
|
||||
WHERE
|
||||
u.catalog_course_id = sqlc.arg('catalog_course_id')), 0) + 1)
|
||||
u.catalog_course_id = sqlc.arg('catalog_course_id')), 0) + 1),
|
||||
sqlc.arg('publish_status')
|
||||
RETURNING
|
||||
*;
|
||||
|
||||
|
|
@ -24,6 +25,9 @@ SELECT
|
|||
INNER JOIN exam_prep.unit_module_lessons l ON l.id = p.unit_module_lesson_id
|
||||
INNER JOIN exam_prep.unit_modules m ON m.id = l.unit_module_id
|
||||
WHERE m.unit_id = u.id
|
||||
AND p.publish_status = 'PUBLISHED'
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
) AS has_practice
|
||||
FROM exam_prep.units u
|
||||
WHERE u.id = $1;
|
||||
|
|
@ -37,6 +41,17 @@ WHERE
|
|||
ORDER BY
|
||||
u.id;
|
||||
|
||||
-- Published units only, for learner-facing progress rollups.
|
||||
-- name: ExamPrepListPublishedUnitIDsByCatalogCourse :many
|
||||
SELECT
|
||||
u.id
|
||||
FROM exam_prep.units u
|
||||
WHERE
|
||||
u.catalog_course_id = $1
|
||||
AND u.publish_status = 'PUBLISHED'
|
||||
ORDER BY
|
||||
u.id;
|
||||
|
||||
-- name: ExamPrepListUnitsByCatalogCourse :many
|
||||
WITH unit_counts AS (
|
||||
SELECT
|
||||
|
|
@ -46,8 +61,11 @@ WITH unit_counts AS (
|
|||
COUNT(DISTINCT p.id)::BIGINT AS practices_count
|
||||
FROM exam_prep.units u
|
||||
LEFT JOIN exam_prep.unit_modules m ON m.unit_id = u.id
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
LEFT JOIN exam_prep.unit_module_lessons l ON l.unit_module_id = m.id
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
LEFT JOIN exam_prep.lesson_practices p ON p.unit_module_lesson_id = l.id
|
||||
AND p.publish_status = 'PUBLISHED'
|
||||
GROUP BY u.id
|
||||
)
|
||||
SELECT
|
||||
|
|
@ -58,6 +76,7 @@ SELECT
|
|||
u.description,
|
||||
u.thumbnail,
|
||||
u.sort_order,
|
||||
u.publish_status,
|
||||
COALESCE(uc.modules_count, 0)::BIGINT AS modules_count,
|
||||
COALESCE(uc.lessons_count, 0)::BIGINT AS lessons_count,
|
||||
COALESCE(uc.practices_count, 0)::BIGINT AS practices_count,
|
||||
|
|
@ -68,6 +87,10 @@ FROM exam_prep.units u
|
|||
LEFT JOIN unit_counts uc ON uc.unit_id = u.id
|
||||
WHERE
|
||||
u.catalog_course_id = $1
|
||||
AND (
|
||||
sqlc.arg('published_only')::boolean = FALSE
|
||||
OR u.publish_status = 'PUBLISHED'::TEXT
|
||||
)
|
||||
ORDER BY
|
||||
u.sort_order ASC,
|
||||
u.id ASC
|
||||
|
|
@ -81,6 +104,7 @@ SET
|
|||
description = coalesce(sqlc.narg('description')::text, description),
|
||||
thumbnail = coalesce(sqlc.narg('thumbnail')::text, thumbnail),
|
||||
sort_order = coalesce(sqlc.narg('sort_order')::int, sort_order),
|
||||
publish_status = coalesce(sqlc.narg('publish_status')::varchar, publish_status),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = sqlc.arg('id')
|
||||
RETURNING
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
-- name: CreateCourse :one
|
||||
INSERT INTO courses (program_id, name, description, thumbnail, sort_order)
|
||||
INSERT INTO courses (program_id, name, description, thumbnail, sort_order, publish_status)
|
||||
SELECT
|
||||
sqlc.arg('program_id'),
|
||||
sqlc.arg('name'),
|
||||
|
|
@ -11,7 +11,8 @@ SELECT
|
|||
max(c.sort_order)
|
||||
FROM courses c
|
||||
WHERE
|
||||
c.program_id = sqlc.arg('program_id')), 0) + 1)
|
||||
c.program_id = sqlc.arg('program_id')), 0) + 1),
|
||||
sqlc.arg('publish_status')
|
||||
RETURNING
|
||||
*;
|
||||
|
||||
|
|
@ -40,6 +41,18 @@ WHERE
|
|||
ORDER BY
|
||||
c.id;
|
||||
|
||||
-- Published courses only, for learner-facing progress rollups.
|
||||
-- name: ListPublishedCourseIDsByProgram :many
|
||||
SELECT
|
||||
c.id
|
||||
FROM
|
||||
courses AS c
|
||||
WHERE
|
||||
c.program_id = $1
|
||||
AND c.publish_status = 'PUBLISHED'
|
||||
ORDER BY
|
||||
c.id;
|
||||
|
||||
-- name: ListCoursesByProgramID :many
|
||||
SELECT
|
||||
COUNT(*) OVER () AS total_count,
|
||||
|
|
@ -49,6 +62,7 @@ SELECT
|
|||
c.description,
|
||||
c.thumbnail,
|
||||
c.sort_order,
|
||||
c.publish_status,
|
||||
c.created_at,
|
||||
c.updated_at,
|
||||
(
|
||||
|
|
@ -57,7 +71,8 @@ SELECT
|
|||
FROM
|
||||
modules m
|
||||
WHERE
|
||||
m.course_id = c.id) AS module_count,
|
||||
m.course_id = c.id
|
||||
AND m.publish_status = 'PUBLISHED') AS module_count,
|
||||
(
|
||||
SELECT
|
||||
COUNT(*)::bigint
|
||||
|
|
@ -91,6 +106,10 @@ FROM
|
|||
courses c
|
||||
WHERE
|
||||
c.program_id = $1
|
||||
AND (
|
||||
sqlc.arg('published_only')::boolean = FALSE
|
||||
OR c.publish_status = 'PUBLISHED'::TEXT
|
||||
)
|
||||
ORDER BY
|
||||
c.sort_order ASC,
|
||||
c.id ASC
|
||||
|
|
@ -103,6 +122,7 @@ SET
|
|||
description = COALESCE(sqlc.narg('description')::text, description),
|
||||
thumbnail = COALESCE(sqlc.narg('thumbnail')::text, thumbnail),
|
||||
sort_order = coalesce(sqlc.narg('sort_order')::int, sort_order),
|
||||
publish_status = COALESCE(sqlc.narg('publish_status')::varchar, publish_status),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE
|
||||
id = sqlc.arg('id')
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
-- name: CreateModule :one
|
||||
INSERT INTO modules (program_id, course_id, name, description, icon, sort_order)
|
||||
INSERT INTO modules (program_id, course_id, name, description, icon, sort_order, publish_status)
|
||||
SELECT
|
||||
sqlc.arg('program_id'),
|
||||
sqlc.arg('course_id'),
|
||||
|
|
@ -12,7 +12,8 @@ SELECT
|
|||
max(m.sort_order)
|
||||
FROM modules m
|
||||
WHERE
|
||||
m.course_id = sqlc.arg('course_id')), 0) + 1)
|
||||
m.course_id = sqlc.arg('course_id')), 0) + 1),
|
||||
sqlc.arg('publish_status')
|
||||
RETURNING
|
||||
*;
|
||||
|
||||
|
|
@ -40,6 +41,18 @@ WHERE
|
|||
ORDER BY
|
||||
m.id;
|
||||
|
||||
-- Published modules only, for learner-facing progress rollups.
|
||||
-- name: ListPublishedModuleIDsByCourse :many
|
||||
SELECT
|
||||
m.id
|
||||
FROM
|
||||
modules AS m
|
||||
WHERE
|
||||
m.course_id = $1
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
ORDER BY
|
||||
m.id;
|
||||
|
||||
-- name: ListModulesByProgramAndCourse :many
|
||||
SELECT
|
||||
COUNT(*) OVER () AS total_count,
|
||||
|
|
@ -50,6 +63,7 @@ SELECT
|
|||
m.description,
|
||||
m.icon,
|
||||
m.sort_order,
|
||||
m.publish_status,
|
||||
m.created_at,
|
||||
m.updated_at,
|
||||
EXISTS (
|
||||
|
|
@ -64,6 +78,10 @@ FROM
|
|||
WHERE
|
||||
m.program_id = $1
|
||||
AND m.course_id = $2
|
||||
AND (
|
||||
sqlc.arg('published_only')::boolean = FALSE
|
||||
OR m.publish_status = 'PUBLISHED'::TEXT
|
||||
)
|
||||
ORDER BY
|
||||
m.sort_order ASC,
|
||||
m.id ASC
|
||||
|
|
@ -77,6 +95,7 @@ SET
|
|||
description = COALESCE(sqlc.narg('description')::text, description),
|
||||
icon = COALESCE(sqlc.narg('icon')::text, icon),
|
||||
sort_order = coalesce(sqlc.narg('sort_order')::int, sort_order),
|
||||
publish_status = COALESCE(sqlc.narg('publish_status')::varchar, publish_status),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE
|
||||
id = sqlc.arg('id')
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ SELECT
|
|||
FROM
|
||||
programs AS p1
|
||||
INNER JOIN programs AS p2 ON p2.category = p1.category
|
||||
AND p2.publish_status = 'PUBLISHED'
|
||||
AND (
|
||||
p2.sort_order < p1.sort_order
|
||||
OR (
|
||||
|
|
@ -25,6 +26,7 @@ SELECT
|
|||
FROM
|
||||
courses AS c1
|
||||
INNER JOIN courses AS c2 ON c2.program_id = c1.program_id
|
||||
AND c2.publish_status = 'PUBLISHED'
|
||||
AND (
|
||||
c2.sort_order < c1.sort_order
|
||||
OR (
|
||||
|
|
@ -45,6 +47,7 @@ SELECT
|
|||
FROM
|
||||
modules AS m1
|
||||
INNER JOIN modules AS m2 ON m2.course_id = m1.course_id
|
||||
AND m2.publish_status = 'PUBLISHED'
|
||||
AND (
|
||||
m2.sort_order < m1.sort_order
|
||||
OR (
|
||||
|
|
@ -226,7 +229,8 @@ SELECT
|
|||
FROM
|
||||
modules
|
||||
WHERE
|
||||
course_id = $1;
|
||||
course_id = $1
|
||||
AND publish_status = 'PUBLISHED';
|
||||
|
||||
-- name: CountUserCompletedModulesInCourse :one
|
||||
SELECT
|
||||
|
|
@ -236,7 +240,8 @@ FROM
|
|||
INNER JOIN modules m ON m.id = ump.module_id
|
||||
WHERE
|
||||
m.course_id = $1
|
||||
AND ump.user_id = $2;
|
||||
AND ump.user_id = $2
|
||||
AND m.publish_status = 'PUBLISHED';
|
||||
|
||||
-- name: CountCoursesInProgram :one
|
||||
SELECT
|
||||
|
|
@ -244,7 +249,8 @@ SELECT
|
|||
FROM
|
||||
courses
|
||||
WHERE
|
||||
program_id = $1;
|
||||
program_id = $1
|
||||
AND publish_status = 'PUBLISHED';
|
||||
|
||||
-- name: CountUserCompletedCoursesInProgram :one
|
||||
SELECT
|
||||
|
|
@ -254,7 +260,8 @@ FROM
|
|||
INNER JOIN courses c ON c.id = ucp.course_id
|
||||
WHERE
|
||||
c.program_id = $1
|
||||
AND ucp.user_id = $2;
|
||||
AND ucp.user_id = $2
|
||||
AND c.publish_status = 'PUBLISHED';
|
||||
|
||||
-- name: ListLMSCompletedLessonIDsByUser :many
|
||||
SELECT
|
||||
|
|
@ -448,7 +455,7 @@ WHERE
|
|||
AND ulp.user_id = $2
|
||||
AND l.publish_status = 'PUBLISHED';
|
||||
|
||||
-- Published practices in a module (direct module practices and practices on lessons in the module).
|
||||
-- Published practices in a module (direct module practices and practices on published lessons in the module).
|
||||
-- name: CountPublishedPracticesInModule :one
|
||||
SELECT
|
||||
count(*)::int AS n
|
||||
|
|
@ -464,7 +471,8 @@ WHERE
|
|||
FROM
|
||||
lessons
|
||||
WHERE
|
||||
module_id = $1))
|
||||
module_id = $1
|
||||
AND publish_status = 'PUBLISHED'))
|
||||
AND qs.set_type = 'PRACTICE'
|
||||
AND qs.status = 'PUBLISHED'
|
||||
AND lp.publish_status = 'PUBLISHED';
|
||||
|
|
@ -485,7 +493,8 @@ WHERE
|
|||
FROM
|
||||
lessons
|
||||
WHERE
|
||||
module_id = $1))
|
||||
module_id = $1
|
||||
AND publish_status = 'PUBLISHED'))
|
||||
AND upp.user_id = $2
|
||||
AND upp.completed_at IS NOT NULL
|
||||
AND qs.set_type = 'PRACTICE'
|
||||
|
|
@ -507,7 +516,8 @@ WHERE
|
|||
FROM
|
||||
modules
|
||||
WHERE
|
||||
course_id = $1)
|
||||
course_id = $1
|
||||
AND publish_status = 'PUBLISHED')
|
||||
OR lp.lesson_id IN (
|
||||
SELECT
|
||||
l.id
|
||||
|
|
@ -515,7 +525,9 @@ WHERE
|
|||
lessons l
|
||||
INNER JOIN modules m ON m.id = l.module_id
|
||||
WHERE
|
||||
m.course_id = $1))
|
||||
m.course_id = $1
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
AND m.publish_status = 'PUBLISHED'))
|
||||
AND qs.set_type = 'PRACTICE'
|
||||
AND qs.status = 'PUBLISHED'
|
||||
AND lp.publish_status = 'PUBLISHED';
|
||||
|
|
@ -536,7 +548,8 @@ WHERE
|
|||
FROM
|
||||
modules
|
||||
WHERE
|
||||
course_id = $1)
|
||||
course_id = $1
|
||||
AND publish_status = 'PUBLISHED')
|
||||
OR lp.lesson_id IN (
|
||||
SELECT
|
||||
l.id
|
||||
|
|
@ -544,7 +557,9 @@ WHERE
|
|||
lessons l
|
||||
INNER JOIN modules m ON m.id = l.module_id
|
||||
WHERE
|
||||
m.course_id = $1))
|
||||
m.course_id = $1
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
AND m.publish_status = 'PUBLISHED'))
|
||||
AND upp.user_id = $2
|
||||
AND upp.completed_at IS NOT NULL
|
||||
AND qs.set_type = 'PRACTICE'
|
||||
|
|
@ -565,7 +580,8 @@ WHERE
|
|||
FROM
|
||||
courses c
|
||||
WHERE
|
||||
c.program_id = $1)
|
||||
c.program_id = $1
|
||||
AND c.publish_status = 'PUBLISHED')
|
||||
OR lp.module_id IN (
|
||||
SELECT
|
||||
m.id
|
||||
|
|
@ -573,7 +589,9 @@ WHERE
|
|||
modules m
|
||||
INNER JOIN courses c ON c.id = m.course_id
|
||||
WHERE
|
||||
c.program_id = $1)
|
||||
c.program_id = $1
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
AND c.publish_status = 'PUBLISHED')
|
||||
OR lp.lesson_id IN (
|
||||
SELECT
|
||||
l.id
|
||||
|
|
@ -582,7 +600,10 @@ WHERE
|
|||
INNER JOIN modules m ON m.id = l.module_id
|
||||
INNER JOIN courses c ON c.id = m.course_id
|
||||
WHERE
|
||||
c.program_id = $1))
|
||||
c.program_id = $1
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
AND c.publish_status = 'PUBLISHED'))
|
||||
AND qs.set_type = 'PRACTICE'
|
||||
AND qs.status = 'PUBLISHED'
|
||||
AND lp.publish_status = 'PUBLISHED';
|
||||
|
|
@ -602,7 +623,8 @@ WHERE
|
|||
FROM
|
||||
courses c
|
||||
WHERE
|
||||
c.program_id = $1)
|
||||
c.program_id = $1
|
||||
AND c.publish_status = 'PUBLISHED')
|
||||
OR lp.module_id IN (
|
||||
SELECT
|
||||
m.id
|
||||
|
|
@ -610,7 +632,9 @@ WHERE
|
|||
modules m
|
||||
INNER JOIN courses c ON c.id = m.course_id
|
||||
WHERE
|
||||
c.program_id = $1)
|
||||
c.program_id = $1
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
AND c.publish_status = 'PUBLISHED')
|
||||
OR lp.lesson_id IN (
|
||||
SELECT
|
||||
l.id
|
||||
|
|
@ -619,7 +643,10 @@ WHERE
|
|||
INNER JOIN modules m ON m.id = l.module_id
|
||||
INNER JOIN courses c ON c.id = m.course_id
|
||||
WHERE
|
||||
c.program_id = $1))
|
||||
c.program_id = $1
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
AND c.publish_status = 'PUBLISHED'))
|
||||
AND upp.user_id = $2
|
||||
AND upp.completed_at IS NOT NULL
|
||||
AND qs.set_type = 'PRACTICE'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
-- name: CreateProgram :one
|
||||
INSERT INTO programs (name, description, category, thumbnail, sort_order)
|
||||
INSERT INTO programs (name, description, category, thumbnail, sort_order, publish_status)
|
||||
SELECT
|
||||
sqlc.arg('name'),
|
||||
sqlc.arg('description'),
|
||||
|
|
@ -8,7 +8,8 @@ SELECT
|
|||
COALESCE(sqlc.narg('sort_order')::int, COALESCE((
|
||||
SELECT
|
||||
max(p.sort_order)
|
||||
FROM programs AS p), 0) + 1)
|
||||
FROM programs AS p), 0) + 1),
|
||||
sqlc.arg('publish_status')
|
||||
RETURNING
|
||||
*;
|
||||
|
||||
|
|
@ -34,9 +35,14 @@ SELECT
|
|||
p.category,
|
||||
p.thumbnail,
|
||||
p.sort_order,
|
||||
p.publish_status,
|
||||
p.created_at,
|
||||
p.updated_at
|
||||
FROM programs p
|
||||
WHERE (
|
||||
sqlc.arg('published_only')::boolean = FALSE
|
||||
OR p.publish_status = 'PUBLISHED'::TEXT
|
||||
)
|
||||
ORDER BY p.sort_order ASC, p.id ASC
|
||||
LIMIT $1 OFFSET $2;
|
||||
|
||||
|
|
@ -48,6 +54,7 @@ SET
|
|||
category = COALESCE(sqlc.narg('category')::varchar, category),
|
||||
thumbnail = COALESCE(sqlc.narg('thumbnail')::text, thumbnail),
|
||||
sort_order = coalesce(sqlc.narg('sort_order')::int, sort_order),
|
||||
publish_status = COALESCE(sqlc.narg('publish_status')::varchar, publish_status),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE
|
||||
id = sqlc.arg('id')
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import (
|
|||
)
|
||||
|
||||
const ExamPrepCreateCatalogCourse = `-- name: ExamPrepCreateCatalogCourse :one
|
||||
INSERT INTO exam_prep.catalog_courses (name, description, category, thumbnail, sort_order)
|
||||
INSERT INTO exam_prep.catalog_courses (name, description, category, thumbnail, sort_order, publish_status)
|
||||
SELECT
|
||||
$1,
|
||||
$2,
|
||||
|
|
@ -21,16 +21,18 @@ SELECT
|
|||
coalesce((
|
||||
SELECT
|
||||
max(c.sort_order)
|
||||
FROM exam_prep.catalog_courses AS c), 0) + 1
|
||||
FROM exam_prep.catalog_courses AS c), 0) + 1,
|
||||
$5
|
||||
RETURNING
|
||||
id, name, description, thumbnail, sort_order, created_at, updated_at, category
|
||||
id, name, description, thumbnail, sort_order, created_at, updated_at, category, publish_status
|
||||
`
|
||||
|
||||
type ExamPrepCreateCatalogCourseParams struct {
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Category string `json:"category"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Category string `json:"category"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
PublishStatus string `json:"publish_status"`
|
||||
}
|
||||
|
||||
func (q *Queries) ExamPrepCreateCatalogCourse(ctx context.Context, arg ExamPrepCreateCatalogCourseParams) (ExamPrepCatalogCourse, error) {
|
||||
|
|
@ -39,6 +41,7 @@ func (q *Queries) ExamPrepCreateCatalogCourse(ctx context.Context, arg ExamPrepC
|
|||
arg.Description,
|
||||
arg.Category,
|
||||
arg.Thumbnail,
|
||||
arg.PublishStatus,
|
||||
)
|
||||
var i ExamPrepCatalogCourse
|
||||
err := row.Scan(
|
||||
|
|
@ -50,6 +53,7 @@ func (q *Queries) ExamPrepCreateCatalogCourse(ctx context.Context, arg ExamPrepC
|
|||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Category,
|
||||
&i.PublishStatus,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
@ -66,7 +70,7 @@ func (q *Queries) ExamPrepDeleteCatalogCourse(ctx context.Context, id int64) err
|
|||
|
||||
const ExamPrepGetCatalogCourseByID = `-- name: ExamPrepGetCatalogCourseByID :one
|
||||
SELECT
|
||||
c.id, c.name, c.description, c.thumbnail, c.sort_order, c.created_at, c.updated_at, c.category,
|
||||
c.id, c.name, c.description, c.thumbnail, c.sort_order, c.created_at, c.updated_at, c.category, c.publish_status,
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM exam_prep.lesson_practices p
|
||||
|
|
@ -74,21 +78,26 @@ SELECT
|
|||
INNER JOIN exam_prep.unit_modules m ON m.id = l.unit_module_id
|
||||
INNER JOIN exam_prep.units u ON u.id = m.unit_id
|
||||
WHERE u.catalog_course_id = c.id
|
||||
AND p.publish_status = 'PUBLISHED'
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
AND u.publish_status = 'PUBLISHED'
|
||||
) AS has_practice
|
||||
FROM exam_prep.catalog_courses c
|
||||
WHERE c.id = $1
|
||||
`
|
||||
|
||||
type ExamPrepGetCatalogCourseByIDRow struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
Category string `json:"category"`
|
||||
HasPractice bool `json:"has_practice"`
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
Category string `json:"category"`
|
||||
PublishStatus string `json:"publish_status"`
|
||||
HasPractice bool `json:"has_practice"`
|
||||
}
|
||||
|
||||
func (q *Queries) ExamPrepGetCatalogCourseByID(ctx context.Context, id int64) (ExamPrepGetCatalogCourseByIDRow, error) {
|
||||
|
|
@ -103,6 +112,7 @@ func (q *Queries) ExamPrepGetCatalogCourseByID(ctx context.Context, id int64) (E
|
|||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Category,
|
||||
&i.PublishStatus,
|
||||
&i.HasPractice,
|
||||
)
|
||||
return i, err
|
||||
|
|
@ -144,7 +154,10 @@ WITH catalog_course_counts AS (
|
|||
COUNT(DISTINCT l.id)::BIGINT AS lessons_count
|
||||
FROM exam_prep.units u
|
||||
LEFT JOIN exam_prep.unit_modules m ON m.unit_id = u.id
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
LEFT JOIN exam_prep.unit_module_lessons l ON l.unit_module_id = m.id
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
WHERE u.publish_status = 'PUBLISHED'
|
||||
GROUP BY u.catalog_course_id
|
||||
)
|
||||
SELECT
|
||||
|
|
@ -155,6 +168,7 @@ SELECT
|
|||
c.category,
|
||||
c.thumbnail,
|
||||
c.sort_order,
|
||||
c.publish_status,
|
||||
COALESCE(cc.units_count, 0)::BIGINT AS units_count,
|
||||
COALESCE(cc.modules_count, 0)::BIGINT AS modules_count,
|
||||
COALESCE(cc.lessons_count, 0)::BIGINT AS lessons_count,
|
||||
|
|
@ -165,38 +179,48 @@ SELECT
|
|||
INNER JOIN exam_prep.unit_modules m ON m.id = l.unit_module_id
|
||||
INNER JOIN exam_prep.units u ON u.id = m.unit_id
|
||||
WHERE u.catalog_course_id = c.id
|
||||
AND p.publish_status = 'PUBLISHED'
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
AND u.publish_status = 'PUBLISHED'
|
||||
) AS has_practice,
|
||||
c.created_at,
|
||||
c.updated_at
|
||||
FROM exam_prep.catalog_courses c
|
||||
LEFT JOIN catalog_course_counts cc ON cc.catalog_course_id = c.id
|
||||
WHERE (
|
||||
$3::boolean = FALSE
|
||||
OR c.publish_status = 'PUBLISHED'::TEXT
|
||||
)
|
||||
ORDER BY c.sort_order ASC, c.id ASC
|
||||
LIMIT $1 OFFSET $2
|
||||
`
|
||||
|
||||
type ExamPrepListCatalogCoursesParams struct {
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
PublishedOnly bool `json:"published_only"`
|
||||
}
|
||||
|
||||
type ExamPrepListCatalogCoursesRow struct {
|
||||
TotalCount int64 `json:"total_count"`
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Category string `json:"category"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
UnitsCount int64 `json:"units_count"`
|
||||
ModulesCount int64 `json:"modules_count"`
|
||||
LessonsCount int64 `json:"lessons_count"`
|
||||
HasPractice bool `json:"has_practice"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
TotalCount int64 `json:"total_count"`
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Category string `json:"category"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
PublishStatus string `json:"publish_status"`
|
||||
UnitsCount int64 `json:"units_count"`
|
||||
ModulesCount int64 `json:"modules_count"`
|
||||
LessonsCount int64 `json:"lessons_count"`
|
||||
HasPractice bool `json:"has_practice"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (q *Queries) ExamPrepListCatalogCourses(ctx context.Context, arg ExamPrepListCatalogCoursesParams) ([]ExamPrepListCatalogCoursesRow, error) {
|
||||
rows, err := q.db.Query(ctx, ExamPrepListCatalogCourses, arg.Limit, arg.Offset)
|
||||
rows, err := q.db.Query(ctx, ExamPrepListCatalogCourses, arg.Limit, arg.Offset, arg.PublishedOnly)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -212,6 +236,7 @@ func (q *Queries) ExamPrepListCatalogCourses(ctx context.Context, arg ExamPrepLi
|
|||
&i.Category,
|
||||
&i.Thumbnail,
|
||||
&i.SortOrder,
|
||||
&i.PublishStatus,
|
||||
&i.UnitsCount,
|
||||
&i.ModulesCount,
|
||||
&i.LessonsCount,
|
||||
|
|
@ -237,19 +262,21 @@ SET
|
|||
category = coalesce($3::varchar, category),
|
||||
thumbnail = coalesce($4::text, thumbnail),
|
||||
sort_order = coalesce($5::int, sort_order),
|
||||
publish_status = coalesce($6::varchar, publish_status),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $6
|
||||
WHERE id = $7
|
||||
RETURNING
|
||||
id, name, description, thumbnail, sort_order, created_at, updated_at, category
|
||||
id, name, description, thumbnail, sort_order, created_at, updated_at, category, publish_status
|
||||
`
|
||||
|
||||
type ExamPrepUpdateCatalogCourseParams struct {
|
||||
Name pgtype.Text `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Category pgtype.Text `json:"category"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||
ID int64 `json:"id"`
|
||||
Name pgtype.Text `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Category pgtype.Text `json:"category"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||
PublishStatus pgtype.Text `json:"publish_status"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
func (q *Queries) ExamPrepUpdateCatalogCourse(ctx context.Context, arg ExamPrepUpdateCatalogCourseParams) (ExamPrepCatalogCourse, error) {
|
||||
|
|
@ -259,6 +286,7 @@ func (q *Queries) ExamPrepUpdateCatalogCourse(ctx context.Context, arg ExamPrepU
|
|||
arg.Category,
|
||||
arg.Thumbnail,
|
||||
arg.SortOrder,
|
||||
arg.PublishStatus,
|
||||
arg.ID,
|
||||
)
|
||||
var i ExamPrepCatalogCourse
|
||||
|
|
@ -271,6 +299,7 @@ func (q *Queries) ExamPrepUpdateCatalogCourse(ctx context.Context, arg ExamPrepU
|
|||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Category,
|
||||
&i.PublishStatus,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ FROM
|
|||
INNER JOIN question_sets qs ON qs.id = p.question_set_id
|
||||
WHERE
|
||||
u.catalog_course_id = $1
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
AND u.publish_status = 'PUBLISHED'
|
||||
AND qs.set_type = 'PRACTICE'
|
||||
AND qs.status = 'PUBLISHED'
|
||||
AND p.publish_status = 'PUBLISHED'
|
||||
|
|
@ -61,6 +64,7 @@ FROM
|
|||
INNER JOIN question_sets qs ON qs.id = p.question_set_id
|
||||
WHERE
|
||||
l.unit_module_id = $1
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
AND qs.set_type = 'PRACTICE'
|
||||
AND qs.status = 'PUBLISHED'
|
||||
AND p.publish_status = 'PUBLISHED'
|
||||
|
|
@ -83,6 +87,8 @@ FROM
|
|||
INNER JOIN question_sets qs ON qs.id = p.question_set_id
|
||||
WHERE
|
||||
m.unit_id = $1
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
AND qs.set_type = 'PRACTICE'
|
||||
AND qs.status = 'PUBLISHED'
|
||||
AND p.publish_status = 'PUBLISHED'
|
||||
|
|
@ -107,6 +113,9 @@ FROM
|
|||
INNER JOIN user_practice_progress upp ON upp.question_set_id = p.question_set_id
|
||||
WHERE
|
||||
u.catalog_course_id = $1
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
AND u.publish_status = 'PUBLISHED'
|
||||
AND upp.user_id = $2
|
||||
AND upp.completed_at IS NOT NULL
|
||||
AND qs.set_type = 'PRACTICE'
|
||||
|
|
@ -164,6 +173,7 @@ FROM
|
|||
INNER JOIN user_practice_progress upp ON upp.question_set_id = p.question_set_id
|
||||
WHERE
|
||||
l.unit_module_id = $1
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
AND upp.user_id = $2
|
||||
AND upp.completed_at IS NOT NULL
|
||||
AND qs.set_type = 'PRACTICE'
|
||||
|
|
@ -194,6 +204,8 @@ FROM
|
|||
INNER JOIN user_practice_progress upp ON upp.question_set_id = p.question_set_id
|
||||
WHERE
|
||||
m.unit_id = $1
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
AND upp.user_id = $2
|
||||
AND upp.completed_at IS NOT NULL
|
||||
AND qs.set_type = 'PRACTICE'
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import (
|
|||
)
|
||||
|
||||
const ExamPrepCreateUnitModuleLesson = `-- name: ExamPrepCreateUnitModuleLesson :one
|
||||
INSERT INTO exam_prep.unit_module_lessons (unit_module_id, title, video_url, thumbnail, description, sort_order)
|
||||
INSERT INTO exam_prep.unit_module_lessons (unit_module_id, title, video_url, thumbnail, description, sort_order, publish_status)
|
||||
SELECT
|
||||
$1,
|
||||
$2,
|
||||
|
|
@ -24,17 +24,19 @@ SELECT
|
|||
max(l.sort_order)
|
||||
FROM exam_prep.unit_module_lessons l
|
||||
WHERE
|
||||
l.unit_module_id = $1), 0) + 1
|
||||
l.unit_module_id = $1), 0) + 1,
|
||||
$6
|
||||
RETURNING
|
||||
id, unit_module_id, title, video_url, thumbnail, description, sort_order, created_at, updated_at
|
||||
id, unit_module_id, title, video_url, thumbnail, description, sort_order, created_at, updated_at, publish_status
|
||||
`
|
||||
|
||||
type ExamPrepCreateUnitModuleLessonParams struct {
|
||||
UnitModuleID int64 `json:"unit_module_id"`
|
||||
Title string `json:"title"`
|
||||
VideoUrl pgtype.Text `json:"video_url"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
UnitModuleID int64 `json:"unit_module_id"`
|
||||
Title string `json:"title"`
|
||||
VideoUrl pgtype.Text `json:"video_url"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
PublishStatus string `json:"publish_status"`
|
||||
}
|
||||
|
||||
func (q *Queries) ExamPrepCreateUnitModuleLesson(ctx context.Context, arg ExamPrepCreateUnitModuleLessonParams) (ExamPrepUnitModuleLesson, error) {
|
||||
|
|
@ -44,6 +46,7 @@ func (q *Queries) ExamPrepCreateUnitModuleLesson(ctx context.Context, arg ExamPr
|
|||
arg.VideoUrl,
|
||||
arg.Thumbnail,
|
||||
arg.Description,
|
||||
arg.PublishStatus,
|
||||
)
|
||||
var i ExamPrepUnitModuleLesson
|
||||
err := row.Scan(
|
||||
|
|
@ -56,6 +59,7 @@ func (q *Queries) ExamPrepCreateUnitModuleLesson(ctx context.Context, arg ExamPr
|
|||
&i.SortOrder,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.PublishStatus,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
@ -72,27 +76,29 @@ func (q *Queries) ExamPrepDeleteUnitModuleLesson(ctx context.Context, id int64)
|
|||
|
||||
const ExamPrepGetUnitModuleLessonByID = `-- name: ExamPrepGetUnitModuleLessonByID :one
|
||||
SELECT
|
||||
l.id, l.unit_module_id, l.title, l.video_url, l.thumbnail, l.description, l.sort_order, l.created_at, l.updated_at,
|
||||
l.id, l.unit_module_id, l.title, l.video_url, l.thumbnail, l.description, l.sort_order, l.created_at, l.updated_at, l.publish_status,
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM exam_prep.lesson_practices p
|
||||
WHERE p.unit_module_lesson_id = l.id
|
||||
AND p.publish_status = 'PUBLISHED'
|
||||
) AS has_practice
|
||||
FROM exam_prep.unit_module_lessons l
|
||||
WHERE l.id = $1
|
||||
`
|
||||
|
||||
type ExamPrepGetUnitModuleLessonByIDRow struct {
|
||||
ID int64 `json:"id"`
|
||||
UnitModuleID int64 `json:"unit_module_id"`
|
||||
Title string `json:"title"`
|
||||
VideoUrl pgtype.Text `json:"video_url"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
HasPractice bool `json:"has_practice"`
|
||||
ID int64 `json:"id"`
|
||||
UnitModuleID int64 `json:"unit_module_id"`
|
||||
Title string `json:"title"`
|
||||
VideoUrl pgtype.Text `json:"video_url"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
PublishStatus string `json:"publish_status"`
|
||||
HasPractice bool `json:"has_practice"`
|
||||
}
|
||||
|
||||
func (q *Queries) ExamPrepGetUnitModuleLessonByID(ctx context.Context, id int64) (ExamPrepGetUnitModuleLessonByIDRow, error) {
|
||||
|
|
@ -108,11 +114,44 @@ func (q *Queries) ExamPrepGetUnitModuleLessonByID(ctx context.Context, id int64)
|
|||
&i.SortOrder,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.PublishStatus,
|
||||
&i.HasPractice,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const ExamPrepListPublishedUnitModuleLessonIDsByUnitModule = `-- name: ExamPrepListPublishedUnitModuleLessonIDsByUnitModule :many
|
||||
SELECT
|
||||
l.id
|
||||
FROM exam_prep.unit_module_lessons l
|
||||
WHERE
|
||||
l.unit_module_id = $1
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
ORDER BY
|
||||
l.id
|
||||
`
|
||||
|
||||
// Published lessons only, for learner-facing progress rollups.
|
||||
func (q *Queries) ExamPrepListPublishedUnitModuleLessonIDsByUnitModule(ctx context.Context, unitModuleID int64) ([]int64, error) {
|
||||
rows, err := q.db.Query(ctx, ExamPrepListPublishedUnitModuleLessonIDsByUnitModule, unitModuleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []int64
|
||||
for rows.Next() {
|
||||
var id int64
|
||||
if err := rows.Scan(&id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, id)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const ExamPrepListUnitModuleLessonIDsByUnitModule = `-- name: ExamPrepListUnitModuleLessonIDsByUnitModule :many
|
||||
SELECT
|
||||
l.id
|
||||
|
|
@ -153,16 +192,22 @@ SELECT
|
|||
l.thumbnail,
|
||||
l.description,
|
||||
l.sort_order,
|
||||
l.publish_status,
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM exam_prep.lesson_practices p
|
||||
WHERE p.unit_module_lesson_id = l.id
|
||||
AND p.publish_status = 'PUBLISHED'
|
||||
) AS has_practice,
|
||||
l.created_at,
|
||||
l.updated_at
|
||||
FROM exam_prep.unit_module_lessons l
|
||||
WHERE
|
||||
l.unit_module_id = $1
|
||||
AND (
|
||||
$4::boolean = FALSE
|
||||
OR l.publish_status = 'PUBLISHED'::TEXT
|
||||
)
|
||||
ORDER BY
|
||||
l.sort_order ASC,
|
||||
l.id ASC
|
||||
|
|
@ -171,27 +216,34 @@ OFFSET $3
|
|||
`
|
||||
|
||||
type ExamPrepListUnitModuleLessonsByUnitModuleIDParams struct {
|
||||
UnitModuleID int64 `json:"unit_module_id"`
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
UnitModuleID int64 `json:"unit_module_id"`
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
PublishedOnly bool `json:"published_only"`
|
||||
}
|
||||
|
||||
type ExamPrepListUnitModuleLessonsByUnitModuleIDRow struct {
|
||||
TotalCount int64 `json:"total_count"`
|
||||
ID int64 `json:"id"`
|
||||
UnitModuleID int64 `json:"unit_module_id"`
|
||||
Title string `json:"title"`
|
||||
VideoUrl pgtype.Text `json:"video_url"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
HasPractice bool `json:"has_practice"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
TotalCount int64 `json:"total_count"`
|
||||
ID int64 `json:"id"`
|
||||
UnitModuleID int64 `json:"unit_module_id"`
|
||||
Title string `json:"title"`
|
||||
VideoUrl pgtype.Text `json:"video_url"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
PublishStatus string `json:"publish_status"`
|
||||
HasPractice bool `json:"has_practice"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (q *Queries) ExamPrepListUnitModuleLessonsByUnitModuleID(ctx context.Context, arg ExamPrepListUnitModuleLessonsByUnitModuleIDParams) ([]ExamPrepListUnitModuleLessonsByUnitModuleIDRow, error) {
|
||||
rows, err := q.db.Query(ctx, ExamPrepListUnitModuleLessonsByUnitModuleID, arg.UnitModuleID, arg.Limit, arg.Offset)
|
||||
rows, err := q.db.Query(ctx, ExamPrepListUnitModuleLessonsByUnitModuleID,
|
||||
arg.UnitModuleID,
|
||||
arg.Limit,
|
||||
arg.Offset,
|
||||
arg.PublishedOnly,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -208,6 +260,7 @@ func (q *Queries) ExamPrepListUnitModuleLessonsByUnitModuleID(ctx context.Contex
|
|||
&i.Thumbnail,
|
||||
&i.Description,
|
||||
&i.SortOrder,
|
||||
&i.PublishStatus,
|
||||
&i.HasPractice,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
|
|
@ -230,19 +283,21 @@ SET
|
|||
thumbnail = coalesce($3::text, thumbnail),
|
||||
description = coalesce($4::text, description),
|
||||
sort_order = coalesce($5::int, sort_order),
|
||||
publish_status = coalesce($6::varchar, publish_status),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $6
|
||||
WHERE id = $7
|
||||
RETURNING
|
||||
id, unit_module_id, title, video_url, thumbnail, description, sort_order, created_at, updated_at
|
||||
id, unit_module_id, title, video_url, thumbnail, description, sort_order, created_at, updated_at, publish_status
|
||||
`
|
||||
|
||||
type ExamPrepUpdateUnitModuleLessonParams struct {
|
||||
Title pgtype.Text `json:"title"`
|
||||
VideoUrl pgtype.Text `json:"video_url"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||
ID int64 `json:"id"`
|
||||
Title pgtype.Text `json:"title"`
|
||||
VideoUrl pgtype.Text `json:"video_url"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||
PublishStatus pgtype.Text `json:"publish_status"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
func (q *Queries) ExamPrepUpdateUnitModuleLesson(ctx context.Context, arg ExamPrepUpdateUnitModuleLessonParams) (ExamPrepUnitModuleLesson, error) {
|
||||
|
|
@ -252,6 +307,7 @@ func (q *Queries) ExamPrepUpdateUnitModuleLesson(ctx context.Context, arg ExamPr
|
|||
arg.Thumbnail,
|
||||
arg.Description,
|
||||
arg.SortOrder,
|
||||
arg.PublishStatus,
|
||||
arg.ID,
|
||||
)
|
||||
var i ExamPrepUnitModuleLesson
|
||||
|
|
@ -265,6 +321,7 @@ func (q *Queries) ExamPrepUpdateUnitModuleLesson(ctx context.Context, arg ExamPr
|
|||
&i.SortOrder,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.PublishStatus,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import (
|
|||
)
|
||||
|
||||
const ExamPrepCreateUnitModule = `-- name: ExamPrepCreateUnitModule :one
|
||||
INSERT INTO exam_prep.unit_modules (unit_id, name, description, thumbnail, icon, sort_order)
|
||||
INSERT INTO exam_prep.unit_modules (unit_id, name, description, thumbnail, icon, sort_order, publish_status)
|
||||
SELECT
|
||||
$1,
|
||||
$2,
|
||||
|
|
@ -24,17 +24,19 @@ SELECT
|
|||
max(m.sort_order)
|
||||
FROM exam_prep.unit_modules m
|
||||
WHERE
|
||||
m.unit_id = $1), 0) + 1
|
||||
m.unit_id = $1), 0) + 1,
|
||||
$6
|
||||
RETURNING
|
||||
id, unit_id, name, description, thumbnail, icon, sort_order, created_at, updated_at
|
||||
id, unit_id, name, description, thumbnail, icon, sort_order, created_at, updated_at, publish_status
|
||||
`
|
||||
|
||||
type ExamPrepCreateUnitModuleParams struct {
|
||||
UnitID int64 `json:"unit_id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
Icon pgtype.Text `json:"icon"`
|
||||
UnitID int64 `json:"unit_id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
Icon pgtype.Text `json:"icon"`
|
||||
PublishStatus string `json:"publish_status"`
|
||||
}
|
||||
|
||||
func (q *Queries) ExamPrepCreateUnitModule(ctx context.Context, arg ExamPrepCreateUnitModuleParams) (ExamPrepUnitModule, error) {
|
||||
|
|
@ -44,6 +46,7 @@ func (q *Queries) ExamPrepCreateUnitModule(ctx context.Context, arg ExamPrepCrea
|
|||
arg.Description,
|
||||
arg.Thumbnail,
|
||||
arg.Icon,
|
||||
arg.PublishStatus,
|
||||
)
|
||||
var i ExamPrepUnitModule
|
||||
err := row.Scan(
|
||||
|
|
@ -56,6 +59,7 @@ func (q *Queries) ExamPrepCreateUnitModule(ctx context.Context, arg ExamPrepCrea
|
|||
&i.SortOrder,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.PublishStatus,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
@ -72,28 +76,31 @@ func (q *Queries) ExamPrepDeleteUnitModule(ctx context.Context, id int64) error
|
|||
|
||||
const ExamPrepGetUnitModuleByID = `-- name: ExamPrepGetUnitModuleByID :one
|
||||
SELECT
|
||||
m.id, m.unit_id, m.name, m.description, m.thumbnail, m.icon, m.sort_order, m.created_at, m.updated_at,
|
||||
m.id, m.unit_id, m.name, m.description, m.thumbnail, m.icon, m.sort_order, m.created_at, m.updated_at, m.publish_status,
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM exam_prep.lesson_practices p
|
||||
INNER JOIN exam_prep.unit_module_lessons l ON l.id = p.unit_module_lesson_id
|
||||
WHERE l.unit_module_id = m.id
|
||||
AND p.publish_status = 'PUBLISHED'
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
) AS has_practice
|
||||
FROM exam_prep.unit_modules m
|
||||
WHERE m.id = $1
|
||||
`
|
||||
|
||||
type ExamPrepGetUnitModuleByIDRow struct {
|
||||
ID int64 `json:"id"`
|
||||
UnitID int64 `json:"unit_id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
Icon pgtype.Text `json:"icon"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
HasPractice bool `json:"has_practice"`
|
||||
ID int64 `json:"id"`
|
||||
UnitID int64 `json:"unit_id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
Icon pgtype.Text `json:"icon"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
PublishStatus string `json:"publish_status"`
|
||||
HasPractice bool `json:"has_practice"`
|
||||
}
|
||||
|
||||
func (q *Queries) ExamPrepGetUnitModuleByID(ctx context.Context, id int64) (ExamPrepGetUnitModuleByIDRow, error) {
|
||||
|
|
@ -109,11 +116,44 @@ func (q *Queries) ExamPrepGetUnitModuleByID(ctx context.Context, id int64) (Exam
|
|||
&i.SortOrder,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.PublishStatus,
|
||||
&i.HasPractice,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const ExamPrepListPublishedUnitModuleIDsByUnit = `-- name: ExamPrepListPublishedUnitModuleIDsByUnit :many
|
||||
SELECT
|
||||
m.id
|
||||
FROM exam_prep.unit_modules m
|
||||
WHERE
|
||||
m.unit_id = $1
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
ORDER BY
|
||||
m.id
|
||||
`
|
||||
|
||||
// Published modules only, for learner-facing progress rollups.
|
||||
func (q *Queries) ExamPrepListPublishedUnitModuleIDsByUnit(ctx context.Context, unitID int64) ([]int64, error) {
|
||||
rows, err := q.db.Query(ctx, ExamPrepListPublishedUnitModuleIDsByUnit, unitID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []int64
|
||||
for rows.Next() {
|
||||
var id int64
|
||||
if err := rows.Scan(&id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, id)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const ExamPrepListUnitModuleIDsByUnit = `-- name: ExamPrepListUnitModuleIDsByUnit :many
|
||||
SELECT
|
||||
m.id
|
||||
|
|
@ -152,7 +192,9 @@ WITH module_counts AS (
|
|||
COUNT(DISTINCT p.id)::BIGINT AS practices_count
|
||||
FROM exam_prep.unit_modules m
|
||||
LEFT JOIN exam_prep.unit_module_lessons l ON l.unit_module_id = m.id
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
LEFT JOIN exam_prep.lesson_practices p ON p.unit_module_lesson_id = l.id
|
||||
AND p.publish_status = 'PUBLISHED'
|
||||
GROUP BY m.id
|
||||
)
|
||||
SELECT
|
||||
|
|
@ -164,6 +206,7 @@ SELECT
|
|||
m.thumbnail,
|
||||
m.icon,
|
||||
m.sort_order,
|
||||
m.publish_status,
|
||||
COALESCE(mc.lessons_count, 0)::BIGINT AS lessons_count,
|
||||
COALESCE(mc.practices_count, 0)::BIGINT AS practices_count,
|
||||
(COALESCE(mc.practices_count, 0)::BIGINT > 0) AS has_practice,
|
||||
|
|
@ -173,6 +216,10 @@ FROM exam_prep.unit_modules m
|
|||
LEFT JOIN module_counts mc ON mc.module_id = m.id
|
||||
WHERE
|
||||
m.unit_id = $1
|
||||
AND (
|
||||
$4::boolean = FALSE
|
||||
OR m.publish_status = 'PUBLISHED'::TEXT
|
||||
)
|
||||
ORDER BY
|
||||
m.sort_order ASC,
|
||||
m.id ASC
|
||||
|
|
@ -181,9 +228,10 @@ OFFSET $3
|
|||
`
|
||||
|
||||
type ExamPrepListUnitModulesByUnitParams struct {
|
||||
UnitID int64 `json:"unit_id"`
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
UnitID int64 `json:"unit_id"`
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
PublishedOnly bool `json:"published_only"`
|
||||
}
|
||||
|
||||
type ExamPrepListUnitModulesByUnitRow struct {
|
||||
|
|
@ -195,6 +243,7 @@ type ExamPrepListUnitModulesByUnitRow struct {
|
|||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
Icon pgtype.Text `json:"icon"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
PublishStatus string `json:"publish_status"`
|
||||
LessonsCount int64 `json:"lessons_count"`
|
||||
PracticesCount int64 `json:"practices_count"`
|
||||
HasPractice bool `json:"has_practice"`
|
||||
|
|
@ -203,7 +252,12 @@ type ExamPrepListUnitModulesByUnitRow struct {
|
|||
}
|
||||
|
||||
func (q *Queries) ExamPrepListUnitModulesByUnit(ctx context.Context, arg ExamPrepListUnitModulesByUnitParams) ([]ExamPrepListUnitModulesByUnitRow, error) {
|
||||
rows, err := q.db.Query(ctx, ExamPrepListUnitModulesByUnit, arg.UnitID, arg.Limit, arg.Offset)
|
||||
rows, err := q.db.Query(ctx, ExamPrepListUnitModulesByUnit,
|
||||
arg.UnitID,
|
||||
arg.Limit,
|
||||
arg.Offset,
|
||||
arg.PublishedOnly,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -220,6 +274,7 @@ func (q *Queries) ExamPrepListUnitModulesByUnit(ctx context.Context, arg ExamPre
|
|||
&i.Thumbnail,
|
||||
&i.Icon,
|
||||
&i.SortOrder,
|
||||
&i.PublishStatus,
|
||||
&i.LessonsCount,
|
||||
&i.PracticesCount,
|
||||
&i.HasPractice,
|
||||
|
|
@ -244,19 +299,21 @@ SET
|
|||
thumbnail = coalesce($3::text, thumbnail),
|
||||
icon = coalesce($4::text, icon),
|
||||
sort_order = coalesce($5::int, sort_order),
|
||||
publish_status = coalesce($6::varchar, publish_status),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $6
|
||||
WHERE id = $7
|
||||
RETURNING
|
||||
id, unit_id, name, description, thumbnail, icon, sort_order, created_at, updated_at
|
||||
id, unit_id, name, description, thumbnail, icon, sort_order, created_at, updated_at, publish_status
|
||||
`
|
||||
|
||||
type ExamPrepUpdateUnitModuleParams struct {
|
||||
Name pgtype.Text `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
Icon pgtype.Text `json:"icon"`
|
||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||
ID int64 `json:"id"`
|
||||
Name pgtype.Text `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
Icon pgtype.Text `json:"icon"`
|
||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||
PublishStatus pgtype.Text `json:"publish_status"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
func (q *Queries) ExamPrepUpdateUnitModule(ctx context.Context, arg ExamPrepUpdateUnitModuleParams) (ExamPrepUnitModule, error) {
|
||||
|
|
@ -266,6 +323,7 @@ func (q *Queries) ExamPrepUpdateUnitModule(ctx context.Context, arg ExamPrepUpda
|
|||
arg.Thumbnail,
|
||||
arg.Icon,
|
||||
arg.SortOrder,
|
||||
arg.PublishStatus,
|
||||
arg.ID,
|
||||
)
|
||||
var i ExamPrepUnitModule
|
||||
|
|
@ -279,6 +337,7 @@ func (q *Queries) ExamPrepUpdateUnitModule(ctx context.Context, arg ExamPrepUpda
|
|||
&i.SortOrder,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.PublishStatus,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import (
|
|||
)
|
||||
|
||||
const ExamPrepCreateUnit = `-- name: ExamPrepCreateUnit :one
|
||||
INSERT INTO exam_prep.units (catalog_course_id, name, description, thumbnail, sort_order)
|
||||
INSERT INTO exam_prep.units (catalog_course_id, name, description, thumbnail, sort_order, publish_status)
|
||||
SELECT
|
||||
$1,
|
||||
$2,
|
||||
|
|
@ -24,9 +24,10 @@ SELECT
|
|||
max(u.sort_order)
|
||||
FROM exam_prep.units u
|
||||
WHERE
|
||||
u.catalog_course_id = $1), 0) + 1)
|
||||
u.catalog_course_id = $1), 0) + 1),
|
||||
$6
|
||||
RETURNING
|
||||
id, catalog_course_id, name, description, thumbnail, sort_order, created_at, updated_at
|
||||
id, catalog_course_id, name, description, thumbnail, sort_order, created_at, updated_at, publish_status
|
||||
`
|
||||
|
||||
type ExamPrepCreateUnitParams struct {
|
||||
|
|
@ -35,6 +36,7 @@ type ExamPrepCreateUnitParams struct {
|
|||
Description pgtype.Text `json:"description"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||
PublishStatus string `json:"publish_status"`
|
||||
}
|
||||
|
||||
func (q *Queries) ExamPrepCreateUnit(ctx context.Context, arg ExamPrepCreateUnitParams) (ExamPrepUnit, error) {
|
||||
|
|
@ -44,6 +46,7 @@ func (q *Queries) ExamPrepCreateUnit(ctx context.Context, arg ExamPrepCreateUnit
|
|||
arg.Description,
|
||||
arg.Thumbnail,
|
||||
arg.SortOrder,
|
||||
arg.PublishStatus,
|
||||
)
|
||||
var i ExamPrepUnit
|
||||
err := row.Scan(
|
||||
|
|
@ -55,6 +58,7 @@ func (q *Queries) ExamPrepCreateUnit(ctx context.Context, arg ExamPrepCreateUnit
|
|||
&i.SortOrder,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.PublishStatus,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
@ -71,13 +75,16 @@ func (q *Queries) ExamPrepDeleteUnit(ctx context.Context, id int64) error {
|
|||
|
||||
const ExamPrepGetUnitByID = `-- name: ExamPrepGetUnitByID :one
|
||||
SELECT
|
||||
u.id, u.catalog_course_id, u.name, u.description, u.thumbnail, u.sort_order, u.created_at, u.updated_at,
|
||||
u.id, u.catalog_course_id, u.name, u.description, u.thumbnail, u.sort_order, u.created_at, u.updated_at, u.publish_status,
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM exam_prep.lesson_practices p
|
||||
INNER JOIN exam_prep.unit_module_lessons l ON l.id = p.unit_module_lesson_id
|
||||
INNER JOIN exam_prep.unit_modules m ON m.id = l.unit_module_id
|
||||
WHERE m.unit_id = u.id
|
||||
AND p.publish_status = 'PUBLISHED'
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
) AS has_practice
|
||||
FROM exam_prep.units u
|
||||
WHERE u.id = $1
|
||||
|
|
@ -92,6 +99,7 @@ type ExamPrepGetUnitByIDRow struct {
|
|||
SortOrder int32 `json:"sort_order"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
PublishStatus string `json:"publish_status"`
|
||||
HasPractice bool `json:"has_practice"`
|
||||
}
|
||||
|
||||
|
|
@ -107,11 +115,44 @@ func (q *Queries) ExamPrepGetUnitByID(ctx context.Context, id int64) (ExamPrepGe
|
|||
&i.SortOrder,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.PublishStatus,
|
||||
&i.HasPractice,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const ExamPrepListPublishedUnitIDsByCatalogCourse = `-- name: ExamPrepListPublishedUnitIDsByCatalogCourse :many
|
||||
SELECT
|
||||
u.id
|
||||
FROM exam_prep.units u
|
||||
WHERE
|
||||
u.catalog_course_id = $1
|
||||
AND u.publish_status = 'PUBLISHED'
|
||||
ORDER BY
|
||||
u.id
|
||||
`
|
||||
|
||||
// Published units only, for learner-facing progress rollups.
|
||||
func (q *Queries) ExamPrepListPublishedUnitIDsByCatalogCourse(ctx context.Context, catalogCourseID int64) ([]int64, error) {
|
||||
rows, err := q.db.Query(ctx, ExamPrepListPublishedUnitIDsByCatalogCourse, catalogCourseID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []int64
|
||||
for rows.Next() {
|
||||
var id int64
|
||||
if err := rows.Scan(&id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, id)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const ExamPrepListUnitIDsByCatalogCourse = `-- name: ExamPrepListUnitIDsByCatalogCourse :many
|
||||
SELECT
|
||||
u.id
|
||||
|
|
@ -151,8 +192,11 @@ WITH unit_counts AS (
|
|||
COUNT(DISTINCT p.id)::BIGINT AS practices_count
|
||||
FROM exam_prep.units u
|
||||
LEFT JOIN exam_prep.unit_modules m ON m.unit_id = u.id
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
LEFT JOIN exam_prep.unit_module_lessons l ON l.unit_module_id = m.id
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
LEFT JOIN exam_prep.lesson_practices p ON p.unit_module_lesson_id = l.id
|
||||
AND p.publish_status = 'PUBLISHED'
|
||||
GROUP BY u.id
|
||||
)
|
||||
SELECT
|
||||
|
|
@ -163,6 +207,7 @@ SELECT
|
|||
u.description,
|
||||
u.thumbnail,
|
||||
u.sort_order,
|
||||
u.publish_status,
|
||||
COALESCE(uc.modules_count, 0)::BIGINT AS modules_count,
|
||||
COALESCE(uc.lessons_count, 0)::BIGINT AS lessons_count,
|
||||
COALESCE(uc.practices_count, 0)::BIGINT AS practices_count,
|
||||
|
|
@ -173,6 +218,10 @@ FROM exam_prep.units u
|
|||
LEFT JOIN unit_counts uc ON uc.unit_id = u.id
|
||||
WHERE
|
||||
u.catalog_course_id = $1
|
||||
AND (
|
||||
$4::boolean = FALSE
|
||||
OR u.publish_status = 'PUBLISHED'::TEXT
|
||||
)
|
||||
ORDER BY
|
||||
u.sort_order ASC,
|
||||
u.id ASC
|
||||
|
|
@ -184,6 +233,7 @@ type ExamPrepListUnitsByCatalogCourseParams struct {
|
|||
CatalogCourseID int64 `json:"catalog_course_id"`
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
PublishedOnly bool `json:"published_only"`
|
||||
}
|
||||
|
||||
type ExamPrepListUnitsByCatalogCourseRow struct {
|
||||
|
|
@ -194,6 +244,7 @@ type ExamPrepListUnitsByCatalogCourseRow struct {
|
|||
Description pgtype.Text `json:"description"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
PublishStatus string `json:"publish_status"`
|
||||
ModulesCount int64 `json:"modules_count"`
|
||||
LessonsCount int64 `json:"lessons_count"`
|
||||
PracticesCount int64 `json:"practices_count"`
|
||||
|
|
@ -203,7 +254,12 @@ type ExamPrepListUnitsByCatalogCourseRow struct {
|
|||
}
|
||||
|
||||
func (q *Queries) ExamPrepListUnitsByCatalogCourse(ctx context.Context, arg ExamPrepListUnitsByCatalogCourseParams) ([]ExamPrepListUnitsByCatalogCourseRow, error) {
|
||||
rows, err := q.db.Query(ctx, ExamPrepListUnitsByCatalogCourse, arg.CatalogCourseID, arg.Limit, arg.Offset)
|
||||
rows, err := q.db.Query(ctx, ExamPrepListUnitsByCatalogCourse,
|
||||
arg.CatalogCourseID,
|
||||
arg.Limit,
|
||||
arg.Offset,
|
||||
arg.PublishedOnly,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -219,6 +275,7 @@ func (q *Queries) ExamPrepListUnitsByCatalogCourse(ctx context.Context, arg Exam
|
|||
&i.Description,
|
||||
&i.Thumbnail,
|
||||
&i.SortOrder,
|
||||
&i.PublishStatus,
|
||||
&i.ModulesCount,
|
||||
&i.LessonsCount,
|
||||
&i.PracticesCount,
|
||||
|
|
@ -243,18 +300,20 @@ SET
|
|||
description = coalesce($2::text, description),
|
||||
thumbnail = coalesce($3::text, thumbnail),
|
||||
sort_order = coalesce($4::int, sort_order),
|
||||
publish_status = coalesce($5::varchar, publish_status),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $5
|
||||
WHERE id = $6
|
||||
RETURNING
|
||||
id, catalog_course_id, name, description, thumbnail, sort_order, created_at, updated_at
|
||||
id, catalog_course_id, name, description, thumbnail, sort_order, created_at, updated_at, publish_status
|
||||
`
|
||||
|
||||
type ExamPrepUpdateUnitParams struct {
|
||||
Name pgtype.Text `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||
ID int64 `json:"id"`
|
||||
Name pgtype.Text `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||
PublishStatus pgtype.Text `json:"publish_status"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
func (q *Queries) ExamPrepUpdateUnit(ctx context.Context, arg ExamPrepUpdateUnitParams) (ExamPrepUnit, error) {
|
||||
|
|
@ -263,6 +322,7 @@ func (q *Queries) ExamPrepUpdateUnit(ctx context.Context, arg ExamPrepUpdateUnit
|
|||
arg.Description,
|
||||
arg.Thumbnail,
|
||||
arg.SortOrder,
|
||||
arg.PublishStatus,
|
||||
arg.ID,
|
||||
)
|
||||
var i ExamPrepUnit
|
||||
|
|
@ -275,6 +335,7 @@ func (q *Queries) ExamPrepUpdateUnit(ctx context.Context, arg ExamPrepUpdateUnit
|
|||
&i.SortOrder,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.PublishStatus,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import (
|
|||
)
|
||||
|
||||
const CreateCourse = `-- name: CreateCourse :one
|
||||
INSERT INTO courses (program_id, name, description, thumbnail, sort_order)
|
||||
INSERT INTO courses (program_id, name, description, thumbnail, sort_order, publish_status)
|
||||
SELECT
|
||||
$1,
|
||||
$2,
|
||||
|
|
@ -24,17 +24,19 @@ SELECT
|
|||
max(c.sort_order)
|
||||
FROM courses c
|
||||
WHERE
|
||||
c.program_id = $1), 0) + 1)
|
||||
c.program_id = $1), 0) + 1),
|
||||
$6
|
||||
RETURNING
|
||||
id, program_id, name, description, thumbnail, created_at, updated_at, sort_order
|
||||
id, program_id, name, description, thumbnail, created_at, updated_at, sort_order, publish_status
|
||||
`
|
||||
|
||||
type CreateCourseParams struct {
|
||||
ProgramID int64 `json:"program_id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||
ProgramID int64 `json:"program_id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||
PublishStatus string `json:"publish_status"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateCourse(ctx context.Context, arg CreateCourseParams) (Course, error) {
|
||||
|
|
@ -44,6 +46,7 @@ func (q *Queries) CreateCourse(ctx context.Context, arg CreateCourseParams) (Cou
|
|||
arg.Description,
|
||||
arg.Thumbnail,
|
||||
arg.SortOrder,
|
||||
arg.PublishStatus,
|
||||
)
|
||||
var i Course
|
||||
err := row.Scan(
|
||||
|
|
@ -55,6 +58,7 @@ func (q *Queries) CreateCourse(ctx context.Context, arg CreateCourseParams) (Cou
|
|||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.SortOrder,
|
||||
&i.PublishStatus,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
@ -71,7 +75,7 @@ func (q *Queries) DeleteCourse(ctx context.Context, id int64) error {
|
|||
|
||||
const GetCourseByID = `-- name: GetCourseByID :one
|
||||
SELECT
|
||||
c.id, c.program_id, c.name, c.description, c.thumbnail, c.created_at, c.updated_at, c.sort_order,
|
||||
c.id, c.program_id, c.name, c.description, c.thumbnail, c.created_at, c.updated_at, c.sort_order, c.publish_status,
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM lms_practices p
|
||||
|
|
@ -86,15 +90,16 @@ WHERE c.id = $1
|
|||
`
|
||||
|
||||
type GetCourseByIDRow struct {
|
||||
ID int64 `json:"id"`
|
||||
ProgramID int64 `json:"program_id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
HasPractice bool `json:"has_practice"`
|
||||
ID int64 `json:"id"`
|
||||
ProgramID int64 `json:"program_id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
PublishStatus string `json:"publish_status"`
|
||||
HasPractice bool `json:"has_practice"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetCourseByID(ctx context.Context, id int64) (GetCourseByIDRow, error) {
|
||||
|
|
@ -109,6 +114,7 @@ func (q *Queries) GetCourseByID(ctx context.Context, id int64) (GetCourseByIDRow
|
|||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.SortOrder,
|
||||
&i.PublishStatus,
|
||||
&i.HasPractice,
|
||||
)
|
||||
return i, err
|
||||
|
|
@ -154,6 +160,7 @@ SELECT
|
|||
c.description,
|
||||
c.thumbnail,
|
||||
c.sort_order,
|
||||
c.publish_status,
|
||||
c.created_at,
|
||||
c.updated_at,
|
||||
(
|
||||
|
|
@ -162,7 +169,8 @@ SELECT
|
|||
FROM
|
||||
modules m
|
||||
WHERE
|
||||
m.course_id = c.id) AS module_count,
|
||||
m.course_id = c.id
|
||||
AND m.publish_status = 'PUBLISHED') AS module_count,
|
||||
(
|
||||
SELECT
|
||||
COUNT(*)::bigint
|
||||
|
|
@ -196,6 +204,10 @@ FROM
|
|||
courses c
|
||||
WHERE
|
||||
c.program_id = $1
|
||||
AND (
|
||||
$4::boolean = FALSE
|
||||
OR c.publish_status = 'PUBLISHED'::TEXT
|
||||
)
|
||||
ORDER BY
|
||||
c.sort_order ASC,
|
||||
c.id ASC
|
||||
|
|
@ -203,9 +215,10 @@ LIMIT $2 OFFSET $3
|
|||
`
|
||||
|
||||
type ListCoursesByProgramIDParams struct {
|
||||
ProgramID int64 `json:"program_id"`
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
ProgramID int64 `json:"program_id"`
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
PublishedOnly bool `json:"published_only"`
|
||||
}
|
||||
|
||||
type ListCoursesByProgramIDRow struct {
|
||||
|
|
@ -216,6 +229,7 @@ type ListCoursesByProgramIDRow struct {
|
|||
Description pgtype.Text `json:"description"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
PublishStatus string `json:"publish_status"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
ModuleCount int64 `json:"module_count"`
|
||||
|
|
@ -225,7 +239,12 @@ type ListCoursesByProgramIDRow struct {
|
|||
}
|
||||
|
||||
func (q *Queries) ListCoursesByProgramID(ctx context.Context, arg ListCoursesByProgramIDParams) ([]ListCoursesByProgramIDRow, error) {
|
||||
rows, err := q.db.Query(ctx, ListCoursesByProgramID, arg.ProgramID, arg.Limit, arg.Offset)
|
||||
rows, err := q.db.Query(ctx, ListCoursesByProgramID,
|
||||
arg.ProgramID,
|
||||
arg.Limit,
|
||||
arg.Offset,
|
||||
arg.PublishedOnly,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -241,6 +260,7 @@ func (q *Queries) ListCoursesByProgramID(ctx context.Context, arg ListCoursesByP
|
|||
&i.Description,
|
||||
&i.Thumbnail,
|
||||
&i.SortOrder,
|
||||
&i.PublishStatus,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.ModuleCount,
|
||||
|
|
@ -258,6 +278,39 @@ func (q *Queries) ListCoursesByProgramID(ctx context.Context, arg ListCoursesByP
|
|||
return items, nil
|
||||
}
|
||||
|
||||
const ListPublishedCourseIDsByProgram = `-- name: ListPublishedCourseIDsByProgram :many
|
||||
SELECT
|
||||
c.id
|
||||
FROM
|
||||
courses AS c
|
||||
WHERE
|
||||
c.program_id = $1
|
||||
AND c.publish_status = 'PUBLISHED'
|
||||
ORDER BY
|
||||
c.id
|
||||
`
|
||||
|
||||
// Published courses only, for learner-facing progress rollups.
|
||||
func (q *Queries) ListPublishedCourseIDsByProgram(ctx context.Context, programID int64) ([]int64, error) {
|
||||
rows, err := q.db.Query(ctx, ListPublishedCourseIDsByProgram, programID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []int64
|
||||
for rows.Next() {
|
||||
var id int64
|
||||
if err := rows.Scan(&id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, id)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const UpdateCourse = `-- name: UpdateCourse :one
|
||||
UPDATE courses
|
||||
SET
|
||||
|
|
@ -265,19 +318,21 @@ SET
|
|||
description = COALESCE($2::text, description),
|
||||
thumbnail = COALESCE($3::text, thumbnail),
|
||||
sort_order = coalesce($4::int, sort_order),
|
||||
publish_status = COALESCE($5::varchar, publish_status),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE
|
||||
id = $5
|
||||
id = $6
|
||||
RETURNING
|
||||
id, program_id, name, description, thumbnail, created_at, updated_at, sort_order
|
||||
id, program_id, name, description, thumbnail, created_at, updated_at, sort_order, publish_status
|
||||
`
|
||||
|
||||
type UpdateCourseParams struct {
|
||||
Name pgtype.Text `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||
ID int64 `json:"id"`
|
||||
Name pgtype.Text `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||
PublishStatus pgtype.Text `json:"publish_status"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateCourse(ctx context.Context, arg UpdateCourseParams) (Course, error) {
|
||||
|
|
@ -286,6 +341,7 @@ func (q *Queries) UpdateCourse(ctx context.Context, arg UpdateCourseParams) (Cou
|
|||
arg.Description,
|
||||
arg.Thumbnail,
|
||||
arg.SortOrder,
|
||||
arg.PublishStatus,
|
||||
arg.ID,
|
||||
)
|
||||
var i Course
|
||||
|
|
@ -298,6 +354,7 @@ func (q *Queries) UpdateCourse(ctx context.Context, arg UpdateCourseParams) (Cou
|
|||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.SortOrder,
|
||||
&i.PublishStatus,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import (
|
|||
)
|
||||
|
||||
const CreateModule = `-- name: CreateModule :one
|
||||
INSERT INTO modules (program_id, course_id, name, description, icon, sort_order)
|
||||
INSERT INTO modules (program_id, course_id, name, description, icon, sort_order, publish_status)
|
||||
SELECT
|
||||
$1,
|
||||
$2,
|
||||
|
|
@ -25,18 +25,20 @@ SELECT
|
|||
max(m.sort_order)
|
||||
FROM modules m
|
||||
WHERE
|
||||
m.course_id = $2), 0) + 1)
|
||||
m.course_id = $2), 0) + 1),
|
||||
$7
|
||||
RETURNING
|
||||
id, program_id, course_id, name, description, icon, created_at, updated_at, sort_order
|
||||
id, program_id, course_id, name, description, icon, created_at, updated_at, sort_order, publish_status
|
||||
`
|
||||
|
||||
type CreateModuleParams struct {
|
||||
ProgramID int64 `json:"program_id"`
|
||||
CourseID int64 `json:"course_id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Icon pgtype.Text `json:"icon"`
|
||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||
ProgramID int64 `json:"program_id"`
|
||||
CourseID int64 `json:"course_id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Icon pgtype.Text `json:"icon"`
|
||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||
PublishStatus string `json:"publish_status"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateModule(ctx context.Context, arg CreateModuleParams) (Module, error) {
|
||||
|
|
@ -47,6 +49,7 @@ func (q *Queries) CreateModule(ctx context.Context, arg CreateModuleParams) (Mod
|
|||
arg.Description,
|
||||
arg.Icon,
|
||||
arg.SortOrder,
|
||||
arg.PublishStatus,
|
||||
)
|
||||
var i Module
|
||||
err := row.Scan(
|
||||
|
|
@ -59,6 +62,7 @@ func (q *Queries) CreateModule(ctx context.Context, arg CreateModuleParams) (Mod
|
|||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.SortOrder,
|
||||
&i.PublishStatus,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
@ -75,7 +79,7 @@ func (q *Queries) DeleteModule(ctx context.Context, id int64) error {
|
|||
|
||||
const GetModuleByID = `-- name: GetModuleByID :one
|
||||
SELECT
|
||||
m.id, m.program_id, m.course_id, m.name, m.description, m.icon, m.created_at, m.updated_at, m.sort_order,
|
||||
m.id, m.program_id, m.course_id, m.name, m.description, m.icon, m.created_at, m.updated_at, m.sort_order, m.publish_status,
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM lms_practices p
|
||||
|
|
@ -89,16 +93,17 @@ WHERE m.id = $1
|
|||
`
|
||||
|
||||
type GetModuleByIDRow struct {
|
||||
ID int64 `json:"id"`
|
||||
ProgramID int64 `json:"program_id"`
|
||||
CourseID int64 `json:"course_id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Icon pgtype.Text `json:"icon"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
HasPractice bool `json:"has_practice"`
|
||||
ID int64 `json:"id"`
|
||||
ProgramID int64 `json:"program_id"`
|
||||
CourseID int64 `json:"course_id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Icon pgtype.Text `json:"icon"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
PublishStatus string `json:"publish_status"`
|
||||
HasPractice bool `json:"has_practice"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetModuleByID(ctx context.Context, id int64) (GetModuleByIDRow, error) {
|
||||
|
|
@ -114,6 +119,7 @@ func (q *Queries) GetModuleByID(ctx context.Context, id int64) (GetModuleByIDRow
|
|||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.SortOrder,
|
||||
&i.PublishStatus,
|
||||
&i.HasPractice,
|
||||
)
|
||||
return i, err
|
||||
|
|
@ -160,6 +166,7 @@ SELECT
|
|||
m.description,
|
||||
m.icon,
|
||||
m.sort_order,
|
||||
m.publish_status,
|
||||
m.created_at,
|
||||
m.updated_at,
|
||||
EXISTS (
|
||||
|
|
@ -174,6 +181,10 @@ FROM
|
|||
WHERE
|
||||
m.program_id = $1
|
||||
AND m.course_id = $2
|
||||
AND (
|
||||
$5::boolean = FALSE
|
||||
OR m.publish_status = 'PUBLISHED'::TEXT
|
||||
)
|
||||
ORDER BY
|
||||
m.sort_order ASC,
|
||||
m.id ASC
|
||||
|
|
@ -182,24 +193,26 @@ OFFSET $4
|
|||
`
|
||||
|
||||
type ListModulesByProgramAndCourseParams struct {
|
||||
ProgramID int64 `json:"program_id"`
|
||||
CourseID int64 `json:"course_id"`
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
ProgramID int64 `json:"program_id"`
|
||||
CourseID int64 `json:"course_id"`
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
PublishedOnly bool `json:"published_only"`
|
||||
}
|
||||
|
||||
type ListModulesByProgramAndCourseRow struct {
|
||||
TotalCount int64 `json:"total_count"`
|
||||
ID int64 `json:"id"`
|
||||
ProgramID int64 `json:"program_id"`
|
||||
CourseID int64 `json:"course_id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Icon pgtype.Text `json:"icon"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
HasPractice bool `json:"has_practice"`
|
||||
TotalCount int64 `json:"total_count"`
|
||||
ID int64 `json:"id"`
|
||||
ProgramID int64 `json:"program_id"`
|
||||
CourseID int64 `json:"course_id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Icon pgtype.Text `json:"icon"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
PublishStatus string `json:"publish_status"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
HasPractice bool `json:"has_practice"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListModulesByProgramAndCourse(ctx context.Context, arg ListModulesByProgramAndCourseParams) ([]ListModulesByProgramAndCourseRow, error) {
|
||||
|
|
@ -208,6 +221,7 @@ func (q *Queries) ListModulesByProgramAndCourse(ctx context.Context, arg ListMod
|
|||
arg.CourseID,
|
||||
arg.Limit,
|
||||
arg.Offset,
|
||||
arg.PublishedOnly,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -225,6 +239,7 @@ func (q *Queries) ListModulesByProgramAndCourse(ctx context.Context, arg ListMod
|
|||
&i.Description,
|
||||
&i.Icon,
|
||||
&i.SortOrder,
|
||||
&i.PublishStatus,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.HasPractice,
|
||||
|
|
@ -239,6 +254,39 @@ func (q *Queries) ListModulesByProgramAndCourse(ctx context.Context, arg ListMod
|
|||
return items, nil
|
||||
}
|
||||
|
||||
const ListPublishedModuleIDsByCourse = `-- name: ListPublishedModuleIDsByCourse :many
|
||||
SELECT
|
||||
m.id
|
||||
FROM
|
||||
modules AS m
|
||||
WHERE
|
||||
m.course_id = $1
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
ORDER BY
|
||||
m.id
|
||||
`
|
||||
|
||||
// Published modules only, for learner-facing progress rollups.
|
||||
func (q *Queries) ListPublishedModuleIDsByCourse(ctx context.Context, courseID int64) ([]int64, error) {
|
||||
rows, err := q.db.Query(ctx, ListPublishedModuleIDsByCourse, courseID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []int64
|
||||
for rows.Next() {
|
||||
var id int64
|
||||
if err := rows.Scan(&id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, id)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const UpdateModule = `-- name: UpdateModule :one
|
||||
UPDATE modules
|
||||
SET
|
||||
|
|
@ -246,19 +294,21 @@ SET
|
|||
description = COALESCE($2::text, description),
|
||||
icon = COALESCE($3::text, icon),
|
||||
sort_order = coalesce($4::int, sort_order),
|
||||
publish_status = COALESCE($5::varchar, publish_status),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE
|
||||
id = $5
|
||||
id = $6
|
||||
RETURNING
|
||||
id, program_id, course_id, name, description, icon, created_at, updated_at, sort_order
|
||||
id, program_id, course_id, name, description, icon, created_at, updated_at, sort_order, publish_status
|
||||
`
|
||||
|
||||
type UpdateModuleParams struct {
|
||||
Name pgtype.Text `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Icon pgtype.Text `json:"icon"`
|
||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||
ID int64 `json:"id"`
|
||||
Name pgtype.Text `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Icon pgtype.Text `json:"icon"`
|
||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||
PublishStatus pgtype.Text `json:"publish_status"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateModule(ctx context.Context, arg UpdateModuleParams) (Module, error) {
|
||||
|
|
@ -267,6 +317,7 @@ func (q *Queries) UpdateModule(ctx context.Context, arg UpdateModuleParams) (Mod
|
|||
arg.Description,
|
||||
arg.Icon,
|
||||
arg.SortOrder,
|
||||
arg.PublishStatus,
|
||||
arg.ID,
|
||||
)
|
||||
var i Module
|
||||
|
|
@ -280,6 +331,7 @@ func (q *Queries) UpdateModule(ctx context.Context, arg UpdateModuleParams) (Mod
|
|||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.SortOrder,
|
||||
&i.PublishStatus,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ FROM
|
|||
courses
|
||||
WHERE
|
||||
program_id = $1
|
||||
AND publish_status = 'PUBLISHED'
|
||||
`
|
||||
|
||||
func (q *Queries) CountCoursesInProgram(ctx context.Context, programID int64) (int32, error) {
|
||||
|
|
@ -90,6 +91,7 @@ FROM
|
|||
modules
|
||||
WHERE
|
||||
course_id = $1
|
||||
AND publish_status = 'PUBLISHED'
|
||||
`
|
||||
|
||||
func (q *Queries) CountModulesInCourse(ctx context.Context, courseID int64) (int32, error) {
|
||||
|
|
@ -156,7 +158,8 @@ WHERE
|
|||
FROM
|
||||
modules
|
||||
WHERE
|
||||
course_id = $1)
|
||||
course_id = $1
|
||||
AND publish_status = 'PUBLISHED')
|
||||
OR lp.lesson_id IN (
|
||||
SELECT
|
||||
l.id
|
||||
|
|
@ -164,7 +167,9 @@ WHERE
|
|||
lessons l
|
||||
INNER JOIN modules m ON m.id = l.module_id
|
||||
WHERE
|
||||
m.course_id = $1))
|
||||
m.course_id = $1
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
AND m.publish_status = 'PUBLISHED'))
|
||||
AND qs.set_type = 'PRACTICE'
|
||||
AND qs.status = 'PUBLISHED'
|
||||
AND lp.publish_status = 'PUBLISHED'
|
||||
|
|
@ -212,13 +217,14 @@ WHERE
|
|||
FROM
|
||||
lessons
|
||||
WHERE
|
||||
module_id = $1))
|
||||
module_id = $1
|
||||
AND publish_status = 'PUBLISHED'))
|
||||
AND qs.set_type = 'PRACTICE'
|
||||
AND qs.status = 'PUBLISHED'
|
||||
AND lp.publish_status = 'PUBLISHED'
|
||||
`
|
||||
|
||||
// Published practices in a module (direct module practices and practices on lessons in the module).
|
||||
// Published practices in a module (direct module practices and practices on published lessons in the module).
|
||||
func (q *Queries) CountPublishedPracticesInModule(ctx context.Context, moduleID pgtype.Int8) (int32, error) {
|
||||
row := q.db.QueryRow(ctx, CountPublishedPracticesInModule, moduleID)
|
||||
var n int32
|
||||
|
|
@ -240,7 +246,8 @@ WHERE
|
|||
FROM
|
||||
courses c
|
||||
WHERE
|
||||
c.program_id = $1)
|
||||
c.program_id = $1
|
||||
AND c.publish_status = 'PUBLISHED')
|
||||
OR lp.module_id IN (
|
||||
SELECT
|
||||
m.id
|
||||
|
|
@ -248,7 +255,9 @@ WHERE
|
|||
modules m
|
||||
INNER JOIN courses c ON c.id = m.course_id
|
||||
WHERE
|
||||
c.program_id = $1)
|
||||
c.program_id = $1
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
AND c.publish_status = 'PUBLISHED')
|
||||
OR lp.lesson_id IN (
|
||||
SELECT
|
||||
l.id
|
||||
|
|
@ -257,7 +266,10 @@ WHERE
|
|||
INNER JOIN modules m ON m.id = l.module_id
|
||||
INNER JOIN courses c ON c.id = m.course_id
|
||||
WHERE
|
||||
c.program_id = $1))
|
||||
c.program_id = $1
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
AND c.publish_status = 'PUBLISHED'))
|
||||
AND qs.set_type = 'PRACTICE'
|
||||
AND qs.status = 'PUBLISHED'
|
||||
AND lp.publish_status = 'PUBLISHED'
|
||||
|
|
@ -279,6 +291,7 @@ FROM
|
|||
WHERE
|
||||
c.program_id = $1
|
||||
AND ucp.user_id = $2
|
||||
AND c.publish_status = 'PUBLISHED'
|
||||
`
|
||||
|
||||
type CountUserCompletedCoursesInProgramParams struct {
|
||||
|
|
@ -377,6 +390,7 @@ FROM
|
|||
WHERE
|
||||
m.course_id = $1
|
||||
AND ump.user_id = $2
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
`
|
||||
|
||||
type CountUserCompletedModulesInCourseParams struct {
|
||||
|
|
@ -463,7 +477,8 @@ WHERE
|
|||
FROM
|
||||
modules
|
||||
WHERE
|
||||
course_id = $1)
|
||||
course_id = $1
|
||||
AND publish_status = 'PUBLISHED')
|
||||
OR lp.lesson_id IN (
|
||||
SELECT
|
||||
l.id
|
||||
|
|
@ -471,7 +486,9 @@ WHERE
|
|||
lessons l
|
||||
INNER JOIN modules m ON m.id = l.module_id
|
||||
WHERE
|
||||
m.course_id = $1))
|
||||
m.course_id = $1
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
AND m.publish_status = 'PUBLISHED'))
|
||||
AND upp.user_id = $2
|
||||
AND upp.completed_at IS NOT NULL
|
||||
AND qs.set_type = 'PRACTICE'
|
||||
|
|
@ -535,7 +552,8 @@ WHERE
|
|||
FROM
|
||||
lessons
|
||||
WHERE
|
||||
module_id = $1))
|
||||
module_id = $1
|
||||
AND publish_status = 'PUBLISHED'))
|
||||
AND upp.user_id = $2
|
||||
AND upp.completed_at IS NOT NULL
|
||||
AND qs.set_type = 'PRACTICE'
|
||||
|
|
@ -570,7 +588,8 @@ WHERE
|
|||
FROM
|
||||
courses c
|
||||
WHERE
|
||||
c.program_id = $1)
|
||||
c.program_id = $1
|
||||
AND c.publish_status = 'PUBLISHED')
|
||||
OR lp.module_id IN (
|
||||
SELECT
|
||||
m.id
|
||||
|
|
@ -578,7 +597,9 @@ WHERE
|
|||
modules m
|
||||
INNER JOIN courses c ON c.id = m.course_id
|
||||
WHERE
|
||||
c.program_id = $1)
|
||||
c.program_id = $1
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
AND c.publish_status = 'PUBLISHED')
|
||||
OR lp.lesson_id IN (
|
||||
SELECT
|
||||
l.id
|
||||
|
|
@ -587,7 +608,10 @@ WHERE
|
|||
INNER JOIN modules m ON m.id = l.module_id
|
||||
INNER JOIN courses c ON c.id = m.course_id
|
||||
WHERE
|
||||
c.program_id = $1))
|
||||
c.program_id = $1
|
||||
AND l.publish_status = 'PUBLISHED'
|
||||
AND m.publish_status = 'PUBLISHED'
|
||||
AND c.publish_status = 'PUBLISHED'))
|
||||
AND upp.user_id = $2
|
||||
AND upp.completed_at IS NOT NULL
|
||||
AND qs.set_type = 'PRACTICE'
|
||||
|
|
@ -643,10 +667,11 @@ func (q *Queries) GetPracticeScopeByQuestionSetID(ctx context.Context, questionS
|
|||
|
||||
const GetPreviousCourseInProgram = `-- name: GetPreviousCourseInProgram :one
|
||||
SELECT
|
||||
c2.id, c2.program_id, c2.name, c2.description, c2.thumbnail, c2.created_at, c2.updated_at, c2.sort_order
|
||||
c2.id, c2.program_id, c2.name, c2.description, c2.thumbnail, c2.created_at, c2.updated_at, c2.sort_order, c2.publish_status
|
||||
FROM
|
||||
courses AS c1
|
||||
INNER JOIN courses AS c2 ON c2.program_id = c1.program_id
|
||||
AND c2.publish_status = 'PUBLISHED'
|
||||
AND (
|
||||
c2.sort_order < c1.sort_order
|
||||
OR (
|
||||
|
|
@ -674,6 +699,7 @@ func (q *Queries) GetPreviousCourseInProgram(ctx context.Context, id int64) (Cou
|
|||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.SortOrder,
|
||||
&i.PublishStatus,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
@ -721,10 +747,11 @@ func (q *Queries) GetPreviousLessonInModule(ctx context.Context, id int64) (Less
|
|||
|
||||
const GetPreviousModuleInCourse = `-- name: GetPreviousModuleInCourse :one
|
||||
SELECT
|
||||
m2.id, m2.program_id, m2.course_id, m2.name, m2.description, m2.icon, m2.created_at, m2.updated_at, m2.sort_order
|
||||
m2.id, m2.program_id, m2.course_id, m2.name, m2.description, m2.icon, m2.created_at, m2.updated_at, m2.sort_order, m2.publish_status
|
||||
FROM
|
||||
modules AS m1
|
||||
INNER JOIN modules AS m2 ON m2.course_id = m1.course_id
|
||||
AND m2.publish_status = 'PUBLISHED'
|
||||
AND (
|
||||
m2.sort_order < m1.sort_order
|
||||
OR (
|
||||
|
|
@ -753,16 +780,18 @@ func (q *Queries) GetPreviousModuleInCourse(ctx context.Context, id int64) (Modu
|
|||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.SortOrder,
|
||||
&i.PublishStatus,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const GetPreviousProgram = `-- name: GetPreviousProgram :one
|
||||
SELECT
|
||||
p2.id, p2.name, p2.description, p2.thumbnail, p2.created_at, p2.updated_at, p2.sort_order, p2.category
|
||||
p2.id, p2.name, p2.description, p2.thumbnail, p2.created_at, p2.updated_at, p2.sort_order, p2.category, p2.publish_status
|
||||
FROM
|
||||
programs AS p1
|
||||
INNER JOIN programs AS p2 ON p2.category = p1.category
|
||||
AND p2.publish_status = 'PUBLISHED'
|
||||
AND (
|
||||
p2.sort_order < p1.sort_order
|
||||
OR (
|
||||
|
|
@ -791,6 +820,7 @@ func (q *Queries) GetPreviousProgram(ctx context.Context, id int64) (Program, er
|
|||
&i.UpdatedAt,
|
||||
&i.SortOrder,
|
||||
&i.Category,
|
||||
&i.PublishStatus,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
|
|||
109
gen/db/models.go
109
gen/db/models.go
|
|
@ -23,14 +23,15 @@ type ActivityLog struct {
|
|||
}
|
||||
|
||||
type Course struct {
|
||||
ID int64 `json:"id"`
|
||||
ProgramID int64 `json:"program_id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
ID int64 `json:"id"`
|
||||
ProgramID int64 `json:"program_id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
PublishStatus string `json:"publish_status"`
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
|
|
@ -58,14 +59,15 @@ type EmailTemplate struct {
|
|||
}
|
||||
|
||||
type ExamPrepCatalogCourse struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
Category string `json:"category"`
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
Category string `json:"category"`
|
||||
PublishStatus string `json:"publish_status"`
|
||||
}
|
||||
|
||||
type ExamPrepLessonPractice struct {
|
||||
|
|
@ -91,30 +93,33 @@ type ExamPrepUnit struct {
|
|||
SortOrder int32 `json:"sort_order"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
PublishStatus string `json:"publish_status"`
|
||||
}
|
||||
|
||||
type ExamPrepUnitModule struct {
|
||||
ID int64 `json:"id"`
|
||||
UnitID int64 `json:"unit_id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
Icon pgtype.Text `json:"icon"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
ID int64 `json:"id"`
|
||||
UnitID int64 `json:"unit_id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
Icon pgtype.Text `json:"icon"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
PublishStatus string `json:"publish_status"`
|
||||
}
|
||||
|
||||
type ExamPrepUnitModuleLesson struct {
|
||||
ID int64 `json:"id"`
|
||||
UnitModuleID int64 `json:"unit_module_id"`
|
||||
Title string `json:"title"`
|
||||
VideoUrl pgtype.Text `json:"video_url"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
ID int64 `json:"id"`
|
||||
UnitModuleID int64 `json:"unit_module_id"`
|
||||
Title string `json:"title"`
|
||||
VideoUrl pgtype.Text `json:"video_url"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
PublishStatus string `json:"publish_status"`
|
||||
}
|
||||
|
||||
type Faq struct {
|
||||
|
|
@ -230,15 +235,16 @@ type MobileAppVersion struct {
|
|||
}
|
||||
|
||||
type Module struct {
|
||||
ID int64 `json:"id"`
|
||||
ProgramID int64 `json:"program_id"`
|
||||
CourseID int64 `json:"course_id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Icon pgtype.Text `json:"icon"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
ID int64 `json:"id"`
|
||||
ProgramID int64 `json:"program_id"`
|
||||
CourseID int64 `json:"course_id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Icon pgtype.Text `json:"icon"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
PublishStatus string `json:"publish_status"`
|
||||
}
|
||||
|
||||
type ModuleToSubCourse struct {
|
||||
|
|
@ -303,14 +309,15 @@ type Permission struct {
|
|||
}
|
||||
|
||||
type Program struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
Category string `json:"category"`
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
Category string `json:"category"`
|
||||
PublishStatus string `json:"publish_status"`
|
||||
}
|
||||
|
||||
type Question struct {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import (
|
|||
)
|
||||
|
||||
const CreateProgram = `-- name: CreateProgram :one
|
||||
INSERT INTO programs (name, description, category, thumbnail, sort_order)
|
||||
INSERT INTO programs (name, description, category, thumbnail, sort_order, publish_status)
|
||||
SELECT
|
||||
$1,
|
||||
$2,
|
||||
|
|
@ -21,17 +21,19 @@ SELECT
|
|||
COALESCE($5::int, COALESCE((
|
||||
SELECT
|
||||
max(p.sort_order)
|
||||
FROM programs AS p), 0) + 1)
|
||||
FROM programs AS p), 0) + 1),
|
||||
$6
|
||||
RETURNING
|
||||
id, name, description, thumbnail, created_at, updated_at, sort_order, category
|
||||
id, name, description, thumbnail, created_at, updated_at, sort_order, category, publish_status
|
||||
`
|
||||
|
||||
type CreateProgramParams struct {
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Category string `json:"category"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Category string `json:"category"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||
PublishStatus string `json:"publish_status"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateProgram(ctx context.Context, arg CreateProgramParams) (Program, error) {
|
||||
|
|
@ -41,6 +43,7 @@ func (q *Queries) CreateProgram(ctx context.Context, arg CreateProgramParams) (P
|
|||
arg.Category,
|
||||
arg.Thumbnail,
|
||||
arg.SortOrder,
|
||||
arg.PublishStatus,
|
||||
)
|
||||
var i Program
|
||||
err := row.Scan(
|
||||
|
|
@ -52,6 +55,7 @@ func (q *Queries) CreateProgram(ctx context.Context, arg CreateProgramParams) (P
|
|||
&i.UpdatedAt,
|
||||
&i.SortOrder,
|
||||
&i.Category,
|
||||
&i.PublishStatus,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
@ -67,7 +71,7 @@ func (q *Queries) DeleteProgram(ctx context.Context, id int64) error {
|
|||
}
|
||||
|
||||
const GetProgramByID = `-- name: GetProgramByID :one
|
||||
SELECT id, name, description, thumbnail, created_at, updated_at, sort_order, category
|
||||
SELECT id, name, description, thumbnail, created_at, updated_at, sort_order, category, publish_status
|
||||
FROM programs
|
||||
WHERE id = $1
|
||||
`
|
||||
|
|
@ -84,6 +88,7 @@ func (q *Queries) GetProgramByID(ctx context.Context, id int64) (Program, error)
|
|||
&i.UpdatedAt,
|
||||
&i.SortOrder,
|
||||
&i.Category,
|
||||
&i.PublishStatus,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
@ -126,32 +131,39 @@ SELECT
|
|||
p.category,
|
||||
p.thumbnail,
|
||||
p.sort_order,
|
||||
p.publish_status,
|
||||
p.created_at,
|
||||
p.updated_at
|
||||
FROM programs p
|
||||
WHERE (
|
||||
$3::boolean = FALSE
|
||||
OR p.publish_status = 'PUBLISHED'::TEXT
|
||||
)
|
||||
ORDER BY p.sort_order ASC, p.id ASC
|
||||
LIMIT $1 OFFSET $2
|
||||
`
|
||||
|
||||
type ListProgramsParams struct {
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
PublishedOnly bool `json:"published_only"`
|
||||
}
|
||||
|
||||
type ListProgramsRow struct {
|
||||
TotalCount int64 `json:"total_count"`
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Category string `json:"category"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
TotalCount int64 `json:"total_count"`
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Category string `json:"category"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
PublishStatus string `json:"publish_status"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListPrograms(ctx context.Context, arg ListProgramsParams) ([]ListProgramsRow, error) {
|
||||
rows, err := q.db.Query(ctx, ListPrograms, arg.Limit, arg.Offset)
|
||||
rows, err := q.db.Query(ctx, ListPrograms, arg.Limit, arg.Offset, arg.PublishedOnly)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -167,6 +179,7 @@ func (q *Queries) ListPrograms(ctx context.Context, arg ListProgramsParams) ([]L
|
|||
&i.Category,
|
||||
&i.Thumbnail,
|
||||
&i.SortOrder,
|
||||
&i.PublishStatus,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
|
|
@ -188,20 +201,22 @@ SET
|
|||
category = COALESCE($3::varchar, category),
|
||||
thumbnail = COALESCE($4::text, thumbnail),
|
||||
sort_order = coalesce($5::int, sort_order),
|
||||
publish_status = COALESCE($6::varchar, publish_status),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE
|
||||
id = $6
|
||||
id = $7
|
||||
RETURNING
|
||||
id, name, description, thumbnail, created_at, updated_at, sort_order, category
|
||||
id, name, description, thumbnail, created_at, updated_at, sort_order, category, publish_status
|
||||
`
|
||||
|
||||
type UpdateProgramParams struct {
|
||||
Name pgtype.Text `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Category pgtype.Text `json:"category"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||
ID int64 `json:"id"`
|
||||
Name pgtype.Text `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Category pgtype.Text `json:"category"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
SortOrder pgtype.Int4 `json:"sort_order"`
|
||||
PublishStatus pgtype.Text `json:"publish_status"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateProgram(ctx context.Context, arg UpdateProgramParams) (Program, error) {
|
||||
|
|
@ -211,6 +226,7 @@ func (q *Queries) UpdateProgram(ctx context.Context, arg UpdateProgramParams) (P
|
|||
arg.Category,
|
||||
arg.Thumbnail,
|
||||
arg.SortOrder,
|
||||
arg.PublishStatus,
|
||||
arg.ID,
|
||||
)
|
||||
var i Program
|
||||
|
|
@ -223,6 +239,7 @@ func (q *Queries) UpdateProgram(ctx context.Context, arg UpdateProgramParams) (P
|
|||
&i.UpdatedAt,
|
||||
&i.SortOrder,
|
||||
&i.Category,
|
||||
&i.PublishStatus,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
|
|||
31
internal/domain/content_publish_status.go
Normal file
31
internal/domain/content_publish_status.go
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package domain
|
||||
|
||||
import "strings"
|
||||
|
||||
// ContentPublishStatus controls learner visibility for LMS hierarchy entities (programs,
|
||||
// courses, modules) and exam-prep hierarchy entities (catalog courses, units, unit modules,
|
||||
// unit module lessons). Mirrors LessonPublishStatus / PracticePublishStatus.
|
||||
type ContentPublishStatus string
|
||||
|
||||
const (
|
||||
ContentPublishDraft ContentPublishStatus = "DRAFT"
|
||||
ContentPublishPublished ContentPublishStatus = "PUBLISHED"
|
||||
)
|
||||
|
||||
// ContentPublishStatusFromDB normalizes persisted values.
|
||||
func ContentPublishStatusFromDB(raw string) ContentPublishStatus {
|
||||
switch strings.TrimSpace(strings.ToUpper(raw)) {
|
||||
case string(ContentPublishPublished):
|
||||
return ContentPublishPublished
|
||||
default:
|
||||
return ContentPublishDraft
|
||||
}
|
||||
}
|
||||
|
||||
// ContentPublishStatusFromCreateInput resolves create body: omit → draft; explicit value validated separately.
|
||||
func ContentPublishStatusFromCreateInput(raw *string) ContentPublishStatus {
|
||||
if raw == nil || strings.TrimSpace(*raw) == "" {
|
||||
return ContentPublishDraft
|
||||
}
|
||||
return ContentPublishStatusFromDB(*raw)
|
||||
}
|
||||
|
|
@ -15,14 +15,15 @@ var DefaultCEFRCourseNames = []string{"A1", "A2", "B1", "B2", "C1", "C2"}
|
|||
|
||||
// Course belongs to a Program.
|
||||
type Course struct {
|
||||
ID int64 `json:"id"`
|
||||
ProgramID int64 `json:"program_id"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||
ID int64 `json:"id"`
|
||||
ProgramID int64 `json:"program_id"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
PublishStatus ContentPublishStatus `json:"publish_status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||
// Populated on list-by-program. Practice count: lms_practices rows with course_id = course only
|
||||
// (not practices attached to a module or lesson under this course).
|
||||
ModuleCount int `json:"module_count"`
|
||||
|
|
@ -32,17 +33,25 @@ type Course struct {
|
|||
Access *LMSEntityAccess `json:"access,omitempty"`
|
||||
}
|
||||
|
||||
// VisibleToLearners is true when the course appears in subscriber/catalog LMS APIs.
|
||||
func (c Course) VisibleToLearners() bool {
|
||||
return c.PublishStatus == ContentPublishPublished
|
||||
}
|
||||
|
||||
type CreateCourseInput struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
// SortOrder within the program when set; omit to append after current max within program_id (uniqueness is per-program).
|
||||
SortOrder *int `json:"sort_order,omitempty" validate:"omitempty,min=0"`
|
||||
// Omit or empty defaults to DRAFT; set PUBLISHED to make visible to learners immediately.
|
||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||
}
|
||||
|
||||
type UpdateCourseInput struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
SortOrder *int `json:"sort_order,omitempty"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
SortOrder *int `json:"sort_order,omitempty"`
|
||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,19 +4,25 @@ import "time"
|
|||
|
||||
// ExamPrepCatalogCourse is a top-level exam-prep track (e.g. DET, IELTS) in schema exam_prep — separate from LMS Learn English courses.
|
||||
type ExamPrepCatalogCourse struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Category string `json:"category"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
UnitsCount *int64 `json:"units_count,omitempty"`
|
||||
ModulesCount *int64 `json:"modules_count,omitempty"`
|
||||
LessonsCount *int64 `json:"lessons_count,omitempty"`
|
||||
HasPractice bool `json:"has_practice"`
|
||||
Access *LMSEntityAccess `json:"access,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Category string `json:"category"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
PublishStatus ContentPublishStatus `json:"publish_status"`
|
||||
UnitsCount *int64 `json:"units_count,omitempty"`
|
||||
ModulesCount *int64 `json:"modules_count,omitempty"`
|
||||
LessonsCount *int64 `json:"lessons_count,omitempty"`
|
||||
HasPractice bool `json:"has_practice"`
|
||||
Access *LMSEntityAccess `json:"access,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||
}
|
||||
|
||||
// VisibleToLearners is true when the catalog course appears in student/learner exam-prep APIs.
|
||||
func (c ExamPrepCatalogCourse) VisibleToLearners() bool {
|
||||
return c.PublishStatus == ContentPublishPublished
|
||||
}
|
||||
|
||||
type CreateExamPrepCatalogCourseInput struct {
|
||||
|
|
@ -24,12 +30,15 @@ type CreateExamPrepCatalogCourseInput struct {
|
|||
Description *string `json:"description,omitempty"`
|
||||
Category string `json:"category" validate:"required,oneof=IELTS DUOLINGO"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
// Omit or empty defaults to DRAFT; set PUBLISHED to make visible to learners immediately.
|
||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||
}
|
||||
|
||||
type UpdateExamPrepCatalogCourseInput struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Category *string `json:"category,omitempty" validate:"omitempty,oneof=IELTS DUOLINGO"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
SortOrder *int `json:"sort_order,omitempty"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Category *string `json:"category,omitempty" validate:"omitempty,oneof=IELTS DUOLINGO"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
SortOrder *int `json:"sort_order,omitempty"`
|
||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,17 +4,23 @@ import "time"
|
|||
|
||||
// ExamPrepLesson is a video lesson under an exam-prep unit module (exam_prep.unit_module_lessons).
|
||||
type ExamPrepLesson struct {
|
||||
ID int64 `json:"id"`
|
||||
UnitModuleID int64 `json:"unit_module_id"`
|
||||
Title string `json:"title"`
|
||||
VideoURL *string `json:"video_url,omitempty"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
HasPractice bool `json:"has_practice"`
|
||||
Access *LMSEntityAccess `json:"access,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||
ID int64 `json:"id"`
|
||||
UnitModuleID int64 `json:"unit_module_id"`
|
||||
Title string `json:"title"`
|
||||
VideoURL *string `json:"video_url,omitempty"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
PublishStatus ContentPublishStatus `json:"publish_status"`
|
||||
HasPractice bool `json:"has_practice"`
|
||||
Access *LMSEntityAccess `json:"access,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||
}
|
||||
|
||||
// VisibleToLearners is true when the lesson appears in student/learner exam-prep APIs.
|
||||
func (l ExamPrepLesson) VisibleToLearners() bool {
|
||||
return l.PublishStatus == ContentPublishPublished
|
||||
}
|
||||
|
||||
type CreateExamPrepLessonInput struct {
|
||||
|
|
@ -22,12 +28,15 @@ type CreateExamPrepLessonInput struct {
|
|||
VideoURL *string `json:"video_url,omitempty"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
// Omit or empty defaults to DRAFT; set PUBLISHED to make visible to learners immediately.
|
||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||
}
|
||||
|
||||
type UpdateExamPrepLessonInput struct {
|
||||
Title *string `json:"title,omitempty"`
|
||||
VideoURL *string `json:"video_url,omitempty"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
SortOrder *int `json:"sort_order,omitempty"`
|
||||
Title *string `json:"title,omitempty"`
|
||||
VideoURL *string `json:"video_url,omitempty"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
SortOrder *int `json:"sort_order,omitempty"`
|
||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,19 +4,25 @@ import "time"
|
|||
|
||||
// ExamPrepModule is a module under an exam-prep unit (stored in exam_prep.unit_modules).
|
||||
type ExamPrepModule struct {
|
||||
ID int64 `json:"id"`
|
||||
UnitID int64 `json:"unit_id"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
Icon *string `json:"icon,omitempty"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
LessonsCount *int64 `json:"lessons_count,omitempty"`
|
||||
PracticesCount *int64 `json:"practices_count,omitempty"`
|
||||
HasPractice bool `json:"has_practice"`
|
||||
Access *LMSEntityAccess `json:"access,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||
ID int64 `json:"id"`
|
||||
UnitID int64 `json:"unit_id"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
Icon *string `json:"icon,omitempty"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
PublishStatus ContentPublishStatus `json:"publish_status"`
|
||||
LessonsCount *int64 `json:"lessons_count,omitempty"`
|
||||
PracticesCount *int64 `json:"practices_count,omitempty"`
|
||||
HasPractice bool `json:"has_practice"`
|
||||
Access *LMSEntityAccess `json:"access,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||
}
|
||||
|
||||
// VisibleToLearners is true when the module appears in student/learner exam-prep APIs.
|
||||
func (m ExamPrepModule) VisibleToLearners() bool {
|
||||
return m.PublishStatus == ContentPublishPublished
|
||||
}
|
||||
|
||||
type CreateExamPrepModuleInput struct {
|
||||
|
|
@ -24,12 +30,15 @@ type CreateExamPrepModuleInput struct {
|
|||
Description *string `json:"description,omitempty"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
Icon *string `json:"icon,omitempty"`
|
||||
// Omit or empty defaults to DRAFT; set PUBLISHED to make visible to learners immediately.
|
||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||
}
|
||||
|
||||
type UpdateExamPrepModuleInput struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
Icon *string `json:"icon,omitempty"`
|
||||
SortOrder *int `json:"sort_order,omitempty"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
Icon *string `json:"icon,omitempty"`
|
||||
SortOrder *int `json:"sort_order,omitempty"`
|
||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,19 +4,25 @@ import "time"
|
|||
|
||||
// ExamPrepUnit is a chapter-like grouping under an exam-prep catalog course (schema exam_prep.units).
|
||||
type ExamPrepUnit struct {
|
||||
ID int64 `json:"id"`
|
||||
CatalogCourseID int64 `json:"catalog_course_id"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
ModulesCount *int64 `json:"modules_count,omitempty"`
|
||||
LessonsCount *int64 `json:"lessons_count,omitempty"`
|
||||
PracticesCount *int64 `json:"practices_count,omitempty"`
|
||||
HasPractice bool `json:"has_practice"`
|
||||
Access *LMSEntityAccess `json:"access,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||
ID int64 `json:"id"`
|
||||
CatalogCourseID int64 `json:"catalog_course_id"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
PublishStatus ContentPublishStatus `json:"publish_status"`
|
||||
ModulesCount *int64 `json:"modules_count,omitempty"`
|
||||
LessonsCount *int64 `json:"lessons_count,omitempty"`
|
||||
PracticesCount *int64 `json:"practices_count,omitempty"`
|
||||
HasPractice bool `json:"has_practice"`
|
||||
Access *LMSEntityAccess `json:"access,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||
}
|
||||
|
||||
// VisibleToLearners is true when the unit appears in student/learner exam-prep APIs.
|
||||
func (u ExamPrepUnit) VisibleToLearners() bool {
|
||||
return u.PublishStatus == ContentPublishPublished
|
||||
}
|
||||
|
||||
type CreateExamPrepUnitInput struct {
|
||||
|
|
@ -25,11 +31,14 @@ type CreateExamPrepUnitInput struct {
|
|||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
// SortOrder within the catalog course when set; omit to append after current max sort_order within catalog_course_id.
|
||||
SortOrder *int `json:"sort_order,omitempty" validate:"omitempty,min=0"`
|
||||
// Omit or empty defaults to DRAFT; set PUBLISHED to make visible to learners immediately.
|
||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||
}
|
||||
|
||||
type UpdateExamPrepUnitInput struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
SortOrder *int `json:"sort_order,omitempty"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
SortOrder *int `json:"sort_order,omitempty"`
|
||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,17 +4,23 @@ import "time"
|
|||
|
||||
// Module belongs to a Course. program_id is the course’s program (stored for querying; not required from the client on create).
|
||||
type Module struct {
|
||||
ID int64 `json:"id"`
|
||||
ProgramID int64 `json:"program_id"`
|
||||
CourseID int64 `json:"course_id"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Icon *string `json:"icon,omitempty"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
HasPractice bool `json:"has_practice"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||
Access *LMSEntityAccess `json:"access,omitempty"`
|
||||
ID int64 `json:"id"`
|
||||
ProgramID int64 `json:"program_id"`
|
||||
CourseID int64 `json:"course_id"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Icon *string `json:"icon,omitempty"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
PublishStatus ContentPublishStatus `json:"publish_status"`
|
||||
HasPractice bool `json:"has_practice"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||
Access *LMSEntityAccess `json:"access,omitempty"`
|
||||
}
|
||||
|
||||
// VisibleToLearners is true when the module appears in subscriber/catalog LMS APIs.
|
||||
func (m Module) VisibleToLearners() bool {
|
||||
return m.PublishStatus == ContentPublishPublished
|
||||
}
|
||||
|
||||
type CreateModuleInput struct {
|
||||
|
|
@ -23,11 +29,14 @@ type CreateModuleInput struct {
|
|||
Icon *string `json:"icon,omitempty"`
|
||||
// SortOrder within the course when set; omit to append after current max within course_id (uniqueness is per-course).
|
||||
SortOrder *int `json:"sort_order,omitempty" validate:"omitempty,min=0"`
|
||||
// Omit or empty defaults to DRAFT; set PUBLISHED to make visible to learners immediately.
|
||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||
}
|
||||
|
||||
type UpdateModuleInput struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Icon *string `json:"icon,omitempty"`
|
||||
SortOrder *int `json:"sort_order,omitempty"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Icon *string `json:"icon,omitempty"`
|
||||
SortOrder *int `json:"sort_order,omitempty"`
|
||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,15 +4,21 @@ import "time"
|
|||
|
||||
// Program is the top-level container in the LMS hierarchy (e.g. tracks like Beginner / Intermediate / Advanced).
|
||||
type Program struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Category string `json:"category"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||
Access *LMSEntityAccess `json:"access,omitempty"`
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Category string `json:"category"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
PublishStatus ContentPublishStatus `json:"publish_status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||
Access *LMSEntityAccess `json:"access,omitempty"`
|
||||
}
|
||||
|
||||
// VisibleToLearners is true when the program appears in subscriber/catalog LMS APIs.
|
||||
func (p Program) VisibleToLearners() bool {
|
||||
return p.PublishStatus == ContentPublishPublished
|
||||
}
|
||||
|
||||
type CreateProgramInput struct {
|
||||
|
|
@ -22,12 +28,15 @@ type CreateProgramInput struct {
|
|||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
// SortOrder inserts at this global program order when set; omit to append after current max (sort_order uniqueness is enforced).
|
||||
SortOrder *int `json:"sort_order,omitempty" validate:"omitempty,min=0"`
|
||||
// Omit or empty defaults to DRAFT; set PUBLISHED to make visible to learners immediately.
|
||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||
}
|
||||
|
||||
type UpdateProgramInput struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Category *string `json:"category,omitempty" validate:"omitempty,oneof=LEARN_ENGLISH IELTS DUOLINGO"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
SortOrder *int `json:"sort_order,omitempty"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Category *string `json:"category,omitempty" validate:"omitempty,oneof=LEARN_ENGLISH IELTS DUOLINGO"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
SortOrder *int `json:"sort_order,omitempty"`
|
||||
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -284,6 +284,63 @@ func resolveDefinitionForRuntimeAUDIO(catalog []QuestionTypeDefinition) Question
|
|||
return candidates[0]
|
||||
}
|
||||
|
||||
// ResolveEffectiveQuestionTypeDefinitionID returns the definition ID clients should use when
|
||||
// rendering a question. Stored FKs are preferred; legacy runtime types and unlinked DYNAMIC
|
||||
// payloads are resolved against the active catalog when possible.
|
||||
func ResolveEffectiveQuestionTypeDefinitionID(
|
||||
storedID *int64,
|
||||
runtimeQuestionType string,
|
||||
catalog []QuestionTypeDefinition,
|
||||
payload *DynamicQuestionPayload,
|
||||
) *int64 {
|
||||
if storedID != nil && *storedID > 0 {
|
||||
return storedID
|
||||
}
|
||||
|
||||
def := ResolveQuestionTypeDefinitionForQuestion(nil, runtimeQuestionType, catalog)
|
||||
if def.ID > 0 {
|
||||
id := def.ID
|
||||
return &id
|
||||
}
|
||||
|
||||
if payload != nil && UsesDynamicQuestionPayload(runtimeQuestionType) {
|
||||
if matched := resolveDefinitionForDynamicPayload(*payload, catalog); matched.ID > 0 {
|
||||
id := matched.ID
|
||||
return &id
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolveDefinitionForDynamicPayload(payload DynamicQuestionPayload, catalog []QuestionTypeDefinition) QuestionTypeDefinition {
|
||||
var matches []QuestionTypeDefinition
|
||||
for _, d := range catalog {
|
||||
if status := strings.ToUpper(strings.TrimSpace(d.Status)); status != "" && status != "ACTIVE" {
|
||||
continue
|
||||
}
|
||||
if err := ValidateDynamicPayloadAgainstDefinition(payload, d); err != nil {
|
||||
continue
|
||||
}
|
||||
matches = append(matches, d)
|
||||
}
|
||||
if len(matches) == 0 {
|
||||
return QuestionTypeDefinition{}
|
||||
}
|
||||
sort.Slice(matches, func(i, j int) bool {
|
||||
if matches[i].IsSystem != matches[j].IsSystem {
|
||||
return !matches[i].IsSystem
|
||||
}
|
||||
si := len(matches[i].StimulusSchema) + len(matches[i].ResponseSchema)
|
||||
sj := len(matches[j].StimulusSchema) + len(matches[j].ResponseSchema)
|
||||
if si != sj {
|
||||
return si > sj
|
||||
}
|
||||
return matches[i].ID < matches[j].ID
|
||||
})
|
||||
return matches[0]
|
||||
}
|
||||
|
||||
func humanizeDefinitionKey(key string) string {
|
||||
parts := strings.Split(strings.ReplaceAll(key, "-", "_"), "_")
|
||||
for i, p := range parts {
|
||||
|
|
|
|||
|
|
@ -201,6 +201,52 @@ func TestResolveQuestionTypeDefinitionForQuestion_linkedDefinition(t *testing.T)
|
|||
}
|
||||
}
|
||||
|
||||
func TestResolveEffectiveQuestionTypeDefinitionID_legacyAUDIO(t *testing.T) {
|
||||
got := ResolveEffectiveQuestionTypeDefinitionID(nil, "AUDIO", testQuestionTypeCatalog(), nil)
|
||||
if got == nil || *got != 10 {
|
||||
t.Fatalf("expected audio_conversation_type id 10, got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveEffectiveQuestionTypeDefinitionID_storedIDPreferred(t *testing.T) {
|
||||
stored := int64(2)
|
||||
got := ResolveEffectiveQuestionTypeDefinitionID(&stored, "AUDIO", testQuestionTypeCatalog(), nil)
|
||||
if got == nil || *got != 2 {
|
||||
t.Fatalf("expected stored id 2, got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveEffectiveQuestionTypeDefinitionID_dynamicPayloadMatch(t *testing.T) {
|
||||
catalog := append(testQuestionTypeCatalog(), QuestionTypeDefinition{
|
||||
ID: 42,
|
||||
Key: "audio_with_timer",
|
||||
DisplayName: "Audio With Timer",
|
||||
StimulusComponentKinds: []string{"AUDIO_PROMPT"},
|
||||
ResponseComponentKinds: []string{"ANSWER_TIMER", "AUDIO_RESPONSE"},
|
||||
StimulusSchema: []DynamicElementDefinition{
|
||||
{ID: "audio_prompt_1", Kind: "AUDIO_PROMPT", Required: true},
|
||||
},
|
||||
ResponseSchema: []DynamicElementDefinition{
|
||||
{ID: "answer_timer_1", Kind: "ANSWER_TIMER", Required: true},
|
||||
{ID: "audio_response_1", Kind: "AUDIO_RESPONSE", Required: true},
|
||||
},
|
||||
Status: "ACTIVE",
|
||||
})
|
||||
payload := &DynamicQuestionPayload{
|
||||
Stimulus: []DynamicElementInstance{
|
||||
{ID: "audio_prompt_1", Kind: "AUDIO_PROMPT", Value: "https://example.com/prompt.mp3"},
|
||||
},
|
||||
Response: []DynamicElementInstance{
|
||||
{ID: "answer_timer_1", Kind: "ANSWER_TIMER", Value: map[string]interface{}{"seconds": 20}},
|
||||
{ID: "audio_response_1", Kind: "AUDIO_RESPONSE", Value: "https://example.com/answer.mp3"},
|
||||
},
|
||||
}
|
||||
got := ResolveEffectiveQuestionTypeDefinitionID(nil, "DYNAMIC", catalog, payload)
|
||||
if got == nil || *got != 42 {
|
||||
t.Fatalf("expected matched definition id 42, got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateQuestionTextNotAllowedForDynamic(t *testing.T) {
|
||||
if err := ValidateQuestionTextNotAllowedForDynamic("DYNAMIC", "nope"); err == nil {
|
||||
t.Fatal("expected error when question_text sent for DYNAMIC")
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import (
|
|||
type ExamPrepCatalogCourseStore interface {
|
||||
CreateExamPrepCatalogCourse(ctx context.Context, input domain.CreateExamPrepCatalogCourseInput) (domain.ExamPrepCatalogCourse, error)
|
||||
GetExamPrepCatalogCourseByID(ctx context.Context, id int64) (domain.ExamPrepCatalogCourse, error)
|
||||
ListExamPrepCatalogCourses(ctx context.Context, limit, offset int32) ([]domain.ExamPrepCatalogCourse, int64, error)
|
||||
ListExamPrepCatalogCourses(ctx context.Context, publishedOnly bool, limit, offset int32) ([]domain.ExamPrepCatalogCourse, int64, error)
|
||||
ListAllExamPrepCatalogCourseIDs(ctx context.Context) ([]int64, error)
|
||||
UpdateExamPrepCatalogCourse(ctx context.Context, id int64, input domain.UpdateExamPrepCatalogCourseInput) (domain.ExamPrepCatalogCourse, error)
|
||||
DeleteExamPrepCatalogCourse(ctx context.Context, id int64) error
|
||||
|
|
|
|||
|
|
@ -9,8 +9,9 @@ import (
|
|||
type ExamPrepLessonStore interface {
|
||||
CreateExamPrepUnitModuleLesson(ctx context.Context, unitModuleID int64, input domain.CreateExamPrepLessonInput) (domain.ExamPrepLesson, error)
|
||||
GetExamPrepUnitModuleLessonByID(ctx context.Context, id int64) (domain.ExamPrepLesson, error)
|
||||
ListExamPrepUnitModuleLessonsByUnitModuleID(ctx context.Context, unitModuleID int64, limit, offset int32) ([]domain.ExamPrepLesson, int64, error)
|
||||
ListExamPrepUnitModuleLessonsByUnitModuleID(ctx context.Context, unitModuleID int64, publishedOnly bool, limit, offset int32) ([]domain.ExamPrepLesson, int64, error)
|
||||
ListExamPrepUnitModuleLessonIDsByUnitModule(ctx context.Context, unitModuleID int64) ([]int64, error)
|
||||
ListPublishedExamPrepUnitModuleLessonIDsByUnitModule(ctx context.Context, unitModuleID int64) ([]int64, error)
|
||||
UpdateExamPrepUnitModuleLesson(ctx context.Context, id int64, input domain.UpdateExamPrepLessonInput) (domain.ExamPrepLesson, error)
|
||||
DeleteExamPrepUnitModuleLesson(ctx context.Context, id int64) error
|
||||
ReorderExamPrepUnitModuleLessonsInUnitModule(ctx context.Context, unitModuleID int64, orderedIDs []int64) error
|
||||
|
|
|
|||
|
|
@ -9,8 +9,9 @@ import (
|
|||
type ExamPrepModuleStore interface {
|
||||
CreateExamPrepUnitModule(ctx context.Context, unitID int64, input domain.CreateExamPrepModuleInput) (domain.ExamPrepModule, error)
|
||||
GetExamPrepUnitModuleByID(ctx context.Context, id int64) (domain.ExamPrepModule, error)
|
||||
ListExamPrepUnitModulesByUnit(ctx context.Context, unitID int64, limit, offset int32) ([]domain.ExamPrepModule, int64, error)
|
||||
ListExamPrepUnitModulesByUnit(ctx context.Context, unitID int64, publishedOnly bool, limit, offset int32) ([]domain.ExamPrepModule, int64, error)
|
||||
ListExamPrepUnitModuleIDsByUnit(ctx context.Context, unitID int64) ([]int64, error)
|
||||
ListPublishedExamPrepUnitModuleIDsByUnit(ctx context.Context, unitID int64) ([]int64, error)
|
||||
UpdateExamPrepUnitModule(ctx context.Context, id int64, input domain.UpdateExamPrepModuleInput) (domain.ExamPrepModule, error)
|
||||
DeleteExamPrepUnitModule(ctx context.Context, id int64) error
|
||||
ReorderExamPrepUnitModulesInUnit(ctx context.Context, unitID int64, orderedIDs []int64) error
|
||||
|
|
|
|||
|
|
@ -9,8 +9,9 @@ import (
|
|||
type ExamPrepUnitStore interface {
|
||||
CreateExamPrepUnit(ctx context.Context, catalogCourseID int64, input domain.CreateExamPrepUnitInput) (domain.ExamPrepUnit, error)
|
||||
GetExamPrepUnitByID(ctx context.Context, id int64) (domain.ExamPrepUnit, error)
|
||||
ListExamPrepUnitsByCatalogCourse(ctx context.Context, catalogCourseID int64, limit, offset int32) ([]domain.ExamPrepUnit, int64, error)
|
||||
ListExamPrepUnitsByCatalogCourse(ctx context.Context, catalogCourseID int64, publishedOnly bool, limit, offset int32) ([]domain.ExamPrepUnit, int64, error)
|
||||
ListExamPrepUnitIDsByCatalogCourse(ctx context.Context, catalogCourseID int64) ([]int64, error)
|
||||
ListPublishedExamPrepUnitIDsByCatalogCourse(ctx context.Context, catalogCourseID int64) ([]int64, error)
|
||||
UpdateExamPrepUnit(ctx context.Context, id int64, input domain.UpdateExamPrepUnitInput) (domain.ExamPrepUnit, error)
|
||||
DeleteExamPrepUnit(ctx context.Context, id int64) error
|
||||
ReorderExamPrepUnitsInCatalogCourse(ctx context.Context, catalogCourseID int64, orderedIDs []int64) error
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@ import (
|
|||
type CourseStore interface {
|
||||
CreateCourse(ctx context.Context, programID int64, input domain.CreateCourseInput) (domain.Course, error)
|
||||
GetCourseByID(ctx context.Context, id int64) (domain.Course, error)
|
||||
ListCoursesByProgramID(ctx context.Context, programID int64, limit, offset int32) ([]domain.Course, int64, error)
|
||||
ListCoursesByProgramID(ctx context.Context, programID int64, publishedOnly bool, limit, offset int32) ([]domain.Course, int64, error)
|
||||
ListCourseIDsByProgram(ctx context.Context, programID int64) ([]int64, error)
|
||||
ListPublishedCourseIDsByProgram(ctx context.Context, programID int64) ([]int64, error)
|
||||
ReorderCoursesInProgram(ctx context.Context, programID int64, orderedIDs []int64) error
|
||||
UpdateCourse(ctx context.Context, id int64, input domain.UpdateCourseInput) (domain.Course, error)
|
||||
DeleteCourse(ctx context.Context, id int64) error
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@ import (
|
|||
type ModuleStore interface {
|
||||
CreateModule(ctx context.Context, programID, courseID int64, input domain.CreateModuleInput) (domain.Module, error)
|
||||
GetModuleByID(ctx context.Context, id int64) (domain.Module, error)
|
||||
ListModulesByProgramAndCourse(ctx context.Context, programID, courseID int64, limit, offset int32) ([]domain.Module, int64, error)
|
||||
ListModulesByProgramAndCourse(ctx context.Context, programID, courseID int64, publishedOnly bool, limit, offset int32) ([]domain.Module, int64, error)
|
||||
ListModuleIDsByCourse(ctx context.Context, courseID int64) ([]int64, error)
|
||||
ListPublishedModuleIDsByCourse(ctx context.Context, courseID int64) ([]int64, error)
|
||||
ReorderModulesInCourse(ctx context.Context, courseID int64, orderedIDs []int64) error
|
||||
UpdateModule(ctx context.Context, id int64, input domain.UpdateModuleInput) (domain.Module, error)
|
||||
DeleteModule(ctx context.Context, id int64) error
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
type ProgramStore interface {
|
||||
CreateProgram(ctx context.Context, input domain.CreateProgramInput) (domain.Program, error)
|
||||
GetProgramByID(ctx context.Context, id int64) (domain.Program, error)
|
||||
ListPrograms(ctx context.Context, limit, offset int32) ([]domain.Program, int64, error)
|
||||
ListPrograms(ctx context.Context, publishedOnly bool, limit, offset int32) ([]domain.Program, int64, error)
|
||||
ListAllProgramIDs(ctx context.Context) ([]int64, error)
|
||||
ReorderPrograms(ctx context.Context, orderedIDs []int64) error
|
||||
UpdateProgram(ctx context.Context, id int64, input domain.UpdateProgramInput) (domain.Program, error)
|
||||
|
|
|
|||
|
|
@ -13,10 +13,11 @@ import (
|
|||
|
||||
func examPrepCatalogCourseToDomain(c dbgen.ExamPrepCatalogCourse) domain.ExamPrepCatalogCourse {
|
||||
out := domain.ExamPrepCatalogCourse{
|
||||
ID: c.ID,
|
||||
Name: c.Name,
|
||||
Category: c.Category,
|
||||
SortOrder: int(c.SortOrder),
|
||||
ID: c.ID,
|
||||
Name: c.Name,
|
||||
Category: c.Category,
|
||||
SortOrder: int(c.SortOrder),
|
||||
PublishStatus: domain.ContentPublishStatusFromDB(c.PublishStatus),
|
||||
}
|
||||
out.Description = fromPgText(c.Description)
|
||||
out.Thumbnail = fromPgText(c.Thumbnail)
|
||||
|
|
@ -30,10 +31,11 @@ func examPrepCatalogCourseToDomain(c dbgen.ExamPrepCatalogCourse) domain.ExamPre
|
|||
|
||||
func (s *Store) CreateExamPrepCatalogCourse(ctx context.Context, input domain.CreateExamPrepCatalogCourseInput) (domain.ExamPrepCatalogCourse, error) {
|
||||
c, err := s.queries.ExamPrepCreateCatalogCourse(ctx, dbgen.ExamPrepCreateCatalogCourseParams{
|
||||
Name: input.Name,
|
||||
Description: toPgText(input.Description),
|
||||
Category: input.Category,
|
||||
Thumbnail: toPgText(input.Thumbnail),
|
||||
Name: input.Name,
|
||||
Description: toPgText(input.Description),
|
||||
Category: input.Category,
|
||||
Thumbnail: toPgText(input.Thumbnail),
|
||||
PublishStatus: string(domain.ContentPublishStatusFromCreateInput(input.PublishStatus)),
|
||||
})
|
||||
if err != nil {
|
||||
return domain.ExamPrepCatalogCourse{}, err
|
||||
|
|
@ -50,23 +52,25 @@ func (s *Store) GetExamPrepCatalogCourseByID(ctx context.Context, id int64) (dom
|
|||
return domain.ExamPrepCatalogCourse{}, err
|
||||
}
|
||||
out := examPrepCatalogCourseToDomain(dbgen.ExamPrepCatalogCourse{
|
||||
ID: c.ID,
|
||||
Name: c.Name,
|
||||
Description: c.Description,
|
||||
Category: c.Category,
|
||||
Thumbnail: c.Thumbnail,
|
||||
SortOrder: c.SortOrder,
|
||||
CreatedAt: c.CreatedAt,
|
||||
UpdatedAt: c.UpdatedAt,
|
||||
ID: c.ID,
|
||||
Name: c.Name,
|
||||
Description: c.Description,
|
||||
Category: c.Category,
|
||||
Thumbnail: c.Thumbnail,
|
||||
SortOrder: c.SortOrder,
|
||||
CreatedAt: c.CreatedAt,
|
||||
UpdatedAt: c.UpdatedAt,
|
||||
PublishStatus: c.PublishStatus,
|
||||
})
|
||||
out.HasPractice = c.HasPractice
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *Store) ListExamPrepCatalogCourses(ctx context.Context, limit, offset int32) ([]domain.ExamPrepCatalogCourse, int64, error) {
|
||||
func (s *Store) ListExamPrepCatalogCourses(ctx context.Context, publishedOnly bool, limit, offset int32) ([]domain.ExamPrepCatalogCourse, int64, error) {
|
||||
rows, err := s.queries.ExamPrepListCatalogCourses(ctx, dbgen.ExamPrepListCatalogCoursesParams{
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
PublishedOnly: publishedOnly,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
|
|
@ -81,14 +85,15 @@ func (s *Store) ListExamPrepCatalogCourses(ctx context.Context, limit, offset in
|
|||
total = r.TotalCount
|
||||
}
|
||||
item := examPrepCatalogCourseToDomain(dbgen.ExamPrepCatalogCourse{
|
||||
ID: r.ID,
|
||||
Name: r.Name,
|
||||
Description: r.Description,
|
||||
Category: r.Category,
|
||||
Thumbnail: r.Thumbnail,
|
||||
SortOrder: r.SortOrder,
|
||||
CreatedAt: r.CreatedAt,
|
||||
UpdatedAt: r.UpdatedAt,
|
||||
ID: r.ID,
|
||||
Name: r.Name,
|
||||
Description: r.Description,
|
||||
Category: r.Category,
|
||||
Thumbnail: r.Thumbnail,
|
||||
SortOrder: r.SortOrder,
|
||||
CreatedAt: r.CreatedAt,
|
||||
UpdatedAt: r.UpdatedAt,
|
||||
PublishStatus: r.PublishStatus,
|
||||
})
|
||||
item.UnitsCount = &r.UnitsCount
|
||||
item.ModulesCount = &r.ModulesCount
|
||||
|
|
@ -111,12 +116,13 @@ func (s *Store) UpdateExamPrepCatalogCourse(ctx context.Context, id int64, input
|
|||
nameText = pgtype.Text{Valid: false}
|
||||
}
|
||||
c, err := s.queries.ExamPrepUpdateCatalogCourse(ctx, dbgen.ExamPrepUpdateCatalogCourseParams{
|
||||
ID: id,
|
||||
Name: nameText,
|
||||
Description: optionalTextUpdate(input.Description),
|
||||
Category: optionalTextUpdate(input.Category),
|
||||
Thumbnail: optionalTextUpdate(input.Thumbnail),
|
||||
SortOrder: optionalInt4Update(input.SortOrder),
|
||||
ID: id,
|
||||
Name: nameText,
|
||||
Description: optionalTextUpdate(input.Description),
|
||||
Category: optionalTextUpdate(input.Category),
|
||||
Thumbnail: optionalTextUpdate(input.Thumbnail),
|
||||
SortOrder: optionalInt4Update(input.SortOrder),
|
||||
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
|
|
|
|||
|
|
@ -13,10 +13,11 @@ import (
|
|||
|
||||
func examPrepLessonToDomain(l dbgen.ExamPrepUnitModuleLesson) domain.ExamPrepLesson {
|
||||
out := domain.ExamPrepLesson{
|
||||
ID: l.ID,
|
||||
UnitModuleID: l.UnitModuleID,
|
||||
Title: l.Title,
|
||||
SortOrder: int(l.SortOrder),
|
||||
ID: l.ID,
|
||||
UnitModuleID: l.UnitModuleID,
|
||||
Title: l.Title,
|
||||
SortOrder: int(l.SortOrder),
|
||||
PublishStatus: domain.ContentPublishStatusFromDB(l.PublishStatus),
|
||||
}
|
||||
out.VideoURL = fromPgText(l.VideoUrl)
|
||||
out.Thumbnail = fromPgText(l.Thumbnail)
|
||||
|
|
@ -31,11 +32,12 @@ func examPrepLessonToDomain(l dbgen.ExamPrepUnitModuleLesson) domain.ExamPrepLes
|
|||
|
||||
func (s *Store) CreateExamPrepUnitModuleLesson(ctx context.Context, unitModuleID int64, input domain.CreateExamPrepLessonInput) (domain.ExamPrepLesson, error) {
|
||||
l, err := s.queries.ExamPrepCreateUnitModuleLesson(ctx, dbgen.ExamPrepCreateUnitModuleLessonParams{
|
||||
UnitModuleID: unitModuleID,
|
||||
Title: input.Title,
|
||||
VideoUrl: toPgText(input.VideoURL),
|
||||
Thumbnail: toPgText(input.Thumbnail),
|
||||
Description: toPgText(input.Description),
|
||||
UnitModuleID: unitModuleID,
|
||||
Title: input.Title,
|
||||
VideoUrl: toPgText(input.VideoURL),
|
||||
Thumbnail: toPgText(input.Thumbnail),
|
||||
Description: toPgText(input.Description),
|
||||
PublishStatus: string(domain.ContentPublishStatusFromCreateInput(input.PublishStatus)),
|
||||
})
|
||||
if err != nil {
|
||||
return domain.ExamPrepLesson{}, err
|
||||
|
|
@ -52,25 +54,27 @@ func (s *Store) GetExamPrepUnitModuleLessonByID(ctx context.Context, id int64) (
|
|||
return domain.ExamPrepLesson{}, err
|
||||
}
|
||||
out := examPrepLessonToDomain(dbgen.ExamPrepUnitModuleLesson{
|
||||
ID: l.ID,
|
||||
UnitModuleID: l.UnitModuleID,
|
||||
Title: l.Title,
|
||||
VideoUrl: l.VideoUrl,
|
||||
Thumbnail: l.Thumbnail,
|
||||
Description: l.Description,
|
||||
SortOrder: l.SortOrder,
|
||||
CreatedAt: l.CreatedAt,
|
||||
UpdatedAt: l.UpdatedAt,
|
||||
ID: l.ID,
|
||||
UnitModuleID: l.UnitModuleID,
|
||||
Title: l.Title,
|
||||
VideoUrl: l.VideoUrl,
|
||||
Thumbnail: l.Thumbnail,
|
||||
Description: l.Description,
|
||||
SortOrder: l.SortOrder,
|
||||
CreatedAt: l.CreatedAt,
|
||||
UpdatedAt: l.UpdatedAt,
|
||||
PublishStatus: l.PublishStatus,
|
||||
})
|
||||
out.HasPractice = l.HasPractice
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *Store) ListExamPrepUnitModuleLessonsByUnitModuleID(ctx context.Context, unitModuleID int64, limit, offset int32) ([]domain.ExamPrepLesson, int64, error) {
|
||||
func (s *Store) ListExamPrepUnitModuleLessonsByUnitModuleID(ctx context.Context, unitModuleID int64, publishedOnly bool, limit, offset int32) ([]domain.ExamPrepLesson, int64, error) {
|
||||
rows, err := s.queries.ExamPrepListUnitModuleLessonsByUnitModuleID(ctx, dbgen.ExamPrepListUnitModuleLessonsByUnitModuleIDParams{
|
||||
UnitModuleID: unitModuleID,
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
UnitModuleID: unitModuleID,
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
PublishedOnly: publishedOnly,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
|
|
@ -85,15 +89,16 @@ func (s *Store) ListExamPrepUnitModuleLessonsByUnitModuleID(ctx context.Context,
|
|||
total = r.TotalCount
|
||||
}
|
||||
item := examPrepLessonToDomain(dbgen.ExamPrepUnitModuleLesson{
|
||||
ID: r.ID,
|
||||
UnitModuleID: r.UnitModuleID,
|
||||
Title: r.Title,
|
||||
VideoUrl: r.VideoUrl,
|
||||
Thumbnail: r.Thumbnail,
|
||||
Description: r.Description,
|
||||
SortOrder: r.SortOrder,
|
||||
CreatedAt: r.CreatedAt,
|
||||
UpdatedAt: r.UpdatedAt,
|
||||
ID: r.ID,
|
||||
UnitModuleID: r.UnitModuleID,
|
||||
Title: r.Title,
|
||||
VideoUrl: r.VideoUrl,
|
||||
Thumbnail: r.Thumbnail,
|
||||
Description: r.Description,
|
||||
SortOrder: r.SortOrder,
|
||||
CreatedAt: r.CreatedAt,
|
||||
UpdatedAt: r.UpdatedAt,
|
||||
PublishStatus: r.PublishStatus,
|
||||
})
|
||||
item.HasPractice = r.HasPractice
|
||||
out = append(out, item)
|
||||
|
|
@ -105,6 +110,10 @@ func (s *Store) ListExamPrepUnitModuleLessonIDsByUnitModule(ctx context.Context,
|
|||
return s.queries.ExamPrepListUnitModuleLessonIDsByUnitModule(ctx, unitModuleID)
|
||||
}
|
||||
|
||||
func (s *Store) ListPublishedExamPrepUnitModuleLessonIDsByUnitModule(ctx context.Context, unitModuleID int64) ([]int64, error) {
|
||||
return s.queries.ExamPrepListPublishedUnitModuleLessonIDsByUnitModule(ctx, unitModuleID)
|
||||
}
|
||||
|
||||
func (s *Store) UpdateExamPrepUnitModuleLesson(ctx context.Context, id int64, input domain.UpdateExamPrepLessonInput) (domain.ExamPrepLesson, error) {
|
||||
var titleText pgtype.Text
|
||||
if input.Title != nil {
|
||||
|
|
@ -113,12 +122,13 @@ func (s *Store) UpdateExamPrepUnitModuleLesson(ctx context.Context, id int64, in
|
|||
titleText = pgtype.Text{Valid: false}
|
||||
}
|
||||
l, err := s.queries.ExamPrepUpdateUnitModuleLesson(ctx, dbgen.ExamPrepUpdateUnitModuleLessonParams{
|
||||
ID: id,
|
||||
Title: titleText,
|
||||
VideoUrl: optionalTextUpdate(input.VideoURL),
|
||||
Thumbnail: optionalTextUpdate(input.Thumbnail),
|
||||
Description: optionalTextUpdate(input.Description),
|
||||
SortOrder: optionalInt4Update(input.SortOrder),
|
||||
ID: id,
|
||||
Title: titleText,
|
||||
VideoUrl: optionalTextUpdate(input.VideoURL),
|
||||
Thumbnail: optionalTextUpdate(input.Thumbnail),
|
||||
Description: optionalTextUpdate(input.Description),
|
||||
SortOrder: optionalInt4Update(input.SortOrder),
|
||||
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
|
|
|
|||
|
|
@ -13,10 +13,11 @@ import (
|
|||
|
||||
func examPrepModuleToDomain(m dbgen.ExamPrepUnitModule) domain.ExamPrepModule {
|
||||
out := domain.ExamPrepModule{
|
||||
ID: m.ID,
|
||||
UnitID: m.UnitID,
|
||||
Name: m.Name,
|
||||
SortOrder: int(m.SortOrder),
|
||||
ID: m.ID,
|
||||
UnitID: m.UnitID,
|
||||
Name: m.Name,
|
||||
SortOrder: int(m.SortOrder),
|
||||
PublishStatus: domain.ContentPublishStatusFromDB(m.PublishStatus),
|
||||
}
|
||||
out.Description = fromPgText(m.Description)
|
||||
out.Thumbnail = fromPgText(m.Thumbnail)
|
||||
|
|
@ -31,11 +32,12 @@ func examPrepModuleToDomain(m dbgen.ExamPrepUnitModule) domain.ExamPrepModule {
|
|||
|
||||
func (s *Store) CreateExamPrepUnitModule(ctx context.Context, unitID int64, input domain.CreateExamPrepModuleInput) (domain.ExamPrepModule, error) {
|
||||
m, err := s.queries.ExamPrepCreateUnitModule(ctx, dbgen.ExamPrepCreateUnitModuleParams{
|
||||
UnitID: unitID,
|
||||
Name: input.Name,
|
||||
Description: toPgText(input.Description),
|
||||
Thumbnail: toPgText(input.Thumbnail),
|
||||
Icon: toPgText(input.Icon),
|
||||
UnitID: unitID,
|
||||
Name: input.Name,
|
||||
Description: toPgText(input.Description),
|
||||
Thumbnail: toPgText(input.Thumbnail),
|
||||
Icon: toPgText(input.Icon),
|
||||
PublishStatus: string(domain.ContentPublishStatusFromCreateInput(input.PublishStatus)),
|
||||
})
|
||||
if err != nil {
|
||||
return domain.ExamPrepModule{}, err
|
||||
|
|
@ -52,25 +54,27 @@ func (s *Store) GetExamPrepUnitModuleByID(ctx context.Context, id int64) (domain
|
|||
return domain.ExamPrepModule{}, err
|
||||
}
|
||||
out := examPrepModuleToDomain(dbgen.ExamPrepUnitModule{
|
||||
ID: m.ID,
|
||||
UnitID: m.UnitID,
|
||||
Name: m.Name,
|
||||
Description: m.Description,
|
||||
Thumbnail: m.Thumbnail,
|
||||
Icon: m.Icon,
|
||||
SortOrder: m.SortOrder,
|
||||
CreatedAt: m.CreatedAt,
|
||||
UpdatedAt: m.UpdatedAt,
|
||||
ID: m.ID,
|
||||
UnitID: m.UnitID,
|
||||
Name: m.Name,
|
||||
Description: m.Description,
|
||||
Thumbnail: m.Thumbnail,
|
||||
Icon: m.Icon,
|
||||
SortOrder: m.SortOrder,
|
||||
CreatedAt: m.CreatedAt,
|
||||
UpdatedAt: m.UpdatedAt,
|
||||
PublishStatus: m.PublishStatus,
|
||||
})
|
||||
out.HasPractice = m.HasPractice
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *Store) ListExamPrepUnitModulesByUnit(ctx context.Context, unitID int64, limit, offset int32) ([]domain.ExamPrepModule, int64, error) {
|
||||
func (s *Store) ListExamPrepUnitModulesByUnit(ctx context.Context, unitID int64, publishedOnly bool, limit, offset int32) ([]domain.ExamPrepModule, int64, error) {
|
||||
rows, err := s.queries.ExamPrepListUnitModulesByUnit(ctx, dbgen.ExamPrepListUnitModulesByUnitParams{
|
||||
UnitID: unitID,
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
UnitID: unitID,
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
PublishedOnly: publishedOnly,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
|
|
@ -85,15 +89,16 @@ func (s *Store) ListExamPrepUnitModulesByUnit(ctx context.Context, unitID int64,
|
|||
total = r.TotalCount
|
||||
}
|
||||
item := examPrepModuleToDomain(dbgen.ExamPrepUnitModule{
|
||||
ID: r.ID,
|
||||
UnitID: r.UnitID,
|
||||
Name: r.Name,
|
||||
Description: r.Description,
|
||||
Thumbnail: r.Thumbnail,
|
||||
Icon: r.Icon,
|
||||
SortOrder: r.SortOrder,
|
||||
CreatedAt: r.CreatedAt,
|
||||
UpdatedAt: r.UpdatedAt,
|
||||
ID: r.ID,
|
||||
UnitID: r.UnitID,
|
||||
Name: r.Name,
|
||||
Description: r.Description,
|
||||
Thumbnail: r.Thumbnail,
|
||||
Icon: r.Icon,
|
||||
SortOrder: r.SortOrder,
|
||||
CreatedAt: r.CreatedAt,
|
||||
UpdatedAt: r.UpdatedAt,
|
||||
PublishStatus: r.PublishStatus,
|
||||
})
|
||||
item.LessonsCount = &r.LessonsCount
|
||||
item.PracticesCount = &r.PracticesCount
|
||||
|
|
@ -107,6 +112,10 @@ func (s *Store) ListExamPrepUnitModuleIDsByUnit(ctx context.Context, unitID int6
|
|||
return s.queries.ExamPrepListUnitModuleIDsByUnit(ctx, unitID)
|
||||
}
|
||||
|
||||
func (s *Store) ListPublishedExamPrepUnitModuleIDsByUnit(ctx context.Context, unitID int64) ([]int64, error) {
|
||||
return s.queries.ExamPrepListPublishedUnitModuleIDsByUnit(ctx, unitID)
|
||||
}
|
||||
|
||||
func (s *Store) UpdateExamPrepUnitModule(ctx context.Context, id int64, input domain.UpdateExamPrepModuleInput) (domain.ExamPrepModule, error) {
|
||||
var nameText pgtype.Text
|
||||
if input.Name != nil {
|
||||
|
|
@ -115,12 +124,13 @@ func (s *Store) UpdateExamPrepUnitModule(ctx context.Context, id int64, input do
|
|||
nameText = pgtype.Text{Valid: false}
|
||||
}
|
||||
m, err := s.queries.ExamPrepUpdateUnitModule(ctx, dbgen.ExamPrepUpdateUnitModuleParams{
|
||||
ID: id,
|
||||
Name: nameText,
|
||||
Description: optionalTextUpdate(input.Description),
|
||||
Thumbnail: optionalTextUpdate(input.Thumbnail),
|
||||
Icon: optionalTextUpdate(input.Icon),
|
||||
SortOrder: optionalInt4Update(input.SortOrder),
|
||||
ID: id,
|
||||
Name: nameText,
|
||||
Description: optionalTextUpdate(input.Description),
|
||||
Thumbnail: optionalTextUpdate(input.Thumbnail),
|
||||
Icon: optionalTextUpdate(input.Icon),
|
||||
SortOrder: optionalInt4Update(input.SortOrder),
|
||||
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ func examPrepUnitToDomain(u dbgen.ExamPrepUnit) domain.ExamPrepUnit {
|
|||
CatalogCourseID: u.CatalogCourseID,
|
||||
Name: u.Name,
|
||||
SortOrder: int(u.SortOrder),
|
||||
PublishStatus: domain.ContentPublishStatusFromDB(u.PublishStatus),
|
||||
}
|
||||
out.Description = fromPgText(u.Description)
|
||||
out.Thumbnail = fromPgText(u.Thumbnail)
|
||||
|
|
@ -29,6 +30,7 @@ func examPrepUnitToDomain(u dbgen.ExamPrepUnit) domain.ExamPrepUnit {
|
|||
}
|
||||
|
||||
func (s *Store) CreateExamPrepUnit(ctx context.Context, catalogCourseID int64, input domain.CreateExamPrepUnitInput) (domain.ExamPrepUnit, error) {
|
||||
pub := string(domain.ContentPublishStatusFromCreateInput(input.PublishStatus))
|
||||
if input.SortOrder != nil {
|
||||
q, tx, err := s.BeginTx(ctx)
|
||||
if err != nil {
|
||||
|
|
@ -48,6 +50,7 @@ func (s *Store) CreateExamPrepUnit(ctx context.Context, catalogCourseID int64, i
|
|||
Description: toPgText(input.Description),
|
||||
Thumbnail: toPgText(input.Thumbnail),
|
||||
SortOrder: pgtype.Int4{Int32: target, Valid: true},
|
||||
PublishStatus: pub,
|
||||
})
|
||||
if err != nil {
|
||||
return domain.ExamPrepUnit{}, err
|
||||
|
|
@ -64,6 +67,7 @@ func (s *Store) CreateExamPrepUnit(ctx context.Context, catalogCourseID int64, i
|
|||
Description: toPgText(input.Description),
|
||||
Thumbnail: toPgText(input.Thumbnail),
|
||||
SortOrder: pgtype.Int4{Valid: false},
|
||||
PublishStatus: pub,
|
||||
})
|
||||
if err != nil {
|
||||
return domain.ExamPrepUnit{}, err
|
||||
|
|
@ -88,16 +92,18 @@ func (s *Store) GetExamPrepUnitByID(ctx context.Context, id int64) (domain.ExamP
|
|||
SortOrder: u.SortOrder,
|
||||
CreatedAt: u.CreatedAt,
|
||||
UpdatedAt: u.UpdatedAt,
|
||||
PublishStatus: u.PublishStatus,
|
||||
})
|
||||
out.HasPractice = u.HasPractice
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *Store) ListExamPrepUnitsByCatalogCourse(ctx context.Context, catalogCourseID int64, limit, offset int32) ([]domain.ExamPrepUnit, int64, error) {
|
||||
func (s *Store) ListExamPrepUnitsByCatalogCourse(ctx context.Context, catalogCourseID int64, publishedOnly bool, limit, offset int32) ([]domain.ExamPrepUnit, int64, error) {
|
||||
rows, err := s.queries.ExamPrepListUnitsByCatalogCourse(ctx, dbgen.ExamPrepListUnitsByCatalogCourseParams{
|
||||
CatalogCourseID: catalogCourseID,
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
PublishedOnly: publishedOnly,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
|
|
@ -120,6 +126,7 @@ func (s *Store) ListExamPrepUnitsByCatalogCourse(ctx context.Context, catalogCou
|
|||
SortOrder: r.SortOrder,
|
||||
CreatedAt: r.CreatedAt,
|
||||
UpdatedAt: r.UpdatedAt,
|
||||
PublishStatus: r.PublishStatus,
|
||||
})
|
||||
item.ModulesCount = &r.ModulesCount
|
||||
item.LessonsCount = &r.LessonsCount
|
||||
|
|
@ -134,6 +141,10 @@ func (s *Store) ListExamPrepUnitIDsByCatalogCourse(ctx context.Context, catalogC
|
|||
return s.queries.ExamPrepListUnitIDsByCatalogCourse(ctx, catalogCourseID)
|
||||
}
|
||||
|
||||
func (s *Store) ListPublishedExamPrepUnitIDsByCatalogCourse(ctx context.Context, catalogCourseID int64) ([]int64, error) {
|
||||
return s.queries.ExamPrepListPublishedUnitIDsByCatalogCourse(ctx, catalogCourseID)
|
||||
}
|
||||
|
||||
func (s *Store) UpdateExamPrepUnit(ctx context.Context, id int64, input domain.UpdateExamPrepUnitInput) (domain.ExamPrepUnit, error) {
|
||||
var nameText pgtype.Text
|
||||
if input.Name != nil {
|
||||
|
|
@ -142,11 +153,12 @@ func (s *Store) UpdateExamPrepUnit(ctx context.Context, id int64, input domain.U
|
|||
nameText = pgtype.Text{Valid: false}
|
||||
}
|
||||
u, err := s.queries.ExamPrepUpdateUnit(ctx, dbgen.ExamPrepUpdateUnitParams{
|
||||
ID: id,
|
||||
Name: nameText,
|
||||
Description: optionalTextUpdate(input.Description),
|
||||
Thumbnail: optionalTextUpdate(input.Thumbnail),
|
||||
SortOrder: optionalInt4Update(input.SortOrder),
|
||||
ID: id,
|
||||
Name: nameText,
|
||||
Description: optionalTextUpdate(input.Description),
|
||||
Thumbnail: optionalTextUpdate(input.Thumbnail),
|
||||
SortOrder: optionalInt4Update(input.SortOrder),
|
||||
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
|
|
|
|||
|
|
@ -13,9 +13,10 @@ import (
|
|||
|
||||
func courseToDomain(c dbgen.Course) domain.Course {
|
||||
out := domain.Course{
|
||||
ID: c.ID,
|
||||
ProgramID: c.ProgramID,
|
||||
Name: c.Name,
|
||||
ID: c.ID,
|
||||
ProgramID: c.ProgramID,
|
||||
Name: c.Name,
|
||||
PublishStatus: domain.ContentPublishStatusFromDB(c.PublishStatus),
|
||||
}
|
||||
out.Description = fromPgText(c.Description)
|
||||
out.Thumbnail = fromPgText(c.Thumbnail)
|
||||
|
|
@ -29,6 +30,7 @@ func courseToDomain(c dbgen.Course) domain.Course {
|
|||
}
|
||||
|
||||
func (s *Store) CreateCourse(ctx context.Context, programID int64, input domain.CreateCourseInput) (domain.Course, error) {
|
||||
pub := string(domain.ContentPublishStatusFromCreateInput(input.PublishStatus))
|
||||
if input.SortOrder != nil {
|
||||
q, tx, err := s.BeginTx(ctx)
|
||||
if err != nil {
|
||||
|
|
@ -43,11 +45,12 @@ func (s *Store) CreateCourse(ctx context.Context, programID int64, input domain.
|
|||
return domain.Course{}, err
|
||||
}
|
||||
c, err := q.CreateCourse(ctx, dbgen.CreateCourseParams{
|
||||
ProgramID: programID,
|
||||
Name: input.Name,
|
||||
Description: toPgText(input.Description),
|
||||
Thumbnail: toPgText(input.Thumbnail),
|
||||
SortOrder: pgtype.Int4{Int32: target, Valid: true},
|
||||
ProgramID: programID,
|
||||
Name: input.Name,
|
||||
Description: toPgText(input.Description),
|
||||
Thumbnail: toPgText(input.Thumbnail),
|
||||
SortOrder: pgtype.Int4{Int32: target, Valid: true},
|
||||
PublishStatus: pub,
|
||||
})
|
||||
if err != nil {
|
||||
return domain.Course{}, err
|
||||
|
|
@ -59,11 +62,12 @@ func (s *Store) CreateCourse(ctx context.Context, programID int64, input domain.
|
|||
}
|
||||
|
||||
c, err := s.queries.CreateCourse(ctx, dbgen.CreateCourseParams{
|
||||
ProgramID: programID,
|
||||
Name: input.Name,
|
||||
Description: toPgText(input.Description),
|
||||
Thumbnail: toPgText(input.Thumbnail),
|
||||
SortOrder: pgtype.Int4{Valid: false},
|
||||
ProgramID: programID,
|
||||
Name: input.Name,
|
||||
Description: toPgText(input.Description),
|
||||
Thumbnail: toPgText(input.Thumbnail),
|
||||
SortOrder: pgtype.Int4{Valid: false},
|
||||
PublishStatus: pub,
|
||||
})
|
||||
if err != nil {
|
||||
return domain.Course{}, err
|
||||
|
|
@ -75,6 +79,10 @@ func (s *Store) ListCourseIDsByProgram(ctx context.Context, programID int64) ([]
|
|||
return s.queries.ListCourseIDsByProgram(ctx, programID)
|
||||
}
|
||||
|
||||
func (s *Store) ListPublishedCourseIDsByProgram(ctx context.Context, programID int64) ([]int64, error) {
|
||||
return s.queries.ListPublishedCourseIDsByProgram(ctx, programID)
|
||||
}
|
||||
|
||||
func (s *Store) GetCourseByID(ctx context.Context, id int64) (domain.Course, error) {
|
||||
c, err := s.queries.GetCourseByID(ctx, id)
|
||||
if err != nil {
|
||||
|
|
@ -84,24 +92,26 @@ func (s *Store) GetCourseByID(ctx context.Context, id int64) (domain.Course, err
|
|||
return domain.Course{}, err
|
||||
}
|
||||
out := courseToDomain(dbgen.Course{
|
||||
ID: c.ID,
|
||||
ProgramID: c.ProgramID,
|
||||
Name: c.Name,
|
||||
Description: c.Description,
|
||||
Thumbnail: c.Thumbnail,
|
||||
SortOrder: c.SortOrder,
|
||||
CreatedAt: c.CreatedAt,
|
||||
UpdatedAt: c.UpdatedAt,
|
||||
ID: c.ID,
|
||||
ProgramID: c.ProgramID,
|
||||
Name: c.Name,
|
||||
Description: c.Description,
|
||||
Thumbnail: c.Thumbnail,
|
||||
SortOrder: c.SortOrder,
|
||||
CreatedAt: c.CreatedAt,
|
||||
UpdatedAt: c.UpdatedAt,
|
||||
PublishStatus: c.PublishStatus,
|
||||
})
|
||||
out.HasPractice = c.HasPractice
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *Store) ListCoursesByProgramID(ctx context.Context, programID int64, limit, offset int32) ([]domain.Course, int64, error) {
|
||||
func (s *Store) ListCoursesByProgramID(ctx context.Context, programID int64, publishedOnly bool, limit, offset int32) ([]domain.Course, int64, error) {
|
||||
rows, err := s.queries.ListCoursesByProgramID(ctx, dbgen.ListCoursesByProgramIDParams{
|
||||
ProgramID: programID,
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
ProgramID: programID,
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
PublishedOnly: publishedOnly,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
|
|
@ -116,14 +126,15 @@ func (s *Store) ListCoursesByProgramID(ctx context.Context, programID int64, lim
|
|||
total = r.TotalCount
|
||||
}
|
||||
co := courseToDomain(dbgen.Course{
|
||||
ID: r.ID,
|
||||
ProgramID: r.ProgramID,
|
||||
Name: r.Name,
|
||||
Description: r.Description,
|
||||
Thumbnail: r.Thumbnail,
|
||||
CreatedAt: r.CreatedAt,
|
||||
UpdatedAt: r.UpdatedAt,
|
||||
SortOrder: r.SortOrder,
|
||||
ID: r.ID,
|
||||
ProgramID: r.ProgramID,
|
||||
Name: r.Name,
|
||||
Description: r.Description,
|
||||
Thumbnail: r.Thumbnail,
|
||||
CreatedAt: r.CreatedAt,
|
||||
UpdatedAt: r.UpdatedAt,
|
||||
SortOrder: r.SortOrder,
|
||||
PublishStatus: r.PublishStatus,
|
||||
})
|
||||
co.ModuleCount = int(r.ModuleCount)
|
||||
co.LessonCount = int(r.LessonCount)
|
||||
|
|
@ -160,11 +171,12 @@ func (s *Store) UpdateCourse(ctx context.Context, id int64, input domain.UpdateC
|
|||
return domain.Course{}, err
|
||||
}
|
||||
c, err := q.UpdateCourse(ctx, dbgen.UpdateCourseParams{
|
||||
ID: id,
|
||||
Name: nameText,
|
||||
Description: optionalTextUpdate(input.Description),
|
||||
Thumbnail: optionalTextUpdate(input.Thumbnail),
|
||||
SortOrder: pgtype.Int4{Valid: false},
|
||||
ID: id,
|
||||
Name: nameText,
|
||||
Description: optionalTextUpdate(input.Description),
|
||||
Thumbnail: optionalTextUpdate(input.Thumbnail),
|
||||
SortOrder: pgtype.Int4{Valid: false},
|
||||
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
|
||||
})
|
||||
if err != nil {
|
||||
return domain.Course{}, err
|
||||
|
|
@ -180,11 +192,12 @@ func (s *Store) UpdateCourse(ctx context.Context, id int64, input domain.UpdateC
|
|||
}
|
||||
|
||||
c, err := s.queries.UpdateCourse(ctx, dbgen.UpdateCourseParams{
|
||||
ID: id,
|
||||
Name: nameText,
|
||||
Description: optionalTextUpdate(input.Description),
|
||||
Thumbnail: optionalTextUpdate(input.Thumbnail),
|
||||
SortOrder: sortParam,
|
||||
ID: id,
|
||||
Name: nameText,
|
||||
Description: optionalTextUpdate(input.Description),
|
||||
Thumbnail: optionalTextUpdate(input.Thumbnail),
|
||||
SortOrder: sortParam,
|
||||
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
|
|
|
|||
|
|
@ -13,10 +13,11 @@ import (
|
|||
|
||||
func moduleToDomain(m dbgen.Module) domain.Module {
|
||||
out := domain.Module{
|
||||
ID: m.ID,
|
||||
ProgramID: m.ProgramID,
|
||||
CourseID: m.CourseID,
|
||||
Name: m.Name,
|
||||
ID: m.ID,
|
||||
ProgramID: m.ProgramID,
|
||||
CourseID: m.CourseID,
|
||||
Name: m.Name,
|
||||
PublishStatus: domain.ContentPublishStatusFromDB(m.PublishStatus),
|
||||
}
|
||||
out.Description = fromPgText(m.Description)
|
||||
out.Icon = fromPgText(m.Icon)
|
||||
|
|
@ -30,6 +31,7 @@ func moduleToDomain(m dbgen.Module) domain.Module {
|
|||
}
|
||||
|
||||
func (s *Store) CreateModule(ctx context.Context, programID, courseID int64, input domain.CreateModuleInput) (domain.Module, error) {
|
||||
pub := string(domain.ContentPublishStatusFromCreateInput(input.PublishStatus))
|
||||
if input.SortOrder != nil {
|
||||
q, tx, err := s.BeginTx(ctx)
|
||||
if err != nil {
|
||||
|
|
@ -44,12 +46,13 @@ func (s *Store) CreateModule(ctx context.Context, programID, courseID int64, inp
|
|||
return domain.Module{}, err
|
||||
}
|
||||
m, err := q.CreateModule(ctx, dbgen.CreateModuleParams{
|
||||
ProgramID: programID,
|
||||
CourseID: courseID,
|
||||
Name: input.Name,
|
||||
Description: toPgText(input.Description),
|
||||
Icon: toPgText(input.Icon),
|
||||
SortOrder: pgtype.Int4{Int32: target, Valid: true},
|
||||
ProgramID: programID,
|
||||
CourseID: courseID,
|
||||
Name: input.Name,
|
||||
Description: toPgText(input.Description),
|
||||
Icon: toPgText(input.Icon),
|
||||
SortOrder: pgtype.Int4{Int32: target, Valid: true},
|
||||
PublishStatus: pub,
|
||||
})
|
||||
if err != nil {
|
||||
return domain.Module{}, err
|
||||
|
|
@ -61,12 +64,13 @@ func (s *Store) CreateModule(ctx context.Context, programID, courseID int64, inp
|
|||
}
|
||||
|
||||
m, err := s.queries.CreateModule(ctx, dbgen.CreateModuleParams{
|
||||
ProgramID: programID,
|
||||
CourseID: courseID,
|
||||
Name: input.Name,
|
||||
Description: toPgText(input.Description),
|
||||
Icon: toPgText(input.Icon),
|
||||
SortOrder: pgtype.Int4{Valid: false},
|
||||
ProgramID: programID,
|
||||
CourseID: courseID,
|
||||
Name: input.Name,
|
||||
Description: toPgText(input.Description),
|
||||
Icon: toPgText(input.Icon),
|
||||
SortOrder: pgtype.Int4{Valid: false},
|
||||
PublishStatus: pub,
|
||||
})
|
||||
if err != nil {
|
||||
return domain.Module{}, err
|
||||
|
|
@ -78,6 +82,10 @@ func (s *Store) ListModuleIDsByCourse(ctx context.Context, courseID int64) ([]in
|
|||
return s.queries.ListModuleIDsByCourse(ctx, courseID)
|
||||
}
|
||||
|
||||
func (s *Store) ListPublishedModuleIDsByCourse(ctx context.Context, courseID int64) ([]int64, error) {
|
||||
return s.queries.ListPublishedModuleIDsByCourse(ctx, courseID)
|
||||
}
|
||||
|
||||
func (s *Store) GetModuleByID(ctx context.Context, id int64) (domain.Module, error) {
|
||||
m, err := s.queries.GetModuleByID(ctx, id)
|
||||
if err != nil {
|
||||
|
|
@ -87,26 +95,28 @@ func (s *Store) GetModuleByID(ctx context.Context, id int64) (domain.Module, err
|
|||
return domain.Module{}, err
|
||||
}
|
||||
out := moduleToDomain(dbgen.Module{
|
||||
ID: m.ID,
|
||||
ProgramID: m.ProgramID,
|
||||
CourseID: m.CourseID,
|
||||
Name: m.Name,
|
||||
Description: m.Description,
|
||||
Icon: m.Icon,
|
||||
SortOrder: m.SortOrder,
|
||||
CreatedAt: m.CreatedAt,
|
||||
UpdatedAt: m.UpdatedAt,
|
||||
ID: m.ID,
|
||||
ProgramID: m.ProgramID,
|
||||
CourseID: m.CourseID,
|
||||
Name: m.Name,
|
||||
Description: m.Description,
|
||||
Icon: m.Icon,
|
||||
SortOrder: m.SortOrder,
|
||||
CreatedAt: m.CreatedAt,
|
||||
UpdatedAt: m.UpdatedAt,
|
||||
PublishStatus: m.PublishStatus,
|
||||
})
|
||||
out.HasPractice = m.HasPractice
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *Store) ListModulesByProgramAndCourse(ctx context.Context, programID, courseID int64, limit, offset int32) ([]domain.Module, int64, error) {
|
||||
func (s *Store) ListModulesByProgramAndCourse(ctx context.Context, programID, courseID int64, publishedOnly bool, limit, offset int32) ([]domain.Module, int64, error) {
|
||||
rows, err := s.queries.ListModulesByProgramAndCourse(ctx, dbgen.ListModulesByProgramAndCourseParams{
|
||||
ProgramID: programID,
|
||||
CourseID: courseID,
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
ProgramID: programID,
|
||||
CourseID: courseID,
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
PublishedOnly: publishedOnly,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
|
|
@ -121,15 +131,16 @@ func (s *Store) ListModulesByProgramAndCourse(ctx context.Context, programID, co
|
|||
total = r.TotalCount
|
||||
}
|
||||
mod := moduleToDomain(dbgen.Module{
|
||||
ID: r.ID,
|
||||
ProgramID: r.ProgramID,
|
||||
CourseID: r.CourseID,
|
||||
Name: r.Name,
|
||||
Description: r.Description,
|
||||
Icon: r.Icon,
|
||||
CreatedAt: r.CreatedAt,
|
||||
UpdatedAt: r.UpdatedAt,
|
||||
SortOrder: r.SortOrder,
|
||||
ID: r.ID,
|
||||
ProgramID: r.ProgramID,
|
||||
CourseID: r.CourseID,
|
||||
Name: r.Name,
|
||||
Description: r.Description,
|
||||
Icon: r.Icon,
|
||||
CreatedAt: r.CreatedAt,
|
||||
UpdatedAt: r.UpdatedAt,
|
||||
SortOrder: r.SortOrder,
|
||||
PublishStatus: r.PublishStatus,
|
||||
})
|
||||
mod.HasPractice = r.HasPractice
|
||||
out = append(out, mod)
|
||||
|
|
@ -168,11 +179,12 @@ func (s *Store) UpdateModule(ctx context.Context, id int64, input domain.UpdateM
|
|||
nameText = pgtype.Text{Valid: false}
|
||||
}
|
||||
m, err := q.UpdateModule(ctx, dbgen.UpdateModuleParams{
|
||||
ID: id,
|
||||
Name: nameText,
|
||||
Description: optionalTextUpdate(input.Description),
|
||||
Icon: optionalTextUpdate(input.Icon),
|
||||
SortOrder: sortParam,
|
||||
ID: id,
|
||||
Name: nameText,
|
||||
Description: optionalTextUpdate(input.Description),
|
||||
Icon: optionalTextUpdate(input.Icon),
|
||||
SortOrder: sortParam,
|
||||
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
|
|
|
|||
|
|
@ -14,9 +14,10 @@ import (
|
|||
|
||||
func programToDomain(p dbgen.Program) domain.Program {
|
||||
out := domain.Program{
|
||||
ID: p.ID,
|
||||
Name: p.Name,
|
||||
Category: p.Category,
|
||||
ID: p.ID,
|
||||
Name: p.Name,
|
||||
Category: p.Category,
|
||||
PublishStatus: domain.ContentPublishStatusFromDB(p.PublishStatus),
|
||||
}
|
||||
out.Description = fromPgText(p.Description)
|
||||
out.Thumbnail = fromPgText(p.Thumbnail)
|
||||
|
|
@ -30,6 +31,7 @@ func programToDomain(p dbgen.Program) domain.Program {
|
|||
}
|
||||
|
||||
func (s *Store) CreateProgram(ctx context.Context, input domain.CreateProgramInput) (domain.Program, error) {
|
||||
pub := string(domain.ContentPublishStatusFromCreateInput(input.PublishStatus))
|
||||
if input.SortOrder != nil {
|
||||
q, tx, err := s.BeginTx(ctx)
|
||||
if err != nil {
|
||||
|
|
@ -41,11 +43,12 @@ func (s *Store) CreateProgram(ctx context.Context, input domain.CreateProgramInp
|
|||
return domain.Program{}, err
|
||||
}
|
||||
p, err := q.CreateProgram(ctx, dbgen.CreateProgramParams{
|
||||
Name: input.Name,
|
||||
Description: toPgText(input.Description),
|
||||
Category: input.Category,
|
||||
Thumbnail: toPgText(input.Thumbnail),
|
||||
SortOrder: pgtype.Int4{Int32: target, Valid: true},
|
||||
Name: input.Name,
|
||||
Description: toPgText(input.Description),
|
||||
Category: input.Category,
|
||||
Thumbnail: toPgText(input.Thumbnail),
|
||||
SortOrder: pgtype.Int4{Int32: target, Valid: true},
|
||||
PublishStatus: pub,
|
||||
})
|
||||
if err != nil {
|
||||
return domain.Program{}, err
|
||||
|
|
@ -57,11 +60,12 @@ func (s *Store) CreateProgram(ctx context.Context, input domain.CreateProgramInp
|
|||
}
|
||||
|
||||
p, err := s.queries.CreateProgram(ctx, dbgen.CreateProgramParams{
|
||||
Name: input.Name,
|
||||
Description: toPgText(input.Description),
|
||||
Category: input.Category,
|
||||
Thumbnail: toPgText(input.Thumbnail),
|
||||
SortOrder: pgtype.Int4{Valid: false},
|
||||
Name: input.Name,
|
||||
Description: toPgText(input.Description),
|
||||
Category: input.Category,
|
||||
Thumbnail: toPgText(input.Thumbnail),
|
||||
SortOrder: pgtype.Int4{Valid: false},
|
||||
PublishStatus: pub,
|
||||
})
|
||||
if err != nil {
|
||||
return domain.Program{}, err
|
||||
|
|
@ -84,10 +88,11 @@ func (s *Store) GetProgramByID(ctx context.Context, id int64) (domain.Program, e
|
|||
return programToDomain(p), nil
|
||||
}
|
||||
|
||||
func (s *Store) ListPrograms(ctx context.Context, limit, offset int32) ([]domain.Program, int64, error) {
|
||||
func (s *Store) ListPrograms(ctx context.Context, publishedOnly bool, limit, offset int32) ([]domain.Program, int64, error) {
|
||||
rows, err := s.queries.ListPrograms(ctx, dbgen.ListProgramsParams{
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
PublishedOnly: publishedOnly,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
|
|
@ -102,14 +107,15 @@ func (s *Store) ListPrograms(ctx context.Context, limit, offset int32) ([]domain
|
|||
total = r.TotalCount
|
||||
}
|
||||
out = append(out, programToDomain(dbgen.Program{
|
||||
ID: r.ID,
|
||||
Name: r.Name,
|
||||
Description: r.Description,
|
||||
Category: r.Category,
|
||||
Thumbnail: r.Thumbnail,
|
||||
CreatedAt: r.CreatedAt,
|
||||
UpdatedAt: r.UpdatedAt,
|
||||
SortOrder: r.SortOrder,
|
||||
ID: r.ID,
|
||||
Name: r.Name,
|
||||
Description: r.Description,
|
||||
Category: r.Category,
|
||||
Thumbnail: r.Thumbnail,
|
||||
CreatedAt: r.CreatedAt,
|
||||
UpdatedAt: r.UpdatedAt,
|
||||
SortOrder: r.SortOrder,
|
||||
PublishStatus: r.PublishStatus,
|
||||
}))
|
||||
}
|
||||
return out, total, nil
|
||||
|
|
@ -167,12 +173,13 @@ func (s *Store) UpdateProgram(ctx context.Context, id int64, input domain.Update
|
|||
nameText = pgtype.Text{Valid: false}
|
||||
}
|
||||
p, err := q.UpdateProgram(ctx, dbgen.UpdateProgramParams{
|
||||
ID: id,
|
||||
Name: nameText,
|
||||
Description: optionalTextUpdate(input.Description),
|
||||
Category: optionalTextUpdate(input.Category),
|
||||
Thumbnail: optionalTextUpdate(input.Thumbnail),
|
||||
SortOrder: pgtype.Int4{Valid: false},
|
||||
ID: id,
|
||||
Name: nameText,
|
||||
Description: optionalTextUpdate(input.Description),
|
||||
Category: optionalTextUpdate(input.Category),
|
||||
Thumbnail: optionalTextUpdate(input.Thumbnail),
|
||||
SortOrder: pgtype.Int4{Valid: false},
|
||||
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
|
||||
})
|
||||
if err != nil {
|
||||
return domain.Program{}, err
|
||||
|
|
@ -192,12 +199,13 @@ func (s *Store) UpdateProgram(ctx context.Context, id int64, input domain.Update
|
|||
nameText = pgtype.Text{Valid: false}
|
||||
}
|
||||
p, err := s.queries.UpdateProgram(ctx, dbgen.UpdateProgramParams{
|
||||
ID: id,
|
||||
Name: nameText,
|
||||
Description: optionalTextUpdate(input.Description),
|
||||
Category: optionalTextUpdate(input.Category),
|
||||
Thumbnail: optionalTextUpdate(input.Thumbnail),
|
||||
SortOrder: sortParam,
|
||||
ID: id,
|
||||
Name: nameText,
|
||||
Description: optionalTextUpdate(input.Description),
|
||||
Category: optionalTextUpdate(input.Category),
|
||||
Thumbnail: optionalTextUpdate(input.Thumbnail),
|
||||
SortOrder: sortParam,
|
||||
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ func (s *Service) GetByID(ctx context.Context, id int64) (domain.Course, error)
|
|||
return c, nil
|
||||
}
|
||||
|
||||
func (s *Service) ListByProgram(ctx context.Context, programID int64, limit, offset int32) ([]domain.Course, int64, error) {
|
||||
func (s *Service) ListByProgram(ctx context.Context, programID int64, publishedOnly bool, limit, offset int32) ([]domain.Course, int64, error) {
|
||||
if _, err := s.programs.GetProgramByID(ctx, programID); err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, 0, programs.ErrProgramNotFound
|
||||
|
|
@ -58,7 +58,7 @@ func (s *Service) ListByProgram(ctx context.Context, programID int64, limit, off
|
|||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
return s.courses.ListCoursesByProgramID(ctx, programID, limit, offset)
|
||||
return s.courses.ListCoursesByProgramID(ctx, programID, publishedOnly, limit, offset)
|
||||
}
|
||||
|
||||
func (s *Service) Update(ctx context.Context, id int64, input domain.UpdateCourseInput) (domain.Course, error) {
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ func (s *Service) GetCatalogCourseByID(ctx context.Context, id int64) (domain.Ex
|
|||
return c, nil
|
||||
}
|
||||
|
||||
func (s *Service) ListCatalogCourses(ctx context.Context, limit, offset int32) ([]domain.ExamPrepCatalogCourse, int64, error) {
|
||||
func (s *Service) ListCatalogCourses(ctx context.Context, publishedOnly bool, limit, offset int32) ([]domain.ExamPrepCatalogCourse, int64, error) {
|
||||
if limit <= 0 {
|
||||
limit = 20
|
||||
}
|
||||
|
|
@ -69,7 +69,7 @@ func (s *Service) ListCatalogCourses(ctx context.Context, limit, offset int32) (
|
|||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
return s.store.ListExamPrepCatalogCourses(ctx, limit, offset)
|
||||
return s.store.ListExamPrepCatalogCourses(ctx, publishedOnly, limit, offset)
|
||||
}
|
||||
|
||||
func (s *Service) UpdateCatalogCourse(ctx context.Context, id int64, input domain.UpdateExamPrepCatalogCourseInput) (domain.ExamPrepCatalogCourse, error) {
|
||||
|
|
@ -124,7 +124,7 @@ func (s *Service) CreateUnit(ctx context.Context, catalogCourseID int64, input d
|
|||
return s.store.CreateExamPrepUnit(ctx, catalogCourseID, input)
|
||||
}
|
||||
|
||||
func (s *Service) ListUnitsByCatalogCourse(ctx context.Context, catalogCourseID int64, limit, offset int32) ([]domain.ExamPrepUnit, int64, error) {
|
||||
func (s *Service) ListUnitsByCatalogCourse(ctx context.Context, catalogCourseID int64, publishedOnly bool, limit, offset int32) ([]domain.ExamPrepUnit, int64, error) {
|
||||
if err := s.ensureCatalogCourse(ctx, catalogCourseID); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
|
@ -137,7 +137,7 @@ func (s *Service) ListUnitsByCatalogCourse(ctx context.Context, catalogCourseID
|
|||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
return s.store.ListExamPrepUnitsByCatalogCourse(ctx, catalogCourseID, limit, offset)
|
||||
return s.store.ListExamPrepUnitsByCatalogCourse(ctx, catalogCourseID, publishedOnly, limit, offset)
|
||||
}
|
||||
|
||||
func (s *Service) GetUnitByID(ctx context.Context, id int64) (domain.ExamPrepUnit, error) {
|
||||
|
|
@ -206,7 +206,7 @@ func (s *Service) CreateModule(ctx context.Context, unitID int64, input domain.C
|
|||
return s.store.CreateExamPrepUnitModule(ctx, unitID, input)
|
||||
}
|
||||
|
||||
func (s *Service) ListModulesByUnit(ctx context.Context, unitID int64, limit, offset int32) ([]domain.ExamPrepModule, int64, error) {
|
||||
func (s *Service) ListModulesByUnit(ctx context.Context, unitID int64, publishedOnly bool, limit, offset int32) ([]domain.ExamPrepModule, int64, error) {
|
||||
if err := s.ensureUnit(ctx, unitID); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
|
@ -219,7 +219,7 @@ func (s *Service) ListModulesByUnit(ctx context.Context, unitID int64, limit, of
|
|||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
return s.store.ListExamPrepUnitModulesByUnit(ctx, unitID, limit, offset)
|
||||
return s.store.ListExamPrepUnitModulesByUnit(ctx, unitID, publishedOnly, limit, offset)
|
||||
}
|
||||
|
||||
func (s *Service) GetModuleByID(ctx context.Context, id int64) (domain.ExamPrepModule, error) {
|
||||
|
|
@ -288,7 +288,7 @@ func (s *Service) CreateLesson(ctx context.Context, unitModuleID int64, input do
|
|||
return s.store.CreateExamPrepUnitModuleLesson(ctx, unitModuleID, input)
|
||||
}
|
||||
|
||||
func (s *Service) ListLessonsByUnitModule(ctx context.Context, unitModuleID int64, limit, offset int32) ([]domain.ExamPrepLesson, int64, error) {
|
||||
func (s *Service) ListLessonsByUnitModule(ctx context.Context, unitModuleID int64, publishedOnly bool, limit, offset int32) ([]domain.ExamPrepLesson, int64, error) {
|
||||
if err := s.ensureModule(ctx, unitModuleID); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
|
@ -301,7 +301,7 @@ func (s *Service) ListLessonsByUnitModule(ctx context.Context, unitModuleID int6
|
|||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
return s.store.ListExamPrepUnitModuleLessonsByUnitModuleID(ctx, unitModuleID, limit, offset)
|
||||
return s.store.ListExamPrepUnitModuleLessonsByUnitModuleID(ctx, unitModuleID, publishedOnly, limit, offset)
|
||||
}
|
||||
|
||||
func (s *Service) GetLessonByID(ctx context.Context, id int64) (domain.ExamPrepLesson, error) {
|
||||
|
|
|
|||
|
|
@ -345,7 +345,7 @@ func (s *Service) lmsCourseProgress(ctx context.Context, userID, courseID int64)
|
|||
return 1, true, 1, 1, nil
|
||||
}
|
||||
|
||||
moduleIDs, err := s.store.ListModuleIDsByCourse(ctx, courseID)
|
||||
moduleIDs, err := s.store.ListPublishedModuleIDsByCourse(ctx, courseID)
|
||||
if err != nil {
|
||||
return 0, false, 0, 0, err
|
||||
}
|
||||
|
|
@ -373,7 +373,7 @@ func (s *Service) lmsCourseProgress(ctx context.Context, userID, courseID int64)
|
|||
}
|
||||
|
||||
func (s *Service) lmsProgramProgress(ctx context.Context, userID, programID int64) (fraction float64, done bool, completed, total int32, err error) {
|
||||
courseIDs, err := s.store.ListCourseIDsByProgram(ctx, programID)
|
||||
courseIDs, err := s.store.ListPublishedCourseIDsByProgram(ctx, programID)
|
||||
if err != nil {
|
||||
return 0, false, 0, 0, err
|
||||
}
|
||||
|
|
@ -428,7 +428,7 @@ func (s *Service) examPrepLessonProgress(ctx context.Context, userID, lessonID i
|
|||
}
|
||||
|
||||
func (s *Service) examPrepModuleProgress(ctx context.Context, userID, moduleID int64) (fraction float64, done bool, completed, total int32, err error) {
|
||||
lessonIDs, err := s.store.ListExamPrepUnitModuleLessonIDsByUnitModule(ctx, moduleID)
|
||||
lessonIDs, err := s.store.ListPublishedExamPrepUnitModuleLessonIDsByUnitModule(ctx, moduleID)
|
||||
if err != nil {
|
||||
return 0, false, 0, 0, err
|
||||
}
|
||||
|
|
@ -456,7 +456,7 @@ func (s *Service) examPrepModuleProgress(ctx context.Context, userID, moduleID i
|
|||
}
|
||||
|
||||
func (s *Service) examPrepUnitProgress(ctx context.Context, userID, unitID int64) (fraction float64, done bool, completed, total int32, err error) {
|
||||
moduleIDs, err := s.store.ListExamPrepUnitModuleIDsByUnit(ctx, unitID)
|
||||
moduleIDs, err := s.store.ListPublishedExamPrepUnitModuleIDsByUnit(ctx, unitID)
|
||||
if err != nil {
|
||||
return 0, false, 0, 0, err
|
||||
}
|
||||
|
|
@ -484,7 +484,7 @@ func (s *Service) examPrepUnitProgress(ctx context.Context, userID, unitID int64
|
|||
}
|
||||
|
||||
func (s *Service) examPrepCatalogCourseProgress(ctx context.Context, userID, catalogCourseID int64) (fraction float64, done bool, completed, total int32, err error) {
|
||||
unitIDs, err := s.store.ListExamPrepUnitIDsByCatalogCourse(ctx, catalogCourseID)
|
||||
unitIDs, err := s.store.ListPublishedExamPrepUnitIDsByCatalogCourse(ctx, catalogCourseID)
|
||||
if err != nil {
|
||||
return 0, false, 0, 0, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ func (s *Service) GetByID(ctx context.Context, id int64) (domain.Module, error)
|
|||
}
|
||||
|
||||
// ListByCourse loads the course and lists modules for its program_id and course_id.
|
||||
func (s *Service) ListByCourse(ctx context.Context, courseID int64, limit, offset int32) ([]domain.Module, int64, error) {
|
||||
func (s *Service) ListByCourse(ctx context.Context, courseID int64, publishedOnly bool, limit, offset int32) ([]domain.Module, int64, error) {
|
||||
c, err := s.getCourseOrErr(ctx, courseID)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
|
|
@ -67,7 +67,7 @@ func (s *Service) ListByCourse(ctx context.Context, courseID int64, limit, offse
|
|||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
return s.modules.ListModulesByProgramAndCourse(ctx, c.ProgramID, courseID, limit, offset)
|
||||
return s.modules.ListModulesByProgramAndCourse(ctx, c.ProgramID, courseID, publishedOnly, limit, offset)
|
||||
}
|
||||
|
||||
func (s *Service) Update(ctx context.Context, id int64, input domain.UpdateModuleInput) (domain.Module, error) {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ func (s *Service) GetByID(ctx context.Context, id int64) (domain.Program, error)
|
|||
return p, nil
|
||||
}
|
||||
|
||||
func (s *Service) List(ctx context.Context, limit, offset int32) ([]domain.Program, int64, error) {
|
||||
func (s *Service) List(ctx context.Context, publishedOnly bool, limit, offset int32) ([]domain.Program, int64, error) {
|
||||
if limit <= 0 {
|
||||
limit = 20
|
||||
}
|
||||
|
|
@ -44,7 +44,7 @@ func (s *Service) List(ctx context.Context, limit, offset int32) ([]domain.Progr
|
|||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
return s.store.ListPrograms(ctx, limit, offset)
|
||||
return s.store.ListPrograms(ctx, publishedOnly, limit, offset)
|
||||
}
|
||||
|
||||
func (s *Service) Update(ctx context.Context, id int64, input domain.UpdateProgramInput) (domain.Program, error) {
|
||||
|
|
|
|||
|
|
@ -44,6 +44,12 @@ func (s *Service) ListQuestionTypeDefinitions(ctx context.Context, status *strin
|
|||
return s.questionStore.ListQuestionTypeDefinitions(ctx, status, includeSystem, limit, offset)
|
||||
}
|
||||
|
||||
func (s *Service) ActiveQuestionTypeDefinitionCatalog(ctx context.Context) ([]domain.QuestionTypeDefinition, error) {
|
||||
active := "ACTIVE"
|
||||
catalog, _, err := s.questionStore.ListQuestionTypeDefinitions(ctx, &active, true, 0, 0)
|
||||
return catalog, err
|
||||
}
|
||||
|
||||
func (s *Service) UpdateQuestionTypeDefinition(ctx context.Context, id int64, input domain.UpdateQuestionTypeDefinitionInput) error {
|
||||
return s.questionStore.UpdateQuestionTypeDefinition(ctx, id, input)
|
||||
}
|
||||
|
|
|
|||
45
internal/web_server/handlers/content_publish_gate.go
Normal file
45
internal/web_server/handlers/content_publish_gate.go
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"Yimaru-Backend/internal/domain"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// Manage helpers for hierarchy publish gating: staff who can create or update an entity see
|
||||
// drafts; everyone else only sees PUBLISHED rows (mirrors canManageLessons / practices).
|
||||
|
||||
func (h *Handler) canManagePrograms(c *fiber.Ctx) bool {
|
||||
rn := string(c.Locals("role").(domain.Role))
|
||||
return h.rbacSvc.HasPermission(rn, "programs.create") || h.rbacSvc.HasPermission(rn, "programs.update")
|
||||
}
|
||||
|
||||
func (h *Handler) canManageLMSCourses(c *fiber.Ctx) bool {
|
||||
rn := string(c.Locals("role").(domain.Role))
|
||||
return h.rbacSvc.HasPermission(rn, "courses.create") || h.rbacSvc.HasPermission(rn, "courses.update")
|
||||
}
|
||||
|
||||
func (h *Handler) canManageLMSModules(c *fiber.Ctx) bool {
|
||||
rn := string(c.Locals("role").(domain.Role))
|
||||
return h.rbacSvc.HasPermission(rn, "modules.create") || h.rbacSvc.HasPermission(rn, "modules.update")
|
||||
}
|
||||
|
||||
func (h *Handler) canManageExamPrepCatalogCourses(c *fiber.Ctx) bool {
|
||||
rn := string(c.Locals("role").(domain.Role))
|
||||
return h.rbacSvc.HasPermission(rn, "exam_prep.catalog_courses.create") || h.rbacSvc.HasPermission(rn, "exam_prep.catalog_courses.update")
|
||||
}
|
||||
|
||||
func (h *Handler) canManageExamPrepUnits(c *fiber.Ctx) bool {
|
||||
rn := string(c.Locals("role").(domain.Role))
|
||||
return h.rbacSvc.HasPermission(rn, "exam_prep.units.create") || h.rbacSvc.HasPermission(rn, "exam_prep.units.update")
|
||||
}
|
||||
|
||||
func (h *Handler) canManageExamPrepModules(c *fiber.Ctx) bool {
|
||||
rn := string(c.Locals("role").(domain.Role))
|
||||
return h.rbacSvc.HasPermission(rn, "exam_prep.modules.create") || h.rbacSvc.HasPermission(rn, "exam_prep.modules.update")
|
||||
}
|
||||
|
||||
func (h *Handler) canManageExamPrepLessons(c *fiber.Ctx) bool {
|
||||
rn := string(c.Locals("role").(domain.Role))
|
||||
return h.rbacSvc.HasPermission(rn, "exam_prep.lessons.create") || h.rbacSvc.HasPermission(rn, "exam_prep.lessons.update")
|
||||
}
|
||||
|
|
@ -92,7 +92,18 @@ func (h *Handler) ListCoursesByProgram(c *fiber.Ctx) error {
|
|||
}
|
||||
limit, _ := strconv.Atoi(c.Query("limit", "20"))
|
||||
offset, _ := strconv.Atoi(c.Query("offset", "0"))
|
||||
items, total, err := h.courseSvc.ListByProgram(c.Context(), programID, int32(limit), int32(offset))
|
||||
publishedOnly := !h.canManageLMSCourses(c)
|
||||
if publishedOnly {
|
||||
// Draft programs hide their courses from non-managers.
|
||||
p, err := h.programSvc.GetByID(c.Context(), programID)
|
||||
if err == nil && !p.VisibleToLearners() {
|
||||
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
|
||||
Message: "Program not found",
|
||||
Error: programs.ErrProgramNotFound.Error(),
|
||||
})
|
||||
}
|
||||
}
|
||||
items, total, err := h.courseSvc.ListByProgram(c.Context(), programID, publishedOnly, int32(limit), int32(offset))
|
||||
if err != nil {
|
||||
if errors.Is(err, programs.ErrProgramNotFound) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
|
||||
|
|
@ -157,6 +168,12 @@ func (h *Handler) GetCourse(c *fiber.Ctx) error {
|
|||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
if !course.VisibleToLearners() && !h.canManageLMSCourses(c) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
|
||||
Message: "Course not found",
|
||||
Error: courses.ErrCourseNotFound.Error(),
|
||||
})
|
||||
}
|
||||
uid := c.Locals("user_id").(int64)
|
||||
role := c.Locals("role").(domain.Role)
|
||||
if err := h.lmsProgressSvc.ApplyAccessCourse(c.Context(), role, uid, &course); err != nil {
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ func (h *Handler) CreateExamPrepCatalogCourse(c *fiber.Ctx) error {
|
|||
func (h *Handler) ListExamPrepCatalogCourses(c *fiber.Ctx) error {
|
||||
limit, _ := strconv.Atoi(c.Query("limit", "20"))
|
||||
offset, _ := strconv.Atoi(c.Query("offset", "0"))
|
||||
publishedOnly := !h.canManageExamPrepCatalogCourses(c)
|
||||
|
||||
role, _ := c.Locals("role").(domain.Role)
|
||||
if role.IsCustomerLearnerRole() && !domain.CategorySubscriptionGateDisabled {
|
||||
|
|
@ -84,7 +85,7 @@ func (h *Handler) ListExamPrepCatalogCourses(c *fiber.Ctx) error {
|
|||
})
|
||||
}
|
||||
|
||||
allItems, _, err := h.examPrepSvc.ListCatalogCourses(c.Context(), 200, 0)
|
||||
allItems, _, err := h.examPrepSvc.ListCatalogCourses(c.Context(), publishedOnly, 200, 0)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to list catalog courses",
|
||||
|
|
@ -137,7 +138,7 @@ func (h *Handler) ListExamPrepCatalogCourses(c *fiber.Ctx) error {
|
|||
})
|
||||
}
|
||||
|
||||
items, total, err := h.examPrepSvc.ListCatalogCourses(c.Context(), int32(limit), int32(offset))
|
||||
items, total, err := h.examPrepSvc.ListCatalogCourses(c.Context(), publishedOnly, int32(limit), int32(offset))
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to list catalog courses",
|
||||
|
|
@ -232,6 +233,12 @@ func (h *Handler) GetExamPrepCatalogCourseByID(c *fiber.Ctx) error {
|
|||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
if !out.VisibleToLearners() && !h.canManageExamPrepCatalogCourses(c) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
|
||||
Message: "Catalog course not found",
|
||||
Error: examprep.ErrCatalogCourseNotFound.Error(),
|
||||
})
|
||||
}
|
||||
if err := h.applyExamPrepAccessCatalogCourse(c.Context(), c, &out); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to build catalog course",
|
||||
|
|
|
|||
|
|
@ -74,7 +74,18 @@ func (h *Handler) ListExamPrepLessonsByUnitModule(c *fiber.Ctx) error {
|
|||
}
|
||||
limit, _ := strconv.Atoi(c.Query("limit", "20"))
|
||||
offset, _ := strconv.Atoi(c.Query("offset", "0"))
|
||||
items, total, err := h.examPrepSvc.ListLessonsByUnitModule(c.Context(), moduleID, int32(limit), int32(offset))
|
||||
publishedOnly := !h.canManageExamPrepLessons(c)
|
||||
if publishedOnly {
|
||||
// Draft modules hide their lessons from non-managers.
|
||||
m, err := h.examPrepSvc.GetModuleByID(c.Context(), moduleID)
|
||||
if err == nil && !m.VisibleToLearners() {
|
||||
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
|
||||
Message: "Module not found",
|
||||
Error: examprep.ErrModuleNotFound.Error(),
|
||||
})
|
||||
}
|
||||
}
|
||||
items, total, err := h.examPrepSvc.ListLessonsByUnitModule(c.Context(), moduleID, publishedOnly, int32(limit), int32(offset))
|
||||
if err != nil {
|
||||
if errors.Is(err, examprep.ErrModuleNotFound) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
|
||||
|
|
@ -181,6 +192,12 @@ func (h *Handler) GetExamPrepLessonByID(c *fiber.Ctx) error {
|
|||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
if !les.VisibleToLearners() && !h.canManageExamPrepLessons(c) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
|
||||
Message: "Lesson not found",
|
||||
Error: examprep.ErrLessonNotFound.Error(),
|
||||
})
|
||||
}
|
||||
if err := h.applyExamPrepAccessLesson(c.Context(), c, &les); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to build lesson",
|
||||
|
|
|
|||
|
|
@ -72,7 +72,18 @@ func (h *Handler) ListExamPrepModulesByUnit(c *fiber.Ctx) error {
|
|||
}
|
||||
limit, _ := strconv.Atoi(c.Query("limit", "20"))
|
||||
offset, _ := strconv.Atoi(c.Query("offset", "0"))
|
||||
items, total, err := h.examPrepSvc.ListModulesByUnit(c.Context(), unitID, int32(limit), int32(offset))
|
||||
publishedOnly := !h.canManageExamPrepModules(c)
|
||||
if publishedOnly {
|
||||
// Draft units hide their modules from non-managers.
|
||||
u, err := h.examPrepSvc.GetUnitByID(c.Context(), unitID)
|
||||
if err == nil && !u.VisibleToLearners() {
|
||||
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
|
||||
Message: "Unit not found",
|
||||
Error: examprep.ErrUnitNotFound.Error(),
|
||||
})
|
||||
}
|
||||
}
|
||||
items, total, err := h.examPrepSvc.ListModulesByUnit(c.Context(), unitID, publishedOnly, int32(limit), int32(offset))
|
||||
if err != nil {
|
||||
if errors.Is(err, examprep.ErrUnitNotFound) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
|
||||
|
|
@ -181,6 +192,12 @@ func (h *Handler) GetExamPrepModuleByID(c *fiber.Ctx) error {
|
|||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
if !out.VisibleToLearners() && !h.canManageExamPrepModules(c) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
|
||||
Message: "Module not found",
|
||||
Error: examprep.ErrModuleNotFound.Error(),
|
||||
})
|
||||
}
|
||||
if err := h.applyExamPrepAccessModule(c.Context(), c, &out); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to build module",
|
||||
|
|
|
|||
|
|
@ -79,7 +79,18 @@ func (h *Handler) ListExamPrepUnitsByCatalogCourse(c *fiber.Ctx) error {
|
|||
}
|
||||
limit, _ := strconv.Atoi(c.Query("limit", "20"))
|
||||
offset, _ := strconv.Atoi(c.Query("offset", "0"))
|
||||
items, total, err := h.examPrepSvc.ListUnitsByCatalogCourse(c.Context(), catalogCourseID, int32(limit), int32(offset))
|
||||
publishedOnly := !h.canManageExamPrepUnits(c)
|
||||
if publishedOnly {
|
||||
// Draft catalog courses hide their units from non-managers.
|
||||
cc, err := h.examPrepSvc.GetCatalogCourseByID(c.Context(), catalogCourseID)
|
||||
if err == nil && !cc.VisibleToLearners() {
|
||||
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
|
||||
Message: "Catalog course not found",
|
||||
Error: examprep.ErrCatalogCourseNotFound.Error(),
|
||||
})
|
||||
}
|
||||
}
|
||||
items, total, err := h.examPrepSvc.ListUnitsByCatalogCourse(c.Context(), catalogCourseID, publishedOnly, int32(limit), int32(offset))
|
||||
if err != nil {
|
||||
if errors.Is(err, examprep.ErrCatalogCourseNotFound) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
|
||||
|
|
@ -189,6 +200,12 @@ func (h *Handler) GetExamPrepUnitByID(c *fiber.Ctx) error {
|
|||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
if !out.VisibleToLearners() && !h.canManageExamPrepUnits(c) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
|
||||
Message: "Unit not found",
|
||||
Error: examprep.ErrUnitNotFound.Error(),
|
||||
})
|
||||
}
|
||||
if err := h.applyExamPrepAccessUnit(c.Context(), c, &out); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to build unit",
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ func (h *Handler) AdminGetUserRecentActivity(c *fiber.Ctx) error {
|
|||
}
|
||||
|
||||
func (h *Handler) buildLMSProgressSummary(ctx context.Context, role domain.Role, userID int64, publishedOnly bool) (domain.LMSProgressSummary, error) {
|
||||
programs, err := h.listAllPrograms(ctx)
|
||||
programs, err := h.listAllPrograms(ctx, publishedOnly)
|
||||
if err != nil {
|
||||
return domain.LMSProgressSummary{}, err
|
||||
}
|
||||
|
|
@ -183,7 +183,7 @@ func (h *Handler) buildLMSProgressSummary(ctx context.Context, role domain.Role,
|
|||
if err := h.lmsProgressSvc.ApplyAccessProgram(ctx, role, userID, &programs[i]); err != nil {
|
||||
return domain.LMSProgressSummary{}, err
|
||||
}
|
||||
courses, err := h.listAllCoursesByProgram(ctx, programs[i].ID)
|
||||
courses, err := h.listAllCoursesByProgram(ctx, programs[i].ID, publishedOnly)
|
||||
if err != nil {
|
||||
return domain.LMSProgressSummary{}, err
|
||||
}
|
||||
|
|
@ -198,7 +198,7 @@ func (h *Handler) buildLMSProgressSummary(ctx context.Context, role domain.Role,
|
|||
if err := h.lmsProgressSvc.ApplyAccessCourse(ctx, role, userID, &courses[j]); err != nil {
|
||||
return domain.LMSProgressSummary{}, err
|
||||
}
|
||||
modules, err := h.listAllModulesByCourse(ctx, courses[j].ID)
|
||||
modules, err := h.listAllModulesByCourse(ctx, courses[j].ID, publishedOnly)
|
||||
if err != nil {
|
||||
return domain.LMSProgressSummary{}, err
|
||||
}
|
||||
|
|
@ -251,13 +251,13 @@ func (h *Handler) buildLMSProgressSummary(ctx context.Context, role domain.Role,
|
|||
return summary, nil
|
||||
}
|
||||
|
||||
func (h *Handler) listAllPrograms(ctx context.Context) ([]domain.Program, error) {
|
||||
func (h *Handler) listAllPrograms(ctx context.Context, publishedOnly bool) ([]domain.Program, error) {
|
||||
var (
|
||||
all []domain.Program
|
||||
offset int32
|
||||
)
|
||||
for {
|
||||
items, total, err := h.programSvc.List(ctx, lmsProgressSummaryPageSize, offset)
|
||||
items, total, err := h.programSvc.List(ctx, publishedOnly, lmsProgressSummaryPageSize, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -272,13 +272,13 @@ func (h *Handler) listAllPrograms(ctx context.Context) ([]domain.Program, error)
|
|||
}
|
||||
}
|
||||
|
||||
func (h *Handler) listAllCoursesByProgram(ctx context.Context, programID int64) ([]domain.Course, error) {
|
||||
func (h *Handler) listAllCoursesByProgram(ctx context.Context, programID int64, publishedOnly bool) ([]domain.Course, error) {
|
||||
var (
|
||||
all []domain.Course
|
||||
offset int32
|
||||
)
|
||||
for {
|
||||
items, total, err := h.courseSvc.ListByProgram(ctx, programID, lmsProgressSummaryPageSize, offset)
|
||||
items, total, err := h.courseSvc.ListByProgram(ctx, programID, publishedOnly, lmsProgressSummaryPageSize, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -293,13 +293,13 @@ func (h *Handler) listAllCoursesByProgram(ctx context.Context, programID int64)
|
|||
}
|
||||
}
|
||||
|
||||
func (h *Handler) listAllModulesByCourse(ctx context.Context, courseID int64) ([]domain.Module, error) {
|
||||
func (h *Handler) listAllModulesByCourse(ctx context.Context, courseID int64, publishedOnly bool) ([]domain.Module, error) {
|
||||
var (
|
||||
all []domain.Module
|
||||
offset int32
|
||||
)
|
||||
for {
|
||||
items, total, err := h.moduleSvc.ListByCourse(ctx, courseID, lmsProgressSummaryPageSize, offset)
|
||||
items, total, err := h.moduleSvc.ListByCourse(ctx, courseID, publishedOnly, lmsProgressSummaryPageSize, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,7 +90,18 @@ func (h *Handler) ListModulesByCourse(c *fiber.Ctx) error {
|
|||
}
|
||||
limit, _ := strconv.Atoi(c.Query("limit", "20"))
|
||||
offset, _ := strconv.Atoi(c.Query("offset", "0"))
|
||||
items, total, err := h.moduleSvc.ListByCourse(c.Context(), courseID, int32(limit), int32(offset))
|
||||
publishedOnly := !h.canManageLMSModules(c)
|
||||
if publishedOnly {
|
||||
// Draft courses hide their modules from non-managers.
|
||||
course, err := h.courseSvc.GetByID(c.Context(), courseID)
|
||||
if err == nil && !course.VisibleToLearners() {
|
||||
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
|
||||
Message: "Course not found",
|
||||
Error: courses.ErrCourseNotFound.Error(),
|
||||
})
|
||||
}
|
||||
}
|
||||
items, total, err := h.moduleSvc.ListByCourse(c.Context(), courseID, publishedOnly, int32(limit), int32(offset))
|
||||
if err != nil {
|
||||
if errors.Is(err, courses.ErrCourseNotFound) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
|
||||
|
|
@ -152,6 +163,12 @@ func (h *Handler) GetModule(c *fiber.Ctx) error {
|
|||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
if !mod.VisibleToLearners() && !h.canManageLMSModules(c) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
|
||||
Message: "Module not found",
|
||||
Error: modules.ErrModuleNotFound.Error(),
|
||||
})
|
||||
}
|
||||
uid := c.Locals("user_id").(int64)
|
||||
role := c.Locals("role").(domain.Role)
|
||||
if err := h.lmsProgressSvc.ApplyAccessModule(c.Context(), role, uid, &mod); err != nil {
|
||||
|
|
|
|||
|
|
@ -9,61 +9,19 @@ import (
|
|||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func fullUpdatePracticeQuestionResponses(questions []domain.QuestionWithDetails) []questionRes {
|
||||
func fullUpdatePracticeQuestionResponses(questions []domain.QuestionWithDetails, catalog []domain.QuestionTypeDefinition) []questionRes {
|
||||
out := make([]questionRes, 0, len(questions))
|
||||
for _, question := range questions {
|
||||
options := make([]optionRes, 0, len(question.Options))
|
||||
for _, opt := range question.Options {
|
||||
options = append(options, optionRes{
|
||||
ID: opt.ID,
|
||||
OptionText: opt.OptionText,
|
||||
OptionOrder: opt.OptionOrder,
|
||||
IsCorrect: opt.IsCorrect,
|
||||
})
|
||||
}
|
||||
|
||||
shortAnswers := make([]shortAnswerRes, 0, len(question.ShortAnswers))
|
||||
for _, sa := range question.ShortAnswers {
|
||||
shortAnswers = append(shortAnswers, shortAnswerRes{
|
||||
ID: sa.ID,
|
||||
AcceptableAnswer: sa.AcceptableAnswer,
|
||||
MatchType: sa.MatchType,
|
||||
})
|
||||
}
|
||||
|
||||
var audioCorrectAnswerText *string
|
||||
if question.AudioAnswer != nil {
|
||||
audioCorrectAnswerText = &question.AudioAnswer.CorrectAnswerText
|
||||
}
|
||||
|
||||
out = append(out, questionRes{
|
||||
ID: question.ID,
|
||||
QuestionText: questionTextField(question.QuestionType, question.QuestionText),
|
||||
QuestionType: question.QuestionType,
|
||||
QuestionTypeDefinitionID: question.QuestionTypeDefinitionID,
|
||||
DynamicPayload: question.DynamicPayload,
|
||||
DifficultyLevel: question.DifficultyLevel,
|
||||
Points: question.Points,
|
||||
Explanation: question.Explanation,
|
||||
Tips: question.Tips,
|
||||
VoicePrompt: question.VoicePrompt,
|
||||
SampleAnswerVoicePrompt: question.SampleAnswerVoicePrompt,
|
||||
ImageURL: question.ImageURL,
|
||||
Status: question.Status,
|
||||
CreatedAt: question.CreatedAt.String(),
|
||||
Options: options,
|
||||
ShortAnswers: shortAnswers,
|
||||
AudioCorrectAnswerText: audioCorrectAnswerText,
|
||||
})
|
||||
out = append(out, buildQuestionRes(question, catalog))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func fullUpdatePracticeResponse(result domain.FullUpdatePracticeResult) fiber.Map {
|
||||
func fullUpdatePracticeResponse(result domain.FullUpdatePracticeResult, catalog []domain.QuestionTypeDefinition) fiber.Map {
|
||||
return fiber.Map{
|
||||
"practice": result.Practice,
|
||||
"question_set": questionSetResFromDomain(result.QuestionSet),
|
||||
"questions": fullUpdatePracticeQuestionResponses(result.Questions),
|
||||
"questions": fullUpdatePracticeQuestionResponses(result.Questions, catalog),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -141,9 +99,17 @@ func (h *Handler) UpdateLmsPracticeFull(c *fiber.Ctx) error {
|
|||
})
|
||||
}
|
||||
|
||||
catalog, err := h.activeQuestionTypeDefinitionCatalog(c.Context())
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to load question type catalog",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
return c.JSON(domain.Response{
|
||||
Message: "Practice updated successfully",
|
||||
Data: fullUpdatePracticeResponse(result),
|
||||
Data: fullUpdatePracticeResponse(result, catalog),
|
||||
Success: true,
|
||||
StatusCode: fiber.StatusOK,
|
||||
})
|
||||
|
|
@ -187,9 +153,17 @@ func (h *Handler) UpdateExamPrepPracticeFull(c *fiber.Ctx) error {
|
|||
})
|
||||
}
|
||||
|
||||
catalog, err := h.activeQuestionTypeDefinitionCatalog(c.Context())
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to load question type catalog",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
return c.JSON(domain.Response{
|
||||
Message: "Practice updated successfully",
|
||||
Data: fullUpdatePracticeResponse(result),
|
||||
Data: fullUpdatePracticeResponse(result, catalog),
|
||||
Success: true,
|
||||
StatusCode: fiber.StatusOK,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -69,7 +69,8 @@ func (h *Handler) CreateProgram(c *fiber.Ctx) error {
|
|||
func (h *Handler) ListPrograms(c *fiber.Ctx) error {
|
||||
limit, _ := strconv.Atoi(c.Query("limit", "20"))
|
||||
offset, _ := strconv.Atoi(c.Query("offset", "0"))
|
||||
items, total, err := h.programSvc.List(c.Context(), int32(limit), int32(offset))
|
||||
publishedOnly := !h.canManagePrograms(c)
|
||||
items, total, err := h.programSvc.List(c.Context(), publishedOnly, int32(limit), int32(offset))
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to list programs",
|
||||
|
|
@ -128,6 +129,12 @@ func (h *Handler) GetProgram(c *fiber.Ctx) error {
|
|||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
if !p.VisibleToLearners() && !h.canManagePrograms(c) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
|
||||
Message: "Program not found",
|
||||
Error: programs.ErrProgramNotFound.Error(),
|
||||
})
|
||||
}
|
||||
uid := c.Locals("user_id").(int64)
|
||||
role := c.Locals("role").(domain.Role)
|
||||
if err := h.lmsProgressSvc.ApplyAccessProgram(c.Context(), role, uid, &p); err != nil {
|
||||
|
|
|
|||
|
|
@ -81,6 +81,65 @@ type listQuestionsRes struct {
|
|||
TotalCount int64 `json:"total_count"`
|
||||
}
|
||||
|
||||
func (h *Handler) activeQuestionTypeDefinitionCatalog(ctx context.Context) ([]domain.QuestionTypeDefinition, error) {
|
||||
return h.questionsSvc.ActiveQuestionTypeDefinitionCatalog(ctx)
|
||||
}
|
||||
|
||||
func buildQuestionRes(q domain.QuestionWithDetails, catalog []domain.QuestionTypeDefinition) questionRes {
|
||||
options := make([]optionRes, 0, len(q.Options))
|
||||
for _, opt := range q.Options {
|
||||
options = append(options, optionRes{
|
||||
ID: opt.ID,
|
||||
OptionText: opt.OptionText,
|
||||
OptionOrder: opt.OptionOrder,
|
||||
IsCorrect: opt.IsCorrect,
|
||||
})
|
||||
}
|
||||
|
||||
shortAnswers := make([]shortAnswerRes, 0, len(q.ShortAnswers))
|
||||
for _, sa := range q.ShortAnswers {
|
||||
shortAnswers = append(shortAnswers, shortAnswerRes{
|
||||
ID: sa.ID,
|
||||
AcceptableAnswer: sa.AcceptableAnswer,
|
||||
MatchType: sa.MatchType,
|
||||
})
|
||||
}
|
||||
|
||||
var audioCorrectAnswerText *string
|
||||
if q.AudioAnswer != nil {
|
||||
audioCorrectAnswerText = &q.AudioAnswer.CorrectAnswerText
|
||||
}
|
||||
|
||||
return questionRes{
|
||||
ID: q.ID,
|
||||
QuestionText: questionTextField(q.QuestionType, q.QuestionText),
|
||||
QuestionType: q.QuestionType,
|
||||
QuestionTypeDefinitionID: domain.ResolveEffectiveQuestionTypeDefinitionID(
|
||||
q.QuestionTypeDefinitionID,
|
||||
q.QuestionType,
|
||||
catalog,
|
||||
q.DynamicPayload,
|
||||
),
|
||||
DynamicPayload: q.DynamicPayload,
|
||||
DifficultyLevel: q.DifficultyLevel,
|
||||
Points: q.Points,
|
||||
Explanation: q.Explanation,
|
||||
Tips: q.Tips,
|
||||
VoicePrompt: q.VoicePrompt,
|
||||
SampleAnswerVoicePrompt: q.SampleAnswerVoicePrompt,
|
||||
ImageURL: q.ImageURL,
|
||||
Status: q.Status,
|
||||
CreatedAt: q.CreatedAt.String(),
|
||||
Options: options,
|
||||
ShortAnswers: shortAnswers,
|
||||
AudioCorrectAnswerText: audioCorrectAnswerText,
|
||||
}
|
||||
}
|
||||
|
||||
func buildQuestionResFromQuestion(q domain.Question, catalog []domain.QuestionTypeDefinition) questionRes {
|
||||
return buildQuestionRes(domain.QuestionWithDetails{Question: q}, catalog)
|
||||
}
|
||||
|
||||
func resolveQuestionTypeFromDefinition(def domain.QuestionTypeDefinition) string {
|
||||
return domain.ResolveRuntimeQuestionTypeFromDefinition(def.Key, def.ResponseComponentKinds)
|
||||
}
|
||||
|
|
@ -264,24 +323,17 @@ func (h *Handler) CreateQuestion(c *fiber.Ctx) error {
|
|||
})
|
||||
go h.activityLogSvc.RecordAction(context.Background(), &actorID, &actorRole, domain.ActionQuestionCreated, domain.ResourceQuestion, &question.ID, "Created question: "+activityLogQuestionSummary(question.QuestionType, question.QuestionText, question.DynamicPayload), meta, &ip, &ua)
|
||||
|
||||
catalog, err := h.activeQuestionTypeDefinitionCatalog(c.Context())
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to load question type catalog",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusCreated).JSON(domain.Response{
|
||||
Message: "Question created successfully",
|
||||
Data: questionRes{
|
||||
ID: question.ID,
|
||||
QuestionText: questionTextField(question.QuestionType, question.QuestionText),
|
||||
QuestionType: question.QuestionType,
|
||||
QuestionTypeDefinitionID: question.QuestionTypeDefinitionID,
|
||||
DynamicPayload: question.DynamicPayload,
|
||||
DifficultyLevel: question.DifficultyLevel,
|
||||
Points: question.Points,
|
||||
Explanation: question.Explanation,
|
||||
Tips: question.Tips,
|
||||
VoicePrompt: question.VoicePrompt,
|
||||
SampleAnswerVoicePrompt: question.SampleAnswerVoicePrompt,
|
||||
ImageURL: question.ImageURL,
|
||||
Status: question.Status,
|
||||
CreatedAt: question.CreatedAt.String(),
|
||||
},
|
||||
Data: buildQuestionResFromQuestion(question, catalog),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -313,51 +365,17 @@ func (h *Handler) GetQuestionByID(c *fiber.Ctx) error {
|
|||
})
|
||||
}
|
||||
|
||||
var options []optionRes
|
||||
for _, opt := range question.Options {
|
||||
options = append(options, optionRes{
|
||||
ID: opt.ID,
|
||||
OptionText: opt.OptionText,
|
||||
OptionOrder: opt.OptionOrder,
|
||||
IsCorrect: opt.IsCorrect,
|
||||
catalog, err := h.activeQuestionTypeDefinitionCatalog(c.Context())
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to load question type catalog",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
var shortAnswers []shortAnswerRes
|
||||
for _, sa := range question.ShortAnswers {
|
||||
shortAnswers = append(shortAnswers, shortAnswerRes{
|
||||
ID: sa.ID,
|
||||
AcceptableAnswer: sa.AcceptableAnswer,
|
||||
MatchType: sa.MatchType,
|
||||
})
|
||||
}
|
||||
|
||||
var audioCorrectAnswerText *string
|
||||
if question.AudioAnswer != nil {
|
||||
audioCorrectAnswerText = &question.AudioAnswer.CorrectAnswerText
|
||||
}
|
||||
|
||||
return c.JSON(domain.Response{
|
||||
Message: "Question retrieved successfully",
|
||||
Data: questionRes{
|
||||
ID: question.ID,
|
||||
QuestionText: questionTextField(question.QuestionType, question.QuestionText),
|
||||
QuestionType: question.QuestionType,
|
||||
QuestionTypeDefinitionID: question.QuestionTypeDefinitionID,
|
||||
DynamicPayload: question.DynamicPayload,
|
||||
DifficultyLevel: question.DifficultyLevel,
|
||||
Points: question.Points,
|
||||
Explanation: question.Explanation,
|
||||
Tips: question.Tips,
|
||||
VoicePrompt: question.VoicePrompt,
|
||||
SampleAnswerVoicePrompt: question.SampleAnswerVoicePrompt,
|
||||
ImageURL: question.ImageURL,
|
||||
Status: question.Status,
|
||||
CreatedAt: question.CreatedAt.String(),
|
||||
Options: options,
|
||||
ShortAnswers: shortAnswers,
|
||||
AudioCorrectAnswerText: audioCorrectAnswerText,
|
||||
},
|
||||
Data: buildQuestionRes(question, catalog),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -404,24 +422,19 @@ func (h *Handler) ListQuestions(c *fiber.Ctx) error {
|
|||
})
|
||||
}
|
||||
|
||||
var questionResponses []questionRes
|
||||
for _, q := range questions {
|
||||
questionResponses = append(questionResponses, questionRes{
|
||||
ID: q.ID,
|
||||
QuestionText: questionTextField(q.QuestionType, q.QuestionText),
|
||||
QuestionType: q.QuestionType,
|
||||
QuestionTypeDefinitionID: q.QuestionTypeDefinitionID,
|
||||
DynamicPayload: q.DynamicPayload,
|
||||
DifficultyLevel: q.DifficultyLevel,
|
||||
Points: q.Points,
|
||||
Explanation: q.Explanation,
|
||||
Tips: q.Tips,
|
||||
VoicePrompt: q.VoicePrompt,
|
||||
Status: q.Status,
|
||||
CreatedAt: q.CreatedAt.String(),
|
||||
catalog, err := h.activeQuestionTypeDefinitionCatalog(c.Context())
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to load question type catalog",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
questionResponses := make([]questionRes, 0, len(questions))
|
||||
for _, q := range questions {
|
||||
questionResponses = append(questionResponses, buildQuestionResFromQuestion(q, catalog))
|
||||
}
|
||||
|
||||
return c.JSON(domain.Response{
|
||||
Message: "Questions retrieved successfully",
|
||||
Data: listQuestionsRes{
|
||||
|
|
@ -464,21 +477,19 @@ func (h *Handler) SearchQuestions(c *fiber.Ctx) error {
|
|||
})
|
||||
}
|
||||
|
||||
var questionResponses []questionRes
|
||||
for _, q := range questions {
|
||||
questionResponses = append(questionResponses, questionRes{
|
||||
ID: q.ID,
|
||||
QuestionText: questionTextField(q.QuestionType, q.QuestionText),
|
||||
QuestionType: q.QuestionType,
|
||||
QuestionTypeDefinitionID: q.QuestionTypeDefinitionID,
|
||||
DynamicPayload: q.DynamicPayload,
|
||||
DifficultyLevel: q.DifficultyLevel,
|
||||
Points: q.Points,
|
||||
Status: q.Status,
|
||||
CreatedAt: q.CreatedAt.String(),
|
||||
catalog, err := h.activeQuestionTypeDefinitionCatalog(c.Context())
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to load question type catalog",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
questionResponses := make([]questionRes, 0, len(questions))
|
||||
for _, q := range questions {
|
||||
questionResponses = append(questionResponses, buildQuestionResFromQuestion(q, catalog))
|
||||
}
|
||||
|
||||
return c.JSON(domain.Response{
|
||||
Message: "Questions retrieved successfully",
|
||||
Data: listQuestionsRes{
|
||||
|
|
@ -1458,6 +1469,14 @@ func (h *Handler) GetQuestionsInSet(c *fiber.Ctx) error {
|
|||
})
|
||||
}
|
||||
|
||||
catalog, err := h.activeQuestionTypeDefinitionCatalog(c.Context())
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to load question type catalog",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
questionResponses := make([]questionRes, 0, len(items))
|
||||
for _, item := range items {
|
||||
question, err := h.questionsSvc.GetQuestionWithDetails(c.Context(), item.QuestionID)
|
||||
|
|
@ -1468,49 +1487,7 @@ func (h *Handler) GetQuestionsInSet(c *fiber.Ctx) error {
|
|||
})
|
||||
}
|
||||
|
||||
options := make([]optionRes, 0, len(question.Options))
|
||||
for _, opt := range question.Options {
|
||||
options = append(options, optionRes{
|
||||
ID: opt.ID,
|
||||
OptionText: opt.OptionText,
|
||||
OptionOrder: opt.OptionOrder,
|
||||
IsCorrect: opt.IsCorrect,
|
||||
})
|
||||
}
|
||||
|
||||
shortAnswers := make([]shortAnswerRes, 0, len(question.ShortAnswers))
|
||||
for _, sa := range question.ShortAnswers {
|
||||
shortAnswers = append(shortAnswers, shortAnswerRes{
|
||||
ID: sa.ID,
|
||||
AcceptableAnswer: sa.AcceptableAnswer,
|
||||
MatchType: sa.MatchType,
|
||||
})
|
||||
}
|
||||
|
||||
var audioCorrectAnswerText *string
|
||||
if question.AudioAnswer != nil {
|
||||
audioCorrectAnswerText = &question.AudioAnswer.CorrectAnswerText
|
||||
}
|
||||
|
||||
questionResponses = append(questionResponses, questionRes{
|
||||
ID: question.ID,
|
||||
QuestionText: questionTextField(question.QuestionType, question.QuestionText),
|
||||
QuestionType: question.QuestionType,
|
||||
QuestionTypeDefinitionID: question.QuestionTypeDefinitionID,
|
||||
DynamicPayload: question.DynamicPayload,
|
||||
DifficultyLevel: question.DifficultyLevel,
|
||||
Points: question.Points,
|
||||
Explanation: question.Explanation,
|
||||
Tips: question.Tips,
|
||||
VoicePrompt: question.VoicePrompt,
|
||||
SampleAnswerVoicePrompt: question.SampleAnswerVoicePrompt,
|
||||
ImageURL: question.ImageURL,
|
||||
Status: question.Status,
|
||||
CreatedAt: question.CreatedAt.String(),
|
||||
Options: options,
|
||||
ShortAnswers: shortAnswers,
|
||||
AudioCorrectAnswerText: audioCorrectAnswerText,
|
||||
})
|
||||
questionResponses = append(questionResponses, buildQuestionRes(question, catalog))
|
||||
}
|
||||
|
||||
return c.JSON(domain.Response{
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user