progress precentage fix

This commit is contained in:
Yared Yemane 2026-05-27 09:18:25 -07:00
parent d3225ca61a
commit a1c6b3c15a
18 changed files with 1080 additions and 120 deletions

View File

@ -0,0 +1,119 @@
-- name: CountPublishedExamPrepPracticesInLesson :one
SELECT
count(*)::int AS n
FROM
exam_prep.lesson_practices p
INNER JOIN question_sets qs ON qs.id = p.question_set_id
WHERE
p.unit_module_lesson_id = $1
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND p.publish_status = 'PUBLISHED';
-- name: CountUserCompletedPublishedExamPrepPracticesInLesson :one
SELECT
count(*)::int AS n
FROM
exam_prep.lesson_practices p
INNER JOIN question_sets qs ON qs.id = p.question_set_id
INNER JOIN user_practice_progress upp ON upp.question_set_id = p.question_set_id
WHERE
p.unit_module_lesson_id = $1
AND upp.user_id = $2
AND upp.completed_at IS NOT NULL
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND p.publish_status = 'PUBLISHED';
-- name: CountPublishedExamPrepPracticesInModule :one
SELECT
count(*)::int AS n
FROM
exam_prep.lesson_practices p
INNER JOIN exam_prep.unit_module_lessons l ON l.id = p.unit_module_lesson_id
INNER JOIN question_sets qs ON qs.id = p.question_set_id
WHERE
l.unit_module_id = $1
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND p.publish_status = 'PUBLISHED';
-- name: CountUserCompletedPublishedExamPrepPracticesInModule :one
SELECT
count(*)::int AS n
FROM
exam_prep.lesson_practices p
INNER JOIN exam_prep.unit_module_lessons l ON l.id = p.unit_module_lesson_id
INNER JOIN question_sets qs ON qs.id = p.question_set_id
INNER JOIN user_practice_progress upp ON upp.question_set_id = p.question_set_id
WHERE
l.unit_module_id = $1
AND upp.user_id = $2
AND upp.completed_at IS NOT NULL
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND p.publish_status = 'PUBLISHED';
-- name: CountPublishedExamPrepPracticesInUnit :one
SELECT
count(*)::int AS n
FROM
exam_prep.lesson_practices p
INNER JOIN exam_prep.unit_module_lessons l ON l.id = p.unit_module_lesson_id
INNER JOIN exam_prep.unit_modules m ON m.id = l.unit_module_id
INNER JOIN question_sets qs ON qs.id = p.question_set_id
WHERE
m.unit_id = $1
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND p.publish_status = 'PUBLISHED';
-- name: CountUserCompletedPublishedExamPrepPracticesInUnit :one
SELECT
count(*)::int AS n
FROM
exam_prep.lesson_practices p
INNER JOIN exam_prep.unit_module_lessons l ON l.id = p.unit_module_lesson_id
INNER JOIN exam_prep.unit_modules m ON m.id = l.unit_module_id
INNER JOIN question_sets qs ON qs.id = p.question_set_id
INNER JOIN user_practice_progress upp ON upp.question_set_id = p.question_set_id
WHERE
m.unit_id = $1
AND upp.user_id = $2
AND upp.completed_at IS NOT NULL
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND p.publish_status = 'PUBLISHED';
-- name: CountPublishedExamPrepPracticesInCatalogCourse :one
SELECT
count(*)::int AS n
FROM
exam_prep.lesson_practices p
INNER JOIN exam_prep.unit_module_lessons l ON l.id = p.unit_module_lesson_id
INNER JOIN exam_prep.unit_modules m ON m.id = l.unit_module_id
INNER JOIN exam_prep.units u ON u.id = m.unit_id
INNER JOIN question_sets qs ON qs.id = p.question_set_id
WHERE
u.catalog_course_id = $1
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND p.publish_status = 'PUBLISHED';
-- name: CountUserCompletedPublishedExamPrepPracticesInCatalogCourse :one
SELECT
count(*)::int AS n
FROM
exam_prep.lesson_practices p
INNER JOIN exam_prep.unit_module_lessons l ON l.id = p.unit_module_lesson_id
INNER JOIN exam_prep.unit_modules m ON m.id = l.unit_module_id
INNER JOIN exam_prep.units u ON u.id = m.unit_id
INNER JOIN question_sets qs ON qs.id = p.question_set_id
INNER JOIN user_practice_progress upp ON upp.question_set_id = p.question_set_id
WHERE
u.catalog_course_id = $1
AND upp.user_id = $2
AND upp.completed_at IS NOT NULL
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND p.publish_status = 'PUBLISHED';

View File

