package handlers import ( "Yimaru-Backend/internal/domain" "Yimaru-Backend/internal/services/examprep" "errors" "strconv" "github.com/gofiber/fiber/v2" ) // CreateExamPrepModule godoc // @Summary Create exam-prep module // @Tags exam-prep // @Param unitId path int true "Unit ID" // @Param body body domain.CreateExamPrepModuleInput true "Module" // @Router /api/v1/exam-prep/units/{unitId}/modules [post] func (h *Handler) CreateExamPrepModule(c *fiber.Ctx) error { unitID, err := strconv.ParseInt(c.Params("unitId"), 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid unit id", Error: err.Error(), }) } var req domain.CreateExamPrepModuleInput if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } if valErrs, ok := h.validator.Validate(c, req); !ok { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Validation failed", Error: firstValidationError(valErrs), }) } out, err := h.examPrepSvc.CreateModule(c.Context(), unitID, req) if err != nil { if errors.Is(err, examprep.ErrUnitNotFound) { return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{ Message: "Unit not found", Error: err.Error(), }) } return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to create module", Error: err.Error(), }) } return c.Status(fiber.StatusCreated).JSON(domain.Response{ Message: "Module created successfully", Data: out, Success: true, StatusCode: fiber.StatusCreated, }) } // ListExamPrepModulesByUnit godoc // @Summary List exam-prep modules for a unit // @Tags exam-prep // @Param unitId path int true "Unit ID" // @Router /api/v1/exam-prep/units/{unitId}/modules [get] func (h *Handler) ListExamPrepModulesByUnit(c *fiber.Ctx) error { unitID, err := strconv.ParseInt(c.Params("unitId"), 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid unit id", Error: err.Error(), }) } limit, _ := strconv.Atoi(c.Query("limit", "20")) offset, _ := strconv.Atoi(c.Query("offset", "0")) items, total, err := h.examPrepSvc.ListModulesByUnit(c.Context(), unitID, int32(limit), int32(offset)) if err != nil { if errors.Is(err, examprep.ErrUnitNotFound) { return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{ Message: "Unit not found", Error: err.Error(), }) } return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to list modules", Error: err.Error(), }) } if err := h.applyExamPrepAccessModules(c.Context(), c, items); err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to build module list", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Modules retrieved successfully", Data: fiber.Map{ "modules": items, "total_count": total, "limit": limit, "offset": offset, }, Success: true, StatusCode: fiber.StatusOK, }) } // ReorderExamPrepModulesInUnit godoc // @Summary Reorder modules within a unit // @Tags exam-prep // @Param unitId path int true "Unit ID" // @Param body body domain.ReorderIDsRequest true "ordered_ids" // @Router /api/v1/exam-prep/units/{unitId}/modules/reorder [put] func (h *Handler) ReorderExamPrepModulesInUnit(c *fiber.Ctx) error { unitID, err := strconv.ParseInt(c.Params("unitId"), 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid unit id", Error: err.Error(), }) } var req domain.ReorderIDsRequest if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } if req.OrderedIDs == nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "ordered_ids is required (use an empty array if there are no modules)", Error: "missing ordered_ids", }) } if err := h.examPrepSvc.ReorderModulesInUnit(c.Context(), unitID, req.OrderedIDs); err != nil { if errors.Is(err, examprep.ErrUnitNotFound) { return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{ Message: "Unit not found", Error: err.Error(), }) } if errors.Is(err, domain.ErrReorderInvalidIDSet) { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: err.Error(), Error: "INVALID_REORDER", }) } return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to reorder modules", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Modules reordered successfully", Success: true, StatusCode: fiber.StatusOK, }) } // GetExamPrepModuleByID godoc // @Summary Get exam-prep module by ID // @Tags exam-prep // @Router /api/v1/exam-prep/modules/{id} [get] func (h *Handler) GetExamPrepModuleByID(c *fiber.Ctx) error { id, err := strconv.ParseInt(c.Params("id"), 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid module id", Error: err.Error(), }) } out, err := h.examPrepSvc.GetModuleByID(c.Context(), id) if err != nil { if errors.Is(err, examprep.ErrModuleNotFound) { return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{ Message: "Module not found", Error: err.Error(), }) } return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to get module", Error: err.Error(), }) } if err := h.applyExamPrepAccessModule(c.Context(), c, &out); err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to build module", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Module retrieved successfully", Data: out, Success: true, StatusCode: fiber.StatusOK, }) } // UpdateExamPrepModule godoc // @Summary Update exam-prep module // @Tags exam-prep // @Router /api/v1/exam-prep/modules/{id} [put] func (h *Handler) UpdateExamPrepModule(c *fiber.Ctx) error { id, err := strconv.ParseInt(c.Params("id"), 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid module id", Error: err.Error(), }) } var req domain.UpdateExamPrepModuleInput if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } out, err := h.examPrepSvc.UpdateModule(c.Context(), id, req) if err != nil { if errors.Is(err, examprep.ErrModuleNotFound) { return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{ Message: "Module not found", Error: err.Error(), }) } return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to update module", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Module updated successfully", Data: out, Success: true, StatusCode: fiber.StatusOK, }) } // DeleteExamPrepModule godoc // @Summary Delete exam-prep module // @Tags exam-prep // @Router /api/v1/exam-prep/modules/{id} [delete] func (h *Handler) DeleteExamPrepModule(c *fiber.Ctx) error { id, err := strconv.ParseInt(c.Params("id"), 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid module id", Error: err.Error(), }) } if err := h.examPrepSvc.DeleteModule(c.Context(), id); err != nil { if errors.Is(err, examprep.ErrModuleNotFound) { return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{ Message: "Module not found", Error: err.Error(), }) } return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to delete module", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Module deleted successfully", Success: true, StatusCode: fiber.StatusOK, }) }