more course CRUD fix

This commit is contained in:
Yared Yemane 2026-04-14 05:04:38 -07:00
parent 5858aeb744
commit 2ff1e89263
3 changed files with 303 additions and 0 deletions

View File

@ -0,0 +1,113 @@
package dbgen
import (
"context"
)
func (q *Queries) GetSubModuleByIDCompat(ctx context.Context, id int64) (SubModule, error) {
row := q.db.QueryRow(ctx, `
SELECT id, module_id, title, description, display_order, is_active, created_at, legacy_sub_course_id
FROM sub_modules
WHERE id = $1
`, id)
var i SubModule
err := row.Scan(
&i.ID,
&i.ModuleID,
&i.Title,
&i.Description,
&i.DisplayOrder,
&i.IsActive,
&i.CreatedAt,
&i.LegacySubCourseID,
)
return i, err
}
func (q *Queries) UpdateSubModuleCompat(ctx context.Context, id int64, title string, description string, isActive bool) error {
_, err := q.db.Exec(ctx, `
UPDATE sub_modules
SET
title = $1,
description = NULLIF($2, ''),
is_active = $3
WHERE id = $4
`, title, description, isActive, id)
return err
}
func (q *Queries) DeleteSubModuleCompat(ctx context.Context, id int64) error {
_, err := q.db.Exec(ctx, `DELETE FROM sub_modules WHERE id = $1`, id)
return err
}
func (q *Queries) UpdateSubModuleVideoCompat(ctx context.Context, id int64, title string, description string, videoURL string) error {
_, err := q.db.Exec(ctx, `
UPDATE sub_module_videos
SET
title = $1,
description = NULLIF($2, ''),
video_url = $3
WHERE id = $4
`, title, description, videoURL, id)
return err
}
func (q *Queries) DeleteSubModuleVideoCompat(ctx context.Context, id int64) error {
_, err := q.db.Exec(ctx, `DELETE FROM sub_module_videos WHERE id = $1`, id)
return err
}
func (q *Queries) UpdatePracticeCompat(ctx context.Context, id int64, title string, description string, persona string) error {
_, err := q.db.Exec(ctx, `
UPDATE question_sets
SET
title = $1,
description = NULLIF($2, ''),
persona = NULLIF($3, ''),
updated_at = CURRENT_TIMESTAMP
WHERE id = $4
`, title, description, persona, id)
if err != nil {
return err
}
_, err = q.db.Exec(ctx, `
UPDATE sub_module_practices
SET
title = $1,
description = NULLIF($2, '')
WHERE question_set_id = $3
`, title, description, id)
return err
}
func (q *Queries) UpdatePracticeStatusCompat(ctx context.Context, id int64, isActive bool) error {
status := "ARCHIVED"
if isActive {
status = "PUBLISHED"
}
_, err := q.db.Exec(ctx, `
UPDATE question_sets
SET
status = $1,
updated_at = CURRENT_TIMESTAMP
WHERE id = $2
`, status, id)
if err != nil {
return err
}
_, err = q.db.Exec(ctx, `
UPDATE sub_module_practices
SET is_active = $1
WHERE question_set_id = $2
`, isActive, id)
return err
}
func (q *Queries) DeletePracticeCompat(ctx context.Context, id int64) error {
_, err := q.db.Exec(ctx, `DELETE FROM question_sets WHERE id = $1`, id)
return err
}

View File

