course CRUD fix
This commit is contained in:
parent
b1a1b97a0a
commit
5858aeb744
|
|
@ -23,6 +23,27 @@ type createCourseCategoryReq struct {
|
|||
IsActive *bool `json:"is_active"`
|
||||
}
|
||||
|
||||
type createCourseReq struct {
|
||||
CategoryID int64 `json:"category_id"`
|
||||
Title string `json:"title"`
|
||||
Description *string `json:"description"`
|
||||
Thumbnail *string `json:"thumbnail"`
|
||||
IntroVideoURL *string `json:"intro_video_url"`
|
||||
IsActive *bool `json:"is_active"`
|
||||
}
|
||||
|
||||
type updateCourseReq struct {
|
||||
Title *string `json:"title"`
|
||||
Description *string `json:"description"`
|
||||
Thumbnail *string `json:"thumbnail"`
|
||||
IntroVideoURL *string `json:"intro_video_url"`
|
||||
IsActive *bool `json:"is_active"`
|
||||
}
|
||||
|
||||
type updateCourseThumbnailReq struct {
|
||||
ThumbnailURL string `json:"thumbnail_url"`
|
||||
}
|
||||
|
||||
type createLevelReq struct {
|
||||
CourseID int64 `json:"course_id"`
|
||||
CEFRLevel string `json:"cefr_level"`
|
||||
|
|
@ -178,6 +199,183 @@ func (h *Handler) CreateCourseCategory(c *fiber.Ctx) error {
|
|||
return c.Status(fiber.StatusCreated).JSON(domain.Response{Message: "Course category created", Data: created})
|
||||
}
|
||||
|
||||
// CreateCourse godoc
|
||||
// @Summary Create course
|
||||
// @Description Legacy-compatible endpoint for creating a course
|
||||
// @Tags course-management
|
||||
// @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()})
|
||||
}
|
||||
if req.CategoryID <= 0 || strings.TrimSpace(req.Title) == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "category_id and title are required"})
|
||||
}
|
||||
|
||||
created, err := h.analyticsDB.CreateCourse(c.Context(), dbgen.CreateCourseParams{
|
||||
CategoryID: req.CategoryID,
|
||||
Title: req.Title,
|
||||
Description: toText(req.Description),
|
||||
Thumbnail: toText(req.Thumbnail),
|
||||
IntroVideoUrl: toText(req.IntroVideoURL),
|
||||
Column6: boolOrNil(req.IsActive),
|
||||
})
|
||||
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", Data: created})
|
||||
}
|
||||
|
||||
// UpdateCourse godoc
|
||||
// @Summary Update course
|
||||
// @Description Legacy-compatible endpoint for updating a course
|
||||
// @Tags course-management
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param courseId 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/{courseId} [put]
|
||||
func (h *Handler) UpdateCourse(c *fiber.Ctx) error {
|
||||
courseID, err := strconv.ParseInt(c.Params("courseId"), 10, 64)
|
||||
if err != nil || courseID <= 0 {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "Invalid course ID", Error: "courseId must be a positive integer"})
|
||||
}
|
||||
|
||||
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()})
|
||||
}
|
||||
|
||||
existing, err := h.analyticsDB.GetCourseByID(c.Context(), courseID)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to load course", Error: err.Error()})
|
||||
}
|
||||
|
||||
title := existing.Title
|
||||
if req.Title != nil {
|
||||
title = *req.Title
|
||||
}
|
||||
description := existing.Description
|
||||
if req.Description != nil {
|
||||
description = toText(req.Description)
|
||||
}
|
||||
thumbnail := existing.Thumbnail
|
||||
if req.Thumbnail != nil {
|
||||
thumbnail = toText(req.Thumbnail)
|
||||
}
|
||||
introVideo := existing.IntroVideoUrl
|
||||
if req.IntroVideoURL != nil {
|
||||
introVideo = toText(req.IntroVideoURL)
|
||||
}
|
||||
isActive := existing.IsActive
|
||||
if req.IsActive != nil {
|
||||
isActive = *req.IsActive
|
||||
}
|
||||
|
||||
if err := h.analyticsDB.UpdateCourse(c.Context(), dbgen.UpdateCourseParams{
|
||||
Title: title,
|
||||
Description: description,
|
||||
Thumbnail: thumbnail,
|
||||
IntroVideoUrl: introVideo,
|
||||
IsActive: isActive,
|
||||
ID: courseID,
|
||||
}); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to update course", Error: err.Error()})
|
||||
}
|
||||
|
||||
updated, err := h.analyticsDB.GetCourseByID(c.Context(), courseID)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Course updated but failed to fetch latest record", Error: err.Error()})
|
||||
}
|
||||
|
||||
return c.JSON(domain.Response{Message: "Course updated", Data: updated})
|
||||
}
|
||||
|
||||
// DeleteCourse godoc
|
||||
// @Summary Delete course
|
||||
// @Description Legacy-compatible endpoint for deleting a course
|
||||
// @Tags course-management
|
||||
// @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} [delete]
|
||||
func (h *Handler) DeleteCourse(c *fiber.Ctx) error {
|
||||
courseID, err := strconv.ParseInt(c.Params("courseId"), 10, 64)
|
||||
if err != nil || courseID <= 0 {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "Invalid course ID", Error: "courseId must be a positive integer"})
|
||||
}
|
||||
|
||||
if err := h.analyticsDB.DeleteCourse(c.Context(), courseID); 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"})
|
||||
}
|
||||
|
||||
// UpdateCourseThumbnail godoc
|
||||
// @Summary Update course thumbnail
|
||||
// @Description Legacy-compatible endpoint for updating course thumbnail
|
||||
// @Tags course-management
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param courseId path int true "Course ID"
|
||||
// @Param body body updateCourseThumbnailReq true "Update course thumbnail payload"
|
||||
// @Success 200 {object} domain.Response
|
||||
// @Failure 400 {object} domain.ErrorResponse
|
||||
// @Failure 500 {object} domain.ErrorResponse
|
||||
// @Router /api/v1/course-management/courses/{courseId}/thumbnail [post]
|
||||
func (h *Handler) UpdateCourseThumbnail(c *fiber.Ctx) error {
|
||||
courseID, err := strconv.ParseInt(c.Params("courseId"), 10, 64)
|
||||
if err != nil || courseID <= 0 {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "Invalid course ID", Error: "courseId must be a positive integer"})
|
||||
}
|
||||
|
||||
var req updateCourseThumbnailReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "Invalid request body", Error: err.Error()})
|
||||
}
|
||||
|
||||
existing, err := h.analyticsDB.GetCourseByID(c.Context(), courseID)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to load course", Error: err.Error()})
|
||||
}
|
||||
thumb := req.ThumbnailURL
|
||||
if strings.TrimSpace(thumb) == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "thumbnail_url is required"})
|
||||
}
|
||||
|
||||
if err := h.analyticsDB.UpdateCourse(c.Context(), dbgen.UpdateCourseParams{
|
||||
Title: existing.Title,
|
||||
Description: existing.Description,
|
||||
Thumbnail: pgtype.Text{String: thumb, Valid: true},
|
||||
IntroVideoUrl: existing.IntroVideoUrl,
|
||||
IsActive: existing.IsActive,
|
||||
ID: courseID,
|
||||
}); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to update course thumbnail", Error: err.Error()})
|
||||
}
|
||||
|
||||
updated, err := h.analyticsDB.GetCourseByID(c.Context(), courseID)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Thumbnail updated but failed to fetch latest record", Error: err.Error()})
|
||||
}
|
||||
|
||||
return c.JSON(domain.Response{Message: "Course thumbnail updated", Data: updated})
|
||||
}
|
||||
|
||||
// UnifiedHierarchy godoc
|
||||
// @Summary Get unified course hierarchy
|
||||
// @Description Returns full hierarchy: category -> sub-category -> course
|
||||
|
|
|
|||
|
|
@ -81,6 +81,10 @@ func (a *App) initAppRoutes() {
|
|||
// Unified Course Management (single hierarchy model)
|
||||
groupV1.Get("/course-management/categories", a.authMiddleware, a.RequirePermission("learning_tree.get"), h.ListCourseCategories)
|
||||
groupV1.Post("/course-management/categories", a.authMiddleware, a.RequirePermission("course_categories.create"), h.CreateCourseCategory)
|
||||
groupV1.Post("/course-management/courses", a.authMiddleware, a.RequirePermission("courses.create"), h.CreateCourse)
|
||||
groupV1.Put("/course-management/courses/:courseId", a.authMiddleware, a.RequirePermission("courses.update"), h.UpdateCourse)
|
||||
groupV1.Delete("/course-management/courses/:courseId", a.authMiddleware, a.RequirePermission("courses.delete"), h.DeleteCourse)
|
||||
groupV1.Post("/course-management/courses/:courseId/thumbnail", a.authMiddleware, a.RequirePermission("courses.upload_thumbnail"), h.UpdateCourseThumbnail)
|
||||
groupV1.Get("/course-management/hierarchy", a.authMiddleware, a.RequirePermission("learning_tree.get"), h.UnifiedHierarchy)
|
||||
groupV1.Get("/course-management/courses/:courseId/hierarchy", a.authMiddleware, a.RequirePermission("learning_tree.get"), h.UnifiedHierarchyByCourse)
|
||||
groupV1.Post("/course-management/sub-categories", a.authMiddleware, a.RequirePermission("course_categories.create"), h.CreateCourseSubCategory)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user