package handlers import ( "errors" "strconv" "Yimaru-Backend/internal/domain" "github.com/gofiber/fiber/v2" ) // GetMyLMSProgress godoc // @Summary Get my LMS completion history // @Description Returns completed lesson, module, course, and program IDs for the authenticated user (ordered by completion time, then id). // @Tags lms // @Produce json // @Success 200 {object} domain.Response // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/lms/progress [get] func (h *Handler) GetMyLMSProgress(c *fiber.Ctx) error { uid := c.Locals("user_id").(int64) prog, err := h.lmsProgressSvc.GetMyProgress(c.Context(), uid) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to load learning progress", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "LMS progress retrieved successfully", Data: prog, Success: true, StatusCode: fiber.StatusOK, }) } // AdminGetUserLMSLearningActivity godoc // @Summary Get a user's nested LMS learning activity (admin) // @Description Returns programs, courses, modules, and lessons with completion details and completed practices. Only persisted completion signals are included (completed lessons, completed published practices, and rollup completion timestamps—not partial or in-progress attempts). // @Tags lms // @Produce json // @Security Bearer // @Param user_id path int true "Target user ID" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/admin/users/{user_id}/lms-learning-activity [get] func (h *Handler) AdminGetUserLMSLearningActivity(c *fiber.Ctx) error { targetIDStr := c.Params("user_id") targetID, err := strconv.ParseInt(targetIDStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid user ID", Error: err.Error(), }) } if targetID <= 0 { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid user ID", Error: "user ID must be a positive integer", }) } tree, err := h.lmsProgressSvc.AdminUserLearningActivityTree(c.Context(), targetID) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to load LMS learning activity", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "LMS learning activity retrieved successfully", Data: tree, Success: true, StatusCode: fiber.StatusOK, }) } // AdminGetUserRecentActivity godoc // @Summary Recent activity timeline for a user (admin) // @Description Reverse-chronological feed for profile UI: account joined plus LMS completion milestones (lessons/modules/courses/programs). Optional practice completions via include_practices. Does not include "started learning path" unless you add persisted engagement events—the schema stores completions only. // @Tags lms // @Produce json // @Security Bearer // @Param user_id path int true "Target user ID" // @Param limit query int false "Max items after merge (default 40, max 120)" // @Param include_practices query bool false "Include completed LMS practices (more verbose)" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 404 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/admin/users/{user_id}/recent-activity [get] func (h *Handler) AdminGetUserRecentActivity(c *fiber.Ctx) error { targetIDStr := c.Params("user_id") targetID, err := strconv.ParseInt(targetIDStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid user ID", Error: err.Error(), }) } if targetID <= 0 { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid user ID", Error: "user ID must be a positive integer", }) } limit := 40 if ls := c.Query("limit"); ls != "" { n, err := strconv.Atoi(ls) if err != nil || n < 1 { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid limit", Error: "limit must be a positive integer", }) } limit = n } includePractices := c.Query("include_practices") == "true" || c.Query("include_practices") == "1" feed, err := h.lmsProgressSvc.AdminUserRecentActivity(c.Context(), targetID, limit, includePractices) if err != nil { if errors.Is(err, domain.ErrUserNotFound) { return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{ Message: "User not found", Error: err.Error(), }) } return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to load recent activity", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Recent activity retrieved successfully", Data: feed, Success: true, StatusCode: fiber.StatusOK, }) }