fix: course-management practice detail without sub_module_practices row
- resolveCourseManagementPractice falls back to SUB_MODULE PRACTICE question_sets - Synthetic practice uses id 0 and question_set_id for orphan sets - Align GET practice and /detail with resolver; sync question_count after load Made-with: Cursor
This commit is contained in:
parent
5fbca53534
commit
9154dec067
|
|
@ -1,6 +1,7 @@
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
dbgen "Yimaru-Backend/gen/db"
|
dbgen "Yimaru-Backend/gen/db"
|
||||||
"Yimaru-Backend/internal/domain"
|
"Yimaru-Backend/internal/domain"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
@ -2244,9 +2245,62 @@ func (h *Handler) GetSubModulePractices(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func practiceRowFromSubModuleQuestionSet(qs domain.QuestionSet) dbgen.GetSubModulePracticeByIDRow {
|
||||||
|
row := dbgen.GetSubModulePracticeByIDRow{
|
||||||
|
ID: 0,
|
||||||
|
Title: qs.Title,
|
||||||
|
QuestionSetID: qs.ID,
|
||||||
|
DisplayOrder: 0,
|
||||||
|
Status: qs.Status,
|
||||||
|
SetType: qs.SetType,
|
||||||
|
IsActive: !strings.EqualFold(qs.Status, "ARCHIVED"),
|
||||||
|
}
|
||||||
|
if qs.OwnerID != nil {
|
||||||
|
row.SubModuleID = *qs.OwnerID
|
||||||
|
}
|
||||||
|
if qs.Description != nil {
|
||||||
|
row.Description = pgtype.Text{String: *qs.Description, Valid: true}
|
||||||
|
}
|
||||||
|
if qs.IntroVideoURL != nil {
|
||||||
|
row.IntroVideoUrl = pgtype.Text{String: *qs.IntroVideoURL, Valid: true}
|
||||||
|
}
|
||||||
|
return row
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveCourseManagementPractice loads an active sub_module_practices row, or a SUB_MODULE-owned PRACTICE
|
||||||
|
// question_set with no bridge row (id is always question_sets.id in that case).
|
||||||
|
func (h *Handler) resolveCourseManagementPractice(ctx context.Context, id int64) (dbgen.GetSubModulePracticeByIDRow, error) {
|
||||||
|
row, err := h.analyticsDB.GetSubModulePracticeByID(ctx, id)
|
||||||
|
if err == nil {
|
||||||
|
return row, nil
|
||||||
|
}
|
||||||
|
if !errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
return dbgen.GetSubModulePracticeByIDRow{}, err
|
||||||
|
}
|
||||||
|
qs, err := h.questionsSvc.GetQuestionSetByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
return dbgen.GetSubModulePracticeByIDRow{}, pgx.ErrNoRows
|
||||||
|
}
|
||||||
|
return dbgen.GetSubModulePracticeByIDRow{}, err
|
||||||
|
}
|
||||||
|
if !strings.EqualFold(qs.SetType, string(domain.QuestionSetTypePractice)) {
|
||||||
|
return dbgen.GetSubModulePracticeByIDRow{}, pgx.ErrNoRows
|
||||||
|
}
|
||||||
|
if qs.OwnerType == nil || !strings.EqualFold(*qs.OwnerType, "SUB_MODULE") || qs.OwnerID == nil {
|
||||||
|
return dbgen.GetSubModulePracticeByIDRow{}, pgx.ErrNoRows
|
||||||
|
}
|
||||||
|
out := practiceRowFromSubModuleQuestionSet(qs)
|
||||||
|
_, total, err := h.questionsSvc.GetQuestionSetItemsPaginated(ctx, qs.ID, nil, 1, 0)
|
||||||
|
if err == nil {
|
||||||
|
out.QuestionCount = total
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetSubModulePracticeByID godoc
|
// GetSubModulePracticeByID godoc
|
||||||
// @Summary Get practice detail
|
// @Summary Get practice detail
|
||||||
// @Description Returns one active practice. practiceId may be sub_module_practices.id or the linked question_sets.id.
|
// @Description Returns one practice. practiceId may be sub_module_practices.id, question_sets.id, or (if no bridge row) a SUB_MODULE PRACTICE question set id.
|
||||||
// @Tags course-management
|
// @Tags course-management
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
|
|
@ -2265,10 +2319,16 @@ func (h *Handler) GetSubModulePracticeByID(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
practice, err := h.analyticsDB.GetSubModulePracticeByID(c.Context(), practiceID)
|
practice, err := h.resolveCourseManagementPractice(c.Context(), practiceID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
Message: "Practice not found",
|
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Practice not found",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to load practice",
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -2283,7 +2343,7 @@ func (h *Handler) GetSubModulePracticeByID(c *fiber.Ctx) error {
|
||||||
|
|
||||||
// GetSubModulePracticeDetail godoc
|
// GetSubModulePracticeDetail godoc
|
||||||
// @Summary Get practice with full question list
|
// @Summary Get practice with full question list
|
||||||
// @Description Returns one active practice with question-set fields and the ordered question list (full item detail). practiceId may be sub_module_practices.id or the linked question_sets.id.
|
// @Description Returns practice metadata and ordered questions. practiceId may be sub_module_practices.id, linked question_sets.id, or a SUB_MODULE PRACTICE set id when no sub_module_practices row exists.
|
||||||
// @Tags course-management
|
// @Tags course-management
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param practiceId path int true "Practice row id or question set id"
|
// @Param practiceId path int true "Practice row id or question set id"
|
||||||
|
|
@ -2300,16 +2360,23 @@ func (h *Handler) GetSubModulePracticeDetail(c *fiber.Ctx) error {
|
||||||
Error: "practiceId must be a positive integer",
|
Error: "practiceId must be a positive integer",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
practice, err := h.analyticsDB.GetSubModulePracticeByID(c.Context(), practiceID)
|
practice, err := h.resolveCourseManagementPractice(c.Context(), practiceID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
Message: "Practice not found",
|
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Practice not found",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to load practice",
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const pageSize int32 = 500
|
const pageSize int32 = 500
|
||||||
var allItems []domain.QuestionSetItemWithQuestion
|
var allItems []domain.QuestionSetItemWithQuestion
|
||||||
var offset int32
|
var offset int32
|
||||||
|
var totalCount int64
|
||||||
for {
|
for {
|
||||||
batch, total, err := h.questionsSvc.GetQuestionSetItemsPaginated(c.Context(), practice.QuestionSetID, nil, pageSize, offset)
|
batch, total, err := h.questionsSvc.GetQuestionSetItemsPaginated(c.Context(), practice.QuestionSetID, nil, pageSize, offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -2318,12 +2385,14 @@ func (h *Handler) GetSubModulePracticeDetail(c *fiber.Ctx) error {
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
totalCount = total
|
||||||
allItems = append(allItems, batch...)
|
allItems = append(allItems, batch...)
|
||||||
if int64(len(allItems)) >= total || len(batch) == 0 {
|
if int64(len(allItems)) >= total || len(batch) == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
offset += pageSize
|
offset += pageSize
|
||||||
}
|
}
|
||||||
|
practice.QuestionCount = totalCount
|
||||||
return c.JSON(domain.Response{
|
return c.JSON(domain.Response{
|
||||||
Message: "Practice retrieved successfully",
|
Message: "Practice retrieved successfully",
|
||||||
Success: true,
|
Success: true,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user