@ -225,72 +225,124 @@ ORDER BY
-- name: ListLMSCompletedModuleIDsByUser :many
SELECT
lp.module_id
FROM
lms_practices AS lp
INNER JOIN question_sets qs ON qs.id = lp.question_set_id
LEFT JOIN user_practice_progress upp ON upp.question_set_id = lp.question_set_id
scoped.module_id
FROM (
SELECT
m.id AS module_id,
lp.question_set_id
FROM
modules m
INNER JOIN lms_practices lp ON (
lp.module_id = m.id
OR lp.lesson_id IN (
SELECT
id
FROM
lessons
WHERE
module_id = m.id))
INNER JOIN question_sets qs ON qs.id = lp.question_set_id
WHERE
qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND lp.publish_status = 'PUBLISHED') scoped
LEFT JOIN user_practice_progress upp ON upp.question_set_id = scoped.question_set_id
AND upp.user_id = $1
AND upp.completed_at IS NOT NULL
WHERE
lp.module_id IS NOT NULL
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND lp.publish_status = 'PUBLISHED'
GROUP BY
lp.module_id
scoped.module_id
HAVING
count(DISTINCT lp.question_set_id) > 0
AND count(DISTINCT upp.question_set_id) >= count(DISTINCT lp.question_set_id)
count(DISTINCT scoped.question_set_id) > 0
AND count(DISTINCT upp.question_set_id) >= count(DISTINCT scoped.question_set_id)
ORDER BY
max(upp.completed_at) ASC,
lp.module_id ASC;
scoped.module_id ASC;
-- name: ListLMSCompletedCourseIDsByUser :many
SELECT
lp.course_id
FROM
lms_practices AS lp
INNER JOIN question_sets qs ON qs.id = lp.question_set_id
LEFT JOIN user_practice_progress upp ON upp.question_set_id = lp.question_set_id
scoped.course_id
FROM (
SELECT
c.id AS course_id,
lp.question_set_id
FROM
courses c
INNER JOIN lms_practices lp ON (
lp.course_id = c.id
OR lp.module_id IN (
SELECT
id
FROM
modules
WHERE
course_id = c.id)
OR lp.lesson_id IN (
SELECT
l.id
FROM
lessons l
INNER JOIN modules m ON m.id = l.module_id
WHERE
m.course_id = c.id))
INNER JOIN question_sets qs ON qs.id = lp.question_set_id
WHERE
qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND lp.publish_status = 'PUBLISHED') scoped
LEFT JOIN user_practice_progress upp ON upp.question_set_id = scoped.question_set_id
AND upp.user_id = $1
AND upp.completed_at IS NOT NULL
WHERE
lp.course_id IS NOT NULL
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND lp.publish_status = 'PUBLISHED'
GROUP BY
lp.course_id
scoped.course_id
HAVING
count(DISTINCT lp.question_set_id) > 0
AND count(DISTINCT upp.question_set_id) >= count(DISTINCT lp.question_set_id)
count(DISTINCT scoped.question_set_id) > 0
AND count(DISTINCT upp.question_set_id) >= count(DISTINCT scoped.question_set_id)
ORDER BY
max(upp.completed_at) ASC,
lp.course_id ASC;
scoped.course_id ASC;
-- name: ListLMSCompletedProgramIDsByUser :many
SELECT
c.program_id
FROM
lms_practices AS lp
INNER JOIN courses c ON c.id = lp.course_id
INNER JOIN question_sets qs ON qs.id = lp.question_set_id
LEFT JOIN user_practice_progress upp ON upp.question_set_id = lp.question_set_id
scoped.program_id
FROM (
SELECT
c.program_id,
lp.question_set_id
FROM
courses c
INNER JOIN lms_practices lp ON (
lp.course_id = c.id
OR lp.module_id IN (
SELECT
m.id
FROM
modules m
WHERE
m.course_id = c.id)
OR lp.lesson_id IN (
SELECT
l.id
FROM
lessons l
INNER JOIN modules m ON m.id = l.module_id
WHERE
m.course_id = c.id))
INNER JOIN question_sets qs ON qs.id = lp.question_set_id
WHERE
qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND lp.publish_status = 'PUBLISHED') scoped
LEFT JOIN user_practice_progress upp ON upp.question_set_id = scoped.question_set_id
AND upp.user_id = $1
AND upp.completed_at IS NOT NULL
WHERE
qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND lp.publish_status = 'PUBLISHED'
GROUP BY
c.program_id
scoped.program_id
HAVING
count(DISTINCT lp.question_set_id) > 0
AND count(DISTINCT upp.question_set_id) >= count(DISTINCT lp.question_set_id)
count(DISTINCT scoped.question_set_id) > 0
AND count(DISTINCT upp.question_set_id) >= count(DISTINCT scoped.question_set_id)
ORDER BY
max(upp.completed_at) ASC,
c.program_id ASC;
scoped.program_id ASC;
-- Lesson-based progress within a course (all modules).
-- name: CountLessonsInCourse :one
@ -340,7 +392,7 @@ WHERE
AND ulp.user_id = $2
AND l.publish_status = 'PUBLISHED';
-- Published practices in a module (module-level and lesson-level practices should carry module_id).
-- Published practices in a module (direct module practices and practices on lessons in the module).
-- name: CountPublishedPracticesInModule :one
SELECT
count(*)::int AS n
@ -348,7 +400,15 @@ FROM
lms_practices lp
INNER JOIN question_sets qs ON qs.id = lp.question_set_id
WHERE
lp.module_id = $1
(
lp.module_id = $1
OR lp.lesson_id IN (
SELECT
id
FROM
lessons
WHERE
module_id = $1))
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND lp.publish_status = 'PUBLISHED';
@ -361,7 +421,15 @@ FROM
INNER JOIN question_sets qs ON qs.id = lp.question_set_id
INNER JOIN user_practice_progress upp ON upp.question_set_id = lp.question_set_id
WHERE
lp.module_id = $1
(
lp.module_id = $1
OR lp.lesson_id IN (
SELECT
id
FROM
lessons
WHERE
module_id = $1))
AND upp.user_id = $2
AND upp.completed_at IS NOT NULL
AND qs.set_type = 'PRACTICE'
@ -375,7 +443,23 @@ FROM
lms_practices lp
INNER JOIN question_sets qs ON qs.id = lp.question_set_id
WHERE
lp.course_id = $1
(
lp.course_id = $1
OR lp.module_id IN (
SELECT
id
FROM
modules
WHERE
course_id = $1)
OR lp.lesson_id IN (
SELECT
l.id
FROM
lessons l
INNER JOIN modules m ON m.id = l.module_id
WHERE
m.course_id = $1))
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND lp.publish_status = 'PUBLISHED';
@ -388,21 +472,61 @@ FROM
INNER JOIN question_sets qs ON qs.id = lp.question_set_id
INNER JOIN user_practice_progress upp ON upp.question_set_id = lp.question_set_id
WHERE
lp.course_id = $1
(
lp.course_id = $1
OR lp.module_id IN (
SELECT
id
FROM
modules
WHERE
course_id = $1)
OR lp.lesson_id IN (
SELECT
l.id
FROM
lessons l
INNER JOIN modules m ON m.id = l.module_id
WHERE
m.course_id = $1))
AND upp.user_id = $2
AND upp.completed_at IS NOT NULL
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED';
AND qs.status = 'PUBLISHED'
AND lp.publish_status = 'PUBLISHED';
-- name: CountPublishedPracticesInProgram :one
SELECT
count(*)::int AS n
FROM
lms_practices lp
INNER JOIN courses c ON c.id = lp.course_id
INNER JOIN question_sets qs ON qs.id = lp.question_set_id
WHERE
c.program_id = $1
(
lp.course_id IN (
SELECT
c.id
FROM
courses c
WHERE
c.program_id = $1)
OR lp.module_id IN (
SELECT
m.id
FROM
modules m
INNER JOIN courses c ON c.id = m.course_id
WHERE
c.program_id = $1)
OR lp.lesson_id IN (
SELECT
l.id
FROM
lessons l
INNER JOIN modules m ON m.id = l.module_id
INNER JOIN courses c ON c.id = m.course_id
WHERE
c.program_id = $1))
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND lp.publish_status = 'PUBLISHED';
@ -412,11 +536,34 @@ SELECT
count(*)::int AS n
FROM
lms_practices lp
INNER JOIN courses c ON c.id = lp.course_id
INNER JOIN question_sets qs ON qs.id = lp.question_set_id
INNER JOIN user_practice_progress upp ON upp.question_set_id = lp.question_set_id
WHERE
c.program_id = $1
(
lp.course_id IN (
SELECT
c.id
FROM
courses c
WHERE
c.program_id = $1)
OR lp.module_id IN (
SELECT
m.id
FROM
modules m
INNER JOIN courses c ON c.id = m.course_id
WHERE
c.program_id = $1)
OR lp.lesson_id IN (
SELECT
l.id
FROM
lessons l
INNER JOIN modules m ON m.id = l.module_id
INNER JOIN courses c ON c.id = m.course_id
WHERE
c.program_id = $1))
AND upp.user_id = $2
AND upp.completed_at IS NOT NULL
AND qs.set_type = 'PRACTICE'