@ -81,6 +81,25 @@ type createSubModuleVideoReq struct {
Status *string `json:"status"`
}
type updateSubModuleReq struct {
Title *string `json:"title"`
Description *string `json:"description"`
IsActive *bool `json:"is_active"`
}
type updateSubModuleVideoReq struct {
Title *string `json:"title"`
Description *string `json:"description"`
VideoURL *string `json:"video_url"`
}
type updatePracticeReq struct {
Title *string `json:"title"`
Description *string `json:"description"`
Persona *string `json:"persona"`
IsActive *bool `json:"is_active"`
}
type attachSubModuleLessonReq struct {
SubModuleID int64 `json:"sub_module_id"`
QuestionSetID int64 `json:"question_set_id"`
@ -738,3 +757,167 @@ func (h *Handler) CreateSubModulePractice(c *fiber.Ctx) error {
return c.Status(fiber.StatusCreated).JSON(domain.Response{Message: "Practice created", Data: created})
}
func (h *Handler) GetSubModuleVideos(c *fiber.Ctx) error {
subModuleID, err := strconv.ParseInt(c.Params("subModuleId"), 10, 64)
if err != nil || subModuleID <= 0 {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "Invalid sub-module ID", Error: "subModuleId must be a positive integer"})
}
videos, err := h.analyticsDB.GetSubModuleVideos(c.Context(), subModuleID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to load sub-module videos", Error: err.Error()})
}
return c.JSON(domain.Response{
Message: "Sub-module videos retrieved successfully",
Data: map[string]interface{}{
"videos": videos,
"total_count": len(videos),
},
})
}
func (h *Handler) UpdateSubModule(c *fiber.Ctx) error {
subModuleID, err := strconv.ParseInt(c.Params("subModuleId"), 10, 64)
if err != nil || subModuleID <= 0 {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "Invalid sub-module ID", Error: "subModuleId must be a positive integer"})
}
existing, err := h.analyticsDB.GetSubModuleByIDCompat(c.Context(), subModuleID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to load sub-module", Error: err.Error()})
}
var req updateSubModuleReq
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "Invalid request body", Error: err.Error()})
}
title := existing.Title
if req.Title != nil {
title = *req.Title
}
description := ""
if existing.Description.Valid {
description = existing.Description.String
}
if req.Description != nil {
description = *req.Description
}
isActive := existing.IsActive
if req.IsActive != nil {
isActive = *req.IsActive
}
if err := h.analyticsDB.UpdateSubModuleCompat(c.Context(), subModuleID, title, description, isActive); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to update sub-module", Error: err.Error()})
}
updated, err := h.analyticsDB.GetSubModuleByIDCompat(c.Context(), subModuleID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Sub-module updated but failed to fetch latest record", Error: err.Error()})
}
return c.JSON(domain.Response{Message: "Sub-module updated", Data: updated})
}
func (h *Handler) DeleteSubModule(c *fiber.Ctx) error {
subModuleID, err := strconv.ParseInt(c.Params("subModuleId"), 10, 64)
if err != nil || subModuleID <= 0 {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "Invalid sub-module ID", Error: "subModuleId must be a positive integer"})
}
if err := h.analyticsDB.DeleteSubModuleCompat(c.Context(), subModuleID); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to delete sub-module", Error: err.Error()})
}
return c.JSON(domain.Response{Message: "Sub-module deleted"})
}
func (h *Handler) UpdateSubModuleVideo(c *fiber.Ctx) error {
videoID, err := strconv.ParseInt(c.Params("videoId"), 10, 64)
if err != nil || videoID <= 0 {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "Invalid video ID", Error: "videoId must be a positive integer"})
}
var req updateSubModuleVideoReq
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "Invalid request body", Error: err.Error()})
}
if req.Title == nil || strings.TrimSpace(*req.Title) == "" || req.VideoURL == nil || strings.TrimSpace(*req.VideoURL) == "" {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "title and video_url are required"})
}
description := ""
if req.Description != nil {
description = *req.Description
}
if err := h.analyticsDB.UpdateSubModuleVideoCompat(c.Context(), videoID, *req.Title, description, *req.VideoURL); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to update sub-module video", Error: err.Error()})
}
return c.JSON(domain.Response{Message: "Sub-module video updated"})
}
func (h *Handler) DeleteSubModuleVideo(c *fiber.Ctx) error {
videoID, err := strconv.ParseInt(c.Params("videoId"), 10, 64)
if err != nil || videoID <= 0 {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "Invalid video ID", Error: "videoId must be a positive integer"})
}
if err := h.analyticsDB.DeleteSubModuleVideoCompat(c.Context(), videoID); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to delete sub-module video", Error: err.Error()})
}
return c.JSON(domain.Response{Message: "Sub-module video deleted"})
}
func (h *Handler) UpdatePractice(c *fiber.Ctx) error {
practiceID, err := strconv.ParseInt(c.Params("practiceId"), 10, 64)
if err != nil || practiceID <= 0 {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "Invalid practice ID", Error: "practiceId must be a positive integer"})
}
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()})
}
if req.IsActive != nil {
if err := h.analyticsDB.UpdatePracticeStatusCompat(c.Context(), practiceID, *req.IsActive); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to update practice status", Error: err.Error()})
}
return c.JSON(domain.Response{Message: "Practice status updated"})
}
title := ""
if req.Title != nil {
title = *req.Title
}
description := ""
if req.Description != nil {
description = *req.Description
}
persona := ""
if req.Persona != nil {
persona = *req.Persona
}
if strings.TrimSpace(title) == "" {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "title is required"})
}
if err := h.analyticsDB.UpdatePracticeCompat(c.Context(), practiceID, title, description, persona); 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"})
}
func (h *Handler) DeletePractice(c *fiber.Ctx) error {
practiceID, err := strconv.ParseInt(c.Params("practiceId"), 10, 64)
if err != nil || practiceID <= 0 {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "Invalid practice ID", Error: "practiceId must be a positive integer"})
}
if err := h.analyticsDB.DeletePracticeCompat(c.Context(), practiceID); 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"})
}

