2152 lines
65 KiB
Go
2152 lines
65 KiB
Go
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,
|
|
})
|
|
}
|