View File

@ -0,0 +1,214 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: exam_prep_progress.sql
package dbgen
import (
"context"
)
const CountPublishedExamPrepPracticesInCatalogCourse = `-- name: CountPublishedExamPrepPracticesInCatalogCourse :one
SELECT
count(*)::int AS n
FROM
exam_prep.lesson_practices p
INNER JOIN exam_prep.unit_module_lessons l ON l.id = p.unit_module_lesson_id
INNER JOIN exam_prep.unit_modules m ON m.id = l.unit_module_id
INNER JOIN exam_prep.units u ON u.id = m.unit_id
INNER JOIN question_sets qs ON qs.id = p.question_set_id
WHERE
u.catalog_course_id = $1
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND p.publish_status = 'PUBLISHED'
`
func (q *Queries) CountPublishedExamPrepPracticesInCatalogCourse(ctx context.Context, catalogCourseID int64) (int32, error) {
row := q.db.QueryRow(ctx, CountPublishedExamPrepPracticesInCatalogCourse, catalogCourseID)
var n int32
err := row.Scan(&n)
return n, err
}
const CountPublishedExamPrepPracticesInLesson = `-- name: CountPublishedExamPrepPracticesInLesson :one
SELECT
count(*)::int AS n
FROM
exam_prep.lesson_practices p
INNER JOIN question_sets qs ON qs.id = p.question_set_id
WHERE
p.unit_module_lesson_id = $1
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND p.publish_status = 'PUBLISHED'
`
func (q *Queries) CountPublishedExamPrepPracticesInLesson(ctx context.Context, unitModuleLessonID int64) (int32, error) {
row := q.db.QueryRow(ctx, CountPublishedExamPrepPracticesInLesson, unitModuleLessonID)
var n int32
err := row.Scan(&n)
return n, err
}
const CountPublishedExamPrepPracticesInModule = `-- name: CountPublishedExamPrepPracticesInModule :one
SELECT
count(*)::int AS n
FROM
exam_prep.lesson_practices p
INNER JOIN exam_prep.unit_module_lessons l ON l.id = p.unit_module_lesson_id
INNER JOIN question_sets qs ON qs.id = p.question_set_id
WHERE
l.unit_module_id = $1
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND p.publish_status = 'PUBLISHED'
`
func (q *Queries) CountPublishedExamPrepPracticesInModule(ctx context.Context, unitModuleID int64) (int32, error) {
row := q.db.QueryRow(ctx, CountPublishedExamPrepPracticesInModule, unitModuleID)
var n int32
err := row.Scan(&n)
return n, err
}
const CountPublishedExamPrepPracticesInUnit = `-- name: CountPublishedExamPrepPracticesInUnit :one
SELECT
count(*)::int AS n
FROM
exam_prep.lesson_practices p
INNER JOIN exam_prep.unit_module_lessons l ON l.id = p.unit_module_lesson_id
INNER JOIN exam_prep.unit_modules m ON m.id = l.unit_module_id
INNER JOIN question_sets qs ON qs.id = p.question_set_id
WHERE
m.unit_id = $1
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND p.publish_status = 'PUBLISHED'
`
func (q *Queries) CountPublishedExamPrepPracticesInUnit(ctx context.Context, unitID int64) (int32, error) {
row := q.db.QueryRow(ctx, CountPublishedExamPrepPracticesInUnit, unitID)
var n int32
err := row.Scan(&n)
return n, err
}
const CountUserCompletedPublishedExamPrepPracticesInCatalogCourse = `-- name: CountUserCompletedPublishedExamPrepPracticesInCatalogCourse :one
SELECT
count(*)::int AS n
FROM
exam_prep.lesson_practices p
INNER JOIN exam_prep.unit_module_lessons l ON l.id = p.unit_module_lesson_id
INNER JOIN exam_prep.unit_modules m ON m.id = l.unit_module_id
INNER JOIN exam_prep.units u ON u.id = m.unit_id
INNER JOIN question_sets qs ON qs.id = p.question_set_id
INNER JOIN user_practice_progress upp ON upp.question_set_id = p.question_set_id
WHERE
u.catalog_course_id = $1
AND upp.user_id = $2
AND upp.completed_at IS NOT NULL
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND p.publish_status = 'PUBLISHED'
`
type CountUserCompletedPublishedExamPrepPracticesInCatalogCourseParams struct {
CatalogCourseID int64 `json:"catalog_course_id"`
UserID int64 `json:"user_id"`
}
func (q *Queries) CountUserCompletedPublishedExamPrepPracticesInCatalogCourse(ctx context.Context, arg CountUserCompletedPublishedExamPrepPracticesInCatalogCourseParams) (int32, error) {
row := q.db.QueryRow(ctx, CountUserCompletedPublishedExamPrepPracticesInCatalogCourse, arg.CatalogCourseID, arg.UserID)
var n int32
err := row.Scan(&n)
return n, err
}
const CountUserCompletedPublishedExamPrepPracticesInLesson = `-- name: CountUserCompletedPublishedExamPrepPracticesInLesson :one
SELECT
count(*)::int AS n
FROM
exam_prep.lesson_practices p
INNER JOIN question_sets qs ON qs.id = p.question_set_id
INNER JOIN user_practice_progress upp ON upp.question_set_id = p.question_set_id
WHERE
p.unit_module_lesson_id = $1
AND upp.user_id = $2
AND upp.completed_at IS NOT NULL
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND p.publish_status = 'PUBLISHED'
`
type CountUserCompletedPublishedExamPrepPracticesInLessonParams struct {
UnitModuleLessonID int64 `json:"unit_module_lesson_id"`
UserID int64 `json:"user_id"`
}
func (q *Queries) CountUserCompletedPublishedExamPrepPracticesInLesson(ctx context.Context, arg CountUserCompletedPublishedExamPrepPracticesInLessonParams) (int32, error) {
row := q.db.QueryRow(ctx, CountUserCompletedPublishedExamPrepPracticesInLesson, arg.UnitModuleLessonID, arg.UserID)
var n int32
err := row.Scan(&n)
return n, err
}
const CountUserCompletedPublishedExamPrepPracticesInModule = `-- name: CountUserCompletedPublishedExamPrepPracticesInModule :one
SELECT
count(*)::int AS n
FROM
exam_prep.lesson_practices p
INNER JOIN exam_prep.unit_module_lessons l ON l.id = p.unit_module_lesson_id
INNER JOIN question_sets qs ON qs.id = p.question_set_id
INNER JOIN user_practice_progress upp ON upp.question_set_id = p.question_set_id
WHERE
l.unit_module_id = $1
AND upp.user_id = $2
AND upp.completed_at IS NOT NULL
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND p.publish_status = 'PUBLISHED'
`
type CountUserCompletedPublishedExamPrepPracticesInModuleParams struct {
UnitModuleID int64 `json:"unit_module_id"`
UserID int64 `json:"user_id"`
}
func (q *Queries) CountUserCompletedPublishedExamPrepPracticesInModule(ctx context.Context, arg CountUserCompletedPublishedExamPrepPracticesInModuleParams) (int32, error) {
row := q.db.QueryRow(ctx, CountUserCompletedPublishedExamPrepPracticesInModule, arg.UnitModuleID, arg.UserID)
var n int32
err := row.Scan(&n)
return n, err
}
const CountUserCompletedPublishedExamPrepPracticesInUnit = `-- name: CountUserCompletedPublishedExamPrepPracticesInUnit :one
SELECT
count(*)::int AS n
FROM
exam_prep.lesson_practices p
INNER JOIN exam_prep.unit_module_lessons l ON l.id = p.unit_module_lesson_id
INNER JOIN exam_prep.unit_modules m ON m.id = l.unit_module_id
INNER JOIN question_sets qs ON qs.id = p.question_set_id
INNER JOIN user_practice_progress upp ON upp.question_set_id = p.question_set_id
WHERE
m.unit_id = $1
AND upp.user_id = $2
AND upp.completed_at IS NOT NULL
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND p.publish_status = 'PUBLISHED'
`
type CountUserCompletedPublishedExamPrepPracticesInUnitParams struct {
UnitID int64 `json:"unit_id"`
UserID int64 `json:"user_id"`
}
func (q *Queries) CountUserCompletedPublishedExamPrepPracticesInUnit(ctx context.Context, arg CountUserCompletedPublishedExamPrepPracticesInUnitParams) (int32, error) {
row := q.db.QueryRow(ctx, CountUserCompletedPublishedExamPrepPracticesInUnit, arg.UnitID, arg.UserID)
var n int32
err := row.Scan(&n)
return n, err
}

