Add legacy hierarchy fallback for pre-migration databases.

Handle missing course_sub_categories table by serving hierarchy data from legacy categories/courses queries so content pages keep loading until unified hierarchy migration is applied.

Made-with: Cursor
This commit is contained in:
Yared Yemane 2026-04-14 01:02:31 -07:00
parent 69d3d440d0
commit d9783310d1

View File

@ -74,6 +74,15 @@ type createSubModulePracticeReq struct {
IsActive *bool `json:"is_active"`
}
type legacyHierarchyRow struct {
CategoryID int64 `json:"category_id"`
CategoryName string `json:"category_name"`
SubCategoryID *int64 `json:"sub_category_id"`
SubCategoryName *string `json:"sub_category_name"`
CourseID *int64 `json:"course_id"`
CourseTitle *string `json:"course_title"`
}
func toText(v *string) pgtype.Text {
if v == nil {
return pgtype.Text{Valid: false}
@ -113,6 +122,13 @@ func intOrNil(v *int32) interface{} {
func (h *Handler) UnifiedHierarchy(c *fiber.Ctx) error {
rows, err := h.analyticsDB.GetCoursesWithHierarchy(c.Context())
if err != nil {
if isMissingCourseSubCategoryTableErr(err) {
legacyRows, legacyErr := h.buildLegacyHierarchyRows(c)
if legacyErr != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to load hierarchy", Error: legacyErr.Error()})
}
return c.JSON(domain.Response{Message: "Unified hierarchy retrieved successfully", Data: legacyRows})
}
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to load hierarchy", Error: err.Error()})
}
return c.JSON(domain.Response{Message: "Unified hierarchy retrieved successfully", Data: rows})
@ -135,11 +151,88 @@ func (h *Handler) UnifiedHierarchyByCourse(c *fiber.Ctx) error {
}
rows, err := h.analyticsDB.GetFullHierarchyByCourseID(c.Context(), courseID)
if err != nil {
if isMissingCourseSubCategoryTableErr(err) {
course, getCourseErr := h.analyticsDB.GetCourseByID(c.Context(), courseID)
if getCourseErr != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to load course hierarchy", Error: getCourseErr.Error()})
}
return c.JSON(domain.Response{
Message: "Course hierarchy retrieved successfully",
Data: []map[string]interface{}{
{
"course_id": course.ID,
"course_title": course.Title,
"level_id": nil,
"cefr_level": nil,
"module_id": nil,
"module_title": nil,
"sub_module_id": nil,
"sub_module_title": nil,
},
},
})
}
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to load course hierarchy", Error: err.Error()})
}
return c.JSON(domain.Response{Message: "Course hierarchy retrieved successfully", Data: rows})
}
func isMissingCourseSubCategoryTableErr(err error) bool {
if err == nil {
return false
}
return strings.Contains(strings.ToLower(err.Error()), "relation \"course_sub_categories\" does not exist")
}
func (h *Handler) buildLegacyHierarchyRows(c *fiber.Ctx) ([]legacyHierarchyRow, error) {
categories, err := h.analyticsDB.GetAllCourseCategories(c.Context(), dbgen.GetAllCourseCategoriesParams{
Offset: pgtype.Int4{Int32: 0, Valid: true},
Limit: pgtype.Int4{Int32: 10000, Valid: true},
})
if err != nil {
return nil, err
}
out := make([]legacyHierarchyRow, 0, len(categories))
for _, cat := range categories {
courses, courseErr := h.analyticsDB.GetCoursesByCategory(c.Context(), dbgen.GetCoursesByCategoryParams{
CategoryID: cat.ID,
Offset: pgtype.Int4{Int32: 0, Valid: true},
Limit: pgtype.Int4{Int32: 10000, Valid: true},
})
if courseErr != nil {
return nil, courseErr
}
if len(courses) == 0 {
out = append(out, legacyHierarchyRow{
CategoryID: cat.ID,
CategoryName: cat.Name,
SubCategoryID: nil,
SubCategoryName: nil,
CourseID: nil,
CourseTitle: nil,
})
continue
}
for _, course := range courses {
courseID := course.ID
courseTitle := course.Title
out = append(out, legacyHierarchyRow{
CategoryID: cat.ID,
CategoryName: cat.Name,
SubCategoryID: nil,
SubCategoryName: nil,
CourseID: &courseID,
CourseTitle: &courseTitle,
})
}
}
return out, nil
}
// CreateCourseSubCategory godoc
// @Summary Create course sub-category
// @Description Creates a sub-category under a course category