package handlers import ( "strconv" "Yimaru-Backend/internal/domain" "github.com/gofiber/fiber/v2" ) // CreateCourseCategory godoc // @Summary Create course category // @Description Creates a new course category // @Tags courses // @Accept json // @Produce json // @Param category body domain.CourseCategory true "Course category payload" // @Success 201 {object} domain.Response{data=domain.CourseCategory} // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-categories [post] func (h *Handler) CreateCourseCategory(c *fiber.Ctx) error { var req domain.CourseCategory if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } cat, err := h.courseMgmtSvc.CreateCourseCategory(c.Context(), req.Name) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Failed to create course category", Error: err.Error(), }) } return c.Status(fiber.StatusCreated).JSON(domain.Response{ Message: "Course category created successfully", Data: cat, }) } // GetCourseCategoryByID godoc // @Summary Get course category // @Description Get course category by ID // @Tags courses // @Accept json // @Produce json // @Param id path int true "Category ID" // @Success 200 {object} domain.Response{data=domain.CourseCategory} // @Failure 400 {object} domain.ErrorResponse // @Failure 404 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-categories/{id} [get] func (h *Handler) GetCourseCategoryByID(c *fiber.Ctx) error { idStr := c.Params("id") id, err := strconv.ParseInt(idStr, 10, 64) if err != nil || id <= 0 { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid category ID", Error: "ID must be a positive integer", }) } cat, err := h.courseMgmtSvc.GetCourseCategoryByID(c.Context(), id) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to fetch course category", Error: err.Error(), }) } return c.Status(fiber.StatusOK).JSON(domain.Response{ Message: "Course category fetched successfully", Data: cat, }) } // ListActiveCourseCategories godoc // @Summary List active course categories // @Description Returns all active course categories // @Tags courses // @Accept json // @Produce json // @Success 200 {object} domain.Response{data=[]domain.CourseCategory} // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-categories [get] func (h *Handler) ListActiveCourseCategories(c *fiber.Ctx) error { cats, err := h.courseMgmtSvc.ListActiveCourseCategories(c.Context()) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to fetch course categories", Error: err.Error(), }) } return c.Status(fiber.StatusOK).JSON(domain.Response{ Message: "Course categories fetched successfully", Data: cats, }) } // UpdateCourseCategory godoc // @Summary Update course category // @Description Updates a course category // @Tags courses // @Accept json // @Produce json // @Param id path int true "Category ID" // @Param category body domain.CourseCategory true "Course category payload" // @Success 200 {object} domain.Response{data=domain.CourseCategory} // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-categories/{id} [put] func (h *Handler) UpdateCourseCategory(c *fiber.Ctx) error { idStr := c.Params("id") id, err := strconv.ParseInt(idStr, 10, 64) if err != nil || id <= 0 { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid category ID", Error: "ID must be a positive integer", }) } var req domain.CourseCategory if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } updated, err := h.courseMgmtSvc.UpdateCourseCategory(c.Context(), id, req.Name, req.IsActive) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to update course category", Error: err.Error(), }) } return c.Status(fiber.StatusOK).JSON(domain.Response{ Message: "Course category updated successfully", Data: updated, }) } // DeactivateCourseCategory godoc // @Summary Deactivate course category // @Description Deactivates a course category // @Tags courses // @Accept json // @Produce json // @Param id path int true "Category ID" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-categories/{id}/deactivate [post] func (h *Handler) DeactivateCourseCategory(c *fiber.Ctx) error { idStr := c.Params("id") id, err := strconv.ParseInt(idStr, 10, 64) if err != nil || id <= 0 { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid category ID", Error: "ID must be a positive integer", }) } if err := h.courseMgmtSvc.DeactivateCourseCategory(c.Context(), id); err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to deactivate course category", Error: err.Error(), }) } return c.Status(fiber.StatusOK).JSON(domain.Response{ Message: "Course category deactivated", }) } // --- Courses handlers --- // CreateCourse godoc // @Summary Create course // @Description Creates a new course // @Tags courses // @Accept json // @Produce json // @Param course body domain.Course true "Course payload" // @Success 201 {object} domain.Response{data=domain.Course} // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/courses [post] func (h *Handler) CreateCourse(c *fiber.Ctx) error { var req domain.Course if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } course, err := h.courseMgmtSvc.CreateCourse(c.Context(), req) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Failed to create course", Error: err.Error(), }) } return c.Status(fiber.StatusCreated).JSON(domain.Response{ Message: "Course created successfully", Data: course, }) } // GetCourseByID godoc // @Summary Get course // @Description Get course by ID // @Tags courses // @Accept json // @Produce json // @Param id path int true "Course ID" // @Success 200 {object} domain.Response{data=domain.Course} // @Failure 400 {object} domain.ErrorResponse // @Failure 404 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/courses/{id} [get] func (h *Handler) GetCourseByID(c *fiber.Ctx) error { idStr := c.Params("id") id, err := strconv.ParseInt(idStr, 10, 64) if err != nil || id <= 0 { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid course ID", Error: "ID must be a positive integer", }) } course, err := h.courseMgmtSvc.GetCourseByID(c.Context(), id) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to fetch course", Error: err.Error(), }) } return c.Status(fiber.StatusOK).JSON(domain.Response{ Message: "Course fetched successfully", Data: course, }) } // ListCoursesByCategory godoc // @Summary List courses by category // @Description Returns courses under a given category // @Tags courses // @Accept json // @Produce json // @Param category_id path int true "Category ID" // @Success 200 {object} domain.Response{data=[]domain.Course} // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-categories/{category_id}/courses [get] func (h *Handler) ListCoursesByCategory(c *fiber.Ctx) error { catIDStr := c.Params("category_id") catID, err := strconv.ParseInt(catIDStr, 10, 64) if err != nil || catID <= 0 { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid category ID", Error: "ID must be a positive integer", }) } courses, err := h.courseMgmtSvc.ListCoursesByCategory(c.Context(), catID) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to fetch courses", Error: err.Error(), }) } return c.Status(fiber.StatusOK).JSON(domain.Response{ Message: "Courses fetched successfully", Data: courses, }) } // ListActiveCourses godoc // @Summary List active courses // @Description Returns all active courses // @Tags courses // @Accept json // @Produce json // @Success 200 {object} domain.Response{data=[]domain.Course} // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/courses [get] func (h *Handler) ListActiveCourses(c *fiber.Ctx) error { courses, err := h.courseMgmtSvc.ListActiveCourses(c.Context()) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to fetch courses", Error: err.Error(), }) } return c.Status(fiber.StatusOK).JSON(domain.Response{ Message: "Courses fetched successfully", Data: courses, }) } // UpdateCourse godoc // @Summary Update course // @Description Updates a course // @Tags courses // @Accept json // @Produce json // @Param id path int true "Course ID" // @Param course body domain.Course true "Course payload" // @Success 200 {object} domain.Response{data=domain.Course} // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/courses/{id} [put] func (h *Handler) UpdateCourse(c *fiber.Ctx) error { var req domain.Course if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } updated, err := h.courseMgmtSvc.UpdateCourse(c.Context(), req) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to update course", Error: err.Error(), }) } return c.Status(fiber.StatusOK).JSON(domain.Response{ Message: "Course updated successfully", Data: updated, }) } // DeactivateCourse godoc // @Summary Deactivate course // @Description Deactivates a course // @Tags courses // @Accept json // @Produce json // @Param id path int true "Course ID" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/courses/{id}/deactivate [post] func (h *Handler) DeactivateCourse(c *fiber.Ctx) error { idStr := c.Params("id") id, err := strconv.ParseInt(idStr, 10, 64) if err != nil || id <= 0 { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid course ID", Error: "ID must be a positive integer", }) } if err := h.courseMgmtSvc.DeactivateCourse(c.Context(), id); err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to deactivate course", Error: err.Error(), }) } return c.Status(fiber.StatusOK).JSON(domain.Response{ Message: "Course deactivated", }) } // --- Programs, Modules, Videos, Practices, Questions, Levels --- // For brevity: implement representative handlers for creating and listing programs, modules, videos, practices, questions, and levels. // CreateProgram godoc // @Summary Create program // @Tags courses // @Accept json // @Produce json // @Param program body domain.Program true "Program payload" // @Success 201 {object} domain.Response{data=domain.Program} // @Router /api/v1/courses/{course_id}/programs [post] func (h *Handler) CreateProgram(c *fiber.Ctx) error { var req domain.Program 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.courseMgmtSvc.CreateProgram(c.Context(), req) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to create program", Error: err.Error()}) } return c.Status(fiber.StatusCreated).JSON(domain.Response{Message: "Program created", Data: p}) } // ListProgramsByCourse godoc // @Summary List programs by course // @Tags courses // @Param course_id path int true "Course ID" // @Success 200 {object} domain.Response{data=[]domain.Program} // @Router /api/v1/courses/{course_id}/programs [get] func (h *Handler) ListProgramsByCourse(c *fiber.Ctx) error { courseIDStr := c.Params("course_id") courseID, err := strconv.ParseInt(courseIDStr, 10, 64) if err != nil || courseID <= 0 { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "Invalid course ID", Error: "ID must be a positive integer"}) } items, err := h.courseMgmtSvc.ListProgramsByCourse(c.Context(), courseID) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to fetch programs", Error: err.Error()}) } return c.Status(fiber.StatusOK).JSON(domain.Response{Message: "Programs fetched", Data: items}) } // CreateModule godoc // @Summary Create module // @Tags courses // @Accept json // @Produce json // @Param module body domain.Module true "Module payload" // @Success 201 {object} domain.Response{data=domain.Module} // @Router /api/v1/modules [post] func (h *Handler) CreateModule(c *fiber.Ctx) error { var req domain.Module if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "Invalid request body", Error: err.Error()}) } m, err := h.courseMgmtSvc.CreateModule(c.Context(), req) if err != nil { 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", Data: m}) } // ListModulesByLevel godoc // @Summary List modules by level // @Tags courses // @Param level_id path int true "Level ID" // @Success 200 {object} domain.Response{data=[]domain.Module} // @Router /api/v1/levels/{level_id}/modules [get] func (h *Handler) ListModulesByLevel(c *fiber.Ctx) error { lvlStr := c.Params("level_id") lvlID, err := strconv.ParseInt(lvlStr, 10, 64) if err != nil || lvlID <= 0 { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "Invalid level ID", Error: "ID must be a positive integer"}) } items, err := h.courseMgmtSvc.ListModulesByLevel(c.Context(), lvlID) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to fetch modules", Error: err.Error()}) } return c.Status(fiber.StatusOK).JSON(domain.Response{Message: "Modules fetched", Data: items}) } // CreateModuleVideo godoc // @Summary Create module video // @Tags courses // @Accept json // @Produce json // @Param video body domain.ModuleVideo true "Module video payload" // @Success 201 {object} domain.Response{data=domain.ModuleVideo} // @Router /api/v1/module-videos [post] func (h *Handler) CreateModuleVideo(c *fiber.Ctx) error { var req domain.ModuleVideo if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "Invalid request body", Error: err.Error()}) } v, err := h.courseMgmtSvc.CreateModuleVideo(c.Context(), req) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to create module video", Error: err.Error()}) } return c.Status(fiber.StatusCreated).JSON(domain.Response{Message: "Module video created", Data: v}) } // CreatePractice godoc // @Summary Create practice // @Tags courses // @Accept json // @Produce json // @Param practice body domain.Practice true "Practice payload" // @Success 201 {object} domain.Response{data=domain.Practice} // @Router /api/v1/practices [post] func (h *Handler) CreatePractice(c *fiber.Ctx) error { var req domain.Practice 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.courseMgmtSvc.CreatePractice(c.Context(), req) if err != nil { 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", Data: p}) } // CreatePracticeQuestion godoc // @Summary Create practice question // @Tags courses // @Accept json // @Produce json // @Param question body domain.PracticeQuestion true "Practice question payload" // @Success 201 {object} domain.Response{data=domain.PracticeQuestion} // @Router /api/v1/practice-questions [post] func (h *Handler) CreatePracticeQuestion(c *fiber.Ctx) error { var req domain.PracticeQuestion if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "Invalid request body", Error: err.Error()}) } q, err := h.courseMgmtSvc.CreatePracticeQuestion(c.Context(), req) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to create practice question", Error: err.Error()}) } return c.Status(fiber.StatusCreated).JSON(domain.Response{Message: "Practice question created", Data: q}) } // CreateLevel godoc // @Summary Create level // @Tags courses // @Accept json // @Produce json // @Param level body domain.Level true "Level payload" // @Success 201 {object} domain.Response{data=domain.Level} // @Router /api/v1/levels [post] func (h *Handler) CreateLevel(c *fiber.Ctx) error { var req domain.Level if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "Invalid request body", Error: err.Error()}) } l, err := h.courseMgmtSvc.CreateLevel(c.Context(), req) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to create level", Error: err.Error()}) } return c.Status(fiber.StatusCreated).JSON(domain.Response{Message: "Level created", Data: l}) } // Helper to surface not-implemented errors for optional handlers func notImplemented(c *fiber.Ctx, name string) error { return c.Status(fiber.StatusNotImplemented).JSON(domain.ErrorResponse{Message: name + " not implemented", Error: "not implemented"}) }