package handlers import ( "Yimaru-Backend/internal/domain" "Yimaru-Backend/internal/services/examprep" "errors" "strconv" "github.com/gofiber/fiber/v2" ) // CreateExamPrepPractice godoc // @Summary Create exam-prep practice (under a lesson; uses shared question_sets) // @Tags exam-prep // @Param lessonId path int true "Exam prep lesson ID (unit_module_lessons.id)" // @Param body body domain.CreateExamPrepPracticeInput true "Practice" // @Router /api/v1/exam-prep/lessons/{lessonId}/practices [post] func (h *Handler) CreateExamPrepPractice(c *fiber.Ctx) error { lessonID, err := strconv.ParseInt(c.Params("lessonId"), 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid lesson id", Error: err.Error(), }) } var req domain.CreateExamPrepPracticeInput 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), }) } p, err := h.examPrepSvc.CreateExamPrepPractice(c.Context(), lessonID, req) if err != nil { if errors.Is(err, examprep.ErrLessonNotFound) { return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{ Message: "Lesson not found", Error: err.Error(), }) } if errors.Is(err, domain.ErrPersonaNotFound) { return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{ Message: "Persona not found", Error: err.Error(), }) } return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to create practice", Error: err.Error(), }) } return c.Status(fiber.StatusCreated).JSON(domain.Response{ Message: "Practice created successfully", Data: p, Success: true, StatusCode: fiber.StatusCreated, }) } // ListExamPrepPracticesByLesson godoc // @Summary List exam-prep practices for a lesson // @Tags exam-prep // @Param lessonId path int true "Exam prep lesson ID" // @Param limit query int false "Page size" default(20) // @Param offset query int false "Offset" default(0) // @Router /api/v1/exam-prep/lessons/{lessonId}/practices [get] func (h *Handler) ListExamPrepPracticesByLesson(c *fiber.Ctx) error { lessonID, err := strconv.ParseInt(c.Params("lessonId"), 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid lesson id", Error: err.Error(), }) } limit, _ := strconv.Atoi(c.Query("limit", "20")) offset, _ := strconv.Atoi(c.Query("offset", "0")) publishedOnly := !h.canManageExamPrepPractices(c) items, total, err := h.examPrepSvc.ListExamPrepPracticesByLesson(c.Context(), lessonID, publishedOnly, int32(limit), int32(offset)) if err != nil { if errors.Is(err, examprep.ErrLessonNotFound) { return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{ Message: "Lesson not found", Error: err.Error(), }) } return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to list practices", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Practices retrieved successfully", Data: fiber.Map{ "practices": items, "total_count": total, "limit": limit, "offset": offset, }, Success: true, StatusCode: fiber.StatusOK, }) } // GetExamPrepPracticeByID godoc // @Summary Get exam-prep practice by ID // @Tags exam-prep // @Param id path int true "Exam prep practice ID" // @Router /api/v1/exam-prep/practices/{id} [get] func (h *Handler) GetExamPrepPracticeByID(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 practice id", Error: err.Error(), }) } p, err := h.examPrepSvc.GetExamPrepPracticeByID(c.Context(), id) if err != nil { if errors.Is(err, examprep.ErrPracticeNotFound) { return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{ Message: "Practice not found", Error: err.Error(), }) } return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to load practice", Error: err.Error(), }) } if !p.VisibleToLearners() && !h.canManageExamPrepPractices(c) { return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{Message: "Practice not found"}) } return c.JSON(domain.Response{ Message: "Practice retrieved successfully", Data: p, Success: true, StatusCode: fiber.StatusOK, }) } // UpdateExamPrepPractice godoc // @Summary Update exam-prep practice // @Tags exam-prep // @Param id path int true "Exam prep practice ID" // @Param body body domain.UpdateExamPrepPracticeInput true "Fields to update" // @Router /api/v1/exam-prep/practices/{id} [put] func (h *Handler) UpdateExamPrepPractice(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 practice id", Error: err.Error(), }) } var req domain.UpdateExamPrepPracticeInput if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } p, err := h.examPrepSvc.UpdateExamPrepPractice(c.Context(), id, req) if err != nil { if errors.Is(err, examprep.ErrPracticeNotFound) { return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{ Message: "Practice not found", Error: err.Error(), }) } if errors.Is(err, domain.ErrPersonaNotFound) { return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{ Message: "Persona not found", Error: err.Error(), }) } return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to update practice", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Practice updated successfully", Data: p, Success: true, StatusCode: fiber.StatusOK, }) } // DeleteExamPrepPractice godoc // @Summary Delete exam-prep practice // @Tags exam-prep // @Param id path int true "Exam prep practice ID" // @Router /api/v1/exam-prep/practices/{id} [delete] func (h *Handler) DeleteExamPrepPractice(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 practice id", Error: err.Error(), }) } if err := h.examPrepSvc.DeleteExamPrepPractice(c.Context(), id); err != nil { if errors.Is(err, examprep.ErrPracticeNotFound) { return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{ Message: "Practice not found", Error: err.Error(), }) } return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to delete practice", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Practice deleted successfully", Success: true, StatusCode: fiber.StatusOK, }) }