View File

@ -106,7 +106,23 @@ FROM
lms_practices lp
INNER JOIN question_sets qs ON qs.id = lp.question_set_id
WHERE
lp.course_id = $1
(
lp.course_id = $1
OR lp.module_id IN (
SELECT
id
FROM
modules
WHERE
course_id = $1)
OR lp.lesson_id IN (
SELECT
l.id
FROM
lessons l
INNER JOIN modules m ON m.id = l.module_id
WHERE
m.course_id = $1))
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND lp.publish_status = 'PUBLISHED'
@ -146,13 +162,21 @@ FROM
lms_practices lp
INNER JOIN question_sets qs ON qs.id = lp.question_set_id
WHERE
lp.module_id = $1
(
lp.module_id = $1
OR lp.lesson_id IN (
SELECT
id
FROM
lessons
WHERE
module_id = $1))
AND qs.set_type = 'PRACTICE'
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 (direct module practices and practices on lessons in the module).
func (q *Queries) CountPublishedPracticesInModule(ctx context.Context, moduleID pgtype.Int8) (int32, error) {
row := q.db.QueryRow(ctx, CountPublishedPracticesInModule, moduleID)
var n int32
@ -165,10 +189,33 @@ SELECT
count(*)::int AS n
FROM
lms_practices lp
INNER JOIN courses c ON c.id = lp.course_id
INNER JOIN question_sets qs ON qs.id = lp.question_set_id
WHERE
c.program_id = $1
(
lp.course_id IN (
SELECT
c.id
FROM
courses c
WHERE
c.program_id = $1)
OR lp.module_id IN (
SELECT
m.id
FROM
modules m
INNER JOIN courses c ON c.id = m.course_id
WHERE
c.program_id = $1)
OR lp.lesson_id IN (
SELECT
l.id
FROM
lessons l
INNER JOIN modules m ON m.id = l.module_id
INNER JOIN courses c ON c.id = m.course_id
WHERE
c.program_id = $1))
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND lp.publish_status = 'PUBLISHED'
@ -310,11 +357,28 @@ FROM
INNER JOIN question_sets qs ON qs.id = lp.question_set_id
INNER JOIN user_practice_progress upp ON upp.question_set_id = lp.question_set_id
WHERE
lp.course_id = $1
(
lp.course_id = $1
OR lp.module_id IN (
SELECT
id
FROM
modules
WHERE
course_id = $1)
OR lp.lesson_id IN (
SELECT
l.id
FROM
lessons l
INNER JOIN modules m ON m.id = l.module_id
WHERE
m.course_id = $1))
AND upp.user_id = $2
AND upp.completed_at IS NOT NULL
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND lp.publish_status = 'PUBLISHED'
`
type CountUserCompletedPublishedPracticesInCourseParams struct {
@ -365,7 +429,15 @@ FROM
INNER JOIN question_sets qs ON qs.id = lp.question_set_id
INNER JOIN user_practice_progress upp ON upp.question_set_id = lp.question_set_id
WHERE
lp.module_id = $1
(
lp.module_id = $1
OR lp.lesson_id IN (
SELECT
id
FROM
lessons
WHERE
module_id = $1))
AND upp.user_id = $2
AND upp.completed_at IS NOT NULL
AND qs.set_type = 'PRACTICE'
@ -390,11 +462,34 @@ SELECT
count(*)::int AS n
FROM
lms_practices lp
INNER JOIN courses c ON c.id = lp.course_id
INNER JOIN question_sets qs ON qs.id = lp.question_set_id
INNER JOIN user_practice_progress upp ON upp.question_set_id = lp.question_set_id
WHERE
c.program_id = $1
(
lp.course_id IN (
SELECT
c.id
FROM
courses c
WHERE
c.program_id = $1)
OR lp.module_id IN (
SELECT
m.id
FROM
modules m
INNER JOIN courses c ON c.id = m.course_id
WHERE
c.program_id = $1)
OR lp.lesson_id IN (
SELECT
l.id
FROM
lessons l
INNER JOIN modules m ON m.id = l.module_id
INNER JOIN courses c ON c.id = m.course_id
WHERE
c.program_id = $1))
AND upp.user_id = $2
AND upp.completed_at IS NOT NULL
AND qs.set_type = 'PRACTICE'
@ -640,37 +735,57 @@ func (q *Queries) InsertUserProgramProgress(ctx context.Context, arg InsertUserP
const ListLMSCompletedCourseIDsByUser = `-- name: ListLMSCompletedCourseIDsByUser :many
SELECT
lp.course_id
FROM
lms_practices AS lp
INNER JOIN question_sets qs ON qs.id = lp.question_set_id
LEFT JOIN user_practice_progress upp ON upp.question_set_id = lp.question_set_id
scoped.course_id
FROM (
SELECT
c.id AS course_id,
lp.question_set_id
FROM
courses c
INNER JOIN lms_practices lp ON (
lp.course_id = c.id
OR lp.module_id IN (
SELECT
id
FROM
modules
WHERE
course_id = c.id)
OR lp.lesson_id IN (
SELECT
l.id
FROM
lessons l
INNER JOIN modules m ON m.id = l.module_id
WHERE
m.course_id = c.id))
INNER JOIN question_sets qs ON qs.id = lp.question_set_id
WHERE
qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND lp.publish_status = 'PUBLISHED') scoped
LEFT JOIN user_practice_progress upp ON upp.question_set_id = scoped.question_set_id
AND upp.user_id = $1
AND upp.completed_at IS NOT NULL
WHERE
lp.course_id IS NOT NULL
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND lp.publish_status = 'PUBLISHED'
GROUP BY
lp.course_id
scoped.course_id
HAVING
count(DISTINCT lp.question_set_id) > 0
AND count(DISTINCT upp.question_set_id) >= count(DISTINCT lp.question_set_id)
count(DISTINCT scoped.question_set_id) > 0
AND count(DISTINCT upp.question_set_id) >= count(DISTINCT scoped.question_set_id)
ORDER BY
max(upp.completed_at) ASC,
lp.course_id ASC
scoped.course_id ASC
`
func (q *Queries) ListLMSCompletedCourseIDsByUser(ctx context.Context, userID int64) ([]pgtype.Int8, error) {
func (q *Queries) ListLMSCompletedCourseIDsByUser(ctx context.Context, userID int64) ([]int64, error) {
rows, err := q.db.Query(ctx, ListLMSCompletedCourseIDsByUser, userID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []pgtype.Int8
var items []int64
for rows.Next() {
var course_id pgtype.Int8
var course_id int64
if err := rows.Scan(&course_id); err != nil {
return nil, err
}
@ -728,37 +843,49 @@ func (q *Queries) ListLMSCompletedLessonIDsByUser(ctx context.Context, userID in
const ListLMSCompletedModuleIDsByUser = `-- name: ListLMSCompletedModuleIDsByUser :many
SELECT
lp.module_id
FROM
lms_practices AS lp
INNER JOIN question_sets qs ON qs.id = lp.question_set_id
LEFT JOIN user_practice_progress upp ON upp.question_set_id = lp.question_set_id
scoped.module_id
FROM (
SELECT
m.id AS module_id,
lp.question_set_id
FROM
modules m
INNER JOIN lms_practices lp ON (
lp.module_id = m.id
OR lp.lesson_id IN (
SELECT
id
FROM
lessons
WHERE
module_id = m.id))
INNER JOIN question_sets qs ON qs.id = lp.question_set_id
WHERE
qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND lp.publish_status = 'PUBLISHED') scoped
LEFT JOIN user_practice_progress upp ON upp.question_set_id = scoped.question_set_id
AND upp.user_id = $1
AND upp.completed_at IS NOT NULL
WHERE
lp.module_id IS NOT NULL
AND qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND lp.publish_status = 'PUBLISHED'
GROUP BY
lp.module_id
scoped.module_id
HAVING
count(DISTINCT lp.question_set_id) > 0
AND count(DISTINCT upp.question_set_id) >= count(DISTINCT lp.question_set_id)
count(DISTINCT scoped.question_set_id) > 0
AND count(DISTINCT upp.question_set_id) >= count(DISTINCT scoped.question_set_id)
ORDER BY
max(upp.completed_at) ASC,
lp.module_id ASC
scoped.module_id ASC
`
func (q *Queries) ListLMSCompletedModuleIDsByUser(ctx context.Context, userID int64) ([]pgtype.Int8, error) {
func (q *Queries) ListLMSCompletedModuleIDsByUser(ctx context.Context, userID int64) ([]int64, error) {
rows, err := q.db.Query(ctx, ListLMSCompletedModuleIDsByUser, userID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []pgtype.Int8
var items []int64
for rows.Next() {
var module_id pgtype.Int8
var module_id int64
if err := rows.Scan(&module_id); err != nil {
return nil, err
}
@ -772,26 +899,46 @@ func (q *Queries) ListLMSCompletedModuleIDsByUser(ctx context.Context, userID in
const ListLMSCompletedProgramIDsByUser = `-- name: ListLMSCompletedProgramIDsByUser :many
SELECT
c.program_id
FROM
lms_practices AS lp
INNER JOIN courses c ON c.id = lp.course_id
INNER JOIN question_sets qs ON qs.id = lp.question_set_id
LEFT JOIN user_practice_progress upp ON upp.question_set_id = lp.question_set_id
scoped.program_id
FROM (
SELECT
c.program_id,
lp.question_set_id
FROM
courses c
INNER JOIN lms_practices lp ON (
lp.course_id = c.id
OR lp.module_id IN (
SELECT
m.id
FROM
modules m
WHERE
m.course_id = c.id)
OR lp.lesson_id IN (
SELECT
l.id
FROM
lessons l
INNER JOIN modules m ON m.id = l.module_id
WHERE
m.course_id = c.id))
INNER JOIN question_sets qs ON qs.id = lp.question_set_id
WHERE
qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND lp.publish_status = 'PUBLISHED') scoped
LEFT JOIN user_practice_progress upp ON upp.question_set_id = scoped.question_set_id
AND upp.user_id = $1
AND upp.completed_at IS NOT NULL
WHERE
qs.set_type = 'PRACTICE'
AND qs.status = 'PUBLISHED'
AND lp.publish_status = 'PUBLISHED'
GROUP BY
c.program_id
scoped.program_id
HAVING
count(DISTINCT lp.question_set_id) > 0
AND count(DISTINCT upp.question_set_id) >= count(DISTINCT lp.question_set_id)
count(DISTINCT scoped.question_set_id) > 0
AND count(DISTINCT upp.question_set_id) >= count(DISTINCT scoped.question_set_id)
ORDER BY
max(upp.completed_at) ASC,
c.program_id ASC
scoped.program_id ASC
`
func (q *Queries) ListLMSCompletedProgramIDsByUser(ctx context.Context, userID int64) ([]int64, error) {

View File

@ -13,8 +13,9 @@ type ExamPrepCatalogCourse struct {
UnitsCount *int64 `json:"units_count,omitempty"`
ModulesCount *int64 `json:"modules_count,omitempty"`
LessonsCount *int64 `json:"lessons_count,omitempty"`
HasPractice bool `json:"has_practice"`
CreatedAt time.Time `json:"created_at"`
HasPractice bool `json:"has_practice"`
Access *LMSEntityAccess `json:"access,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
}