View File

@ -91,9 +91,16 @@ func (a *App) initAppRoutes() {
groupV1.Post("/course-management/levels", a.authMiddleware, a.RequirePermission("subcourses.create"), h.CreateLevel)
groupV1.Post("/course-management/modules", a.authMiddleware, a.RequirePermission("subcourses.create"), h.CreateModule)
groupV1.Post("/course-management/sub-modules", a.authMiddleware, a.RequirePermission("subcourses.create"), h.CreateSubModule)
groupV1.Put("/course-management/sub-modules/:subModuleId", a.authMiddleware, a.RequirePermission("subcourses.update"), h.UpdateSubModule)
groupV1.Delete("/course-management/sub-modules/:subModuleId", a.authMiddleware, a.RequirePermission("subcourses.delete"), h.DeleteSubModule)
groupV1.Get("/course-management/sub-modules/:subModuleId/videos", a.authMiddleware, a.RequirePermission("videos.list"), h.GetSubModuleVideos)
groupV1.Post("/course-management/sub-module-videos", a.authMiddleware, a.RequirePermission("videos.create"), h.CreateSubModuleVideo)
groupV1.Put("/course-management/sub-module-videos/:videoId", a.authMiddleware, a.RequirePermission("videos.update"), h.UpdateSubModuleVideo)
groupV1.Delete("/course-management/sub-module-videos/:videoId", a.authMiddleware, a.RequirePermission("videos.delete"), h.DeleteSubModuleVideo)
groupV1.Post("/course-management/sub-module-lessons", a.authMiddleware, a.RequirePermission("question_sets.update"), h.AttachSubModuleLesson)
groupV1.Post("/course-management/sub-module-practices", a.authMiddleware, a.RequirePermission("question_sets.update"), h.CreateSubModulePractice)
groupV1.Put("/course-management/practices/:practiceId", a.authMiddleware, a.RequirePermission("question_sets.update"), h.UpdatePractice)
groupV1.Delete("/course-management/practices/:practiceId", a.authMiddleware, a.RequirePermission("question_sets.delete"), h.DeletePractice)
// Questions
groupV1.Post("/questions", a.authMiddleware, a.RequirePermission("questions.create"), h.CreateQuestion)