diff --git a/db/query/hierarchy.sql b/db/query/hierarchy.sql index 8e39b05..751507e 100644 --- a/db/query/hierarchy.sql +++ b/db/query/hierarchy.sql @@ -60,6 +60,25 @@ WHERE smp.sub_module_id = $1 AND qs.set_type = 'QUIZ' ORDER BY smp.display_order ASC, smp.id ASC; +-- name: GetSubModuleLessonByID :one +SELECT + smp.id, + smp.sub_module_id, + smp.question_set_id, + smp.intro_video_url, + smp.display_order, + smp.is_active, + qs.title, + qs.description, + qs.status, + qs.set_type, + (SELECT COUNT(*) FROM question_set_items qsi WHERE qsi.set_id = qs.id) AS question_count +FROM sub_module_lessons smp +JOIN question_sets qs ON qs.id = smp.question_set_id +WHERE smp.id = $1 + AND smp.is_active = TRUE + AND qs.set_type = 'QUIZ'; + -- name: GetSubModulePractices :many SELECT smp.id, diff --git a/gen/db/hierarchy.sql.go b/gen/db/hierarchy.sql.go index 600458f..6e124c9 100644 --- a/gen/db/hierarchy.sql.go +++ b/gen/db/hierarchy.sql.go @@ -544,6 +544,59 @@ func (q *Queries) GetModulesByLevelID(ctx context.Context, levelID int64) ([]Mod return items, nil } +const GetSubModuleLessonByID = `-- name: GetSubModuleLessonByID :one +SELECT + smp.id, + smp.sub_module_id, + smp.question_set_id, + smp.intro_video_url, + smp.display_order, + smp.is_active, + qs.title, + qs.description, + qs.status, + qs.set_type, + (SELECT COUNT(*) FROM question_set_items qsi WHERE qsi.set_id = qs.id) AS question_count +FROM sub_module_lessons smp +JOIN question_sets qs ON qs.id = smp.question_set_id +WHERE smp.id = $1 + AND smp.is_active = TRUE + AND qs.set_type = 'QUIZ' +` + +type GetSubModuleLessonByIDRow struct { + ID int64 `json:"id"` + SubModuleID int64 `json:"sub_module_id"` + QuestionSetID int64 `json:"question_set_id"` + IntroVideoUrl pgtype.Text `json:"intro_video_url"` + DisplayOrder int32 `json:"display_order"` + IsActive bool `json:"is_active"` + Title string `json:"title"` + Description pgtype.Text `json:"description"` + Status string `json:"status"` + SetType string `json:"set_type"` + QuestionCount int64 `json:"question_count"` +} + +func (q *Queries) GetSubModuleLessonByID(ctx context.Context, id int64) (GetSubModuleLessonByIDRow, error) { + row := q.db.QueryRow(ctx, GetSubModuleLessonByID, id) + var i GetSubModuleLessonByIDRow + err := row.Scan( + &i.ID, + &i.SubModuleID, + &i.QuestionSetID, + &i.IntroVideoUrl, + &i.DisplayOrder, + &i.IsActive, + &i.Title, + &i.Description, + &i.Status, + &i.SetType, + &i.QuestionCount, + ) + return i, err +} + const GetSubModuleLessons = `-- name: GetSubModuleLessons :many SELECT smp.id, diff --git a/internal/web_server/handlers/hierarchy_handler.go b/internal/web_server/handlers/hierarchy_handler.go index c692832..9b0d727 100644 --- a/internal/web_server/handlers/hierarchy_handler.go +++ b/internal/web_server/handlers/hierarchy_handler.go @@ -945,6 +945,75 @@ func (h *Handler) AttachSubModuleLesson(c *fiber.Ctx) error { return c.Status(fiber.StatusCreated).JSON(domain.Response{Message: "Lesson attached to sub-module", Data: attached}) } +// GetSubModuleLessons godoc +// @Summary Get lessons under sub-module +// @Description Returns all active lessons attached to a sub-module with question-set details +// @Tags course-management +// @Accept json +// @Produce json +// @Param subModuleId path int true "Sub-module ID" +// @Success 200 {object} domain.Response +// @Failure 400 {object} domain.ErrorResponse +// @Failure 500 {object} domain.ErrorResponse +// @Router /api/v1/course-management/sub-modules/{subModuleId}/lessons [get] +func (h *Handler) GetSubModuleLessons(c *fiber.Ctx) error { + subModuleID, err := strconv.ParseInt(c.Params("subModuleId"), 10, 64) + if err != nil || subModuleID <= 0 { + return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ + Message: "Invalid sub-module ID", + Error: "subModuleId must be a valid positive integer", + }) + } + + lessons, err := h.analyticsDB.GetSubModuleLessons(c.Context(), subModuleID) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ + Message: "Failed to get sub-module lessons", + Error: err.Error(), + }) + } + + return c.Status(fiber.StatusOK).JSON(domain.Response{ + Message: "Sub-module lessons retrieved successfully", + Data: lessons, + }) +} + +// GetSubModuleLessonByID godoc +// @Summary Get lesson detail +// @Description Returns one active lesson detail by lesson ID +// @Tags course-management +// @Accept json +// @Produce json +// @Param lessonId path int true "Lesson ID" +// @Success 200 {object} domain.Response +// @Failure 400 {object} domain.ErrorResponse +// @Failure 404 {object} domain.ErrorResponse +// @Failure 500 {object} domain.ErrorResponse +// @Router /api/v1/course-management/sub-module-lessons/{lessonId} [get] +func (h *Handler) GetSubModuleLessonByID(c *fiber.Ctx) error { + lessonID, err := strconv.ParseInt(c.Params("lessonId"), 10, 64) + if err != nil || lessonID <= 0 { + return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ + Message: "Invalid lesson ID", + Error: "lessonId must be a valid positive integer", + }) + } + + lesson, err := h.analyticsDB.GetSubModuleLessonByID(c.Context(), lessonID) + if err != nil { + return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{ + Message: "Lesson not found", + Error: err.Error(), + }) + } + + return c.Status(fiber.StatusOK).JSON(domain.Response{ + Message: "Lesson detail retrieved successfully", + Data: lesson, + }) +} + // CreateSubModulePractice godoc // @Summary Create practice under sub-module // @Description Creates a sub-module practice with metadata and linked question set diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index c3a575c..1b7811c 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -103,6 +103,8 @@ func (a *App) initAppRoutes() { groupV1.Post("/course-management/sub-module-videos", a.authMiddleware, a.RequirePermission("videos.create"), h.CreateSubModuleVideo) groupV1.Put("/course-management/sub-module-videos/:videoId", a.authMiddleware, a.RequirePermission("videos.update"), h.UpdateSubModuleVideo) groupV1.Delete("/course-management/sub-module-videos/:videoId", a.authMiddleware, a.RequirePermission("videos.delete"), h.DeleteSubModuleVideo) + groupV1.Get("/course-management/sub-modules/:subModuleId/lessons", a.authMiddleware, a.RequirePermission("question_sets.list"), h.GetSubModuleLessons) + groupV1.Get("/course-management/sub-module-lessons/:lessonId", a.authMiddleware, a.RequirePermission("question_sets.get"), h.GetSubModuleLessonByID) groupV1.Post("/course-management/sub-module-lessons", a.authMiddleware, a.RequirePermission("question_sets.update"), h.AttachSubModuleLesson) groupV1.Post("/course-management/sub-module-practices", a.authMiddleware, a.RequirePermission("question_sets.update"), h.CreateSubModulePractice) groupV1.Put("/course-management/practices/:practiceId", a.authMiddleware, a.RequirePermission("question_sets.update"), h.UpdatePractice)