add legacy learning-path GET endpoint for flows compatibility
Expose GET /course-management/courses/:courseId/learning-path and build response from unified hierarchy tables so first-time Flows tab loads no longer fail with Cannot GET. Made-with: Cursor
This commit is contained in:
parent
06d86c9098
commit
a9c6966820
|
|
@ -157,6 +157,14 @@ func intOrNil(v *int32) interface{} {
|
||||||
return *v
|
return *v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func textPtr(v pgtype.Text) *string {
|
||||||
|
if !v.Valid {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
s := v.String
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
// ListCourseCategories godoc
|
// ListCourseCategories godoc
|
||||||
// @Summary List course categories
|
// @Summary List course categories
|
||||||
// @Description Legacy-compatible endpoint for listing course categories
|
// @Description Legacy-compatible endpoint for listing course categories
|
||||||
|
|
@ -544,6 +552,125 @@ func (h *Handler) UnifiedHierarchyByCourse(c *fiber.Ctx) error {
|
||||||
return c.JSON(domain.Response{Message: "Course hierarchy retrieved successfully", Data: rows})
|
return c.JSON(domain.Response{Message: "Course hierarchy retrieved successfully", Data: rows})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CourseLearningPath godoc
|
||||||
|
// @Summary Get course learning path
|
||||||
|
// @Description Legacy-compatible endpoint for course learning path
|
||||||
|
// @Tags course-management
|
||||||
|
// @Produce json
|
||||||
|
// @Param courseId path int true "Course ID"
|
||||||
|
// @Success 200 {object} domain.Response
|
||||||
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/course-management/courses/{courseId}/learning-path [get]
|
||||||
|
func (h *Handler) CourseLearningPath(c *fiber.Ctx) error {
|
||||||
|
courseID, err := strconv.ParseInt(c.Params("courseId"), 10, 64)
|
||||||
|
if err != nil || courseID <= 0 {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "Invalid course ID", Error: "courseId must be a positive integer"})
|
||||||
|
}
|
||||||
|
|
||||||
|
course, err := h.analyticsDB.GetCourseByID(c.Context(), courseID)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to load course", Error: err.Error()})
|
||||||
|
}
|
||||||
|
category, err := h.analyticsDB.GetCourseCategoryByID(c.Context(), course.CategoryID)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to load course category", Error: err.Error()})
|
||||||
|
}
|
||||||
|
rows, err := h.analyticsDB.GetFullHierarchyByCourseID(c.Context(), courseID)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to load course learning path", Error: err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
subCourseByID := map[int64]*domain.LearningPathSubCourse{}
|
||||||
|
subCourseOrder := make([]int64, 0)
|
||||||
|
for _, row := range rows {
|
||||||
|
if !row.SubModuleID.Valid {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
subModuleID := row.SubModuleID.Int64
|
||||||
|
if _, exists := subCourseByID[subModuleID]; exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
title := ""
|
||||||
|
if row.SubModuleTitle.Valid {
|
||||||
|
title = row.SubModuleTitle.String
|
||||||
|
}
|
||||||
|
level := ""
|
||||||
|
if row.CefrLevel.Valid {
|
||||||
|
level = row.CefrLevel.String
|
||||||
|
}
|
||||||
|
subCourseByID[subModuleID] = &domain.LearningPathSubCourse{
|
||||||
|
ID: subModuleID,
|
||||||
|
Title: title,
|
||||||
|
DisplayOrder: int32(len(subCourseOrder)),
|
||||||
|
Level: level,
|
||||||
|
SubLevel: level,
|
||||||
|
PrerequisiteCount: 0,
|
||||||
|
Prerequisites: []domain.LearningPathPrerequisite{},
|
||||||
|
Videos: []domain.LearningPathVideo{},
|
||||||
|
Practices: []domain.LearningPathPractice{},
|
||||||
|
}
|
||||||
|
subCourseOrder = append(subCourseOrder, subModuleID)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, subModuleID := range subCourseOrder {
|
||||||
|
node := subCourseByID[subModuleID]
|
||||||
|
videos, videoErr := h.analyticsDB.GetSubModuleVideos(c.Context(), subModuleID)
|
||||||
|
if videoErr != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to load sub-module videos", Error: videoErr.Error()})
|
||||||
|
}
|
||||||
|
for _, v := range videos {
|
||||||
|
node.Videos = append(node.Videos, domain.LearningPathVideo{
|
||||||
|
ID: v.ID,
|
||||||
|
Title: v.Title,
|
||||||
|
Description: textPtr(v.Description),
|
||||||
|
VideoURL: v.VideoUrl,
|
||||||
|
Duration: int32(v.Duration.Int32),
|
||||||
|
Resolution: textPtr(v.Resolution),
|
||||||
|
DisplayOrder: v.DisplayOrder,
|
||||||
|
VimeoID: textPtr(v.VimeoID),
|
||||||
|
VimeoEmbedURL: textPtr(v.VimeoEmbedUrl),
|
||||||
|
VideoHostProvider: textPtr(v.VideoHostProvider),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
node.VideoCount = int64(len(node.Videos))
|
||||||
|
|
||||||
|
practices, practiceErr := h.analyticsDB.GetSubModulePractices(c.Context(), subModuleID)
|
||||||
|
if practiceErr != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to load sub-module practices", Error: practiceErr.Error()})
|
||||||
|
}
|
||||||
|
for _, p := range practices {
|
||||||
|
node.Practices = append(node.Practices, domain.LearningPathPractice{
|
||||||
|
ID: p.ID,
|
||||||
|
Title: p.Title,
|
||||||
|
Description: textPtr(p.Description),
|
||||||
|
Status: p.Status,
|
||||||
|
IntroVideoURL: textPtr(p.IntroVideoUrl),
|
||||||
|
QuestionCount: p.QuestionCount,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
node.PracticeCount = int64(len(node.Practices))
|
||||||
|
}
|
||||||
|
|
||||||
|
subCourses := make([]domain.LearningPathSubCourse, 0, len(subCourseOrder))
|
||||||
|
for _, id := range subCourseOrder {
|
||||||
|
subCourses = append(subCourses, *subCourseByID[id])
|
||||||
|
}
|
||||||
|
|
||||||
|
path := domain.LearningPath{
|
||||||
|
CourseID: course.ID,
|
||||||
|
CourseTitle: course.Title,
|
||||||
|
Description: textPtr(course.Description),
|
||||||
|
Thumbnail: textPtr(course.Thumbnail),
|
||||||
|
IntroVideoURL: textPtr(course.IntroVideoUrl),
|
||||||
|
CategoryID: category.ID,
|
||||||
|
CategoryName: category.Name,
|
||||||
|
SubCourses: subCourses,
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(domain.Response{Message: "Course learning path retrieved successfully", Data: path})
|
||||||
|
}
|
||||||
|
|
||||||
func isMissingCourseSubCategoryTableErr(err error) bool {
|
func isMissingCourseSubCategoryTableErr(err error) bool {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return false
|
return false
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,7 @@ func (a *App) initAppRoutes() {
|
||||||
groupV1.Put("/course-management/courses/:courseId", a.authMiddleware, a.RequirePermission("courses.update"), h.UpdateCourse)
|
groupV1.Put("/course-management/courses/:courseId", a.authMiddleware, a.RequirePermission("courses.update"), h.UpdateCourse)
|
||||||
groupV1.Delete("/course-management/courses/:courseId", a.authMiddleware, a.RequirePermission("courses.delete"), h.DeleteCourse)
|
groupV1.Delete("/course-management/courses/:courseId", a.authMiddleware, a.RequirePermission("courses.delete"), h.DeleteCourse)
|
||||||
groupV1.Post("/course-management/courses/:courseId/thumbnail", a.authMiddleware, a.RequirePermission("courses.upload_thumbnail"), h.UpdateCourseThumbnail)
|
groupV1.Post("/course-management/courses/:courseId/thumbnail", a.authMiddleware, a.RequirePermission("courses.upload_thumbnail"), h.UpdateCourseThumbnail)
|
||||||
|
groupV1.Get("/course-management/courses/:courseId/learning-path", a.authMiddleware, a.RequirePermission("learning_tree.get"), h.CourseLearningPath)
|
||||||
groupV1.Get("/course-management/hierarchy", a.authMiddleware, a.RequirePermission("learning_tree.get"), h.UnifiedHierarchy)
|
groupV1.Get("/course-management/hierarchy", a.authMiddleware, a.RequirePermission("learning_tree.get"), h.UnifiedHierarchy)
|
||||||
groupV1.Get("/course-management/human-language/hierarchy", a.authMiddleware, a.RequirePermission("learning_tree.get"), h.UnifiedHierarchy)
|
groupV1.Get("/course-management/human-language/hierarchy", a.authMiddleware, a.RequirePermission("learning_tree.get"), h.UnifiedHierarchy)
|
||||||
groupV1.Get("/course-management/courses/:courseId/hierarchy", a.authMiddleware, a.RequirePermission("learning_tree.get"), h.UnifiedHierarchyByCourse)
|
groupV1.Get("/course-management/courses/:courseId/hierarchy", a.authMiddleware, a.RequirePermission("learning_tree.get"), h.UnifiedHierarchyByCourse)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user