package handlers import ( "Yimaru-Backend/internal/domain" "strconv" "github.com/gofiber/fiber/v2" ) // Course Category Handlers type createCourseCategoryReq struct { Name string `json:"name" validate:"required"` } type courseCategoryRes struct { ID int64 `json:"id"` Name string `json:"name"` IsActive bool `json:"is_active"` CreatedAt string `json:"created_at"` } // CreateCourseCategory godoc // @Summary Create a new course category // @Description Creates a new course category with the provided name // @Tags course-categories // @Accept json // @Produce json // @Param body body createCourseCategoryReq true "Create category payload" // @Success 201 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/categories [post] func (h *Handler) CreateCourseCategory(c *fiber.Ctx) error { var req createCourseCategoryReq if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } category, err := h.courseMgmtSvc.CreateCourseCategory(c.Context(), req.Name) if err != nil { return c.Status(fiber.StatusInternalServerError).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: courseCategoryRes{ ID: category.ID, Name: category.Name, IsActive: category.IsActive, CreatedAt: category.CreatedAt.String(), }, }) } // GetCourseCategoryByID godoc // @Summary Get course category by ID // @Description Returns a single course category by its ID // @Tags course-categories // @Produce json // @Param id path int true "Category 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/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 { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid category ID", Error: err.Error(), }) } category, err := h.courseMgmtSvc.GetCourseCategoryByID(c.Context(), id) if err != nil { return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{ Message: "Course category not found", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Course category retrieved successfully", Data: courseCategoryRes{ ID: category.ID, Name: category.Name, IsActive: category.IsActive, CreatedAt: category.CreatedAt.String(), }, }) } type getAllCourseCategoriesRes struct { Categories []courseCategoryRes `json:"categories"` TotalCount int64 `json:"total_count"` } // GetAllCourseCategories godoc // @Summary Get all course categories // @Description Returns a paginated list of all course categories // @Tags course-categories // @Produce json // @Param limit query int false "Limit" default(10) // @Param offset query int false "Offset" default(0) // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/categories [get] func (h *Handler) GetAllCourseCategories(c *fiber.Ctx) error { limitStr := c.Query("limit", "10") offsetStr := c.Query("offset", "0") limit, err := strconv.Atoi(limitStr) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid limit parameter", Error: err.Error(), }) } offset, err := strconv.Atoi(offsetStr) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid offset parameter", Error: err.Error(), }) } categories, totalCount, err := h.courseMgmtSvc.GetAllCourseCategories(c.Context(), int32(limit), int32(offset)) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to retrieve course categories", Error: err.Error(), }) } var categoryResponses []courseCategoryRes for _, category := range categories { categoryResponses = append(categoryResponses, courseCategoryRes{ ID: category.ID, Name: category.Name, IsActive: category.IsActive, CreatedAt: category.CreatedAt.String(), }) } return c.JSON(domain.Response{ Message: "Course categories retrieved successfully", Data: getAllCourseCategoriesRes{ Categories: categoryResponses, TotalCount: totalCount, }, }) } type updateCourseCategoryReq struct { Name *string `json:"name"` IsActive *bool `json:"is_active"` } // UpdateCourseCategory godoc // @Summary Update course category // @Description Updates a course category's name and/or active status // @Tags course-categories // @Accept json // @Produce json // @Param id path int true "Category ID" // @Param body body updateCourseCategoryReq true "Update category payload" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/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 { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid category ID", Error: err.Error(), }) } var req updateCourseCategoryReq if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } 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.JSON(domain.Response{ Message: "Course category updated successfully", }) } // DeleteCourseCategory godoc // @Summary Delete course category // @Description Deletes a course category by its ID // @Tags course-categories // @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-management/categories/{id} [delete] func (h *Handler) DeleteCourseCategory(c *fiber.Ctx) error { idStr := c.Params("id") id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid category ID", Error: err.Error(), }) } err = h.courseMgmtSvc.DeleteCourseCategory(c.Context(), id) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to delete course category", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Course category deleted successfully", }) } // Course Handlers type createCourseReq struct { CategoryID int64 `json:"category_id" validate:"required"` Title string `json:"title" validate:"required"` Description *string `json:"description"` } type courseRes struct { ID int64 `json:"id"` CategoryID int64 `json:"category_id"` Title string `json:"title"` Description *string `json:"description"` IsActive bool `json:"is_active"` } // CreateCourse godoc // @Summary Create a new course // @Description Creates a new course under a specific category // @Tags courses // @Accept json // @Produce json // @Param body body createCourseReq true "Create course payload" // @Success 201 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/courses [post] func (h *Handler) CreateCourse(c *fiber.Ctx) error { var req createCourseReq 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.CategoryID, req.Title, req.Description) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to create course", Error: err.Error(), }) } return c.Status(fiber.StatusCreated).JSON(domain.Response{ Message: "Course created successfully", Data: courseRes{ ID: course.ID, CategoryID: course.CategoryID, Title: course.Title, Description: course.Description, IsActive: course.IsActive, }, }) } // GetCourseByID godoc // @Summary Get course by ID // @Description Returns a single course by its ID // @Tags courses // @Produce json // @Param id path int true "Course 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/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 { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid course ID", Error: err.Error(), }) } course, err := h.courseMgmtSvc.GetCourseByID(c.Context(), id) if err != nil { return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{ Message: "Course not found", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Course retrieved successfully", Data: courseRes{ ID: course.ID, CategoryID: course.CategoryID, Title: course.Title, Description: course.Description, IsActive: course.IsActive, }, }) } type getCoursesByCategoryRes struct { Courses []courseRes `json:"courses"` TotalCount int64 `json:"total_count"` } // GetCoursesByCategory godoc // @Summary Get courses by category // @Description Returns a paginated list of courses under a specific category // @Tags courses // @Produce json // @Param categoryId path int true "Category ID" // @Param limit query int false "Limit" default(10) // @Param offset query int false "Offset" default(0) // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/categories/{categoryId}/courses [get] func (h *Handler) GetCoursesByCategory(c *fiber.Ctx) error { categoryIDStr := c.Params("categoryId") categoryID, err := strconv.ParseInt(categoryIDStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid category ID", Error: err.Error(), }) } limitStr := c.Query("limit", "10") offsetStr := c.Query("offset", "0") limit, err := strconv.Atoi(limitStr) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid limit parameter", Error: err.Error(), }) } offset, err := strconv.Atoi(offsetStr) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid offset parameter", Error: err.Error(), }) } courses, totalCount, err := h.courseMgmtSvc.GetCoursesByCategory(c.Context(), categoryID, int32(limit), int32(offset)) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to retrieve courses", Error: err.Error(), }) } var courseResponses []courseRes for _, course := range courses { courseResponses = append(courseResponses, courseRes{ ID: course.ID, CategoryID: course.CategoryID, Title: course.Title, Description: course.Description, IsActive: course.IsActive, }) } return c.JSON(domain.Response{ Message: "Courses retrieved successfully", Data: getCoursesByCategoryRes{ Courses: courseResponses, TotalCount: totalCount, }, }) } type updateCourseReq struct { Title *string `json:"title"` Description *string `json:"description"` IsActive *bool `json:"is_active"` } // UpdateCourse godoc // @Summary Update course // @Description Updates a course's title, description, and/or active status // @Tags courses // @Accept json // @Produce json // @Param id path int true "Course ID" // @Param body body updateCourseReq true "Update course payload" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/courses/{id} [put] func (h *Handler) UpdateCourse(c *fiber.Ctx) error { idStr := c.Params("id") id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid course ID", Error: err.Error(), }) } var req updateCourseReq if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } err = h.courseMgmtSvc.UpdateCourse(c.Context(), id, req.Title, req.Description, req.IsActive) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to update course", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Course updated successfully", }) } // DeleteCourse godoc // @Summary Delete course // @Description Deletes a course by its ID // @Tags courses // @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/course-management/courses/{id} [delete] func (h *Handler) DeleteCourse(c *fiber.Ctx) error { idStr := c.Params("id") id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid course ID", Error: err.Error(), }) } err = h.courseMgmtSvc.DeleteCourse(c.Context(), id) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to delete course", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Course deleted successfully", }) } // Program Handlers type createProgramReq struct { CourseID int64 `json:"course_id" validate:"required"` Title string `json:"title" validate:"required"` Description *string `json:"description"` Thumbnail *string `json:"thumbnail"` DisplayOrder *int32 `json:"display_order"` } type programRes struct { ID int64 `json:"id"` CourseID int64 `json:"course_id"` Title string `json:"title"` Description *string `json:"description"` Thumbnail *string `json:"thumbnail"` DisplayOrder int32 `json:"display_order"` IsActive bool `json:"is_active"` } // CreateProgram godoc // @Summary Create a new program // @Description Creates a new program under a specific course // @Tags programs // @Accept json // @Produce json // @Param body body createProgramReq true "Create program payload" // @Success 201 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/programs [post] func (h *Handler) CreateProgram(c *fiber.Ctx) error { var req createProgramReq if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } program, err := h.courseMgmtSvc.CreateProgram(c.Context(), req.CourseID, req.Title, req.Description, req.Thumbnail, req.DisplayOrder) 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 successfully", Data: programRes{ ID: program.ID, CourseID: program.CourseID, Title: program.Title, Description: program.Description, Thumbnail: program.Thumbnail, DisplayOrder: program.DisplayOrder, IsActive: program.IsActive, }, }) } // GetProgramByID godoc // @Summary Get program by ID // @Description Returns a single program by its ID // @Tags programs // @Produce json // @Param id path int true "Program 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/programs/{id} [get] func (h *Handler) GetProgramByID(c *fiber.Ctx) error { idStr := c.Params("id") id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid program ID", Error: err.Error(), }) } program, err := h.courseMgmtSvc.GetProgramByID(c.Context(), id) if err != nil { return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{ Message: "Program not found", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Program retrieved successfully", Data: programRes{ ID: program.ID, CourseID: program.CourseID, Title: program.Title, Description: program.Description, Thumbnail: program.Thumbnail, DisplayOrder: program.DisplayOrder, IsActive: program.IsActive, }, }) } type getProgramsByCourseRes struct { Programs []programRes `json:"programs"` TotalCount int64 `json:"total_count"` } // GetProgramsByCourse godoc // @Summary Get programs by course // @Description Returns all programs under a specific course with total count // @Tags programs // @Produce json // @Param courseId path int true "Course ID" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/courses/{courseId}/programs [get] func (h *Handler) GetProgramsByCourse(c *fiber.Ctx) error { courseIDStr := c.Params("courseId") courseID, err := strconv.ParseInt(courseIDStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid course ID", Error: err.Error(), }) } programs, totalCount, err := h.courseMgmtSvc.GetProgramsByCourse(c.Context(), courseID) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to retrieve programs", Error: err.Error(), }) } var programResponses []programRes for _, program := range programs { programResponses = append(programResponses, programRes{ ID: program.ID, CourseID: program.CourseID, Title: program.Title, Description: program.Description, Thumbnail: program.Thumbnail, DisplayOrder: program.DisplayOrder, IsActive: program.IsActive, }) } return c.JSON(domain.Response{ Message: "Programs retrieved successfully", Data: getProgramsByCourseRes{ Programs: programResponses, TotalCount: totalCount, }, }) } type listProgramsByCourseRes struct { Programs []programRes `json:"programs"` } // ListProgramsByCourse godoc // @Summary List programs by course // @Description Returns a simple list of programs under a specific course // @Tags programs // @Produce json // @Param courseId path int true "Course ID" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/courses/{courseId}/programs/list [get] func (h *Handler) ListProgramsByCourse(c *fiber.Ctx) error { courseIDStr := c.Params("courseId") courseID, err := strconv.ParseInt(courseIDStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid course ID", Error: err.Error(), }) } programs, err := h.courseMgmtSvc.ListProgramsByCourse(c.Context(), courseID) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to retrieve programs", Error: err.Error(), }) } var programResponses []programRes for _, program := range programs { programResponses = append(programResponses, programRes{ ID: program.ID, CourseID: program.CourseID, Title: program.Title, Description: program.Description, Thumbnail: program.Thumbnail, DisplayOrder: program.DisplayOrder, IsActive: program.IsActive, }) } return c.JSON(domain.Response{ Message: "Programs retrieved successfully", Data: listProgramsByCourseRes{ Programs: programResponses, }, }) } type listActiveProgramsRes struct { Programs []programRes `json:"programs"` } // ListActivePrograms godoc // @Summary List active programs // @Description Returns all active programs across all courses // @Tags programs // @Produce json // @Success 200 {object} domain.Response // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/programs/active [get] func (h *Handler) ListActivePrograms(c *fiber.Ctx) error { programs, err := h.courseMgmtSvc.ListActivePrograms(c.Context()) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to retrieve active programs", Error: err.Error(), }) } var programResponses []programRes for _, program := range programs { programResponses = append(programResponses, programRes{ ID: program.ID, CourseID: program.CourseID, Title: program.Title, Description: program.Description, Thumbnail: program.Thumbnail, DisplayOrder: program.DisplayOrder, IsActive: program.IsActive, }) } return c.JSON(domain.Response{ Message: "Active programs retrieved successfully", Data: listActiveProgramsRes{ Programs: programResponses, }, }) } type updateProgramPartialReq struct { Title *string `json:"title"` Description *string `json:"description"` Thumbnail *string `json:"thumbnail"` DisplayOrder *int32 `json:"display_order"` IsActive *bool `json:"is_active"` } // UpdateProgramPartial godoc // @Summary Update program partially // @Description Updates selected fields of a program // @Tags programs // @Accept json // @Produce json // @Param id path int true "Program ID" // @Param body body updateProgramPartialReq true "Update program payload" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/programs/{id} [patch] func (h *Handler) UpdateProgramPartial(c *fiber.Ctx) error { idStr := c.Params("id") id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid program ID", Error: err.Error(), }) } var req updateProgramPartialReq if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } err = h.courseMgmtSvc.UpdateProgramPartial(c.Context(), id, req.Title, req.Description, req.Thumbnail, req.DisplayOrder, req.IsActive) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to update program", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Program updated successfully", }) } type updateProgramFullReq struct { CourseID int64 `json:"course_id" validate:"required"` Title string `json:"title" validate:"required"` Description *string `json:"description"` Thumbnail *string `json:"thumbnail"` DisplayOrder int32 `json:"display_order"` IsActive bool `json:"is_active"` } // UpdateProgramFull godoc // @Summary Update program fully // @Description Updates all fields of a program // @Tags programs // @Accept json // @Produce json // @Param id path int true "Program ID" // @Param body body updateProgramFullReq true "Update program payload" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/programs/{id}/full [put] func (h *Handler) UpdateProgramFull(c *fiber.Ctx) error { idStr := c.Params("id") id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid program ID", Error: err.Error(), }) } var req updateProgramFullReq if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } program := domain.Program{ ID: id, CourseID: req.CourseID, Title: req.Title, Description: req.Description, Thumbnail: req.Thumbnail, DisplayOrder: req.DisplayOrder, IsActive: req.IsActive, } updatedProgram, err := h.courseMgmtSvc.UpdateProgramFull(c.Context(), program) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to update program", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Program updated successfully", Data: programRes{ ID: updatedProgram.ID, CourseID: updatedProgram.CourseID, Title: updatedProgram.Title, Description: updatedProgram.Description, Thumbnail: updatedProgram.Thumbnail, DisplayOrder: updatedProgram.DisplayOrder, IsActive: updatedProgram.IsActive, }, }) } // DeactivateProgram godoc // @Summary Deactivate program // @Description Deactivates a program by setting is_active to false // @Tags programs // @Produce json // @Param id path int true "Program ID" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/programs/{id}/deactivate [put] func (h *Handler) DeactivateProgram(c *fiber.Ctx) error { idStr := c.Params("id") id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid program ID", Error: err.Error(), }) } err = h.courseMgmtSvc.DeactivateProgram(c.Context(), id) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to deactivate program", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Program deactivated successfully", }) } // DeleteProgram godoc // @Summary Delete program // @Description Deletes a program by its ID // @Tags programs // @Produce json // @Param id path int true "Program ID" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/programs/{id} [delete] func (h *Handler) DeleteProgram(c *fiber.Ctx) error { idStr := c.Params("id") id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid program ID", Error: err.Error(), }) } deletedProgram, err := h.courseMgmtSvc.DeleteProgram(c.Context(), id) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to delete program", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Program deleted successfully", Data: programRes{ ID: deletedProgram.ID, CourseID: deletedProgram.CourseID, Title: deletedProgram.Title, Description: deletedProgram.Description, Thumbnail: deletedProgram.Thumbnail, DisplayOrder: deletedProgram.DisplayOrder, IsActive: deletedProgram.IsActive, }, }) } // Level Handlers type createLevelReq struct { ProgramID int64 `json:"program_id" validate:"required"` Title string `json:"title" validate:"required"` Description *string `json:"description"` LevelIndex int `json:"level_index" validate:"required"` IsActive *bool `json:"is_active"` } type levelRes struct { ID int64 `json:"id"` ProgramID int64 `json:"program_id"` Title string `json:"title"` Description *string `json:"description"` LevelIndex int `json:"level_index"` NumberOfModules int `json:"number_of_modules"` NumberOfPractices int `json:"number_of_practices"` NumberOfVideos int `json:"number_of_videos"` IsActive bool `json:"is_active"` } // CreateLevel godoc // @Summary Create a new level // @Description Creates a new level under a specific program // @Tags levels // @Accept json // @Produce json // @Param body body createLevelReq true "Create level payload" // @Success 201 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/levels [post] func (h *Handler) CreateLevel(c *fiber.Ctx) error { var req createLevelReq if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } level, err := h.courseMgmtSvc.CreateLevel(c.Context(), req.ProgramID, req.Title, req.Description, req.LevelIndex, req.IsActive) 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 successfully", Data: levelRes{ ID: level.ID, ProgramID: level.ProgramID, Title: level.Title, Description: level.Description, LevelIndex: level.LevelIndex, NumberOfModules: level.NumberOfModules, NumberOfPractices: level.NumberOfPractices, NumberOfVideos: level.NumberOfVideos, IsActive: level.IsActive, }, }) } type getLevelsByProgramRes struct { Levels []levelRes `json:"levels"` } // GetLevelsByProgram godoc // @Summary Get levels by program // @Description Returns all levels under a specific program // @Tags levels // @Produce json // @Param programId path int true "Program ID" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/programs/{programId}/levels [get] func (h *Handler) GetLevelsByProgram(c *fiber.Ctx) error { programIDStr := c.Params("programId") programID, err := strconv.ParseInt(programIDStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid program ID", Error: err.Error(), }) } levels, err := h.courseMgmtSvc.GetLevelsByProgram(c.Context(), programID) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to retrieve levels", Error: err.Error(), }) } var levelResponses []levelRes for _, level := range levels { levelResponses = append(levelResponses, levelRes{ ID: level.ID, ProgramID: level.ProgramID, Title: level.Title, Description: level.Description, LevelIndex: level.LevelIndex, NumberOfModules: level.NumberOfModules, NumberOfPractices: level.NumberOfPractices, NumberOfVideos: level.NumberOfVideos, IsActive: level.IsActive, }) } return c.JSON(domain.Response{ Message: "Levels retrieved successfully", Data: getLevelsByProgramRes{ Levels: levelResponses, }, }) } type updateLevelReq struct { Title *string `json:"title"` Description *string `json:"description"` LevelIndex *int `json:"level_index"` IsActive *bool `json:"is_active"` } // UpdateLevel godoc // @Summary Update level // @Description Updates a level's title, description, index, and/or active status // @Tags levels // @Accept json // @Produce json // @Param id path int true "Level ID" // @Param body body updateLevelReq true "Update level payload" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/levels/{id} [put] func (h *Handler) UpdateLevel(c *fiber.Ctx) error { idStr := c.Params("id") id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid level ID", Error: err.Error(), }) } var req updateLevelReq if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } err = h.courseMgmtSvc.UpdateLevel(c.Context(), id, req.Title, req.Description, req.LevelIndex, req.IsActive) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to update level", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Level updated successfully", }) } // IncrementLevelModuleCount godoc // @Summary Increment level module count // @Description Increments the module count for a specific level // @Tags levels // @Produce json // @Param levelId path int true "Level ID" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/levels/{levelId}/increment-module [put] func (h *Handler) IncrementLevelModuleCount(c *fiber.Ctx) error { levelIDStr := c.Params("levelId") levelID, err := strconv.ParseInt(levelIDStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid level ID", Error: err.Error(), }) } err = h.courseMgmtSvc.IncrementLevelModuleCount(c.Context(), levelID) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to increment level module count", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Level module count incremented successfully", }) } // IncrementLevelPracticeCount godoc // @Summary Increment level practice count // @Description Increments the practice count for a specific level // @Tags levels // @Produce json // @Param levelId path int true "Level ID" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/levels/{levelId}/increment-practice [put] func (h *Handler) IncrementLevelPracticeCount(c *fiber.Ctx) error { levelIDStr := c.Params("levelId") levelID, err := strconv.ParseInt(levelIDStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid level ID", Error: err.Error(), }) } err = h.courseMgmtSvc.IncrementLevelPracticeCount(c.Context(), levelID) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to increment level practice count", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Level practice count incremented successfully", }) } // IncrementLevelVideoCount godoc // @Summary Increment level video count // @Description Increments the video count for a specific level // @Tags levels // @Produce json // @Param levelId path int true "Level ID" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/levels/{levelId}/increment-video [put] func (h *Handler) IncrementLevelVideoCount(c *fiber.Ctx) error { levelIDStr := c.Params("levelId") levelID, err := strconv.ParseInt(levelIDStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid level ID", Error: err.Error(), }) } err = h.courseMgmtSvc.IncrementLevelVideoCount(c.Context(), levelID) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to increment level video count", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Level video count incremented successfully", }) } // DeleteLevel godoc // @Summary Delete level // @Description Deletes a level by its ID // @Tags levels // @Produce json // @Param levelId path int true "Level ID" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/levels/{levelId} [delete] func (h *Handler) DeleteLevel(c *fiber.Ctx) error { levelIDStr := c.Params("levelId") levelID, err := strconv.ParseInt(levelIDStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid level ID", Error: err.Error(), }) } err = h.courseMgmtSvc.DeleteLevel(c.Context(), levelID) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to delete level", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Level deleted successfully", }) } // Module Handlers type createModuleReq struct { LevelID int64 `json:"level_id" validate:"required"` Title string `json:"title" validate:"required"` Content *string `json:"content"` DisplayOrder *int32 `json:"display_order"` } type moduleRes struct { ID int64 `json:"id"` LevelID int64 `json:"level_id"` Title string `json:"title"` Content *string `json:"content"` DisplayOrder int32 `json:"display_order"` IsActive bool `json:"is_active"` } // CreateModule godoc // @Summary Create a new module // @Description Creates a new module under a specific level // @Tags modules // @Accept json // @Produce json // @Param body body createModuleReq true "Create module payload" // @Success 201 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/modules [post] func (h *Handler) CreateModule(c *fiber.Ctx) error { var req createModuleReq if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } module, err := h.courseMgmtSvc.CreateModule(c.Context(), req.LevelID, req.Title, req.Content, req.DisplayOrder) 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 successfully", Data: moduleRes{ ID: module.ID, LevelID: module.LevelID, Title: module.Title, Content: module.Content, DisplayOrder: module.DisplayOrder, IsActive: module.IsActive, }, }) } type getModulesByLevelRes struct { Modules []moduleRes `json:"modules"` TotalCount int64 `json:"total_count"` } // GetModulesByLevel godoc // @Summary Get modules by level // @Description Returns a paginated list of modules under a specific level // @Tags modules // @Produce json // @Param levelId path int true "Level ID" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/levels/{levelId}/modules [get] func (h *Handler) GetModulesByLevel(c *fiber.Ctx) error { levelIDStr := c.Params("levelId") levelID, err := strconv.ParseInt(levelIDStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid level ID", Error: err.Error(), }) } modules, totalCount, err := h.courseMgmtSvc.GetModulesByLevel(c.Context(), levelID) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to retrieve modules", Error: err.Error(), }) } var moduleResponses []moduleRes for _, module := range modules { moduleResponses = append(moduleResponses, moduleRes{ ID: module.ID, LevelID: module.LevelID, Title: module.Title, Content: module.Content, DisplayOrder: module.DisplayOrder, IsActive: module.IsActive, }) } return c.JSON(domain.Response{ Message: "Modules retrieved successfully", Data: getModulesByLevelRes{ Modules: moduleResponses, TotalCount: totalCount, }, }) } type updateModuleReq struct { Title *string `json:"title"` Content *string `json:"content"` DisplayOrder *int32 `json:"display_order"` IsActive *bool `json:"is_active"` } // UpdateModule godoc // @Summary Update module // @Description Updates a module's title, content, display order, and/or active status // @Tags modules // @Accept json // @Produce json // @Param id path int true "Module ID" // @Param body body updateModuleReq true "Update module payload" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/modules/{id} [put] func (h *Handler) UpdateModule(c *fiber.Ctx) error { idStr := c.Params("id") id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid module ID", Error: err.Error(), }) } var req updateModuleReq if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } err = h.courseMgmtSvc.UpdateModule(c.Context(), id, req.Title, req.Content, req.DisplayOrder, req.IsActive) if err != nil { 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", }) } // DeleteModule godoc // @Summary Delete module // @Description Deletes a module by its ID // @Tags modules // @Produce json // @Param id path int true "Module ID" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/modules/{id} [delete] func (h *Handler) DeleteModule(c *fiber.Ctx) error { idStr := c.Params("id") id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid module ID", Error: err.Error(), }) } err = h.courseMgmtSvc.DeleteModule(c.Context(), id) if err != nil { 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", }) } // Module Video Handlers type createModuleVideoReq struct { ModuleID int64 `json:"module_id" validate:"required"` Title string `json:"title" validate:"required"` Description *string `json:"description"` VideoURL string `json:"video_url" validate:"required"` Duration int32 `json:"duration" validate:"required"` Resolution *string `json:"resolution"` InstructorID *string `json:"instructor_id"` Thumbnail *string `json:"thumbnail"` Visibility *string `json:"visibility"` } type moduleVideoRes struct { ID int64 `json:"id"` ModuleID int64 `json:"module_id"` Title string `json:"title"` Description *string `json:"description"` VideoURL string `json:"video_url"` Duration int32 `json:"duration"` Resolution *string `json:"resolution"` InstructorID *string `json:"instructor_id"` Thumbnail *string `json:"thumbnail"` Visibility *string `json:"visibility"` IsPublished bool `json:"is_published"` PublishDate *string `json:"publish_date,omitempty"` IsActive bool `json:"is_active"` } // CreateModuleVideo godoc // @Summary Create a new module video // @Description Creates a new video under a specific module // @Tags module-videos // @Accept json // @Produce json // @Param body body createModuleVideoReq true "Create video payload" // @Success 201 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/videos [post] func (h *Handler) CreateModuleVideo(c *fiber.Ctx) error { var req createModuleVideoReq if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } video, err := h.courseMgmtSvc.CreateModuleVideo(c.Context(), req.ModuleID, req.Title, req.Description, req.VideoURL, req.Duration, req.Resolution, req.InstructorID, req.Thumbnail, req.Visibility) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to create module video", Error: err.Error(), }) } var publishDateStr *string if video.PublishDate != nil { publishDateStr = new(string) *publishDateStr = video.PublishDate.String() } return c.Status(fiber.StatusCreated).JSON(domain.Response{ Message: "Module video created successfully", Data: moduleVideoRes{ ID: video.ID, ModuleID: video.ModuleID, Title: video.Title, Description: video.Description, VideoURL: video.VideoURL, Duration: video.Duration, Resolution: video.Resolution, InstructorID: video.InstructorID, Thumbnail: video.Thumbnail, Visibility: video.Visibility, IsPublished: video.IsPublished, PublishDate: publishDateStr, IsActive: video.IsActive, }, }) } // PublishModuleVideo godoc // @Summary Publish module video // @Description Publishes a module video by setting publish date // @Tags module-videos // @Produce json // @Param videoId path int true "Video ID" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/videos/{videoId}/publish [put] func (h *Handler) PublishModuleVideo(c *fiber.Ctx) error { videoIDStr := c.Params("videoId") videoID, err := strconv.ParseInt(videoIDStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid video ID", Error: err.Error(), }) } err = h.courseMgmtSvc.PublishModuleVideo(c.Context(), videoID) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to publish module video", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Module video published successfully", }) } type getPublishedVideosByModuleRes struct { Videos []moduleVideoRes `json:"videos"` } // GetPublishedVideosByModule godoc // @Summary Get published videos by module // @Description Returns all published videos under a specific module // @Tags module-videos // @Produce json // @Param moduleId path int true "Module ID" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/modules/{moduleId}/videos/published [get] func (h *Handler) GetPublishedVideosByModule(c *fiber.Ctx) error { moduleIDStr := c.Params("moduleId") moduleID, err := strconv.ParseInt(moduleIDStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid module ID", Error: err.Error(), }) } videos, err := h.courseMgmtSvc.GetPublishedVideosByModule(c.Context(), moduleID) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to retrieve published videos", Error: err.Error(), }) } var videoResponses []moduleVideoRes for _, video := range videos { var publishDateStr *string if video.PublishDate != nil { publishDateStr = new(string) *publishDateStr = video.PublishDate.String() } videoResponses = append(videoResponses, moduleVideoRes{ ID: video.ID, ModuleID: video.ModuleID, Title: video.Title, Description: video.Description, VideoURL: video.VideoURL, Duration: video.Duration, Resolution: video.Resolution, InstructorID: video.InstructorID, Thumbnail: video.Thumbnail, Visibility: video.Visibility, IsPublished: video.IsPublished, PublishDate: publishDateStr, IsActive: video.IsActive, }) } return c.JSON(domain.Response{ Message: "Published videos retrieved successfully", Data: getPublishedVideosByModuleRes{ Videos: videoResponses, }, }) } type updateModuleVideoReq struct { Title *string `json:"title"` Description *string `json:"description"` VideoURL *string `json:"video_url"` Duration *int32 `json:"duration"` Resolution *string `json:"resolution"` Visibility *string `json:"visibility"` Thumbnail *string `json:"thumbnail"` IsActive *bool `json:"is_active"` } // UpdateModuleVideo godoc // @Summary Update module video // @Description Updates a module video's fields // @Tags module-videos // @Accept json // @Produce json // @Param id path int true "Video ID" // @Param body body updateModuleVideoReq true "Update video payload" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/videos/{id} [put] func (h *Handler) UpdateModuleVideo(c *fiber.Ctx) error { idStr := c.Params("id") id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid video ID", Error: err.Error(), }) } var req updateModuleVideoReq if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } err = h.courseMgmtSvc.UpdateModuleVideo(c.Context(), id, req.Title, req.Description, req.VideoURL, req.Duration, req.Resolution, req.Visibility, req.Thumbnail, req.IsActive) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to update module video", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Module video updated successfully", }) } // DeleteModuleVideo godoc // @Summary Delete module video // @Description Deletes a module video by its ID // @Tags module-videos // @Produce json // @Param id path int true "Video ID" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/videos/{id} [delete] func (h *Handler) DeleteModuleVideo(c *fiber.Ctx) error { idStr := c.Params("id") id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid video ID", Error: err.Error(), }) } err = h.courseMgmtSvc.DeleteModuleVideo(c.Context(), id) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to delete module video", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Module video deleted successfully", }) } // Practice Handlers type createPracticeReq struct { OwnerType string `json:"owner_type" validate:"required"` OwnerID int64 `json:"owner_id" validate:"required"` Title string `json:"title" validate:"required"` Description *string `json:"description"` BannerImage *string `json:"banner_image"` Persona *string `json:"persona"` IsActive *bool `json:"is_active"` } type practiceRes struct { ID int64 `json:"id"` OwnerType string `json:"owner_type"` OwnerID int64 `json:"owner_id"` Title string `json:"title"` Description *string `json:"description"` BannerImage *string `json:"banner_image"` Persona *string `json:"persona"` IsActive bool `json:"is_active"` } // CreatePractice godoc // @Summary Create a new practice // @Description Creates a new practice for a specific owner (module or level) // @Tags practices // @Accept json // @Produce json // @Param body body createPracticeReq true "Create practice payload" // @Success 201 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/practices [post] func (h *Handler) CreatePractice(c *fiber.Ctx) error { var req createPracticeReq if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } practice, err := h.courseMgmtSvc.CreatePractice(c.Context(), req.OwnerType, req.OwnerID, req.Title, req.Description, req.BannerImage, req.Persona, req.IsActive) 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 successfully", Data: practiceRes{ ID: practice.ID, OwnerType: practice.OwnerType, OwnerID: practice.OwnerID, Title: practice.Title, Description: practice.Description, BannerImage: practice.BannerImage, Persona: practice.Persona, IsActive: practice.IsActive, }, }) } type getPracticesByOwnerRes struct { Practices []practiceRes `json:"practices"` } // GetPracticesByOwner godoc // @Summary Get practices by owner // @Description Returns all practices for a specific owner type and ID // @Tags practices // @Produce json // @Param ownerType path string true "Owner Type" // @Param ownerId path int true "Owner ID" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/owners/{ownerType}/{ownerId}/practices [get] func (h *Handler) GetPracticesByOwner(c *fiber.Ctx) error { ownerType := c.Params("ownerType") ownerIDStr := c.Params("ownerId") ownerID, err := strconv.ParseInt(ownerIDStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid owner ID", Error: err.Error(), }) } practices, err := h.courseMgmtSvc.GetPracticesByOwner(c.Context(), ownerType, ownerID) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to retrieve practices", Error: err.Error(), }) } var practiceResponses []practiceRes for _, practice := range practices { practiceResponses = append(practiceResponses, practiceRes{ ID: practice.ID, OwnerType: practice.OwnerType, OwnerID: practice.OwnerID, Title: practice.Title, Description: practice.Description, BannerImage: practice.BannerImage, Persona: practice.Persona, IsActive: practice.IsActive, }) } return c.JSON(domain.Response{ Message: "Practices retrieved successfully", Data: getPracticesByOwnerRes{ Practices: practiceResponses, }, }) } type updatePracticeReq struct { Title *string `json:"title"` Description *string `json:"description"` BannerImage *string `json:"banner_image"` Persona *string `json:"persona"` IsActive *bool `json:"is_active"` } // UpdatePractice godoc // @Summary Update practice // @Description Updates a practice's fields // @Tags practices // @Accept json // @Produce json // @Param id path int true "Practice ID" // @Param body body updatePracticeReq true "Update practice payload" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/practices/{id} [put] func (h *Handler) UpdatePractice(c *fiber.Ctx) error { idStr := c.Params("id") id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid practice ID", Error: err.Error(), }) } var req updatePracticeReq if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } err = h.courseMgmtSvc.UpdatePractice(c.Context(), id, req.Title, req.Description, req.BannerImage, req.Persona, req.IsActive) if err != nil { 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", }) } // DeletePractice godoc // @Summary Delete practice // @Description Deletes a practice by its ID // @Tags practices // @Produce json // @Param id path int true "Practice ID" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/practices/{id} [delete] func (h *Handler) DeletePractice(c *fiber.Ctx) error { idStr := c.Params("id") id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid practice ID", Error: err.Error(), }) } err = h.courseMgmtSvc.DeletePractice(c.Context(), id) if err != nil { 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", }) } // Practice Question Handlers type createPracticeQuestionReq struct { PracticeID int64 `json:"practice_id" validate:"required"` Question string `json:"question" validate:"required"` QuestionVoicePrompt *string `json:"question_voice_prompt"` SampleAnswerVoicePrompt *string `json:"sample_answer_voice_prompt"` SampleAnswer *string `json:"sample_answer"` Tips *string `json:"tips"` QType string `json:"q_type" validate:"required"` } type practiceQuestionRes struct { ID int64 `json:"id"` PracticeID int64 `json:"practice_id"` Question string `json:"question"` QuestionVoicePrompt *string `json:"question_voice_prompt"` SampleAnswerVoicePrompt *string `json:"sample_answer_voice_prompt"` SampleAnswer *string `json:"sample_answer"` Tips *string `json:"tips"` Type string `json:"type"` } // CreatePracticeQuestion godoc // @Summary Create a new practice question // @Description Creates a new question under a specific practice // @Tags practice-questions // @Accept json // @Produce json // @Param body body createPracticeQuestionReq true "Create question payload" // @Success 201 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/questions [post] func (h *Handler) CreatePracticeQuestion(c *fiber.Ctx) error { var req createPracticeQuestionReq if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } question, err := h.courseMgmtSvc.CreatePracticeQuestion(c.Context(), req.PracticeID, req.Question, req.QuestionVoicePrompt, req.SampleAnswerVoicePrompt, req.SampleAnswer, req.Tips, req.QType) 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 successfully", Data: practiceQuestionRes{ ID: question.ID, PracticeID: question.PracticeID, Question: question.Question, QuestionVoicePrompt: question.QuestionVoicePrompt, SampleAnswerVoicePrompt: question.SampleAnswerVoicePrompt, SampleAnswer: question.SampleAnswer, Tips: question.Tips, Type: question.Type, }, }) } type getQuestionsByPracticeRes struct { Questions []practiceQuestionRes `json:"questions"` } // GetQuestionsByPractice godoc // @Summary Get questions by practice // @Description Returns all questions under a specific practice // @Tags practice-questions // @Produce json // @Param practiceId path int true "Practice ID" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/practices/{practiceId}/questions [get] func (h *Handler) GetQuestionsByPractice(c *fiber.Ctx) error { practiceIDStr := c.Params("practiceId") practiceID, err := strconv.ParseInt(practiceIDStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid practice ID", Error: err.Error(), }) } questions, err := h.courseMgmtSvc.GetQuestionsByPractice(c.Context(), practiceID) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to retrieve questions", Error: err.Error(), }) } var questionResponses []practiceQuestionRes for _, question := range questions { questionResponses = append(questionResponses, practiceQuestionRes{ ID: question.ID, PracticeID: question.PracticeID, Question: question.Question, QuestionVoicePrompt: question.QuestionVoicePrompt, SampleAnswerVoicePrompt: question.SampleAnswerVoicePrompt, SampleAnswer: question.SampleAnswer, Tips: question.Tips, Type: question.Type, }) } return c.JSON(domain.Response{ Message: "Questions retrieved successfully", Data: getQuestionsByPracticeRes{ Questions: questionResponses, }, }) } type updatePracticeQuestionReq struct { Question *string `json:"question"` SampleAnswer *string `json:"sample_answer"` Tips *string `json:"tips"` QType *string `json:"q_type"` } // UpdatePracticeQuestion godoc // @Summary Update practice question // @Description Updates a practice question's fields // @Tags practice-questions // @Accept json // @Produce json // @Param id path int true "Question ID" // @Param body body updatePracticeQuestionReq true "Update question payload" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/questions/{id} [put] func (h *Handler) UpdatePracticeQuestion(c *fiber.Ctx) error { idStr := c.Params("id") id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid question ID", Error: err.Error(), }) } var req updatePracticeQuestionReq if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } err = h.courseMgmtSvc.UpdatePracticeQuestion(c.Context(), id, req.Question, req.SampleAnswer, req.Tips, req.QType) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to update practice question", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Practice question updated successfully", }) } // DeletePracticeQuestion godoc // @Summary Delete practice question // @Description Deletes a practice question by its ID // @Tags practice-questions // @Produce json // @Param id path int true "Question ID" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/questions/{id} [delete] func (h *Handler) DeletePracticeQuestion(c *fiber.Ctx) error { idStr := c.Params("id") id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid question ID", Error: err.Error(), }) } err = h.courseMgmtSvc.DeletePracticeQuestion(c.Context(), id) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to delete practice question", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Practice question deleted successfully", }) } // Learning Tree Handler // GetFullLearningTree godoc // @Summary Get full learning tree // @Description Returns the complete learning tree structure with courses, programs, levels, and modules // @Tags learning-tree // @Produce json // @Success 200 {object} domain.Response // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/course-management/learning-tree [get] func (h *Handler) GetFullLearningTree(c *fiber.Ctx) error { courses, err := h.courseMgmtSvc.GetFullLearningTree(c.Context()) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to retrieve learning tree", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Learning tree retrieved successfully", Data: courses, }) }