View File

@ -11,8 +11,9 @@ type ExamPrepLesson struct {
Thumbnail *string `json:"thumbnail,omitempty"`
Description *string `json:"description,omitempty"`
SortOrder int `json:"sort_order"`
HasPractice bool `json:"has_practice"`
CreatedAt time.Time `json:"created_at"`
HasPractice bool `json:"has_practice"`
Access *LMSEntityAccess `json:"access,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
}

View File

@ -13,8 +13,9 @@ type ExamPrepModule struct {
SortOrder int `json:"sort_order"`
LessonsCount *int64 `json:"lessons_count,omitempty"`
PracticesCount *int64 `json:"practices_count,omitempty"`
HasPractice bool `json:"has_practice"`
CreatedAt time.Time `json:"created_at"`
HasPractice bool `json:"has_practice"`
Access *LMSEntityAccess `json:"access,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
}

View File

@ -13,8 +13,9 @@ type ExamPrepUnit struct {
ModulesCount *int64 `json:"modules_count,omitempty"`
LessonsCount *int64 `json:"lessons_count,omitempty"`
PracticesCount *int64 `json:"practices_count,omitempty"`
HasPractice bool `json:"has_practice"`
CreatedAt time.Time `json:"created_at"`
HasPractice bool `json:"has_practice"`
Access *LMSEntityAccess `json:"access,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
}

View File

@ -0,0 +1,71 @@
package repository
import (
"context"
dbgen "Yimaru-Backend/gen/db"
)
// ExamPrepUserPracticeProgressInLesson returns published practice completion counts scoped to an exam-prep lesson.
func (s *Store) ExamPrepUserPracticeProgressInLesson(ctx context.Context, userID, lessonID int64) (completed, total int32, err error) {
total, err = s.queries.CountPublishedExamPrepPracticesInLesson(ctx, lessonID)
if err != nil {
return 0, 0, err
}
completed, err = s.queries.CountUserCompletedPublishedExamPrepPracticesInLesson(ctx, dbgen.CountUserCompletedPublishedExamPrepPracticesInLessonParams{
UnitModuleLessonID: lessonID,
UserID: userID,
})
if err != nil {
return 0, 0, err
}
return completed, total, nil
}
// ExamPrepUserPracticeProgressInModule returns published practice completion counts in an exam-prep module.
func (s *Store) ExamPrepUserPracticeProgressInModule(ctx context.Context, userID, moduleID int64) (completed, total int32, err error) {
total, err = s.queries.CountPublishedExamPrepPracticesInModule(ctx, moduleID)
if err != nil {
return 0, 0, err
}
completed, err = s.queries.CountUserCompletedPublishedExamPrepPracticesInModule(ctx, dbgen.CountUserCompletedPublishedExamPrepPracticesInModuleParams{
UnitModuleID: moduleID,
UserID: userID,
})
if err != nil {
return 0, 0, err
}
return completed, total, nil
}
// ExamPrepUserPracticeProgressInUnit returns published practice completion counts in an exam-prep unit.
func (s *Store) ExamPrepUserPracticeProgressInUnit(ctx context.Context, userID, unitID int64) (completed, total int32, err error) {
total, err = s.queries.CountPublishedExamPrepPracticesInUnit(ctx, unitID)
if err != nil {
return 0, 0, err
}
completed, err = s.queries.CountUserCompletedPublishedExamPrepPracticesInUnit(ctx, dbgen.CountUserCompletedPublishedExamPrepPracticesInUnitParams{
UnitID: unitID,
UserID: userID,
})
if err != nil {
return 0, 0, err
}
return completed, total, nil
}
// ExamPrepUserPracticeProgressInCatalogCourse returns published practice completion counts in a catalog course.
func (s *Store) ExamPrepUserPracticeProgressInCatalogCourse(ctx context.Context, userID, catalogCourseID int64) (completed, total int32, err error) {
total, err = s.queries.CountPublishedExamPrepPracticesInCatalogCourse(ctx, catalogCourseID)
if err != nil {
return 0, 0, err
}
completed, err = s.queries.CountUserCompletedPublishedExamPrepPracticesInCatalogCourse(ctx, dbgen.CountUserCompletedPublishedExamPrepPracticesInCatalogCourseParams{
CatalogCourseID: catalogCourseID,
UserID: userID,
})
if err != nil {
return 0, 0, err
}
return completed, total, nil
}

View File

@ -2,9 +2,11 @@ package repository
import (
"context"
"errors"
"fmt"
dbgen "Yimaru-Backend/gen/db"
"github.com/jackc/pgx/v5"
)
// CompleteLessonForUser records lesson completion for sequential lesson gating and
@ -60,6 +62,13 @@ func (s *Store) CompletePracticeForUser(ctx context.Context, userID, questionSet
scope, err := q.GetPracticeScopeByQuestionSetID(ctx, questionSetID)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
// Exam-prep practices are not in lms_practices; completion is tracked in user_practice_progress only.
if err := tx.Commit(ctx); err != nil {
return fmt.Errorf("commit: %w", err)
}
return nil
}
return err
}
var (

View File

@ -29,8 +29,8 @@ func (s *Store) GetLMSUserProgressSnapshot(ctx context.Context, userID int64) (d
}
return domain.LMSUserProgress{
LessonIDs: pgInt8IDsToInt64(lessons),
ModuleIDs: pgInt8IDsToInt64(mods),
CourseIDs: pgInt8IDsToInt64(courses),
ModuleIDs: int64IDsOrEmpty(mods),
CourseIDs: int64IDsOrEmpty(courses),
ProgramIDs: int64IDsOrEmpty(programs),
}, nil
}

View File

@ -234,6 +234,66 @@ func (s *Service) ApplyAccessLesson(ctx context.Context, role domain.Role, userI
return nil
}
// ApplyExamPrepAccessCatalogCourse sets progress on an exam-prep catalog course for learner roles.
func (s *Service) ApplyExamPrepAccessCatalogCourse(ctx context.Context, role domain.Role, userID int64, cc *domain.ExamPrepCatalogCourse) error {
if !role.IsCustomerLearnerRole() {
cc.Access = nil
return nil
}
comp, tot, err := s.store.ExamPrepUserPracticeProgressInCatalogCourse(ctx, userID, cc.ID)
if err != nil {
return err
}
done := lmsProgressComplete(comp, tot)
cc.Access = buildLMSEntityAccess(true, "", done, comp, tot)
return nil
}
// ApplyExamPrepAccessUnit sets progress on an exam-prep unit for learner roles.
func (s *Service) ApplyExamPrepAccessUnit(ctx context.Context, role domain.Role, userID int64, u *domain.ExamPrepUnit) error {
if !role.IsCustomerLearnerRole() {
u.Access = nil
return nil
}
comp, tot, err := s.store.ExamPrepUserPracticeProgressInUnit(ctx, userID, u.ID)
if err != nil {
return err
}
done := lmsProgressComplete(comp, tot)
u.Access = buildLMSEntityAccess(true, "", done, comp, tot)
return nil
}
// ApplyExamPrepAccessModule sets progress on an exam-prep module for learner roles.
func (s *Service) ApplyExamPrepAccessModule(ctx context.Context, role domain.Role, userID int64, m *domain.ExamPrepModule) error {
if !role.IsCustomerLearnerRole() {
m.Access = nil
return nil
}
comp, tot, err := s.store.ExamPrepUserPracticeProgressInModule(ctx, userID, m.ID)
if err != nil {
return err
}
done := lmsProgressComplete(comp, tot)
m.Access = buildLMSEntityAccess(true, "", done, comp, tot)
return nil
}
// ApplyExamPrepAccessLesson sets progress on an exam-prep lesson for learner roles.
func (s *Service) ApplyExamPrepAccessLesson(ctx context.Context, role domain.Role, userID int64, les *domain.ExamPrepLesson) error {
if !role.IsCustomerLearnerRole() {
les.Access = nil
return nil
}
comp, tot, err := s.store.ExamPrepUserPracticeProgressInLesson(ctx, userID, les.ID)
if err != nil {
return err
}
done := lmsProgressComplete(comp, tot)
les.Access = buildLMSEntityAccess(true, "", done, comp, tot)
return nil
}
func lmsProgressComplete(completed, total int32) bool {
return total > 0 && completed >= total
}

View File

@ -116,10 +116,18 @@ func (h *Handler) ListExamPrepCatalogCourses(c *fiber.Ctx) error {
end = total
}
page := filtered[start:end]
if err := h.applyExamPrepAccessCatalogCourses(c.Context(), c, page); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to build catalog course list",
Error: err.Error(),
})
}
return c.JSON(domain.Response{
Message: "Catalog courses retrieved successfully",
Data: fiber.Map{
"catalog_courses": filtered[start:end],
"catalog_courses": page,
"total_count": total,
"limit": limit,
"offset": offset,
@ -136,6 +144,12 @@ func (h *Handler) ListExamPrepCatalogCourses(c *fiber.Ctx) error {
Error: err.Error(),
})
}
if err := h.applyExamPrepAccessCatalogCourses(c.Context(), c, items); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to build catalog course list",
Error: err.Error(),
})
}
return c.JSON(domain.Response{
Message: "Catalog courses retrieved successfully",
Data: fiber.Map{
@ -218,6 +232,12 @@ func (h *Handler) GetExamPrepCatalogCourseByID(c *fiber.Ctx) error {
Error: err.Error(),
})
}
if err := h.applyExamPrepAccessCatalogCourse(c.Context(), c, &out); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to build catalog course",
Error: err.Error(),
})
}
return c.JSON(domain.Response{
Message: "Catalog course retrieved successfully",
Data: out,

View File

@ -87,6 +87,12 @@ func (h *Handler) ListExamPrepLessonsByUnitModule(c *fiber.Ctx) error {
Error: err.Error(),
})
}
if err := h.applyExamPrepAccessLessons(c.Context(), c, items); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to build lesson list",
Error: err.Error(),
})
}
return c.JSON(domain.Response{
Message: "Lessons retrieved successfully",
Data: fiber.Map{
@ -175,6 +181,12 @@ func (h *Handler) GetExamPrepLessonByID(c *fiber.Ctx) error {
Error: err.Error(),
})
}
if err := h.applyExamPrepAccessLesson(c.Context(), c, &les); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to build lesson",
Error: err.Error(),
})
}
return c.JSON(domain.Response{
Message: "Lesson retrieved successfully",
Data: les,

View File

@ -85,6 +85,12 @@ func (h *Handler) ListExamPrepModulesByUnit(c *fiber.Ctx) error {
Error: err.Error(),
})
}
if err := h.applyExamPrepAccessModules(c.Context(), c, items); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to build module list",
Error: err.Error(),
})
}
return c.JSON(domain.Response{
Message: "Modules retrieved successfully",
Data: fiber.Map{
@ -175,6 +181,12 @@ func (h *Handler) GetExamPrepModuleByID(c *fiber.Ctx) error {
Error: err.Error(),
})
}
if err := h.applyExamPrepAccessModule(c.Context(), c, &out); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to build module",
Error: err.Error(),
})
}
return c.JSON(domain.Response{
Message: "Module retrieved successfully",
Data: out,

View File

@ -0,0 +1,125 @@
package handlers
import (
"context"
"Yimaru-Backend/internal/domain"
"github.com/gofiber/fiber/v2"
)
func (h *Handler) applyExamPrepAccessCatalogCourses(ctx context.Context, c *fiber.Ctx, items []domain.ExamPrepCatalogCourse) error {
role, _ := c.Locals("role").(domain.Role)
if !role.IsCustomerLearnerRole() {
return nil
}
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
return nil
}
for i := range items {
if err := h.lmsProgressSvc.ApplyExamPrepAccessCatalogCourse(ctx, role, userID, &items[i]); err != nil {
return err
}
}
return nil
}
func (h *Handler) applyExamPrepAccessCatalogCourse(ctx context.Context, c *fiber.Ctx, item *domain.ExamPrepCatalogCourse) error {
role, _ := c.Locals("role").(domain.Role)
if !role.IsCustomerLearnerRole() {
return nil
}
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
return nil
}
return h.lmsProgressSvc.ApplyExamPrepAccessCatalogCourse(ctx, role, userID, item)
}
func (h *Handler) applyExamPrepAccessUnits(ctx context.Context, c *fiber.Ctx, items []domain.ExamPrepUnit) error {
role, _ := c.Locals("role").(domain.Role)
if !role.IsCustomerLearnerRole() {
return nil
}
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
return nil
}
for i := range items {
if err := h.lmsProgressSvc.ApplyExamPrepAccessUnit(ctx, role, userID, &items[i]); err != nil {
return err
}
}
return nil
}
func (h *Handler) applyExamPrepAccessUnit(ctx context.Context, c *fiber.Ctx, item *domain.ExamPrepUnit) error {
role, _ := c.Locals("role").(domain.Role)
if !role.IsCustomerLearnerRole() {
return nil
}
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
return nil
}
return h.lmsProgressSvc.ApplyExamPrepAccessUnit(ctx, role, userID, item)
}
func (h *Handler) applyExamPrepAccessModules(ctx context.Context, c *fiber.Ctx, items []domain.ExamPrepModule) error {
role, _ := c.Locals("role").(domain.Role)
if !role.IsCustomerLearnerRole() {
return nil
}
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
return nil
}
for i := range items {
if err := h.lmsProgressSvc.ApplyExamPrepAccessModule(ctx, role, userID, &items[i]); err != nil {
return err
}
}
return nil
}
func (h *Handler) applyExamPrepAccessModule(ctx context.Context, c *fiber.Ctx, item *domain.ExamPrepModule) error {
role, _ := c.Locals("role").(domain.Role)
if !role.IsCustomerLearnerRole() {
return nil
}
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
return nil
}
return h.lmsProgressSvc.ApplyExamPrepAccessModule(ctx, role, userID, item)
}
func (h *Handler) applyExamPrepAccessLessons(ctx context.Context, c *fiber.Ctx, items []domain.ExamPrepLesson) error {
role, _ := c.Locals("role").(domain.Role)
if !role.IsCustomerLearnerRole() {
return nil
}
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
return nil
}
for i := range items {
if err := h.lmsProgressSvc.ApplyExamPrepAccessLesson(ctx, role, userID, &items[i]); err != nil {
return err
}
}
return nil
}
func (h *Handler) applyExamPrepAccessLesson(ctx context.Context, c *fiber.Ctx, item *domain.ExamPrepLesson) error {
role, _ := c.Locals("role").(domain.Role)
if !role.IsCustomerLearnerRole() {
return nil
}
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
return nil
}
return h.lmsProgressSvc.ApplyExamPrepAccessLesson(ctx, role, userID, item)
}

View File

@ -92,6 +92,12 @@ func (h *Handler) ListExamPrepUnitsByCatalogCourse(c *fiber.Ctx) error {
Error: err.Error(),
})
}
if err := h.applyExamPrepAccessUnits(c.Context(), c, items); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to build unit list",
Error: err.Error(),
})
}
return c.JSON(domain.Response{
Message: "Units retrieved successfully",
Data: fiber.Map{
@ -183,6 +189,12 @@ func (h *Handler) GetExamPrepUnitByID(c *fiber.Ctx) error {
Error: err.Error(),
})
}
if err := h.applyExamPrepAccessUnit(c.Context(), c, &out); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to build unit",
Error: err.Error(),
})
}
return c.JSON(domain.Response{
Message: "Unit retrieved successfully",
Data: out,

View File

@ -1573,6 +1573,14 @@ func (h *Handler) CompletePractice(c *fiber.Ctx) error {
})
}
set, setErr = h.questionsSvc.GetQuestionSetByID(c.Context(), practice.QuestionSetID)
} else if examPractice, examPracticeErr := h.examPrepSvc.GetExamPrepPracticeByID(c.Context(), id); examPracticeErr == nil {
if !examPractice.VisibleToLearners() {
return c.Status(fiber.StatusForbidden).JSON(domain.ErrorResponse{
Message: "Only published practices can be completed",
})
}
set, setErr = h.questionsSvc.GetQuestionSetByID(c.Context(), examPractice.QuestionSetID)
practiceErr = nil
} else {
// Backward compatibility: also accept question_set.id directly.
set, setErr = h.questionsSvc.GetQuestionSetByID(c.Context(), id)