Include nested lesson and practice counts in exam-prep modules list response.

Return per-module lesson and practice aggregates under unit modules listing so clients can render module depth without additional queries.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Yared Yemane 2026-05-05 07:05:35 -07:00
parent 16c3f6b613
commit b62d89574e
4 changed files with 56 additions and 21 deletions

View File

@ -30,6 +30,16 @@ ORDER BY
m.id;
-- name: ExamPrepListUnitModulesByUnit :many
WITH module_counts AS (
SELECT
m.id AS module_id,
COUNT(DISTINCT l.id)::BIGINT AS lessons_count,
COUNT(DISTINCT p.id)::BIGINT AS practices_count
FROM exam_prep.unit_modules m
LEFT JOIN exam_prep.unit_module_lessons l ON l.unit_module_id = m.id
LEFT JOIN exam_prep.lesson_practices p ON p.unit_module_lesson_id = l.id
GROUP BY m.id
)
SELECT
COUNT(*) OVER () AS total_count,
m.id,
@ -39,9 +49,12 @@ SELECT
m.thumbnail,
m.icon,
m.sort_order,
COALESCE(mc.lessons_count, 0)::BIGINT AS lessons_count,
COALESCE(mc.practices_count, 0)::BIGINT AS practices_count,
m.created_at,
m.updated_at
FROM exam_prep.unit_modules m
LEFT JOIN module_counts mc ON mc.module_id = m.id
WHERE
m.unit_id = $1
ORDER BY

View File

@ -124,6 +124,16 @@ func (q *Queries) ExamPrepListUnitModuleIDsByUnit(ctx context.Context, unitID in
}
const ExamPrepListUnitModulesByUnit = `-- name: ExamPrepListUnitModulesByUnit :many
WITH module_counts AS (
SELECT
m.id AS module_id,
COUNT(DISTINCT l.id)::BIGINT AS lessons_count,
COUNT(DISTINCT p.id)::BIGINT AS practices_count
FROM exam_prep.unit_modules m
LEFT JOIN exam_prep.unit_module_lessons l ON l.unit_module_id = m.id
LEFT JOIN exam_prep.lesson_practices p ON p.unit_module_lesson_id = l.id
GROUP BY m.id
)
SELECT
COUNT(*) OVER () AS total_count,
m.id,
@ -133,9 +143,12 @@ SELECT
m.thumbnail,
m.icon,
m.sort_order,
COALESCE(mc.lessons_count, 0)::BIGINT AS lessons_count,
COALESCE(mc.practices_count, 0)::BIGINT AS practices_count,
m.created_at,
m.updated_at
FROM exam_prep.unit_modules m
LEFT JOIN module_counts mc ON mc.module_id = m.id
WHERE
m.unit_id = $1
ORDER BY
@ -152,16 +165,18 @@ type ExamPrepListUnitModulesByUnitParams struct {
}
type ExamPrepListUnitModulesByUnitRow struct {
TotalCount int64 `json:"total_count"`
ID int64 `json:"id"`
UnitID int64 `json:"unit_id"`
Name string `json:"name"`
Description pgtype.Text `json:"description"`
Thumbnail pgtype.Text `json:"thumbnail"`
Icon pgtype.Text `json:"icon"`
SortOrder int32 `json:"sort_order"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
TotalCount int64 `json:"total_count"`
ID int64 `json:"id"`
UnitID int64 `json:"unit_id"`
Name string `json:"name"`
Description pgtype.Text `json:"description"`
Thumbnail pgtype.Text `json:"thumbnail"`
Icon pgtype.Text `json:"icon"`
SortOrder int32 `json:"sort_order"`
LessonsCount int64 `json:"lessons_count"`
PracticesCount int64 `json:"practices_count"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
}
func (q *Queries) ExamPrepListUnitModulesByUnit(ctx context.Context, arg ExamPrepListUnitModulesByUnitParams) ([]ExamPrepListUnitModulesByUnitRow, error) {
@ -182,6 +197,8 @@ func (q *Queries) ExamPrepListUnitModulesByUnit(ctx context.Context, arg ExamPre
&i.Thumbnail,
&i.Icon,
&i.SortOrder,
&i.LessonsCount,
&i.PracticesCount,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {

View File

@ -4,15 +4,17 @@ import "time"
// ExamPrepModule is a module under an exam-prep unit (stored in exam_prep.unit_modules).
type ExamPrepModule struct {
ID int64 `json:"id"`
UnitID int64 `json:"unit_id"`
Name string `json:"name"`
Description *string `json:"description,omitempty"`
Thumbnail *string `json:"thumbnail,omitempty"`
Icon *string `json:"icon,omitempty"`
SortOrder int `json:"sort_order"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
ID int64 `json:"id"`
UnitID int64 `json:"unit_id"`
Name string `json:"name"`
Description *string `json:"description,omitempty"`
Thumbnail *string `json:"thumbnail,omitempty"`
Icon *string `json:"icon,omitempty"`
SortOrder int `json:"sort_order"`
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"`
}
type CreateExamPrepModuleInput struct {

View File

@ -72,7 +72,7 @@ func (s *Store) ListExamPrepUnitModulesByUnit(ctx context.Context, unitID int64,
if i == 0 {
total = r.TotalCount
}
out = append(out, examPrepModuleToDomain(dbgen.ExamPrepUnitModule{
item := examPrepModuleToDomain(dbgen.ExamPrepUnitModule{
ID: r.ID,
UnitID: r.UnitID,
Name: r.Name,
@ -82,7 +82,10 @@ func (s *Store) ListExamPrepUnitModulesByUnit(ctx context.Context, unitID int64,
SortOrder: r.SortOrder,
CreatedAt: r.CreatedAt,
UpdatedAt: r.UpdatedAt,
}))
})
item.LessonsCount = &r.LessonsCount
item.PracticesCount = &r.PracticesCount
out = append(out, item)
}
return out, total, nil
}