Add draft vs published status for LMS and exam-prep practices.
Expose publish_status on create/update, filter learner-facing lists and gates, and add migration 000060. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
37aef49e28
commit
12ad59c409
5
db/migrations/000060_practice_publish_status.down.sql
Normal file
5
db/migrations/000060_practice_publish_status.down.sql
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
ALTER TABLE lms_practices DROP CONSTRAINT chk_lms_practices_publish_status;
|
||||||
|
ALTER TABLE lms_practices DROP COLUMN publish_status;
|
||||||
|
|
||||||
|
ALTER TABLE exam_prep.lesson_practices DROP CONSTRAINT chk_exam_prep_lesson_practices_publish_status;
|
||||||
|
ALTER TABLE exam_prep.lesson_practices DROP COLUMN publish_status;
|
||||||
8
db/migrations/000060_practice_publish_status.up.sql
Normal file
8
db/migrations/000060_practice_publish_status.up.sql
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
-- Draft vs published visibility for LMS and exam-prep practices.
|
||||||
|
ALTER TABLE lms_practices
|
||||||
|
ADD COLUMN publish_status VARCHAR(16) NOT NULL DEFAULT 'PUBLISHED'
|
||||||
|
CONSTRAINT chk_lms_practices_publish_status CHECK (publish_status IN ('DRAFT', 'PUBLISHED'));
|
||||||
|
|
||||||
|
ALTER TABLE exam_prep.lesson_practices
|
||||||
|
ADD COLUMN publish_status VARCHAR(16) NOT NULL DEFAULT 'PUBLISHED'
|
||||||
|
CONSTRAINT chk_exam_prep_lesson_practices_publish_status CHECK (publish_status IN ('DRAFT', 'PUBLISHED'));
|
||||||
|
|
@ -6,8 +6,9 @@ INSERT INTO exam_prep.lesson_practices (
|
||||||
story_image,
|
story_image,
|
||||||
persona_id,
|
persona_id,
|
||||||
question_set_id,
|
question_set_id,
|
||||||
quick_tips
|
quick_tips,
|
||||||
) VALUES ($1, $2, $3, $4, $5, $6, $7)
|
publish_status
|
||||||
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: ExamPrepGetLessonPracticeByID :one
|
-- name: ExamPrepGetLessonPracticeByID :one
|
||||||
|
|
@ -15,6 +16,13 @@ SELECT *
|
||||||
FROM exam_prep.lesson_practices
|
FROM exam_prep.lesson_practices
|
||||||
WHERE id = $1;
|
WHERE id = $1;
|
||||||
|
|
||||||
|
-- name: ExamPrepGetLessonPracticeByQuestionSetID :one
|
||||||
|
SELECT *
|
||||||
|
FROM exam_prep.lesson_practices
|
||||||
|
WHERE question_set_id = $1
|
||||||
|
ORDER BY id DESC
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
-- name: ExamPrepListLessonPracticesByLessonID :many
|
-- name: ExamPrepListLessonPracticesByLessonID :many
|
||||||
SELECT
|
SELECT
|
||||||
COUNT(*) OVER () AS total_count,
|
COUNT(*) OVER () AS total_count,
|
||||||
|
|
@ -26,10 +34,15 @@ SELECT
|
||||||
p.persona_id,
|
p.persona_id,
|
||||||
p.question_set_id,
|
p.question_set_id,
|
||||||
p.quick_tips,
|
p.quick_tips,
|
||||||
|
p.publish_status,
|
||||||
p.created_at,
|
p.created_at,
|
||||||
p.updated_at
|
p.updated_at
|
||||||
FROM exam_prep.lesson_practices p
|
FROM exam_prep.lesson_practices p
|
||||||
WHERE p.unit_module_lesson_id = $1
|
WHERE p.unit_module_lesson_id = $1
|
||||||
|
AND (
|
||||||
|
sqlc.arg('published_only')::boolean = FALSE
|
||||||
|
OR p.publish_status = 'PUBLISHED'::TEXT
|
||||||
|
)
|
||||||
ORDER BY p.created_at DESC
|
ORDER BY p.created_at DESC
|
||||||
LIMIT $2
|
LIMIT $2
|
||||||
OFFSET $3;
|
OFFSET $3;
|
||||||
|
|
@ -43,6 +56,7 @@ SET
|
||||||
persona_id = coalesce(sqlc.narg('persona_id')::bigint, persona_id),
|
persona_id = coalesce(sqlc.narg('persona_id')::bigint, persona_id),
|
||||||
question_set_id = coalesce(sqlc.narg('question_set_id')::bigint, question_set_id),
|
question_set_id = coalesce(sqlc.narg('question_set_id')::bigint, question_set_id),
|
||||||
quick_tips = coalesce(sqlc.narg('quick_tips')::text, quick_tips),
|
quick_tips = coalesce(sqlc.narg('quick_tips')::text, quick_tips),
|
||||||
|
publish_status = coalesce(sqlc.narg('publish_status')::varchar, publish_status),
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = sqlc.arg('id')
|
WHERE id = sqlc.arg('id')
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,7 @@ UNION ALL
|
||||||
user_practice_progress upp
|
user_practice_progress upp
|
||||||
INNER JOIN lms_practices lp ON lp.question_set_id = upp.question_set_id
|
INNER JOIN lms_practices lp ON lp.question_set_id = upp.question_set_id
|
||||||
AND lp.lesson_id IS NOT NULL
|
AND lp.lesson_id IS NOT NULL
|
||||||
|
AND lp.publish_status = 'PUBLISHED'
|
||||||
INNER JOIN question_sets qs ON qs.id = upp.question_set_id
|
INNER JOIN question_sets qs ON qs.id = upp.question_set_id
|
||||||
AND qs.set_type = 'PRACTICE'
|
AND qs.set_type = 'PRACTICE'
|
||||||
AND qs.status = 'PUBLISHED'
|
AND qs.status = 'PUBLISHED'
|
||||||
|
|
@ -132,6 +133,7 @@ UNION ALL
|
||||||
INNER JOIN lms_practices lp ON lp.question_set_id = upp.question_set_id
|
INNER JOIN lms_practices lp ON lp.question_set_id = upp.question_set_id
|
||||||
AND lp.module_id IS NOT NULL
|
AND lp.module_id IS NOT NULL
|
||||||
AND lp.lesson_id IS NULL
|
AND lp.lesson_id IS NULL
|
||||||
|
AND lp.publish_status = 'PUBLISHED'
|
||||||
INNER JOIN question_sets qs ON qs.id = upp.question_set_id
|
INNER JOIN question_sets qs ON qs.id = upp.question_set_id
|
||||||
AND qs.set_type = 'PRACTICE'
|
AND qs.set_type = 'PRACTICE'
|
||||||
AND qs.status = 'PUBLISHED'
|
AND qs.status = 'PUBLISHED'
|
||||||
|
|
@ -176,6 +178,7 @@ UNION ALL
|
||||||
AND lp.course_id IS NOT NULL
|
AND lp.course_id IS NOT NULL
|
||||||
AND lp.module_id IS NULL
|
AND lp.module_id IS NULL
|
||||||
AND lp.lesson_id IS NULL
|
AND lp.lesson_id IS NULL
|
||||||
|
AND lp.publish_status = 'PUBLISHED'
|
||||||
INNER JOIN question_sets qs ON qs.id = upp.question_set_id
|
INNER JOIN question_sets qs ON qs.id = upp.question_set_id
|
||||||
AND qs.set_type = 'PRACTICE'
|
AND qs.set_type = 'PRACTICE'
|
||||||
AND qs.status = 'PUBLISHED'
|
AND qs.status = 'PUBLISHED'
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ SELECT
|
||||||
WHERE p.course_id = c.id
|
WHERE p.course_id = c.id
|
||||||
AND p.module_id IS NULL
|
AND p.module_id IS NULL
|
||||||
AND p.lesson_id IS NULL
|
AND p.lesson_id IS NULL
|
||||||
|
AND p.publish_status = 'PUBLISHED'
|
||||||
) AS has_practice
|
) AS has_practice
|
||||||
FROM courses
|
FROM courses
|
||||||
c
|
c
|
||||||
|
|
@ -75,13 +76,15 @@ SELECT
|
||||||
WHERE
|
WHERE
|
||||||
p.course_id = c.id
|
p.course_id = c.id
|
||||||
AND p.module_id IS NULL
|
AND p.module_id IS NULL
|
||||||
AND p.lesson_id IS NULL) AS practice_count,
|
AND p.lesson_id IS NULL
|
||||||
|
AND p.publish_status = 'PUBLISHED') AS practice_count,
|
||||||
EXISTS (
|
EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM lms_practices p
|
FROM lms_practices p
|
||||||
WHERE p.course_id = c.id
|
WHERE p.course_id = c.id
|
||||||
AND p.module_id IS NULL
|
AND p.module_id IS NULL
|
||||||
AND p.lesson_id IS NULL
|
AND p.lesson_id IS NULL
|
||||||
|
AND p.publish_status = 'PUBLISHED'
|
||||||
) AS has_practice
|
) AS has_practice
|
||||||
FROM
|
FROM
|
||||||
courses c
|
courses c
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ SELECT
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM lms_practices p
|
FROM lms_practices p
|
||||||
WHERE p.lesson_id = l.id
|
WHERE p.lesson_id = l.id
|
||||||
|
AND p.publish_status = 'PUBLISHED'
|
||||||
) AS has_practice
|
) AS has_practice
|
||||||
FROM lessons
|
FROM lessons
|
||||||
l
|
l
|
||||||
|
|
@ -43,6 +44,7 @@ SELECT
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM lms_practices p
|
FROM lms_practices p
|
||||||
WHERE p.lesson_id = l.id
|
WHERE p.lesson_id = l.id
|
||||||
|
AND p.publish_status = 'PUBLISHED'
|
||||||
) AS has_practice
|
) AS has_practice
|
||||||
FROM
|
FROM
|
||||||
lessons l
|
lessons l
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ SELECT
|
||||||
FROM lms_practices p
|
FROM lms_practices p
|
||||||
WHERE p.module_id = m.id
|
WHERE p.module_id = m.id
|
||||||
AND p.lesson_id IS NULL
|
AND p.lesson_id IS NULL
|
||||||
|
AND p.publish_status = 'PUBLISHED'
|
||||||
) AS has_practice
|
) AS has_practice
|
||||||
FROM modules
|
FROM modules
|
||||||
m
|
m
|
||||||
|
|
@ -55,6 +56,7 @@ SELECT
|
||||||
FROM lms_practices p
|
FROM lms_practices p
|
||||||
WHERE p.module_id = m.id
|
WHERE p.module_id = m.id
|
||||||
AND p.lesson_id IS NULL
|
AND p.lesson_id IS NULL
|
||||||
|
AND p.publish_status = 'PUBLISHED'
|
||||||
) AS has_practice
|
) AS has_practice
|
||||||
FROM
|
FROM
|
||||||
modules m
|
modules m
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
-- name: CreateLmsPractice :one
|
-- name: CreateLmsPractice :one
|
||||||
INSERT INTO lms_practices (
|
INSERT INTO lms_practices (
|
||||||
course_id, module_id, lesson_id,
|
course_id, module_id, lesson_id,
|
||||||
title, story_description, story_image, persona_id, question_set_id, quick_tips
|
title, story_description, story_image, persona_id, question_set_id, quick_tips, publish_status
|
||||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: GetLmsPracticeByID :one
|
-- name: GetLmsPracticeByID :one
|
||||||
|
|
@ -10,6 +10,13 @@ SELECT *
|
||||||
FROM lms_practices
|
FROM lms_practices
|
||||||
WHERE id = $1;
|
WHERE id = $1;
|
||||||
|
|
||||||
|
-- name: GetLmsPracticeByQuestionSetID :one
|
||||||
|
SELECT *
|
||||||
|
FROM lms_practices
|
||||||
|
WHERE question_set_id = $1
|
||||||
|
ORDER BY id DESC
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
-- name: ListLmsPracticesByCourseID :many
|
-- name: ListLmsPracticesByCourseID :many
|
||||||
SELECT
|
SELECT
|
||||||
COUNT(*) OVER () AS total_count,
|
COUNT(*) OVER () AS total_count,
|
||||||
|
|
@ -23,10 +30,15 @@ SELECT
|
||||||
p.persona_id,
|
p.persona_id,
|
||||||
p.question_set_id,
|
p.question_set_id,
|
||||||
p.quick_tips,
|
p.quick_tips,
|
||||||
|
p.publish_status,
|
||||||
p.created_at,
|
p.created_at,
|
||||||
p.updated_at
|
p.updated_at
|
||||||
FROM lms_practices p
|
FROM lms_practices p
|
||||||
WHERE p.course_id = $1
|
WHERE p.course_id = $1
|
||||||
|
AND (
|
||||||
|
sqlc.arg('published_only')::boolean = FALSE
|
||||||
|
OR p.publish_status = 'PUBLISHED'::TEXT
|
||||||
|
)
|
||||||
ORDER BY p.created_at DESC
|
ORDER BY p.created_at DESC
|
||||||
LIMIT $2 OFFSET $3;
|
LIMIT $2 OFFSET $3;
|
||||||
|
|
||||||
|
|
@ -43,10 +55,15 @@ SELECT
|
||||||
p.persona_id,
|
p.persona_id,
|
||||||
p.question_set_id,
|
p.question_set_id,
|
||||||
p.quick_tips,
|
p.quick_tips,
|
||||||
|
p.publish_status,
|
||||||
p.created_at,
|
p.created_at,
|
||||||
p.updated_at
|
p.updated_at
|
||||||
FROM lms_practices p
|
FROM lms_practices p
|
||||||
WHERE p.module_id = $1
|
WHERE p.module_id = $1
|
||||||
|
AND (
|
||||||
|
sqlc.arg('published_only')::boolean = FALSE
|
||||||
|
OR p.publish_status = 'PUBLISHED'::TEXT
|
||||||
|
)
|
||||||
ORDER BY p.created_at DESC
|
ORDER BY p.created_at DESC
|
||||||
LIMIT $2 OFFSET $3;
|
LIMIT $2 OFFSET $3;
|
||||||
|
|
||||||
|
|
@ -63,10 +80,15 @@ SELECT
|
||||||
p.persona_id,
|
p.persona_id,
|
||||||
p.question_set_id,
|
p.question_set_id,
|
||||||
p.quick_tips,
|
p.quick_tips,
|
||||||
|
p.publish_status,
|
||||||
p.created_at,
|
p.created_at,
|
||||||
p.updated_at
|
p.updated_at
|
||||||
FROM lms_practices p
|
FROM lms_practices p
|
||||||
WHERE p.lesson_id = $1
|
WHERE p.lesson_id = $1
|
||||||
|
AND (
|
||||||
|
sqlc.arg('published_only')::boolean = FALSE
|
||||||
|
OR p.publish_status = 'PUBLISHED'::TEXT
|
||||||
|
)
|
||||||
ORDER BY p.created_at DESC
|
ORDER BY p.created_at DESC
|
||||||
LIMIT $2 OFFSET $3;
|
LIMIT $2 OFFSET $3;
|
||||||
|
|
||||||
|
|
@ -79,6 +101,7 @@ SET
|
||||||
persona_id = COALESCE(sqlc.narg('persona_id')::bigint, persona_id),
|
persona_id = COALESCE(sqlc.narg('persona_id')::bigint, persona_id),
|
||||||
question_set_id = COALESCE(sqlc.narg('question_set_id')::bigint, question_set_id),
|
question_set_id = COALESCE(sqlc.narg('question_set_id')::bigint, question_set_id),
|
||||||
quick_tips = COALESCE(sqlc.narg('quick_tips')::text, quick_tips),
|
quick_tips = COALESCE(sqlc.narg('quick_tips')::text, quick_tips),
|
||||||
|
publish_status = COALESCE(sqlc.narg('publish_status')::varchar, publish_status),
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = sqlc.arg('id')
|
WHERE id = sqlc.arg('id')
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
|
||||||
|
|
@ -257,7 +257,8 @@ FROM
|
||||||
WHERE
|
WHERE
|
||||||
lp.module_id = $1
|
lp.module_id = $1
|
||||||
AND qs.set_type = 'PRACTICE'
|
AND qs.set_type = 'PRACTICE'
|
||||||
AND qs.status = 'PUBLISHED';
|
AND qs.status = 'PUBLISHED'
|
||||||
|
AND lp.publish_status = 'PUBLISHED';
|
||||||
|
|
||||||
-- name: CountUserCompletedPublishedPracticesInModule :one
|
-- name: CountUserCompletedPublishedPracticesInModule :one
|
||||||
SELECT
|
SELECT
|
||||||
|
|
@ -271,7 +272,8 @@ WHERE
|
||||||
AND upp.user_id = $2
|
AND upp.user_id = $2
|
||||||
AND upp.completed_at IS NOT NULL
|
AND upp.completed_at IS NOT NULL
|
||||||
AND qs.set_type = 'PRACTICE'
|
AND qs.set_type = 'PRACTICE'
|
||||||
AND qs.status = 'PUBLISHED';
|
AND qs.status = 'PUBLISHED'
|
||||||
|
AND lp.publish_status = 'PUBLISHED';
|
||||||
|
|
||||||
-- name: CountPublishedPracticesInCourse :one
|
-- name: CountPublishedPracticesInCourse :one
|
||||||
SELECT
|
SELECT
|
||||||
|
|
@ -282,7 +284,8 @@ FROM
|
||||||
WHERE
|
WHERE
|
||||||
lp.course_id = $1
|
lp.course_id = $1
|
||||||
AND qs.set_type = 'PRACTICE'
|
AND qs.set_type = 'PRACTICE'
|
||||||
AND qs.status = 'PUBLISHED';
|
AND qs.status = 'PUBLISHED'
|
||||||
|
AND lp.publish_status = 'PUBLISHED';
|
||||||
|
|
||||||
-- name: CountUserCompletedPublishedPracticesInCourse :one
|
-- name: CountUserCompletedPublishedPracticesInCourse :one
|
||||||
SELECT
|
SELECT
|
||||||
|
|
@ -308,7 +311,8 @@ FROM
|
||||||
WHERE
|
WHERE
|
||||||
c.program_id = $1
|
c.program_id = $1
|
||||||
AND qs.set_type = 'PRACTICE'
|
AND qs.set_type = 'PRACTICE'
|
||||||
AND qs.status = 'PUBLISHED';
|
AND qs.status = 'PUBLISHED'
|
||||||
|
AND lp.publish_status = 'PUBLISHED';
|
||||||
|
|
||||||
-- name: CountUserCompletedPublishedPracticesInProgram :one
|
-- name: CountUserCompletedPublishedPracticesInProgram :one
|
||||||
SELECT
|
SELECT
|
||||||
|
|
@ -323,7 +327,8 @@ WHERE
|
||||||
AND upp.user_id = $2
|
AND upp.user_id = $2
|
||||||
AND upp.completed_at IS NOT NULL
|
AND upp.completed_at IS NOT NULL
|
||||||
AND qs.set_type = 'PRACTICE'
|
AND qs.set_type = 'PRACTICE'
|
||||||
AND qs.status = 'PUBLISHED';
|
AND qs.status = 'PUBLISHED'
|
||||||
|
AND lp.publish_status = 'PUBLISHED';
|
||||||
|
|
||||||
-- name: GetPracticeScopeByQuestionSetID :one
|
-- name: GetPracticeScopeByQuestionSetID :one
|
||||||
SELECT
|
SELECT
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,7 @@ FROM (
|
||||||
user_practice_progress upp
|
user_practice_progress upp
|
||||||
INNER JOIN lms_practices lp ON lp.question_set_id = upp.question_set_id
|
INNER JOIN lms_practices lp ON lp.question_set_id = upp.question_set_id
|
||||||
AND lp.lesson_id IS NOT NULL
|
AND lp.lesson_id IS NOT NULL
|
||||||
|
AND lp.publish_status = 'PUBLISHED'
|
||||||
INNER JOIN question_sets qs ON qs.id = upp.question_set_id
|
INNER JOIN question_sets qs ON qs.id = upp.question_set_id
|
||||||
AND qs.set_type = 'PRACTICE'
|
AND qs.set_type = 'PRACTICE'
|
||||||
AND qs.status = 'PUBLISHED'
|
AND qs.status = 'PUBLISHED'
|
||||||
|
|
@ -131,6 +132,7 @@ FROM (
|
||||||
INNER JOIN lms_practices lp ON lp.question_set_id = upp.question_set_id
|
INNER JOIN lms_practices lp ON lp.question_set_id = upp.question_set_id
|
||||||
AND lp.module_id IS NOT NULL
|
AND lp.module_id IS NOT NULL
|
||||||
AND lp.lesson_id IS NULL
|
AND lp.lesson_id IS NULL
|
||||||
|
AND lp.publish_status = 'PUBLISHED'
|
||||||
INNER JOIN question_sets qs ON qs.id = upp.question_set_id
|
INNER JOIN question_sets qs ON qs.id = upp.question_set_id
|
||||||
AND qs.set_type = 'PRACTICE'
|
AND qs.set_type = 'PRACTICE'
|
||||||
AND qs.status = 'PUBLISHED'
|
AND qs.status = 'PUBLISHED'
|
||||||
|
|
@ -162,6 +164,7 @@ FROM (
|
||||||
AND lp.course_id IS NOT NULL
|
AND lp.course_id IS NOT NULL
|
||||||
AND lp.module_id IS NULL
|
AND lp.module_id IS NULL
|
||||||
AND lp.lesson_id IS NULL
|
AND lp.lesson_id IS NULL
|
||||||
|
AND lp.publish_status = 'PUBLISHED'
|
||||||
INNER JOIN question_sets qs ON qs.id = upp.question_set_id
|
INNER JOIN question_sets qs ON qs.id = upp.question_set_id
|
||||||
AND qs.set_type = 'PRACTICE'
|
AND qs.set_type = 'PRACTICE'
|
||||||
AND qs.status = 'PUBLISHED'
|
AND qs.status = 'PUBLISHED'
|
||||||
|
|
|
||||||
|
|
@ -415,6 +415,8 @@ This creates the practice record scoped to lesson.
|
||||||
|
|
||||||
### Request
|
### Request
|
||||||
|
|
||||||
|
Include `publish_status`: `DRAFT` to hide the practice from subscribed learners until you set it to `PUBLISHED` (via create or `PUT /practices/:id`). Omit the field or send `PUBLISHED` to go live immediately (backward compatible).
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"parent_kind": "LESSON",
|
"parent_kind": "LESSON",
|
||||||
|
|
@ -423,7 +425,8 @@ This creates the practice record scoped to lesson.
|
||||||
"story_description": "A short two-speaker scenario.",
|
"story_description": "A short two-speaker scenario.",
|
||||||
"story_image": "https://cdn.example.com/images/story.webp",
|
"story_image": "https://cdn.example.com/images/story.webp",
|
||||||
"question_set_id": 55,
|
"question_set_id": 55,
|
||||||
"quick_tips": "Listen carefully before answering."
|
"quick_tips": "Listen carefully before answering.",
|
||||||
|
"publish_status": "DRAFT"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,10 @@ INSERT INTO exam_prep.lesson_practices (
|
||||||
story_image,
|
story_image,
|
||||||
persona_id,
|
persona_id,
|
||||||
question_set_id,
|
question_set_id,
|
||||||
quick_tips
|
quick_tips,
|
||||||
) VALUES ($1, $2, $3, $4, $5, $6, $7)
|
publish_status
|
||||||
RETURNING id, unit_module_lesson_id, title, story_description, story_image, persona_id, question_set_id, quick_tips, created_at, updated_at
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||||
|
RETURNING id, unit_module_lesson_id, title, story_description, story_image, persona_id, question_set_id, quick_tips, created_at, updated_at, publish_status
|
||||||
`
|
`
|
||||||
|
|
||||||
type ExamPrepCreateLessonPracticeParams struct {
|
type ExamPrepCreateLessonPracticeParams struct {
|
||||||
|
|
@ -32,6 +33,7 @@ type ExamPrepCreateLessonPracticeParams struct {
|
||||||
PersonaID pgtype.Int8 `json:"persona_id"`
|
PersonaID pgtype.Int8 `json:"persona_id"`
|
||||||
QuestionSetID int64 `json:"question_set_id"`
|
QuestionSetID int64 `json:"question_set_id"`
|
||||||
QuickTips pgtype.Text `json:"quick_tips"`
|
QuickTips pgtype.Text `json:"quick_tips"`
|
||||||
|
PublishStatus string `json:"publish_status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) ExamPrepCreateLessonPractice(ctx context.Context, arg ExamPrepCreateLessonPracticeParams) (ExamPrepLessonPractice, error) {
|
func (q *Queries) ExamPrepCreateLessonPractice(ctx context.Context, arg ExamPrepCreateLessonPracticeParams) (ExamPrepLessonPractice, error) {
|
||||||
|
|
@ -43,6 +45,7 @@ func (q *Queries) ExamPrepCreateLessonPractice(ctx context.Context, arg ExamPrep
|
||||||
arg.PersonaID,
|
arg.PersonaID,
|
||||||
arg.QuestionSetID,
|
arg.QuestionSetID,
|
||||||
arg.QuickTips,
|
arg.QuickTips,
|
||||||
|
arg.PublishStatus,
|
||||||
)
|
)
|
||||||
var i ExamPrepLessonPractice
|
var i ExamPrepLessonPractice
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
|
|
@ -56,6 +59,7 @@ func (q *Queries) ExamPrepCreateLessonPractice(ctx context.Context, arg ExamPrep
|
||||||
&i.QuickTips,
|
&i.QuickTips,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
|
&i.PublishStatus,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
@ -71,7 +75,7 @@ func (q *Queries) ExamPrepDeleteLessonPractice(ctx context.Context, id int64) er
|
||||||
}
|
}
|
||||||
|
|
||||||
const ExamPrepGetLessonPracticeByID = `-- name: ExamPrepGetLessonPracticeByID :one
|
const ExamPrepGetLessonPracticeByID = `-- name: ExamPrepGetLessonPracticeByID :one
|
||||||
SELECT id, unit_module_lesson_id, title, story_description, story_image, persona_id, question_set_id, quick_tips, created_at, updated_at
|
SELECT id, unit_module_lesson_id, title, story_description, story_image, persona_id, question_set_id, quick_tips, created_at, updated_at, publish_status
|
||||||
FROM exam_prep.lesson_practices
|
FROM exam_prep.lesson_practices
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
`
|
`
|
||||||
|
|
@ -90,6 +94,34 @@ func (q *Queries) ExamPrepGetLessonPracticeByID(ctx context.Context, id int64) (
|
||||||
&i.QuickTips,
|
&i.QuickTips,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
|
&i.PublishStatus,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const ExamPrepGetLessonPracticeByQuestionSetID = `-- name: ExamPrepGetLessonPracticeByQuestionSetID :one
|
||||||
|
SELECT id, unit_module_lesson_id, title, story_description, story_image, persona_id, question_set_id, quick_tips, created_at, updated_at, publish_status
|
||||||
|
FROM exam_prep.lesson_practices
|
||||||
|
WHERE question_set_id = $1
|
||||||
|
ORDER BY id DESC
|
||||||
|
LIMIT 1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) ExamPrepGetLessonPracticeByQuestionSetID(ctx context.Context, questionSetID int64) (ExamPrepLessonPractice, error) {
|
||||||
|
row := q.db.QueryRow(ctx, ExamPrepGetLessonPracticeByQuestionSetID, questionSetID)
|
||||||
|
var i ExamPrepLessonPractice
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.UnitModuleLessonID,
|
||||||
|
&i.Title,
|
||||||
|
&i.StoryDescription,
|
||||||
|
&i.StoryImage,
|
||||||
|
&i.PersonaID,
|
||||||
|
&i.QuestionSetID,
|
||||||
|
&i.QuickTips,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.PublishStatus,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
@ -105,10 +137,15 @@ SELECT
|
||||||
p.persona_id,
|
p.persona_id,
|
||||||
p.question_set_id,
|
p.question_set_id,
|
||||||
p.quick_tips,
|
p.quick_tips,
|
||||||
|
p.publish_status,
|
||||||
p.created_at,
|
p.created_at,
|
||||||
p.updated_at
|
p.updated_at
|
||||||
FROM exam_prep.lesson_practices p
|
FROM exam_prep.lesson_practices p
|
||||||
WHERE p.unit_module_lesson_id = $1
|
WHERE p.unit_module_lesson_id = $1
|
||||||
|
AND (
|
||||||
|
$4::boolean = FALSE
|
||||||
|
OR p.publish_status = 'PUBLISHED'::TEXT
|
||||||
|
)
|
||||||
ORDER BY p.created_at DESC
|
ORDER BY p.created_at DESC
|
||||||
LIMIT $2
|
LIMIT $2
|
||||||
OFFSET $3
|
OFFSET $3
|
||||||
|
|
@ -118,6 +155,7 @@ type ExamPrepListLessonPracticesByLessonIDParams struct {
|
||||||
UnitModuleLessonID int64 `json:"unit_module_lesson_id"`
|
UnitModuleLessonID int64 `json:"unit_module_lesson_id"`
|
||||||
Limit int32 `json:"limit"`
|
Limit int32 `json:"limit"`
|
||||||
Offset int32 `json:"offset"`
|
Offset int32 `json:"offset"`
|
||||||
|
PublishedOnly bool `json:"published_only"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExamPrepListLessonPracticesByLessonIDRow struct {
|
type ExamPrepListLessonPracticesByLessonIDRow struct {
|
||||||
|
|
@ -130,12 +168,18 @@ type ExamPrepListLessonPracticesByLessonIDRow struct {
|
||||||
PersonaID pgtype.Int8 `json:"persona_id"`
|
PersonaID pgtype.Int8 `json:"persona_id"`
|
||||||
QuestionSetID int64 `json:"question_set_id"`
|
QuestionSetID int64 `json:"question_set_id"`
|
||||||
QuickTips pgtype.Text `json:"quick_tips"`
|
QuickTips pgtype.Text `json:"quick_tips"`
|
||||||
|
PublishStatus string `json:"publish_status"`
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) ExamPrepListLessonPracticesByLessonID(ctx context.Context, arg ExamPrepListLessonPracticesByLessonIDParams) ([]ExamPrepListLessonPracticesByLessonIDRow, error) {
|
func (q *Queries) ExamPrepListLessonPracticesByLessonID(ctx context.Context, arg ExamPrepListLessonPracticesByLessonIDParams) ([]ExamPrepListLessonPracticesByLessonIDRow, error) {
|
||||||
rows, err := q.db.Query(ctx, ExamPrepListLessonPracticesByLessonID, arg.UnitModuleLessonID, arg.Limit, arg.Offset)
|
rows, err := q.db.Query(ctx, ExamPrepListLessonPracticesByLessonID,
|
||||||
|
arg.UnitModuleLessonID,
|
||||||
|
arg.Limit,
|
||||||
|
arg.Offset,
|
||||||
|
arg.PublishedOnly,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -153,6 +197,7 @@ func (q *Queries) ExamPrepListLessonPracticesByLessonID(ctx context.Context, arg
|
||||||
&i.PersonaID,
|
&i.PersonaID,
|
||||||
&i.QuestionSetID,
|
&i.QuestionSetID,
|
||||||
&i.QuickTips,
|
&i.QuickTips,
|
||||||
|
&i.PublishStatus,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
|
@ -175,9 +220,10 @@ SET
|
||||||
persona_id = coalesce($4::bigint, persona_id),
|
persona_id = coalesce($4::bigint, persona_id),
|
||||||
question_set_id = coalesce($5::bigint, question_set_id),
|
question_set_id = coalesce($5::bigint, question_set_id),
|
||||||
quick_tips = coalesce($6::text, quick_tips),
|
quick_tips = coalesce($6::text, quick_tips),
|
||||||
|
publish_status = coalesce($7::varchar, publish_status),
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = $7
|
WHERE id = $8
|
||||||
RETURNING id, unit_module_lesson_id, title, story_description, story_image, persona_id, question_set_id, quick_tips, created_at, updated_at
|
RETURNING id, unit_module_lesson_id, title, story_description, story_image, persona_id, question_set_id, quick_tips, created_at, updated_at, publish_status
|
||||||
`
|
`
|
||||||
|
|
||||||
type ExamPrepUpdateLessonPracticeParams struct {
|
type ExamPrepUpdateLessonPracticeParams struct {
|
||||||
|
|
@ -187,6 +233,7 @@ type ExamPrepUpdateLessonPracticeParams struct {
|
||||||
PersonaID pgtype.Int8 `json:"persona_id"`
|
PersonaID pgtype.Int8 `json:"persona_id"`
|
||||||
QuestionSetID pgtype.Int8 `json:"question_set_id"`
|
QuestionSetID pgtype.Int8 `json:"question_set_id"`
|
||||||
QuickTips pgtype.Text `json:"quick_tips"`
|
QuickTips pgtype.Text `json:"quick_tips"`
|
||||||
|
PublishStatus pgtype.Text `json:"publish_status"`
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -198,6 +245,7 @@ func (q *Queries) ExamPrepUpdateLessonPractice(ctx context.Context, arg ExamPrep
|
||||||
arg.PersonaID,
|
arg.PersonaID,
|
||||||
arg.QuestionSetID,
|
arg.QuestionSetID,
|
||||||
arg.QuickTips,
|
arg.QuickTips,
|
||||||
|
arg.PublishStatus,
|
||||||
arg.ID,
|
arg.ID,
|
||||||
)
|
)
|
||||||
var i ExamPrepLessonPractice
|
var i ExamPrepLessonPractice
|
||||||
|
|
@ -212,6 +260,7 @@ func (q *Queries) ExamPrepUpdateLessonPractice(ctx context.Context, arg ExamPrep
|
||||||
&i.QuickTips,
|
&i.QuickTips,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
|
&i.PublishStatus,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,7 @@ UNION ALL
|
||||||
user_practice_progress upp
|
user_practice_progress upp
|
||||||
INNER JOIN lms_practices lp ON lp.question_set_id = upp.question_set_id
|
INNER JOIN lms_practices lp ON lp.question_set_id = upp.question_set_id
|
||||||
AND lp.lesson_id IS NOT NULL
|
AND lp.lesson_id IS NOT NULL
|
||||||
|
AND lp.publish_status = 'PUBLISHED'
|
||||||
INNER JOIN question_sets qs ON qs.id = upp.question_set_id
|
INNER JOIN question_sets qs ON qs.id = upp.question_set_id
|
||||||
AND qs.set_type = 'PRACTICE'
|
AND qs.set_type = 'PRACTICE'
|
||||||
AND qs.status = 'PUBLISHED'
|
AND qs.status = 'PUBLISHED'
|
||||||
|
|
@ -143,6 +144,7 @@ UNION ALL
|
||||||
INNER JOIN lms_practices lp ON lp.question_set_id = upp.question_set_id
|
INNER JOIN lms_practices lp ON lp.question_set_id = upp.question_set_id
|
||||||
AND lp.module_id IS NOT NULL
|
AND lp.module_id IS NOT NULL
|
||||||
AND lp.lesson_id IS NULL
|
AND lp.lesson_id IS NULL
|
||||||
|
AND lp.publish_status = 'PUBLISHED'
|
||||||
INNER JOIN question_sets qs ON qs.id = upp.question_set_id
|
INNER JOIN question_sets qs ON qs.id = upp.question_set_id
|
||||||
AND qs.set_type = 'PRACTICE'
|
AND qs.set_type = 'PRACTICE'
|
||||||
AND qs.status = 'PUBLISHED'
|
AND qs.status = 'PUBLISHED'
|
||||||
|
|
@ -187,6 +189,7 @@ UNION ALL
|
||||||
AND lp.course_id IS NOT NULL
|
AND lp.course_id IS NOT NULL
|
||||||
AND lp.module_id IS NULL
|
AND lp.module_id IS NULL
|
||||||
AND lp.lesson_id IS NULL
|
AND lp.lesson_id IS NULL
|
||||||
|
AND lp.publish_status = 'PUBLISHED'
|
||||||
INNER JOIN question_sets qs ON qs.id = upp.question_set_id
|
INNER JOIN question_sets qs ON qs.id = upp.question_set_id
|
||||||
AND qs.set_type = 'PRACTICE'
|
AND qs.set_type = 'PRACTICE'
|
||||||
AND qs.status = 'PUBLISHED'
|
AND qs.status = 'PUBLISHED'
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,7 @@ SELECT
|
||||||
WHERE p.course_id = c.id
|
WHERE p.course_id = c.id
|
||||||
AND p.module_id IS NULL
|
AND p.module_id IS NULL
|
||||||
AND p.lesson_id IS NULL
|
AND p.lesson_id IS NULL
|
||||||
|
AND p.publish_status = 'PUBLISHED'
|
||||||
) AS has_practice
|
) AS has_practice
|
||||||
FROM courses
|
FROM courses
|
||||||
c
|
c
|
||||||
|
|
@ -180,13 +181,15 @@ SELECT
|
||||||
WHERE
|
WHERE
|
||||||
p.course_id = c.id
|
p.course_id = c.id
|
||||||
AND p.module_id IS NULL
|
AND p.module_id IS NULL
|
||||||
AND p.lesson_id IS NULL) AS practice_count,
|
AND p.lesson_id IS NULL
|
||||||
|
AND p.publish_status = 'PUBLISHED') AS practice_count,
|
||||||
EXISTS (
|
EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM lms_practices p
|
FROM lms_practices p
|
||||||
WHERE p.course_id = c.id
|
WHERE p.course_id = c.id
|
||||||
AND p.module_id IS NULL
|
AND p.module_id IS NULL
|
||||||
AND p.lesson_id IS NULL
|
AND p.lesson_id IS NULL
|
||||||
|
AND p.publish_status = 'PUBLISHED'
|
||||||
) AS has_practice
|
) AS has_practice
|
||||||
FROM
|
FROM
|
||||||
courses c
|
courses c
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@ SELECT
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM lms_practices p
|
FROM lms_practices p
|
||||||
WHERE p.lesson_id = l.id
|
WHERE p.lesson_id = l.id
|
||||||
|
AND p.publish_status = 'PUBLISHED'
|
||||||
) AS has_practice
|
) AS has_practice
|
||||||
FROM lessons
|
FROM lessons
|
||||||
l
|
l
|
||||||
|
|
@ -130,6 +131,7 @@ SELECT
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM lms_practices p
|
FROM lms_practices p
|
||||||
WHERE p.lesson_id = l.id
|
WHERE p.lesson_id = l.id
|
||||||
|
AND p.publish_status = 'PUBLISHED'
|
||||||
) AS has_practice
|
) AS has_practice
|
||||||
FROM
|
FROM
|
||||||
lessons l
|
lessons l
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,7 @@ SELECT
|
||||||
FROM lms_practices p
|
FROM lms_practices p
|
||||||
WHERE p.module_id = m.id
|
WHERE p.module_id = m.id
|
||||||
AND p.lesson_id IS NULL
|
AND p.lesson_id IS NULL
|
||||||
|
AND p.publish_status = 'PUBLISHED'
|
||||||
) AS has_practice
|
) AS has_practice
|
||||||
FROM modules
|
FROM modules
|
||||||
m
|
m
|
||||||
|
|
@ -163,6 +164,7 @@ SELECT
|
||||||
FROM lms_practices p
|
FROM lms_practices p
|
||||||
WHERE p.module_id = m.id
|
WHERE p.module_id = m.id
|
||||||
AND p.lesson_id IS NULL
|
AND p.lesson_id IS NULL
|
||||||
|
AND p.publish_status = 'PUBLISHED'
|
||||||
) AS has_practice
|
) AS has_practice
|
||||||
FROM
|
FROM
|
||||||
modules m
|
modules m
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,9 @@ import (
|
||||||
const CreateLmsPractice = `-- name: CreateLmsPractice :one
|
const CreateLmsPractice = `-- name: CreateLmsPractice :one
|
||||||
INSERT INTO lms_practices (
|
INSERT INTO lms_practices (
|
||||||
course_id, module_id, lesson_id,
|
course_id, module_id, lesson_id,
|
||||||
title, story_description, story_image, persona_id, question_set_id, quick_tips
|
title, story_description, story_image, persona_id, question_set_id, quick_tips, publish_status
|
||||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||||
RETURNING id, course_id, module_id, lesson_id, title, story_description, story_image, persona_id, question_set_id, quick_tips, created_at, updated_at
|
RETURNING id, course_id, module_id, lesson_id, title, story_description, story_image, persona_id, question_set_id, quick_tips, created_at, updated_at, publish_status
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateLmsPracticeParams struct {
|
type CreateLmsPracticeParams struct {
|
||||||
|
|
@ -29,6 +29,7 @@ type CreateLmsPracticeParams struct {
|
||||||
PersonaID pgtype.Int8 `json:"persona_id"`
|
PersonaID pgtype.Int8 `json:"persona_id"`
|
||||||
QuestionSetID int64 `json:"question_set_id"`
|
QuestionSetID int64 `json:"question_set_id"`
|
||||||
QuickTips pgtype.Text `json:"quick_tips"`
|
QuickTips pgtype.Text `json:"quick_tips"`
|
||||||
|
PublishStatus string `json:"publish_status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateLmsPractice(ctx context.Context, arg CreateLmsPracticeParams) (LmsPractice, error) {
|
func (q *Queries) CreateLmsPractice(ctx context.Context, arg CreateLmsPracticeParams) (LmsPractice, error) {
|
||||||
|
|
@ -42,6 +43,7 @@ func (q *Queries) CreateLmsPractice(ctx context.Context, arg CreateLmsPracticePa
|
||||||
arg.PersonaID,
|
arg.PersonaID,
|
||||||
arg.QuestionSetID,
|
arg.QuestionSetID,
|
||||||
arg.QuickTips,
|
arg.QuickTips,
|
||||||
|
arg.PublishStatus,
|
||||||
)
|
)
|
||||||
var i LmsPractice
|
var i LmsPractice
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
|
|
@ -57,6 +59,7 @@ func (q *Queries) CreateLmsPractice(ctx context.Context, arg CreateLmsPracticePa
|
||||||
&i.QuickTips,
|
&i.QuickTips,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
|
&i.PublishStatus,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
@ -72,7 +75,7 @@ func (q *Queries) DeleteLmsPractice(ctx context.Context, id int64) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetLmsPracticeByID = `-- name: GetLmsPracticeByID :one
|
const GetLmsPracticeByID = `-- name: GetLmsPracticeByID :one
|
||||||
SELECT id, course_id, module_id, lesson_id, title, story_description, story_image, persona_id, question_set_id, quick_tips, created_at, updated_at
|
SELECT id, course_id, module_id, lesson_id, title, story_description, story_image, persona_id, question_set_id, quick_tips, created_at, updated_at, publish_status
|
||||||
FROM lms_practices
|
FROM lms_practices
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
`
|
`
|
||||||
|
|
@ -93,6 +96,36 @@ func (q *Queries) GetLmsPracticeByID(ctx context.Context, id int64) (LmsPractice
|
||||||
&i.QuickTips,
|
&i.QuickTips,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
|
&i.PublishStatus,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetLmsPracticeByQuestionSetID = `-- name: GetLmsPracticeByQuestionSetID :one
|
||||||
|
SELECT id, course_id, module_id, lesson_id, title, story_description, story_image, persona_id, question_set_id, quick_tips, created_at, updated_at, publish_status
|
||||||
|
FROM lms_practices
|
||||||
|
WHERE question_set_id = $1
|
||||||
|
ORDER BY id DESC
|
||||||
|
LIMIT 1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetLmsPracticeByQuestionSetID(ctx context.Context, questionSetID int64) (LmsPractice, error) {
|
||||||
|
row := q.db.QueryRow(ctx, GetLmsPracticeByQuestionSetID, questionSetID)
|
||||||
|
var i LmsPractice
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.CourseID,
|
||||||
|
&i.ModuleID,
|
||||||
|
&i.LessonID,
|
||||||
|
&i.Title,
|
||||||
|
&i.StoryDescription,
|
||||||
|
&i.StoryImage,
|
||||||
|
&i.PersonaID,
|
||||||
|
&i.QuestionSetID,
|
||||||
|
&i.QuickTips,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.PublishStatus,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
@ -110,10 +143,15 @@ SELECT
|
||||||
p.persona_id,
|
p.persona_id,
|
||||||
p.question_set_id,
|
p.question_set_id,
|
||||||
p.quick_tips,
|
p.quick_tips,
|
||||||
|
p.publish_status,
|
||||||
p.created_at,
|
p.created_at,
|
||||||
p.updated_at
|
p.updated_at
|
||||||
FROM lms_practices p
|
FROM lms_practices p
|
||||||
WHERE p.course_id = $1
|
WHERE p.course_id = $1
|
||||||
|
AND (
|
||||||
|
$4::boolean = FALSE
|
||||||
|
OR p.publish_status = 'PUBLISHED'::TEXT
|
||||||
|
)
|
||||||
ORDER BY p.created_at DESC
|
ORDER BY p.created_at DESC
|
||||||
LIMIT $2 OFFSET $3
|
LIMIT $2 OFFSET $3
|
||||||
`
|
`
|
||||||
|
|
@ -122,6 +160,7 @@ type ListLmsPracticesByCourseIDParams struct {
|
||||||
CourseID pgtype.Int8 `json:"course_id"`
|
CourseID pgtype.Int8 `json:"course_id"`
|
||||||
Limit int32 `json:"limit"`
|
Limit int32 `json:"limit"`
|
||||||
Offset int32 `json:"offset"`
|
Offset int32 `json:"offset"`
|
||||||
|
PublishedOnly bool `json:"published_only"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListLmsPracticesByCourseIDRow struct {
|
type ListLmsPracticesByCourseIDRow struct {
|
||||||
|
|
@ -136,12 +175,18 @@ type ListLmsPracticesByCourseIDRow struct {
|
||||||
PersonaID pgtype.Int8 `json:"persona_id"`
|
PersonaID pgtype.Int8 `json:"persona_id"`
|
||||||
QuestionSetID int64 `json:"question_set_id"`
|
QuestionSetID int64 `json:"question_set_id"`
|
||||||
QuickTips pgtype.Text `json:"quick_tips"`
|
QuickTips pgtype.Text `json:"quick_tips"`
|
||||||
|
PublishStatus string `json:"publish_status"`
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) ListLmsPracticesByCourseID(ctx context.Context, arg ListLmsPracticesByCourseIDParams) ([]ListLmsPracticesByCourseIDRow, error) {
|
func (q *Queries) ListLmsPracticesByCourseID(ctx context.Context, arg ListLmsPracticesByCourseIDParams) ([]ListLmsPracticesByCourseIDRow, error) {
|
||||||
rows, err := q.db.Query(ctx, ListLmsPracticesByCourseID, arg.CourseID, arg.Limit, arg.Offset)
|
rows, err := q.db.Query(ctx, ListLmsPracticesByCourseID,
|
||||||
|
arg.CourseID,
|
||||||
|
arg.Limit,
|
||||||
|
arg.Offset,
|
||||||
|
arg.PublishedOnly,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -161,6 +206,7 @@ func (q *Queries) ListLmsPracticesByCourseID(ctx context.Context, arg ListLmsPra
|
||||||
&i.PersonaID,
|
&i.PersonaID,
|
||||||
&i.QuestionSetID,
|
&i.QuestionSetID,
|
||||||
&i.QuickTips,
|
&i.QuickTips,
|
||||||
|
&i.PublishStatus,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
|
@ -187,10 +233,15 @@ SELECT
|
||||||
p.persona_id,
|
p.persona_id,
|
||||||
p.question_set_id,
|
p.question_set_id,
|
||||||
p.quick_tips,
|
p.quick_tips,
|
||||||
|
p.publish_status,
|
||||||
p.created_at,
|
p.created_at,
|
||||||
p.updated_at
|
p.updated_at
|
||||||
FROM lms_practices p
|
FROM lms_practices p
|
||||||
WHERE p.lesson_id = $1
|
WHERE p.lesson_id = $1
|
||||||
|
AND (
|
||||||
|
$4::boolean = FALSE
|
||||||
|
OR p.publish_status = 'PUBLISHED'::TEXT
|
||||||
|
)
|
||||||
ORDER BY p.created_at DESC
|
ORDER BY p.created_at DESC
|
||||||
LIMIT $2 OFFSET $3
|
LIMIT $2 OFFSET $3
|
||||||
`
|
`
|
||||||
|
|
@ -199,6 +250,7 @@ type ListLmsPracticesByLessonIDParams struct {
|
||||||
LessonID pgtype.Int8 `json:"lesson_id"`
|
LessonID pgtype.Int8 `json:"lesson_id"`
|
||||||
Limit int32 `json:"limit"`
|
Limit int32 `json:"limit"`
|
||||||
Offset int32 `json:"offset"`
|
Offset int32 `json:"offset"`
|
||||||
|
PublishedOnly bool `json:"published_only"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListLmsPracticesByLessonIDRow struct {
|
type ListLmsPracticesByLessonIDRow struct {
|
||||||
|
|
@ -213,12 +265,18 @@ type ListLmsPracticesByLessonIDRow struct {
|
||||||
PersonaID pgtype.Int8 `json:"persona_id"`
|
PersonaID pgtype.Int8 `json:"persona_id"`
|
||||||
QuestionSetID int64 `json:"question_set_id"`
|
QuestionSetID int64 `json:"question_set_id"`
|
||||||
QuickTips pgtype.Text `json:"quick_tips"`
|
QuickTips pgtype.Text `json:"quick_tips"`
|
||||||
|
PublishStatus string `json:"publish_status"`
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) ListLmsPracticesByLessonID(ctx context.Context, arg ListLmsPracticesByLessonIDParams) ([]ListLmsPracticesByLessonIDRow, error) {
|
func (q *Queries) ListLmsPracticesByLessonID(ctx context.Context, arg ListLmsPracticesByLessonIDParams) ([]ListLmsPracticesByLessonIDRow, error) {
|
||||||
rows, err := q.db.Query(ctx, ListLmsPracticesByLessonID, arg.LessonID, arg.Limit, arg.Offset)
|
rows, err := q.db.Query(ctx, ListLmsPracticesByLessonID,
|
||||||
|
arg.LessonID,
|
||||||
|
arg.Limit,
|
||||||
|
arg.Offset,
|
||||||
|
arg.PublishedOnly,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -238,6 +296,7 @@ func (q *Queries) ListLmsPracticesByLessonID(ctx context.Context, arg ListLmsPra
|
||||||
&i.PersonaID,
|
&i.PersonaID,
|
||||||
&i.QuestionSetID,
|
&i.QuestionSetID,
|
||||||
&i.QuickTips,
|
&i.QuickTips,
|
||||||
|
&i.PublishStatus,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
|
@ -264,10 +323,15 @@ SELECT
|
||||||
p.persona_id,
|
p.persona_id,
|
||||||
p.question_set_id,
|
p.question_set_id,
|
||||||
p.quick_tips,
|
p.quick_tips,
|
||||||
|
p.publish_status,
|
||||||
p.created_at,
|
p.created_at,
|
||||||
p.updated_at
|
p.updated_at
|
||||||
FROM lms_practices p
|
FROM lms_practices p
|
||||||
WHERE p.module_id = $1
|
WHERE p.module_id = $1
|
||||||
|
AND (
|
||||||
|
$4::boolean = FALSE
|
||||||
|
OR p.publish_status = 'PUBLISHED'::TEXT
|
||||||
|
)
|
||||||
ORDER BY p.created_at DESC
|
ORDER BY p.created_at DESC
|
||||||
LIMIT $2 OFFSET $3
|
LIMIT $2 OFFSET $3
|
||||||
`
|
`
|
||||||
|
|
@ -276,6 +340,7 @@ type ListLmsPracticesByModuleIDParams struct {
|
||||||
ModuleID pgtype.Int8 `json:"module_id"`
|
ModuleID pgtype.Int8 `json:"module_id"`
|
||||||
Limit int32 `json:"limit"`
|
Limit int32 `json:"limit"`
|
||||||
Offset int32 `json:"offset"`
|
Offset int32 `json:"offset"`
|
||||||
|
PublishedOnly bool `json:"published_only"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListLmsPracticesByModuleIDRow struct {
|
type ListLmsPracticesByModuleIDRow struct {
|
||||||
|
|
@ -290,12 +355,18 @@ type ListLmsPracticesByModuleIDRow struct {
|
||||||
PersonaID pgtype.Int8 `json:"persona_id"`
|
PersonaID pgtype.Int8 `json:"persona_id"`
|
||||||
QuestionSetID int64 `json:"question_set_id"`
|
QuestionSetID int64 `json:"question_set_id"`
|
||||||
QuickTips pgtype.Text `json:"quick_tips"`
|
QuickTips pgtype.Text `json:"quick_tips"`
|
||||||
|
PublishStatus string `json:"publish_status"`
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) ListLmsPracticesByModuleID(ctx context.Context, arg ListLmsPracticesByModuleIDParams) ([]ListLmsPracticesByModuleIDRow, error) {
|
func (q *Queries) ListLmsPracticesByModuleID(ctx context.Context, arg ListLmsPracticesByModuleIDParams) ([]ListLmsPracticesByModuleIDRow, error) {
|
||||||
rows, err := q.db.Query(ctx, ListLmsPracticesByModuleID, arg.ModuleID, arg.Limit, arg.Offset)
|
rows, err := q.db.Query(ctx, ListLmsPracticesByModuleID,
|
||||||
|
arg.ModuleID,
|
||||||
|
arg.Limit,
|
||||||
|
arg.Offset,
|
||||||
|
arg.PublishedOnly,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -315,6 +386,7 @@ func (q *Queries) ListLmsPracticesByModuleID(ctx context.Context, arg ListLmsPra
|
||||||
&i.PersonaID,
|
&i.PersonaID,
|
||||||
&i.QuestionSetID,
|
&i.QuestionSetID,
|
||||||
&i.QuickTips,
|
&i.QuickTips,
|
||||||
|
&i.PublishStatus,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
|
@ -337,9 +409,10 @@ SET
|
||||||
persona_id = COALESCE($4::bigint, persona_id),
|
persona_id = COALESCE($4::bigint, persona_id),
|
||||||
question_set_id = COALESCE($5::bigint, question_set_id),
|
question_set_id = COALESCE($5::bigint, question_set_id),
|
||||||
quick_tips = COALESCE($6::text, quick_tips),
|
quick_tips = COALESCE($6::text, quick_tips),
|
||||||
|
publish_status = COALESCE($7::varchar, publish_status),
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = $7
|
WHERE id = $8
|
||||||
RETURNING id, course_id, module_id, lesson_id, title, story_description, story_image, persona_id, question_set_id, quick_tips, created_at, updated_at
|
RETURNING id, course_id, module_id, lesson_id, title, story_description, story_image, persona_id, question_set_id, quick_tips, created_at, updated_at, publish_status
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateLmsPracticeParams struct {
|
type UpdateLmsPracticeParams struct {
|
||||||
|
|
@ -349,6 +422,7 @@ type UpdateLmsPracticeParams struct {
|
||||||
PersonaID pgtype.Int8 `json:"persona_id"`
|
PersonaID pgtype.Int8 `json:"persona_id"`
|
||||||
QuestionSetID pgtype.Int8 `json:"question_set_id"`
|
QuestionSetID pgtype.Int8 `json:"question_set_id"`
|
||||||
QuickTips pgtype.Text `json:"quick_tips"`
|
QuickTips pgtype.Text `json:"quick_tips"`
|
||||||
|
PublishStatus pgtype.Text `json:"publish_status"`
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -360,6 +434,7 @@ func (q *Queries) UpdateLmsPractice(ctx context.Context, arg UpdateLmsPracticePa
|
||||||
arg.PersonaID,
|
arg.PersonaID,
|
||||||
arg.QuestionSetID,
|
arg.QuestionSetID,
|
||||||
arg.QuickTips,
|
arg.QuickTips,
|
||||||
|
arg.PublishStatus,
|
||||||
arg.ID,
|
arg.ID,
|
||||||
)
|
)
|
||||||
var i LmsPractice
|
var i LmsPractice
|
||||||
|
|
@ -376,6 +451,7 @@ func (q *Queries) UpdateLmsPractice(ctx context.Context, arg UpdateLmsPracticePa
|
||||||
&i.QuickTips,
|
&i.QuickTips,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
|
&i.PublishStatus,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,7 @@ WHERE
|
||||||
lp.course_id = $1
|
lp.course_id = $1
|
||||||
AND qs.set_type = 'PRACTICE'
|
AND qs.set_type = 'PRACTICE'
|
||||||
AND qs.status = 'PUBLISHED'
|
AND qs.status = 'PUBLISHED'
|
||||||
|
AND lp.publish_status = 'PUBLISHED'
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) CountPublishedPracticesInCourse(ctx context.Context, courseID pgtype.Int8) (int32, error) {
|
func (q *Queries) CountPublishedPracticesInCourse(ctx context.Context, courseID pgtype.Int8) (int32, error) {
|
||||||
|
|
@ -125,6 +126,7 @@ WHERE
|
||||||
lp.module_id = $1
|
lp.module_id = $1
|
||||||
AND qs.set_type = 'PRACTICE'
|
AND qs.set_type = 'PRACTICE'
|
||||||
AND qs.status = 'PUBLISHED'
|
AND qs.status = 'PUBLISHED'
|
||||||
|
AND lp.publish_status = 'PUBLISHED'
|
||||||
`
|
`
|
||||||
|
|
||||||
// Published practices in a module (module-level and lesson-level practices should carry module_id).
|
// Published practices in a module (module-level and lesson-level practices should carry module_id).
|
||||||
|
|
@ -146,6 +148,7 @@ WHERE
|
||||||
c.program_id = $1
|
c.program_id = $1
|
||||||
AND qs.set_type = 'PRACTICE'
|
AND qs.set_type = 'PRACTICE'
|
||||||
AND qs.status = 'PUBLISHED'
|
AND qs.status = 'PUBLISHED'
|
||||||
|
AND lp.publish_status = 'PUBLISHED'
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) CountPublishedPracticesInProgram(ctx context.Context, programID int64) (int32, error) {
|
func (q *Queries) CountPublishedPracticesInProgram(ctx context.Context, programID int64) (int32, error) {
|
||||||
|
|
@ -313,6 +316,7 @@ WHERE
|
||||||
AND upp.completed_at IS NOT NULL
|
AND upp.completed_at IS NOT NULL
|
||||||
AND qs.set_type = 'PRACTICE'
|
AND qs.set_type = 'PRACTICE'
|
||||||
AND qs.status = 'PUBLISHED'
|
AND qs.status = 'PUBLISHED'
|
||||||
|
AND lp.publish_status = 'PUBLISHED'
|
||||||
`
|
`
|
||||||
|
|
||||||
type CountUserCompletedPublishedPracticesInModuleParams struct {
|
type CountUserCompletedPublishedPracticesInModuleParams struct {
|
||||||
|
|
@ -341,6 +345,7 @@ WHERE
|
||||||
AND upp.completed_at IS NOT NULL
|
AND upp.completed_at IS NOT NULL
|
||||||
AND qs.set_type = 'PRACTICE'
|
AND qs.set_type = 'PRACTICE'
|
||||||
AND qs.status = 'PUBLISHED'
|
AND qs.status = 'PUBLISHED'
|
||||||
|
AND lp.publish_status = 'PUBLISHED'
|
||||||
`
|
`
|
||||||
|
|
||||||
type CountUserCompletedPublishedPracticesInProgramParams struct {
|
type CountUserCompletedPublishedPracticesInProgramParams struct {
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ type ExamPrepLessonPractice struct {
|
||||||
QuickTips pgtype.Text `json:"quick_tips"`
|
QuickTips pgtype.Text `json:"quick_tips"`
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
|
PublishStatus string `json:"publish_status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExamPrepUnit struct {
|
type ExamPrepUnit struct {
|
||||||
|
|
@ -149,6 +150,7 @@ type LmsPractice struct {
|
||||||
QuickTips pgtype.Text `json:"quick_tips"`
|
QuickTips pgtype.Text `json:"quick_tips"`
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
|
PublishStatus string `json:"publish_status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LmsUserCourseProgress struct {
|
type LmsUserCourseProgress struct {
|
||||||
|
|
|
||||||
|
|
@ -223,6 +223,7 @@ FROM (
|
||||||
user_practice_progress upp
|
user_practice_progress upp
|
||||||
INNER JOIN lms_practices lp ON lp.question_set_id = upp.question_set_id
|
INNER JOIN lms_practices lp ON lp.question_set_id = upp.question_set_id
|
||||||
AND lp.lesson_id IS NOT NULL
|
AND lp.lesson_id IS NOT NULL
|
||||||
|
AND lp.publish_status = 'PUBLISHED'
|
||||||
INNER JOIN question_sets qs ON qs.id = upp.question_set_id
|
INNER JOIN question_sets qs ON qs.id = upp.question_set_id
|
||||||
AND qs.set_type = 'PRACTICE'
|
AND qs.set_type = 'PRACTICE'
|
||||||
AND qs.status = 'PUBLISHED'
|
AND qs.status = 'PUBLISHED'
|
||||||
|
|
@ -254,6 +255,7 @@ FROM (
|
||||||
INNER JOIN lms_practices lp ON lp.question_set_id = upp.question_set_id
|
INNER JOIN lms_practices lp ON lp.question_set_id = upp.question_set_id
|
||||||
AND lp.module_id IS NOT NULL
|
AND lp.module_id IS NOT NULL
|
||||||
AND lp.lesson_id IS NULL
|
AND lp.lesson_id IS NULL
|
||||||
|
AND lp.publish_status = 'PUBLISHED'
|
||||||
INNER JOIN question_sets qs ON qs.id = upp.question_set_id
|
INNER JOIN question_sets qs ON qs.id = upp.question_set_id
|
||||||
AND qs.set_type = 'PRACTICE'
|
AND qs.set_type = 'PRACTICE'
|
||||||
AND qs.status = 'PUBLISHED'
|
AND qs.status = 'PUBLISHED'
|
||||||
|
|
@ -285,6 +287,7 @@ FROM (
|
||||||
AND lp.course_id IS NOT NULL
|
AND lp.course_id IS NOT NULL
|
||||||
AND lp.module_id IS NULL
|
AND lp.module_id IS NULL
|
||||||
AND lp.lesson_id IS NULL
|
AND lp.lesson_id IS NULL
|
||||||
|
AND lp.publish_status = 'PUBLISHED'
|
||||||
INNER JOIN question_sets qs ON qs.id = upp.question_set_id
|
INNER JOIN question_sets qs ON qs.id = upp.question_set_id
|
||||||
AND qs.set_type = 'PRACTICE'
|
AND qs.set_type = 'PRACTICE'
|
||||||
AND qs.status = 'PUBLISHED'
|
AND qs.status = 'PUBLISHED'
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,17 @@ type ExamPrepPractice struct {
|
||||||
StoryImage *string `json:"story_image,omitempty"`
|
StoryImage *string `json:"story_image,omitempty"`
|
||||||
PersonaID *int64 `json:"persona_id,omitempty"`
|
PersonaID *int64 `json:"persona_id,omitempty"`
|
||||||
QuestionSetID int64 `json:"question_set_id"`
|
QuestionSetID int64 `json:"question_set_id"`
|
||||||
|
PublishStatus PracticePublishStatus `json:"publish_status"`
|
||||||
QuickTips *string `json:"quick_tips,omitempty"`
|
QuickTips *string `json:"quick_tips,omitempty"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VisibleToLearners mirrors LMS practice visibility rules for subscribers.
|
||||||
|
func (p ExamPrepPractice) VisibleToLearners() bool {
|
||||||
|
return p.PublishStatus == PracticePublishPublished
|
||||||
|
}
|
||||||
|
|
||||||
// CreateExamPrepPracticeInput is the body for POST .../exam-prep/lessons/{lessonId}/practices (lesson from path).
|
// CreateExamPrepPracticeInput is the body for POST .../exam-prep/lessons/{lessonId}/practices (lesson from path).
|
||||||
type CreateExamPrepPracticeInput struct {
|
type CreateExamPrepPracticeInput struct {
|
||||||
Title string `json:"title" validate:"required"`
|
Title string `json:"title" validate:"required"`
|
||||||
|
|
@ -24,6 +30,7 @@ type CreateExamPrepPracticeInput struct {
|
||||||
PersonaID *int64 `json:"persona_id,omitempty"`
|
PersonaID *int64 `json:"persona_id,omitempty"`
|
||||||
QuestionSetID int64 `json:"question_set_id" validate:"required,gt=0"`
|
QuestionSetID int64 `json:"question_set_id" validate:"required,gt=0"`
|
||||||
QuickTips *string `json:"quick_tips,omitempty"`
|
QuickTips *string `json:"quick_tips,omitempty"`
|
||||||
|
PublishStatus string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateExamPrepPracticeInput struct {
|
type UpdateExamPrepPracticeInput struct {
|
||||||
|
|
@ -33,4 +40,5 @@ type UpdateExamPrepPracticeInput struct {
|
||||||
PersonaID *int64 `json:"persona_id,omitempty"`
|
PersonaID *int64 `json:"persona_id,omitempty"`
|
||||||
QuestionSetID *int64 `json:"question_set_id,omitempty"`
|
QuestionSetID *int64 `json:"question_set_id,omitempty"`
|
||||||
QuickTips *string `json:"quick_tips,omitempty"`
|
QuickTips *string `json:"quick_tips,omitempty"`
|
||||||
|
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
package domain
|
package domain
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
// ParentKind identifies which hierarchy entity owns a practice (exactly one).
|
// ParentKind identifies which hierarchy entity owns a practice (exactly one).
|
||||||
type ParentKind string
|
type ParentKind string
|
||||||
|
|
@ -11,6 +14,31 @@ const (
|
||||||
ParentKindLesson ParentKind = "LESSON"
|
ParentKindLesson ParentKind = "LESSON"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// PracticePublishStatus controls learner visibility for a practice shell (independent of question_set.status).
|
||||||
|
type PracticePublishStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
PracticePublishDraft PracticePublishStatus = "DRAFT"
|
||||||
|
PracticePublishPublished PracticePublishStatus = "PUBLISHED"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParsePracticePublishStatusInput maps API input. Empty or unknown values default to PUBLISHED for backward compatibility.
|
||||||
|
func ParsePracticePublishStatusInput(raw string) PracticePublishStatus {
|
||||||
|
switch strings.TrimSpace(strings.ToUpper(raw)) {
|
||||||
|
case string(PracticePublishDraft):
|
||||||
|
return PracticePublishDraft
|
||||||
|
case string(PracticePublishPublished):
|
||||||
|
return PracticePublishPublished
|
||||||
|
default:
|
||||||
|
return PracticePublishPublished
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PracticePublishStatusFromDB maps persisted values into the domain type.
|
||||||
|
func PracticePublishStatusFromDB(raw string) PracticePublishStatus {
|
||||||
|
return ParsePracticePublishStatusInput(raw)
|
||||||
|
}
|
||||||
|
|
||||||
// Practice is question-set content (story, persona, tips) scoped to a course, module, or lesson.
|
// Practice is question-set content (story, persona, tips) scoped to a course, module, or lesson.
|
||||||
type Practice struct {
|
type Practice struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
|
|
@ -21,11 +49,17 @@ type Practice struct {
|
||||||
StoryImage *string `json:"story_image,omitempty"`
|
StoryImage *string `json:"story_image,omitempty"`
|
||||||
PersonaID *int64 `json:"persona_id,omitempty"`
|
PersonaID *int64 `json:"persona_id,omitempty"`
|
||||||
QuestionSetID int64 `json:"question_set_id"`
|
QuestionSetID int64 `json:"question_set_id"`
|
||||||
|
PublishStatus PracticePublishStatus `json:"publish_status"`
|
||||||
QuickTips *string `json:"quick_tips,omitempty"`
|
QuickTips *string `json:"quick_tips,omitempty"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VisibleToLearners is true when the practice shell should appear in subscribed learner catalogs and progression.
|
||||||
|
func (p Practice) VisibleToLearners() bool {
|
||||||
|
return p.PublishStatus == PracticePublishPublished
|
||||||
|
}
|
||||||
|
|
||||||
type CreatePracticeInput struct {
|
type CreatePracticeInput struct {
|
||||||
ParentKind ParentKind `json:"parent_kind" validate:"required,oneof=COURSE MODULE LESSON"`
|
ParentKind ParentKind `json:"parent_kind" validate:"required,oneof=COURSE MODULE LESSON"`
|
||||||
ParentID int64 `json:"parent_id" validate:"required,gt=0"`
|
ParentID int64 `json:"parent_id" validate:"required,gt=0"`
|
||||||
|
|
@ -35,6 +69,8 @@ type CreatePracticeInput struct {
|
||||||
PersonaID *int64 `json:"persona_id,omitempty"`
|
PersonaID *int64 `json:"persona_id,omitempty"`
|
||||||
QuestionSetID int64 `json:"question_set_id" validate:"required,gt=0"`
|
QuestionSetID int64 `json:"question_set_id" validate:"required,gt=0"`
|
||||||
QuickTips *string `json:"quick_tips,omitempty"`
|
QuickTips *string `json:"quick_tips,omitempty"`
|
||||||
|
// Omit or empty for backward compatibility defaults to PUBLISHED; set DRAFT to save hidden from learners until published.
|
||||||
|
PublishStatus string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdatePracticeInput struct {
|
type UpdatePracticeInput struct {
|
||||||
|
|
@ -44,4 +80,5 @@ type UpdatePracticeInput struct {
|
||||||
PersonaID *int64 `json:"persona_id,omitempty"`
|
PersonaID *int64 `json:"persona_id,omitempty"`
|
||||||
QuestionSetID *int64 `json:"question_set_id,omitempty"`
|
QuestionSetID *int64 `json:"question_set_id,omitempty"`
|
||||||
QuickTips *string `json:"quick_tips,omitempty"`
|
QuickTips *string `json:"quick_tips,omitempty"`
|
||||||
|
PublishStatus *string `json:"publish_status,omitempty" validate:"omitempty,oneof=DRAFT draft PUBLISHED published"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@ import (
|
||||||
type ExamPrepPracticeStore interface {
|
type ExamPrepPracticeStore interface {
|
||||||
CreateExamPrepLessonPractice(ctx context.Context, lessonID int64, in domain.CreateExamPrepPracticeInput) (domain.ExamPrepPractice, error)
|
CreateExamPrepLessonPractice(ctx context.Context, lessonID int64, in domain.CreateExamPrepPracticeInput) (domain.ExamPrepPractice, error)
|
||||||
GetExamPrepLessonPracticeByID(ctx context.Context, id int64) (domain.ExamPrepPractice, error)
|
GetExamPrepLessonPracticeByID(ctx context.Context, id int64) (domain.ExamPrepPractice, error)
|
||||||
ListExamPrepLessonPracticesByLessonID(ctx context.Context, lessonID int64, limit, offset int32) ([]domain.ExamPrepPractice, int64, error)
|
TryGetExamPrepLessonPracticeByQuestionSetID(ctx context.Context, questionSetID int64) (domain.ExamPrepPractice, bool, error)
|
||||||
|
ListExamPrepLessonPracticesByLessonID(ctx context.Context, lessonID int64, publishedOnly bool, limit, offset int32) ([]domain.ExamPrepPractice, int64, error)
|
||||||
UpdateExamPrepLessonPractice(ctx context.Context, id int64, input domain.UpdateExamPrepPracticeInput) (domain.ExamPrepPractice, error)
|
UpdateExamPrepLessonPractice(ctx context.Context, id int64, input domain.UpdateExamPrepPracticeInput) (domain.ExamPrepPractice, error)
|
||||||
DeleteExamPrepLessonPractice(ctx context.Context, id int64) error
|
DeleteExamPrepLessonPractice(ctx context.Context, id int64) error
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,11 @@ type LmsPracticeStore interface {
|
||||||
courseID, moduleID, lessonID *int64,
|
courseID, moduleID, lessonID *int64,
|
||||||
) (domain.Practice, error)
|
) (domain.Practice, error)
|
||||||
GetLmsPracticeByID(ctx context.Context, id int64) (domain.Practice, error)
|
GetLmsPracticeByID(ctx context.Context, id int64) (domain.Practice, error)
|
||||||
ListLmsPracticesByCourseID(ctx context.Context, courseID int64, limit, offset int32) ([]domain.Practice, int64, error)
|
// TryGetLmsPracticeByQuestionSetID returns false when no LMS practice row references the question set.
|
||||||
ListLmsPracticesByModuleID(ctx context.Context, moduleID int64, limit, offset int32) ([]domain.Practice, int64, error)
|
TryGetLmsPracticeByQuestionSetID(ctx context.Context, questionSetID int64) (domain.Practice, bool, error)
|
||||||
ListLmsPracticesByLessonID(ctx context.Context, lessonID int64, limit, offset int32) ([]domain.Practice, int64, error)
|
ListLmsPracticesByCourseID(ctx context.Context, courseID int64, publishedOnly bool, limit, offset int32) ([]domain.Practice, int64, error)
|
||||||
|
ListLmsPracticesByModuleID(ctx context.Context, moduleID int64, publishedOnly bool, limit, offset int32) ([]domain.Practice, int64, error)
|
||||||
|
ListLmsPracticesByLessonID(ctx context.Context, lessonID int64, publishedOnly bool, limit, offset int32) ([]domain.Practice, int64, error)
|
||||||
UpdateLmsPractice(ctx context.Context, id int64, input domain.UpdatePracticeInput) (domain.Practice, error)
|
UpdateLmsPractice(ctx context.Context, id int64, input domain.UpdatePracticeInput) (domain.Practice, error)
|
||||||
DeleteLmsPractice(ctx context.Context, id int64) error
|
DeleteLmsPractice(ctx context.Context, id int64) error
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ func examPrepPracticeFromListRow(r dbgen.ExamPrepListLessonPracticesByLessonIDRo
|
||||||
PersonaID: r.PersonaID,
|
PersonaID: r.PersonaID,
|
||||||
QuestionSetID: r.QuestionSetID,
|
QuestionSetID: r.QuestionSetID,
|
||||||
QuickTips: r.QuickTips,
|
QuickTips: r.QuickTips,
|
||||||
|
PublishStatus: r.PublishStatus,
|
||||||
CreatedAt: r.CreatedAt,
|
CreatedAt: r.CreatedAt,
|
||||||
UpdatedAt: r.UpdatedAt,
|
UpdatedAt: r.UpdatedAt,
|
||||||
})
|
})
|
||||||
|
|
@ -32,6 +33,7 @@ func examPrepPracticeToDomain(p dbgen.ExamPrepLessonPractice) domain.ExamPrepPra
|
||||||
LessonID: p.UnitModuleLessonID,
|
LessonID: p.UnitModuleLessonID,
|
||||||
Title: p.Title,
|
Title: p.Title,
|
||||||
QuestionSetID: p.QuestionSetID,
|
QuestionSetID: p.QuestionSetID,
|
||||||
|
PublishStatus: domain.PracticePublishStatusFromDB(p.PublishStatus),
|
||||||
}
|
}
|
||||||
out.StoryDescription = fromPgText(p.StoryDescription)
|
out.StoryDescription = fromPgText(p.StoryDescription)
|
||||||
out.StoryImage = fromPgText(p.StoryImage)
|
out.StoryImage = fromPgText(p.StoryImage)
|
||||||
|
|
@ -46,6 +48,7 @@ func examPrepPracticeToDomain(p dbgen.ExamPrepLessonPractice) domain.ExamPrepPra
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) CreateExamPrepLessonPractice(ctx context.Context, lessonID int64, in domain.CreateExamPrepPracticeInput) (domain.ExamPrepPractice, error) {
|
func (s *Store) CreateExamPrepLessonPractice(ctx context.Context, lessonID int64, in domain.CreateExamPrepPracticeInput) (domain.ExamPrepPractice, error) {
|
||||||
|
ps := domain.ParsePracticePublishStatusInput(in.PublishStatus)
|
||||||
p, err := s.queries.ExamPrepCreateLessonPractice(ctx, dbgen.ExamPrepCreateLessonPracticeParams{
|
p, err := s.queries.ExamPrepCreateLessonPractice(ctx, dbgen.ExamPrepCreateLessonPracticeParams{
|
||||||
UnitModuleLessonID: lessonID,
|
UnitModuleLessonID: lessonID,
|
||||||
Title: in.Title,
|
Title: in.Title,
|
||||||
|
|
@ -54,6 +57,7 @@ func (s *Store) CreateExamPrepLessonPractice(ctx context.Context, lessonID int64
|
||||||
PersonaID: int64PtrToPg8(in.PersonaID),
|
PersonaID: int64PtrToPg8(in.PersonaID),
|
||||||
QuestionSetID: in.QuestionSetID,
|
QuestionSetID: in.QuestionSetID,
|
||||||
QuickTips: toPgText(in.QuickTips),
|
QuickTips: toPgText(in.QuickTips),
|
||||||
|
PublishStatus: string(ps),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.ExamPrepPractice{}, err
|
return domain.ExamPrepPractice{}, err
|
||||||
|
|
@ -72,9 +76,22 @@ func (s *Store) GetExamPrepLessonPracticeByID(ctx context.Context, id int64) (do
|
||||||
return examPrepPracticeToDomain(p), nil
|
return examPrepPracticeToDomain(p), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) ListExamPrepLessonPracticesByLessonID(ctx context.Context, lessonID int64, limit, offset int32) ([]domain.ExamPrepPractice, int64, error) {
|
// TryGetExamPrepLessonPracticeByQuestionSetID returns false when no row exists.
|
||||||
|
func (s *Store) TryGetExamPrepLessonPracticeByQuestionSetID(ctx context.Context, questionSetID int64) (domain.ExamPrepPractice, bool, error) {
|
||||||
|
p, err := s.queries.ExamPrepGetLessonPracticeByQuestionSetID(ctx, questionSetID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
return domain.ExamPrepPractice{}, false, nil
|
||||||
|
}
|
||||||
|
return domain.ExamPrepPractice{}, false, err
|
||||||
|
}
|
||||||
|
return examPrepPracticeToDomain(p), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) ListExamPrepLessonPracticesByLessonID(ctx context.Context, lessonID int64, publishedOnly bool, limit, offset int32) ([]domain.ExamPrepPractice, int64, error) {
|
||||||
rows, err := s.queries.ExamPrepListLessonPracticesByLessonID(ctx, dbgen.ExamPrepListLessonPracticesByLessonIDParams{
|
rows, err := s.queries.ExamPrepListLessonPracticesByLessonID(ctx, dbgen.ExamPrepListLessonPracticesByLessonIDParams{
|
||||||
UnitModuleLessonID: lessonID,
|
UnitModuleLessonID: lessonID,
|
||||||
|
PublishedOnly: publishedOnly,
|
||||||
Limit: limit,
|
Limit: limit,
|
||||||
Offset: offset,
|
Offset: offset,
|
||||||
})
|
})
|
||||||
|
|
@ -111,6 +128,7 @@ func (s *Store) UpdateExamPrepLessonPractice(ctx context.Context, id int64, inpu
|
||||||
PersonaID: optionalInt8UpdateID(input.PersonaID),
|
PersonaID: optionalInt8UpdateID(input.PersonaID),
|
||||||
QuestionSetID: qs,
|
QuestionSetID: qs,
|
||||||
QuickTips: optionalTextUpdate(input.QuickTips),
|
QuickTips: optionalTextUpdate(input.QuickTips),
|
||||||
|
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, pgx.ErrNoRows) {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,19 @@ func fromPgInt8ID(c pgtype.Int8) *int64 {
|
||||||
return &v
|
return &v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func optionalInt8UpdateID(val *int64) pgtype.Int8 {
|
||||||
|
if val == nil {
|
||||||
|
return pgtype.Int8{Valid: false}
|
||||||
|
}
|
||||||
|
return pgtype.Int8{Int64: *val, Valid: true}
|
||||||
|
}
|
||||||
|
|
||||||
func lmsPracticeToDomain(p dbgen.LmsPractice) domain.Practice {
|
func lmsPracticeToDomain(p dbgen.LmsPractice) domain.Practice {
|
||||||
out := domain.Practice{
|
out := domain.Practice{
|
||||||
ID: p.ID,
|
ID: p.ID,
|
||||||
Title: p.Title,
|
Title: p.Title,
|
||||||
QuestionSetID: p.QuestionSetID,
|
QuestionSetID: p.QuestionSetID,
|
||||||
|
PublishStatus: domain.PracticePublishStatusFromDB(p.PublishStatus),
|
||||||
}
|
}
|
||||||
if p.CourseID.Valid {
|
if p.CourseID.Valid {
|
||||||
out.ParentKind = domain.ParentKindCourse
|
out.ParentKind = domain.ParentKindCourse
|
||||||
|
|
@ -55,7 +63,9 @@ func lmsPracticeToDomain(p dbgen.LmsPractice) domain.Practice {
|
||||||
}
|
}
|
||||||
|
|
||||||
func lmsFromListRow(
|
func lmsFromListRow(
|
||||||
id, qid int64, title string,
|
id, qid int64,
|
||||||
|
publishStatus string,
|
||||||
|
title string,
|
||||||
cid, mid, lid pgtype.Int8,
|
cid, mid, lid pgtype.Int8,
|
||||||
sd, si, qt pgtype.Text, pid pgtype.Int8,
|
sd, si, qt pgtype.Text, pid pgtype.Int8,
|
||||||
ca, ua pgtype.Timestamptz,
|
ca, ua pgtype.Timestamptz,
|
||||||
|
|
@ -71,6 +81,7 @@ func lmsFromListRow(
|
||||||
PersonaID: pid,
|
PersonaID: pid,
|
||||||
QuestionSetID: qid,
|
QuestionSetID: qid,
|
||||||
QuickTips: qt,
|
QuickTips: qt,
|
||||||
|
PublishStatus: publishStatus,
|
||||||
CreatedAt: ca,
|
CreatedAt: ca,
|
||||||
UpdatedAt: ua,
|
UpdatedAt: ua,
|
||||||
})
|
})
|
||||||
|
|
@ -82,6 +93,7 @@ func (s *Store) CreateLmsPractice(
|
||||||
in domain.CreatePracticeInput,
|
in domain.CreatePracticeInput,
|
||||||
courseID, moduleID, lessonID *int64,
|
courseID, moduleID, lessonID *int64,
|
||||||
) (domain.Practice, error) {
|
) (domain.Practice, error) {
|
||||||
|
ps := domain.ParsePracticePublishStatusInput(in.PublishStatus)
|
||||||
p, err := s.queries.CreateLmsPractice(ctx, dbgen.CreateLmsPracticeParams{
|
p, err := s.queries.CreateLmsPractice(ctx, dbgen.CreateLmsPracticeParams{
|
||||||
CourseID: int64PtrToPg8(courseID),
|
CourseID: int64PtrToPg8(courseID),
|
||||||
ModuleID: int64PtrToPg8(moduleID),
|
ModuleID: int64PtrToPg8(moduleID),
|
||||||
|
|
@ -92,6 +104,7 @@ func (s *Store) CreateLmsPractice(
|
||||||
PersonaID: int64PtrToPg8(in.PersonaID),
|
PersonaID: int64PtrToPg8(in.PersonaID),
|
||||||
QuestionSetID: in.QuestionSetID,
|
QuestionSetID: in.QuestionSetID,
|
||||||
QuickTips: toPgText(in.QuickTips),
|
QuickTips: toPgText(in.QuickTips),
|
||||||
|
PublishStatus: string(ps),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.Practice{}, err
|
return domain.Practice{}, err
|
||||||
|
|
@ -110,9 +123,22 @@ func (s *Store) GetLmsPracticeByID(ctx context.Context, id int64) (domain.Practi
|
||||||
return lmsPracticeToDomain(p), nil
|
return lmsPracticeToDomain(p), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) ListLmsPracticesByCourseID(ctx context.Context, courseID int64, limit, offset int32) ([]domain.Practice, int64, error) {
|
// TryGetLmsPracticeByQuestionSetID returns false when no row exists.
|
||||||
|
func (s *Store) TryGetLmsPracticeByQuestionSetID(ctx context.Context, questionSetID int64) (domain.Practice, bool, error) {
|
||||||
|
p, err := s.queries.GetLmsPracticeByQuestionSetID(ctx, questionSetID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
return domain.Practice{}, false, nil
|
||||||
|
}
|
||||||
|
return domain.Practice{}, false, err
|
||||||
|
}
|
||||||
|
return lmsPracticeToDomain(p), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) ListLmsPracticesByCourseID(ctx context.Context, courseID int64, publishedOnly bool, limit, offset int32) ([]domain.Practice, int64, error) {
|
||||||
rows, err := s.queries.ListLmsPracticesByCourseID(ctx, dbgen.ListLmsPracticesByCourseIDParams{
|
rows, err := s.queries.ListLmsPracticesByCourseID(ctx, dbgen.ListLmsPracticesByCourseIDParams{
|
||||||
CourseID: pgtype.Int8{Int64: courseID, Valid: true},
|
CourseID: pgtype.Int8{Int64: courseID, Valid: true},
|
||||||
|
PublishedOnly: publishedOnly,
|
||||||
Limit: limit,
|
Limit: limit,
|
||||||
Offset: offset,
|
Offset: offset,
|
||||||
})
|
})
|
||||||
|
|
@ -129,7 +155,7 @@ func (s *Store) ListLmsPracticesByCourseID(ctx context.Context, courseID int64,
|
||||||
total = r.TotalCount
|
total = r.TotalCount
|
||||||
}
|
}
|
||||||
out = append(out, lmsFromListRow(
|
out = append(out, lmsFromListRow(
|
||||||
r.ID, r.QuestionSetID, r.Title,
|
r.ID, r.QuestionSetID, r.PublishStatus, r.Title,
|
||||||
r.CourseID, r.ModuleID, r.LessonID,
|
r.CourseID, r.ModuleID, r.LessonID,
|
||||||
r.StoryDescription, r.StoryImage, r.QuickTips,
|
r.StoryDescription, r.StoryImage, r.QuickTips,
|
||||||
r.PersonaID, r.CreatedAt, r.UpdatedAt,
|
r.PersonaID, r.CreatedAt, r.UpdatedAt,
|
||||||
|
|
@ -138,9 +164,10 @@ func (s *Store) ListLmsPracticesByCourseID(ctx context.Context, courseID int64,
|
||||||
return out, total, nil
|
return out, total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) ListLmsPracticesByModuleID(ctx context.Context, moduleID int64, limit, offset int32) ([]domain.Practice, int64, error) {
|
func (s *Store) ListLmsPracticesByModuleID(ctx context.Context, moduleID int64, publishedOnly bool, limit, offset int32) ([]domain.Practice, int64, error) {
|
||||||
rows, err := s.queries.ListLmsPracticesByModuleID(ctx, dbgen.ListLmsPracticesByModuleIDParams{
|
rows, err := s.queries.ListLmsPracticesByModuleID(ctx, dbgen.ListLmsPracticesByModuleIDParams{
|
||||||
ModuleID: pgtype.Int8{Int64: moduleID, Valid: true},
|
ModuleID: pgtype.Int8{Int64: moduleID, Valid: true},
|
||||||
|
PublishedOnly: publishedOnly,
|
||||||
Limit: limit,
|
Limit: limit,
|
||||||
Offset: offset,
|
Offset: offset,
|
||||||
})
|
})
|
||||||
|
|
@ -157,7 +184,7 @@ func (s *Store) ListLmsPracticesByModuleID(ctx context.Context, moduleID int64,
|
||||||
total = r.TotalCount
|
total = r.TotalCount
|
||||||
}
|
}
|
||||||
out = append(out, lmsFromListRow(
|
out = append(out, lmsFromListRow(
|
||||||
r.ID, r.QuestionSetID, r.Title,
|
r.ID, r.QuestionSetID, r.PublishStatus, r.Title,
|
||||||
r.CourseID, r.ModuleID, r.LessonID,
|
r.CourseID, r.ModuleID, r.LessonID,
|
||||||
r.StoryDescription, r.StoryImage, r.QuickTips,
|
r.StoryDescription, r.StoryImage, r.QuickTips,
|
||||||
r.PersonaID, r.CreatedAt, r.UpdatedAt,
|
r.PersonaID, r.CreatedAt, r.UpdatedAt,
|
||||||
|
|
@ -166,9 +193,10 @@ func (s *Store) ListLmsPracticesByModuleID(ctx context.Context, moduleID int64,
|
||||||
return out, total, nil
|
return out, total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) ListLmsPracticesByLessonID(ctx context.Context, lessonID int64, limit, offset int32) ([]domain.Practice, int64, error) {
|
func (s *Store) ListLmsPracticesByLessonID(ctx context.Context, lessonID int64, publishedOnly bool, limit, offset int32) ([]domain.Practice, int64, error) {
|
||||||
rows, err := s.queries.ListLmsPracticesByLessonID(ctx, dbgen.ListLmsPracticesByLessonIDParams{
|
rows, err := s.queries.ListLmsPracticesByLessonID(ctx, dbgen.ListLmsPracticesByLessonIDParams{
|
||||||
LessonID: pgtype.Int8{Int64: lessonID, Valid: true},
|
LessonID: pgtype.Int8{Int64: lessonID, Valid: true},
|
||||||
|
PublishedOnly: publishedOnly,
|
||||||
Limit: limit,
|
Limit: limit,
|
||||||
Offset: offset,
|
Offset: offset,
|
||||||
})
|
})
|
||||||
|
|
@ -185,7 +213,7 @@ func (s *Store) ListLmsPracticesByLessonID(ctx context.Context, lessonID int64,
|
||||||
total = r.TotalCount
|
total = r.TotalCount
|
||||||
}
|
}
|
||||||
out = append(out, lmsFromListRow(
|
out = append(out, lmsFromListRow(
|
||||||
r.ID, r.QuestionSetID, r.Title,
|
r.ID, r.QuestionSetID, r.PublishStatus, r.Title,
|
||||||
r.CourseID, r.ModuleID, r.LessonID,
|
r.CourseID, r.ModuleID, r.LessonID,
|
||||||
r.StoryDescription, r.StoryImage, r.QuickTips,
|
r.StoryDescription, r.StoryImage, r.QuickTips,
|
||||||
r.PersonaID, r.CreatedAt, r.UpdatedAt,
|
r.PersonaID, r.CreatedAt, r.UpdatedAt,
|
||||||
|
|
@ -194,13 +222,6 @@ func (s *Store) ListLmsPracticesByLessonID(ctx context.Context, lessonID int64,
|
||||||
return out, total, nil
|
return out, total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func optionalInt8UpdateID(val *int64) pgtype.Int8 {
|
|
||||||
if val == nil {
|
|
||||||
return pgtype.Int8{Valid: false}
|
|
||||||
}
|
|
||||||
return pgtype.Int8{Int64: *val, Valid: true}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Store) UpdateLmsPractice(ctx context.Context, id int64, input domain.UpdatePracticeInput) (domain.Practice, error) {
|
func (s *Store) UpdateLmsPractice(ctx context.Context, id int64, input domain.UpdatePracticeInput) (domain.Practice, error) {
|
||||||
var titleText pgtype.Text
|
var titleText pgtype.Text
|
||||||
if input.Title != nil {
|
if input.Title != nil {
|
||||||
|
|
@ -217,6 +238,7 @@ func (s *Store) UpdateLmsPractice(ctx context.Context, id int64, input domain.Up
|
||||||
PersonaID: optionalInt8UpdateID(input.PersonaID),
|
PersonaID: optionalInt8UpdateID(input.PersonaID),
|
||||||
QuestionSetID: qs,
|
QuestionSetID: qs,
|
||||||
QuickTips: optionalTextUpdate(input.QuickTips),
|
QuickTips: optionalTextUpdate(input.QuickTips),
|
||||||
|
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, pgx.ErrNoRows) {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package repository
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
dbgen "Yimaru-Backend/gen/db"
|
dbgen "Yimaru-Backend/gen/db"
|
||||||
"Yimaru-Backend/internal/domain"
|
"Yimaru-Backend/internal/domain"
|
||||||
|
|
@ -117,6 +118,19 @@ func optionalTextUpdate(val *string) pgtype.Text {
|
||||||
return pgtype.Text{String: *val, Valid: true}
|
return pgtype.Text{String: *val, Valid: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func optionalPublishStatusUpdate(val *string) pgtype.Text {
|
||||||
|
if val == nil {
|
||||||
|
return pgtype.Text{Valid: false}
|
||||||
|
}
|
||||||
|
s := strings.TrimSpace(strings.ToUpper(*val))
|
||||||
|
switch s {
|
||||||
|
case string(domain.PracticePublishDraft), string(domain.PracticePublishPublished):
|
||||||
|
return pgtype.Text{String: s, Valid: true}
|
||||||
|
default:
|
||||||
|
return pgtype.Text{Valid: false}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func optionalInt4Update(v *int) pgtype.Int4 {
|
func optionalInt4Update(v *int) pgtype.Int4 {
|
||||||
if v == nil {
|
if v == nil {
|
||||||
return pgtype.Int4{Valid: false}
|
return pgtype.Int4{Valid: false}
|
||||||
|
|
|
||||||
|
|
@ -358,7 +358,7 @@ func (s *Service) CreateExamPrepPractice(ctx context.Context, lessonID int64, in
|
||||||
return s.store.CreateExamPrepLessonPractice(ctx, lessonID, input)
|
return s.store.CreateExamPrepLessonPractice(ctx, lessonID, input)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) ListExamPrepPracticesByLesson(ctx context.Context, lessonID int64, limit, offset int32) ([]domain.ExamPrepPractice, int64, error) {
|
func (s *Service) ListExamPrepPracticesByLesson(ctx context.Context, lessonID int64, publishedOnly bool, limit, offset int32) ([]domain.ExamPrepPractice, int64, error) {
|
||||||
if err := s.ensureLesson(ctx, lessonID); err != nil {
|
if err := s.ensureLesson(ctx, lessonID); err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
@ -371,7 +371,7 @@ func (s *Service) ListExamPrepPracticesByLesson(ctx context.Context, lessonID in
|
||||||
if offset < 0 {
|
if offset < 0 {
|
||||||
offset = 0
|
offset = 0
|
||||||
}
|
}
|
||||||
return s.store.ListExamPrepLessonPracticesByLessonID(ctx, lessonID, limit, offset)
|
return s.store.ListExamPrepLessonPracticesByLessonID(ctx, lessonID, publishedOnly, limit, offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) GetExamPrepPracticeByID(ctx context.Context, id int64) (domain.ExamPrepPractice, error) {
|
func (s *Service) GetExamPrepPracticeByID(ctx context.Context, id int64) (domain.ExamPrepPractice, error) {
|
||||||
|
|
@ -385,6 +385,10 @@ func (s *Service) GetExamPrepPracticeByID(ctx context.Context, id int64) (domain
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) TryGetExamPrepPracticeByQuestionSetID(ctx context.Context, questionSetID int64) (domain.ExamPrepPractice, bool, error) {
|
||||||
|
return s.store.TryGetExamPrepLessonPracticeByQuestionSetID(ctx, questionSetID)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) UpdateExamPrepPractice(ctx context.Context, id int64, input domain.UpdateExamPrepPracticeInput) (domain.ExamPrepPractice, error) {
|
func (s *Service) UpdateExamPrepPractice(ctx context.Context, id int64, input domain.UpdateExamPrepPracticeInput) (domain.ExamPrepPractice, error) {
|
||||||
p, err := s.store.UpdateExamPrepLessonPractice(ctx, id, input)
|
p, err := s.store.UpdateExamPrepLessonPractice(ctx, id, input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,10 @@ func (s *Service) Create(ctx context.Context, in domain.CreatePracticeInput) (do
|
||||||
return s.practices.CreateLmsPractice(ctx, in, courseID, moduleID, lessonID)
|
return s.practices.CreateLmsPractice(ctx, in, courseID, moduleID, lessonID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) TryGetByQuestionSetID(ctx context.Context, questionSetID int64) (domain.Practice, bool, error) {
|
||||||
|
return s.practices.TryGetLmsPracticeByQuestionSetID(ctx, questionSetID)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) GetByID(ctx context.Context, id int64) (domain.Practice, error) {
|
func (s *Service) GetByID(ctx context.Context, id int64) (domain.Practice, error) {
|
||||||
p, err := s.practices.GetLmsPracticeByID(ctx, id)
|
p, err := s.practices.GetLmsPracticeByID(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -139,7 +143,7 @@ func clampPracticePage(limit, offset int32) (int32, int32) {
|
||||||
return limit, offset
|
return limit, offset
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) ListByCourse(ctx context.Context, courseID int64, limit, offset int32) ([]domain.Practice, int64, error) {
|
func (s *Service) ListByCourse(ctx context.Context, courseID int64, publishedOnly bool, limit, offset int32) ([]domain.Practice, int64, error) {
|
||||||
if _, err := s.courses.GetCourseByID(ctx, courseID); err != nil {
|
if _, err := s.courses.GetCourseByID(ctx, courseID); err != nil {
|
||||||
if errors.Is(err, pgx.ErrNoRows) {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
return nil, 0, courses.ErrCourseNotFound
|
return nil, 0, courses.ErrCourseNotFound
|
||||||
|
|
@ -147,10 +151,10 @@ func (s *Service) ListByCourse(ctx context.Context, courseID int64, limit, offse
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
limit, offset = clampPracticePage(limit, offset)
|
limit, offset = clampPracticePage(limit, offset)
|
||||||
return s.practices.ListLmsPracticesByCourseID(ctx, courseID, limit, offset)
|
return s.practices.ListLmsPracticesByCourseID(ctx, courseID, publishedOnly, limit, offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) ListByModule(ctx context.Context, moduleID int64, limit, offset int32) ([]domain.Practice, int64, error) {
|
func (s *Service) ListByModule(ctx context.Context, moduleID int64, publishedOnly bool, limit, offset int32) ([]domain.Practice, int64, error) {
|
||||||
if _, err := s.modules.GetModuleByID(ctx, moduleID); err != nil {
|
if _, err := s.modules.GetModuleByID(ctx, moduleID); err != nil {
|
||||||
if errors.Is(err, pgx.ErrNoRows) {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
return nil, 0, modules.ErrModuleNotFound
|
return nil, 0, modules.ErrModuleNotFound
|
||||||
|
|
@ -158,10 +162,10 @@ func (s *Service) ListByModule(ctx context.Context, moduleID int64, limit, offse
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
limit, offset = clampPracticePage(limit, offset)
|
limit, offset = clampPracticePage(limit, offset)
|
||||||
return s.practices.ListLmsPracticesByModuleID(ctx, moduleID, limit, offset)
|
return s.practices.ListLmsPracticesByModuleID(ctx, moduleID, publishedOnly, limit, offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) ListByLesson(ctx context.Context, lessonID int64, limit, offset int32) ([]domain.Practice, int64, error) {
|
func (s *Service) ListByLesson(ctx context.Context, lessonID int64, publishedOnly bool, limit, offset int32) ([]domain.Practice, int64, error) {
|
||||||
if _, err := s.lessons.GetLessonByID(ctx, lessonID); err != nil {
|
if _, err := s.lessons.GetLessonByID(ctx, lessonID); err != nil {
|
||||||
if errors.Is(err, pgx.ErrNoRows) {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
return nil, 0, lessons.ErrLessonNotFound
|
return nil, 0, lessons.ErrLessonNotFound
|
||||||
|
|
@ -169,7 +173,7 @@ func (s *Service) ListByLesson(ctx context.Context, lessonID int64, limit, offse
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
limit, offset = clampPracticePage(limit, offset)
|
limit, offset = clampPracticePage(limit, offset)
|
||||||
return s.practices.ListLmsPracticesByLessonID(ctx, lessonID, limit, offset)
|
return s.practices.ListLmsPracticesByLessonID(ctx, lessonID, publishedOnly, limit, offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) Update(ctx context.Context, id int64, input domain.UpdatePracticeInput) (domain.Practice, error) {
|
func (s *Service) Update(ctx context.Context, id int64, input domain.UpdatePracticeInput) (domain.Practice, error) {
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,8 @@ func (h *Handler) ListExamPrepPracticesByLesson(c *fiber.Ctx) error {
|
||||||
}
|
}
|
||||||
limit, _ := strconv.Atoi(c.Query("limit", "20"))
|
limit, _ := strconv.Atoi(c.Query("limit", "20"))
|
||||||
offset, _ := strconv.Atoi(c.Query("offset", "0"))
|
offset, _ := strconv.Atoi(c.Query("offset", "0"))
|
||||||
items, total, err := h.examPrepSvc.ListExamPrepPracticesByLesson(c.Context(), lessonID, int32(limit), int32(offset))
|
publishedOnly := !h.canManageExamPrepPractices(c)
|
||||||
|
items, total, err := h.examPrepSvc.ListExamPrepPracticesByLesson(c.Context(), lessonID, publishedOnly, int32(limit), int32(offset))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, examprep.ErrLessonNotFound) {
|
if errors.Is(err, examprep.ErrLessonNotFound) {
|
||||||
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
|
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
|
||||||
|
|
@ -126,6 +127,9 @@ func (h *Handler) GetExamPrepPracticeByID(c *fiber.Ctx) error {
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if !p.VisibleToLearners() && !h.canManageExamPrepPractices(c) {
|
||||||
|
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{Message: "Practice not found"})
|
||||||
|
}
|
||||||
return c.JSON(domain.Response{
|
return c.JSON(domain.Response{
|
||||||
Message: "Practice retrieved successfully",
|
Message: "Practice retrieved successfully",
|
||||||
Data: p,
|
Data: p,
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,8 @@ func (h *Handler) ListPracticesByCourse(c *fiber.Ctx) error {
|
||||||
}
|
}
|
||||||
limit, _ := strconv.Atoi(c.Query("limit", "20"))
|
limit, _ := strconv.Atoi(c.Query("limit", "20"))
|
||||||
offset, _ := strconv.Atoi(c.Query("offset", "0"))
|
offset, _ := strconv.Atoi(c.Query("offset", "0"))
|
||||||
items, total, err := h.practiceSvc.ListByCourse(c.Context(), courseID, int32(limit), int32(offset))
|
publishedOnly := !h.canManageLMSPractices(c)
|
||||||
|
items, total, err := h.practiceSvc.ListByCourse(c.Context(), courseID, publishedOnly, int32(limit), int32(offset))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, courses.ErrCourseNotFound) {
|
if errors.Is(err, courses.ErrCourseNotFound) {
|
||||||
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{Message: "Course not found", Error: err.Error()})
|
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{Message: "Course not found", Error: err.Error()})
|
||||||
|
|
@ -107,7 +108,8 @@ func (h *Handler) ListPracticesByModule(c *fiber.Ctx) error {
|
||||||
}
|
}
|
||||||
limit, _ := strconv.Atoi(c.Query("limit", "20"))
|
limit, _ := strconv.Atoi(c.Query("limit", "20"))
|
||||||
offset, _ := strconv.Atoi(c.Query("offset", "0"))
|
offset, _ := strconv.Atoi(c.Query("offset", "0"))
|
||||||
items, total, err := h.practiceSvc.ListByModule(c.Context(), moduleID, int32(limit), int32(offset))
|
publishedOnly := !h.canManageLMSPractices(c)
|
||||||
|
items, total, err := h.practiceSvc.ListByModule(c.Context(), moduleID, publishedOnly, int32(limit), int32(offset))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, modules.ErrModuleNotFound) {
|
if errors.Is(err, modules.ErrModuleNotFound) {
|
||||||
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{Message: "Module not found", Error: err.Error()})
|
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{Message: "Module not found", Error: err.Error()})
|
||||||
|
|
@ -138,7 +140,8 @@ func (h *Handler) ListPracticesByLesson(c *fiber.Ctx) error {
|
||||||
}
|
}
|
||||||
limit, _ := strconv.Atoi(c.Query("limit", "20"))
|
limit, _ := strconv.Atoi(c.Query("limit", "20"))
|
||||||
offset, _ := strconv.Atoi(c.Query("offset", "0"))
|
offset, _ := strconv.Atoi(c.Query("offset", "0"))
|
||||||
items, total, err := h.practiceSvc.ListByLesson(c.Context(), lessonID, int32(limit), int32(offset))
|
publishedOnly := !h.canManageLMSPractices(c)
|
||||||
|
items, total, err := h.practiceSvc.ListByLesson(c.Context(), lessonID, publishedOnly, int32(limit), int32(offset))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, lessons.ErrLessonNotFound) {
|
if errors.Is(err, lessons.ErrLessonNotFound) {
|
||||||
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{Message: "Lesson not found", Error: err.Error()})
|
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{Message: "Lesson not found", Error: err.Error()})
|
||||||
|
|
@ -174,6 +177,9 @@ func (h *Handler) GetPractice(c *fiber.Ctx) error {
|
||||||
}
|
}
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to load practice", Error: err.Error()})
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to load practice", Error: err.Error()})
|
||||||
}
|
}
|
||||||
|
if !p.VisibleToLearners() && !h.canManageLMSPractices(c) {
|
||||||
|
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{Message: "Practice not found"})
|
||||||
|
}
|
||||||
return c.JSON(domain.Response{Message: "Practice retrieved successfully", Data: p, Success: true, StatusCode: fiber.StatusOK})
|
return c.JSON(domain.Response{Message: "Practice retrieved successfully", Data: p, Success: true, StatusCode: fiber.StatusOK})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
51
internal/web_server/handlers/practice_publish_gate.go
Normal file
51
internal/web_server/handlers/practice_publish_gate.go
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"Yimaru-Backend/internal/domain"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *Handler) canManageLMSPractices(c *fiber.Ctx) bool {
|
||||||
|
rn := string(c.Locals("role").(domain.Role))
|
||||||
|
return h.rbacSvc.HasPermission(rn, "practices.create") || h.rbacSvc.HasPermission(rn, "practices.update")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) canManageExamPrepPractices(c *fiber.Ctx) bool {
|
||||||
|
rn := string(c.Locals("role").(domain.Role))
|
||||||
|
return h.rbacSvc.HasPermission(rn, "exam_prep.practices.create") || h.rbacSvc.HasPermission(rn, "exam_prep.practices.update")
|
||||||
|
}
|
||||||
|
|
||||||
|
// forbidIfLinkedPracticeDraftForSubscriber returns 404 for draft LMS/exam-prep shells when the caller cannot manage content.
|
||||||
|
func (h *Handler) forbidIfLinkedPracticeDraftForSubscriber(c *fiber.Ctx, questionSetID int64) error {
|
||||||
|
if lp, ok, err := h.practiceSvc.TryGetByQuestionSetID(c.Context(), questionSetID); err != nil {
|
||||||
|
return err
|
||||||
|
} else if ok && !lp.VisibleToLearners() && !h.canManageLMSPractices(c) {
|
||||||
|
return fiber.NewError(fiber.StatusNotFound, "Practice not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ep, ok, err := h.examPrepSvc.TryGetExamPrepPracticeByQuestionSetID(c.Context(), questionSetID); err != nil {
|
||||||
|
return err
|
||||||
|
} else if ok && !ep.VisibleToLearners() && !h.canManageExamPrepPractices(c) {
|
||||||
|
return fiber.NewError(fiber.StatusNotFound, "Practice not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// forbidCompletingDraftPractice blocks completion when an LMS/exam-prep shell is still in draft (learners/students path).
|
||||||
|
func (h *Handler) forbidCompletingDraftPractice(c *fiber.Ctx, questionSetID int64) error {
|
||||||
|
if lp, ok, err := h.practiceSvc.TryGetByQuestionSetID(c.Context(), questionSetID); err != nil {
|
||||||
|
return err
|
||||||
|
} else if ok && !lp.VisibleToLearners() {
|
||||||
|
return fiber.NewError(fiber.StatusForbidden, "Only published practices can be completed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ep, ok, err := h.examPrepSvc.TryGetExamPrepPracticeByQuestionSetID(c.Context(), questionSetID); err != nil {
|
||||||
|
return err
|
||||||
|
} else if ok && !ep.VisibleToLearners() {
|
||||||
|
return fiber.NewError(fiber.StatusForbidden, "Only published practices can be completed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"Yimaru-Backend/internal/domain"
|
"Yimaru-Backend/internal/domain"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -1469,6 +1470,20 @@ func (h *Handler) GetQuestionsByPractice(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := h.forbidIfLinkedPracticeDraftForSubscriber(c, set.ID); err != nil {
|
||||||
|
code := fiber.StatusInternalServerError
|
||||||
|
msg := err.Error()
|
||||||
|
var ferr *fiber.Error
|
||||||
|
if errors.As(err, &ferr) {
|
||||||
|
code = ferr.Code
|
||||||
|
msg = ferr.Message
|
||||||
|
}
|
||||||
|
return c.Status(code).JSON(domain.ErrorResponse{
|
||||||
|
Message: msg,
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if err := h.enforcePracticeSequenceForStudent(c, set); err != nil {
|
if err := h.enforcePracticeSequenceForStudent(c, set); err != nil {
|
||||||
status := fiber.StatusForbidden
|
status := fiber.StatusForbidden
|
||||||
if ferr, ok := err.(*fiber.Error); ok {
|
if ferr, ok := err.(*fiber.Error); ok {
|
||||||
|
|
@ -1552,6 +1567,11 @@ func (h *Handler) CompletePractice(c *fiber.Ctx) error {
|
||||||
var set domain.QuestionSet
|
var set domain.QuestionSet
|
||||||
var setErr error
|
var setErr error
|
||||||
if practiceErr == nil {
|
if practiceErr == nil {
|
||||||
|
if !practice.VisibleToLearners() {
|
||||||
|
return c.Status(fiber.StatusForbidden).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Only published practices can be completed",
|
||||||
|
})
|
||||||
|
}
|
||||||
set, setErr = h.questionsSvc.GetQuestionSetByID(c.Context(), practice.QuestionSetID)
|
set, setErr = h.questionsSvc.GetQuestionSetByID(c.Context(), practice.QuestionSetID)
|
||||||
} else {
|
} else {
|
||||||
// Backward compatibility: also accept question_set.id directly.
|
// Backward compatibility: also accept question_set.id directly.
|
||||||
|
|
@ -1566,6 +1586,21 @@ func (h *Handler) CompletePractice(c *fiber.Ctx) error {
|
||||||
if !strings.EqualFold(set.SetType, string(domain.QuestionSetTypePractice)) {
|
if !strings.EqualFold(set.SetType, string(domain.QuestionSetTypePractice)) {
|
||||||
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{Message: "Practice not found"})
|
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{Message: "Practice not found"})
|
||||||
}
|
}
|
||||||
|
if practiceErr != nil {
|
||||||
|
if err := h.forbidCompletingDraftPractice(c, set.ID); err != nil {
|
||||||
|
code := fiber.StatusInternalServerError
|
||||||
|
msg := err.Error()
|
||||||
|
var ferr *fiber.Error
|
||||||
|
if errors.As(err, &ferr) {
|
||||||
|
code = ferr.Code
|
||||||
|
msg = ferr.Message
|
||||||
|
}
|
||||||
|
return c.Status(code).JSON(domain.ErrorResponse{
|
||||||
|
Message: msg,
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Enforce sequential gating only for published practices.
|
// Enforce sequential gating only for published practices.
|
||||||
if strings.EqualFold(set.Status, "PUBLISHED") {
|
if strings.EqualFold(set.Status, "PUBLISHED") {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user