From 16c3f6b6135b6d803bc9bb94df8202fe5a7547de Mon Sep 17 00:00:00 2001 From: Yared Yemane Date: Tue, 5 May 2026 06:35:13 -0700 Subject: [PATCH] Include nested module, lesson, and practice counts in exam-prep units list response. Expose per-unit aggregate counts under catalog-course units listing so clients can display unit depth without extra chained requests. Co-authored-by: Cursor --- db/query/exam_prep_units.sql | 16 ++++++++++++++++ gen/db/exam_prep_units.sql.go | 22 ++++++++++++++++++++++ internal/domain/exam_prep_unit.go | 5 ++++- internal/repository/exam_prep_units.go | 12 ++++++++---- 4 files changed, 50 insertions(+), 5 deletions(-) diff --git a/db/query/exam_prep_units.sql b/db/query/exam_prep_units.sql index fb77b16..98bb082 100644 --- a/db/query/exam_prep_units.sql +++ b/db/query/exam_prep_units.sql @@ -29,6 +29,18 @@ ORDER BY u.id; -- name: ExamPrepListUnitsByCatalogCourse :many +WITH unit_counts AS ( + SELECT + u.id AS unit_id, + COUNT(DISTINCT m.id)::BIGINT AS modules_count, + COUNT(DISTINCT l.id)::BIGINT AS lessons_count, + COUNT(DISTINCT p.id)::BIGINT AS practices_count + FROM exam_prep.units u + LEFT JOIN exam_prep.unit_modules m ON m.unit_id = u.id + LEFT JOIN exam_prep.unit_module_lessons l ON l.unit_module_id = m.id + LEFT JOIN exam_prep.lesson_practices p ON p.unit_module_lesson_id = l.id + GROUP BY u.id +) SELECT COUNT(*) OVER () AS total_count, u.id, @@ -37,9 +49,13 @@ SELECT u.description, u.thumbnail, u.sort_order, + COALESCE(uc.modules_count, 0)::BIGINT AS modules_count, + COALESCE(uc.lessons_count, 0)::BIGINT AS lessons_count, + COALESCE(uc.practices_count, 0)::BIGINT AS practices_count, u.created_at, u.updated_at FROM exam_prep.units u +LEFT JOIN unit_counts uc ON uc.unit_id = u.id WHERE u.catalog_course_id = $1 ORDER BY diff --git a/gen/db/exam_prep_units.sql.go b/gen/db/exam_prep_units.sql.go index 5fb8a99..b64b422 100644 --- a/gen/db/exam_prep_units.sql.go +++ b/gen/db/exam_prep_units.sql.go @@ -119,6 +119,18 @@ func (q *Queries) ExamPrepListUnitIDsByCatalogCourse(ctx context.Context, catalo } const ExamPrepListUnitsByCatalogCourse = `-- name: ExamPrepListUnitsByCatalogCourse :many +WITH unit_counts AS ( + SELECT + u.id AS unit_id, + COUNT(DISTINCT m.id)::BIGINT AS modules_count, + COUNT(DISTINCT l.id)::BIGINT AS lessons_count, + COUNT(DISTINCT p.id)::BIGINT AS practices_count + FROM exam_prep.units u + LEFT JOIN exam_prep.unit_modules m ON m.unit_id = u.id + LEFT JOIN exam_prep.unit_module_lessons l ON l.unit_module_id = m.id + LEFT JOIN exam_prep.lesson_practices p ON p.unit_module_lesson_id = l.id + GROUP BY u.id +) SELECT COUNT(*) OVER () AS total_count, u.id, @@ -127,9 +139,13 @@ SELECT u.description, u.thumbnail, u.sort_order, + COALESCE(uc.modules_count, 0)::BIGINT AS modules_count, + COALESCE(uc.lessons_count, 0)::BIGINT AS lessons_count, + COALESCE(uc.practices_count, 0)::BIGINT AS practices_count, u.created_at, u.updated_at FROM exam_prep.units u +LEFT JOIN unit_counts uc ON uc.unit_id = u.id WHERE u.catalog_course_id = $1 ORDER BY @@ -153,6 +169,9 @@ type ExamPrepListUnitsByCatalogCourseRow struct { Description pgtype.Text `json:"description"` Thumbnail pgtype.Text `json:"thumbnail"` SortOrder int32 `json:"sort_order"` + ModulesCount int64 `json:"modules_count"` + LessonsCount int64 `json:"lessons_count"` + PracticesCount int64 `json:"practices_count"` CreatedAt pgtype.Timestamptz `json:"created_at"` UpdatedAt pgtype.Timestamptz `json:"updated_at"` } @@ -174,6 +193,9 @@ func (q *Queries) ExamPrepListUnitsByCatalogCourse(ctx context.Context, arg Exam &i.Description, &i.Thumbnail, &i.SortOrder, + &i.ModulesCount, + &i.LessonsCount, + &i.PracticesCount, &i.CreatedAt, &i.UpdatedAt, ); err != nil { diff --git a/internal/domain/exam_prep_unit.go b/internal/domain/exam_prep_unit.go index c196457..4ef6041 100644 --- a/internal/domain/exam_prep_unit.go +++ b/internal/domain/exam_prep_unit.go @@ -5,11 +5,14 @@ import "time" // ExamPrepUnit is a chapter-like grouping under an exam-prep catalog course (schema exam_prep.units). type ExamPrepUnit struct { ID int64 `json:"id"` - CatalogCourseID int64 `json:"catalog_course_id"` + CatalogCourseID int64 `json:"catalog_course_id"` Name string `json:"name"` Description *string `json:"description,omitempty"` Thumbnail *string `json:"thumbnail,omitempty"` SortOrder int `json:"sort_order"` + ModulesCount *int64 `json:"modules_count,omitempty"` + LessonsCount *int64 `json:"lessons_count,omitempty"` + PracticesCount *int64 `json:"practices_count,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt *time.Time `json:"updated_at,omitempty"` } diff --git a/internal/repository/exam_prep_units.go b/internal/repository/exam_prep_units.go index 462792c..ebdd83f 100644 --- a/internal/repository/exam_prep_units.go +++ b/internal/repository/exam_prep_units.go @@ -55,8 +55,8 @@ func (s *Store) GetExamPrepUnitByID(ctx context.Context, id int64) (domain.ExamP func (s *Store) ListExamPrepUnitsByCatalogCourse(ctx context.Context, catalogCourseID int64, limit, offset int32) ([]domain.ExamPrepUnit, int64, error) { rows, err := s.queries.ExamPrepListUnitsByCatalogCourse(ctx, dbgen.ExamPrepListUnitsByCatalogCourseParams{ CatalogCourseID: catalogCourseID, - Limit: limit, - Offset: offset, + Limit: limit, + Offset: offset, }) if err != nil { return nil, 0, err @@ -70,7 +70,7 @@ func (s *Store) ListExamPrepUnitsByCatalogCourse(ctx context.Context, catalogCou if i == 0 { total = r.TotalCount } - out = append(out, examPrepUnitToDomain(dbgen.ExamPrepUnit{ + item := examPrepUnitToDomain(dbgen.ExamPrepUnit{ ID: r.ID, CatalogCourseID: r.CatalogCourseID, Name: r.Name, @@ -79,7 +79,11 @@ func (s *Store) ListExamPrepUnitsByCatalogCourse(ctx context.Context, catalogCou SortOrder: r.SortOrder, CreatedAt: r.CreatedAt, UpdatedAt: r.UpdatedAt, - })) + }) + item.ModulesCount = &r.ModulesCount + item.LessonsCount = &r.LessonsCount + item.PracticesCount = &r.PracticesCount + out = append(out, item) } return out, total, nil }