added more structure to levels

This commit is contained in:
Yared Yemane 2026-04-17 08:33:58 -07:00
parent 518c3ee751
commit c5d3935062
10 changed files with 476 additions and 41 deletions

View File

@ -0,0 +1,4 @@
ALTER TABLE levels
DROP COLUMN IF EXISTS title,
DROP COLUMN IF EXISTS description,
DROP COLUMN IF EXISTS thumbnail;

View File

@ -0,0 +1,11 @@
ALTER TABLE levels
ADD COLUMN IF NOT EXISTS title VARCHAR(255),
ADD COLUMN IF NOT EXISTS description TEXT,
ADD COLUMN IF NOT EXISTS thumbnail TEXT;
UPDATE levels
SET title = cefr_level
WHERE title IS NULL OR trim(title) = '';
ALTER TABLE levels
ALTER COLUMN title SET NOT NULL;

View File

@ -142,6 +142,9 @@ SELECT
c.title AS course_title, c.title AS course_title,
l.id AS level_id, l.id AS level_id,
l.cefr_level, l.cefr_level,
l.title AS level_title,
l.description AS level_description,
l.thumbnail AS level_thumbnail,
m.id AS module_id, m.id AS module_id,
m.title AS module_title, m.title AS module_title,
sm.id AS sub_module_id, sm.id AS sub_module_id,
@ -206,10 +209,24 @@ OFFSET sqlc.narg('offset')::INT;
INSERT INTO levels ( INSERT INTO levels (
course_id, course_id,
cefr_level, cefr_level,
title,
description,
thumbnail,
display_order, display_order,
is_active is_active
) )
VALUES ($1, $2, COALESCE($3, 0), COALESCE($4, TRUE)) VALUES ($1, $2, $3, $4, $5, COALESCE($6, 0), COALESCE($7, TRUE))
RETURNING *;
-- name: UpdateLevel :one
UPDATE levels
SET
title = $1,
description = $2,
thumbnail = $3,
display_order = $4,
is_active = $5
WHERE id = $6
RETURNING *; RETURNING *;
-- name: CreateModule :one -- name: CreateModule :one

View File

@ -1379,7 +1379,7 @@ const docTemplate = `{
} }
}, },
"post": { "post": {
"description": "Creates a CEFR level under a course", "description": "Creates a CEFR level under a course with optional title (defaults to cefr_level), description, and thumbnail",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@ -1468,6 +1468,63 @@ const docTemplate = `{
} }
} }
} }
},
"put": {
"description": "Updates level title, description, thumbnail, display order, and active flag",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Update level",
"parameters": [
{
"type": "integer",
"description": "Level ID",
"name": "levelId",
"in": "path",
"required": true
},
{
"description": "Update level payload",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.updateLevelReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
} }
}, },
"/api/v1/course-management/levels/{levelId}/modules": { "/api/v1/course-management/levels/{levelId}/modules": {
@ -10358,11 +10415,20 @@ const docTemplate = `{
"course_id": { "course_id": {
"type": "integer" "type": "integer"
}, },
"description": {
"type": "string"
},
"display_order": { "display_order": {
"type": "integer" "type": "integer"
}, },
"is_active": { "is_active": {
"type": "boolean" "type": "boolean"
},
"thumbnail": {
"type": "string"
},
"title": {
"type": "string"
} }
} }
}, },
@ -10987,6 +11053,26 @@ const docTemplate = `{
} }
} }
}, },
"handlers.updateLevelReq": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"display_order": {
"type": "integer"
},
"is_active": {
"type": "boolean"
},
"thumbnail": {
"type": "string"
},
"title": {
"type": "string"
}
}
},
"handlers.updatePlanReq": { "handlers.updatePlanReq": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -1371,7 +1371,7 @@
} }
}, },
"post": { "post": {
"description": "Creates a CEFR level under a course", "description": "Creates a CEFR level under a course with optional title (defaults to cefr_level), description, and thumbnail",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@ -1460,6 +1460,63 @@
} }
} }
} }
},
"put": {
"description": "Updates level title, description, thumbnail, display order, and active flag",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Update level",
"parameters": [
{
"type": "integer",
"description": "Level ID",
"name": "levelId",
"in": "path",
"required": true
},
{
"description": "Update level payload",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.updateLevelReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
} }
}, },
"/api/v1/course-management/levels/{levelId}/modules": { "/api/v1/course-management/levels/{levelId}/modules": {
@ -10350,11 +10407,20 @@
"course_id": { "course_id": {
"type": "integer" "type": "integer"
}, },
"description": {
"type": "string"
},
"display_order": { "display_order": {
"type": "integer" "type": "integer"
}, },
"is_active": { "is_active": {
"type": "boolean" "type": "boolean"
},
"thumbnail": {
"type": "string"
},
"title": {
"type": "string"
} }
} }
}, },
@ -10979,6 +11045,26 @@
} }
} }
}, },
"handlers.updateLevelReq": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"display_order": {
"type": "integer"
},
"is_active": {
"type": "boolean"
},
"thumbnail": {
"type": "string"
},
"title": {
"type": "string"
}
}
},
"handlers.updatePlanReq": { "handlers.updatePlanReq": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -993,10 +993,16 @@ definitions:
type: string type: string
course_id: course_id:
type: integer type: integer
description:
type: string
display_order: display_order:
type: integer type: integer
is_active: is_active:
type: boolean type: boolean
thumbnail:
type: string
title:
type: string
type: object type: object
handlers.createModuleReq: handlers.createModuleReq:
properties: properties:
@ -1418,6 +1424,19 @@ definitions:
required: required:
- status - status
type: object type: object
handlers.updateLevelReq:
properties:
description:
type: string
display_order:
type: integer
is_active:
type: boolean
thumbnail:
type: string
title:
type: string
type: object
handlers.updatePlanReq: handlers.updatePlanReq:
properties: properties:
currency: currency:
@ -2839,7 +2858,8 @@ paths:
post: post:
consumes: consumes:
- application/json - application/json
description: Creates a CEFR level under a course description: Creates a CEFR level under a course with optional title (defaults
to cefr_level), description, and thumbnail
parameters: parameters:
- description: Create level payload - description: Create level payload
in: body in: body
@ -2896,6 +2916,45 @@ paths:
summary: Get level detail summary: Get level detail
tags: tags:
- course-management - course-management
put:
consumes:
- application/json
description: Updates level title, description, thumbnail, display order, and
active flag
parameters:
- description: Level ID
in: path
name: levelId
required: true
type: integer
- description: Update level payload
in: body
name: body
required: true
schema:
$ref: '#/definitions/handlers.updateLevelReq'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/domain.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/domain.ErrorResponse'
"404":
description: Not Found
schema:
$ref: '#/definitions/domain.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/domain.ErrorResponse'
summary: Update level
tags:
- course-management
/api/v1/course-management/levels/{levelId}/modules: /api/v1/course-management/levels/{levelId}/modules:
get: get:
description: Returns all active modules for one level description: Returns all active modules for one level

View File

@ -56,26 +56,35 @@ const CreateLevel = `-- name: CreateLevel :one
INSERT INTO levels ( INSERT INTO levels (
course_id, course_id,
cefr_level, cefr_level,
title,
description,
thumbnail,
display_order, display_order,
is_active is_active
) )
VALUES ($1, $2, COALESCE($3, 0), COALESCE($4, TRUE)) VALUES ($1, $2, $3, $4, $5, COALESCE($6, 0), COALESCE($7, TRUE))
RETURNING id, course_id, cefr_level, display_order, is_active, created_at RETURNING id, course_id, cefr_level, display_order, is_active, created_at, title, description, thumbnail
` `
type CreateLevelParams struct { type CreateLevelParams struct {
CourseID int64 `json:"course_id"` CourseID int64 `json:"course_id"`
CefrLevel string `json:"cefr_level"` CefrLevel string `json:"cefr_level"`
Column3 interface{} `json:"column_3"` Title string `json:"title"`
Column4 interface{} `json:"column_4"` Description pgtype.Text `json:"description"`
Thumbnail pgtype.Text `json:"thumbnail"`
Column6 interface{} `json:"column_6"`
Column7 interface{} `json:"column_7"`
} }
func (q *Queries) CreateLevel(ctx context.Context, arg CreateLevelParams) (Level, error) { func (q *Queries) CreateLevel(ctx context.Context, arg CreateLevelParams) (Level, error) {
row := q.db.QueryRow(ctx, CreateLevel, row := q.db.QueryRow(ctx, CreateLevel,
arg.CourseID, arg.CourseID,
arg.CefrLevel, arg.CefrLevel,
arg.Column3, arg.Title,
arg.Column4, arg.Description,
arg.Thumbnail,
arg.Column6,
arg.Column7,
) )
var i Level var i Level
err := row.Scan( err := row.Scan(
@ -85,6 +94,9 @@ func (q *Queries) CreateLevel(ctx context.Context, arg CreateLevelParams) (Level
&i.DisplayOrder, &i.DisplayOrder,
&i.IsActive, &i.IsActive,
&i.CreatedAt, &i.CreatedAt,
&i.Title,
&i.Description,
&i.Thumbnail,
) )
return i, err return i, err
} }
@ -398,7 +410,7 @@ func (q *Queries) CreateSubModuleVideo(ctx context.Context, arg CreateSubModuleV
const GetAllLevels = `-- name: GetAllLevels :many const GetAllLevels = `-- name: GetAllLevels :many
SELECT SELECT
COUNT(*) OVER () AS total_count, COUNT(*) OVER () AS total_count,
l.id, l.course_id, l.cefr_level, l.display_order, l.is_active, l.created_at l.id, l.course_id, l.cefr_level, l.display_order, l.is_active, l.created_at, l.title, l.description, l.thumbnail
FROM levels l FROM levels l
ORDER BY l.display_order ASC, l.id ASC ORDER BY l.display_order ASC, l.id ASC
LIMIT $2::INT LIMIT $2::INT
@ -418,6 +430,9 @@ type GetAllLevelsRow struct {
DisplayOrder int32 `json:"display_order"` DisplayOrder int32 `json:"display_order"`
IsActive bool `json:"is_active"` IsActive bool `json:"is_active"`
CreatedAt pgtype.Timestamptz `json:"created_at"` CreatedAt pgtype.Timestamptz `json:"created_at"`
Title string `json:"title"`
Description pgtype.Text `json:"description"`
Thumbnail pgtype.Text `json:"thumbnail"`
} }
func (q *Queries) GetAllLevels(ctx context.Context, arg GetAllLevelsParams) ([]GetAllLevelsRow, error) { func (q *Queries) GetAllLevels(ctx context.Context, arg GetAllLevelsParams) ([]GetAllLevelsRow, error) {
@ -437,6 +452,9 @@ func (q *Queries) GetAllLevels(ctx context.Context, arg GetAllLevelsParams) ([]G
&i.DisplayOrder, &i.DisplayOrder,
&i.IsActive, &i.IsActive,
&i.CreatedAt, &i.CreatedAt,
&i.Title,
&i.Description,
&i.Thumbnail,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@ -683,6 +701,9 @@ SELECT
c.title AS course_title, c.title AS course_title,
l.id AS level_id, l.id AS level_id,
l.cefr_level, l.cefr_level,
l.title AS level_title,
l.description AS level_description,
l.thumbnail AS level_thumbnail,
m.id AS module_id, m.id AS module_id,
m.title AS module_title, m.title AS module_title,
sm.id AS sub_module_id, sm.id AS sub_module_id,
@ -700,6 +721,9 @@ type GetFullHierarchyByCourseIDRow struct {
CourseTitle string `json:"course_title"` CourseTitle string `json:"course_title"`
LevelID pgtype.Int8 `json:"level_id"` LevelID pgtype.Int8 `json:"level_id"`
CefrLevel pgtype.Text `json:"cefr_level"` CefrLevel pgtype.Text `json:"cefr_level"`
LevelTitle pgtype.Text `json:"level_title"`
LevelDescription pgtype.Text `json:"level_description"`
LevelThumbnail pgtype.Text `json:"level_thumbnail"`
ModuleID pgtype.Int8 `json:"module_id"` ModuleID pgtype.Int8 `json:"module_id"`
ModuleTitle pgtype.Text `json:"module_title"` ModuleTitle pgtype.Text `json:"module_title"`
SubModuleID pgtype.Int8 `json:"sub_module_id"` SubModuleID pgtype.Int8 `json:"sub_module_id"`
@ -720,6 +744,9 @@ func (q *Queries) GetFullHierarchyByCourseID(ctx context.Context, id int64) ([]G
&i.CourseTitle, &i.CourseTitle,
&i.LevelID, &i.LevelID,
&i.CefrLevel, &i.CefrLevel,
&i.LevelTitle,
&i.LevelDescription,
&i.LevelThumbnail,
&i.ModuleID, &i.ModuleID,
&i.ModuleTitle, &i.ModuleTitle,
&i.SubModuleID, &i.SubModuleID,
@ -804,7 +831,7 @@ func (q *Queries) GetHumanLanguageCourseSubCategories(ctx context.Context, arg G
} }
const GetLevelByID = `-- name: GetLevelByID :one const GetLevelByID = `-- name: GetLevelByID :one
SELECT id, course_id, cefr_level, display_order, is_active, created_at SELECT id, course_id, cefr_level, display_order, is_active, created_at, title, description, thumbnail
FROM levels FROM levels
WHERE id = $1 WHERE id = $1
` `
@ -819,12 +846,15 @@ func (q *Queries) GetLevelByID(ctx context.Context, id int64) (Level, error) {
&i.DisplayOrder, &i.DisplayOrder,
&i.IsActive, &i.IsActive,
&i.CreatedAt, &i.CreatedAt,
&i.Title,
&i.Description,
&i.Thumbnail,
) )
return i, err return i, err
} }
const GetLevelsByCourseID = `-- name: GetLevelsByCourseID :many const GetLevelsByCourseID = `-- name: GetLevelsByCourseID :many
SELECT id, course_id, cefr_level, display_order, is_active, created_at SELECT id, course_id, cefr_level, display_order, is_active, created_at, title, description, thumbnail
FROM levels FROM levels
WHERE course_id = $1 WHERE course_id = $1
AND is_active = TRUE AND is_active = TRUE
@ -847,6 +877,9 @@ func (q *Queries) GetLevelsByCourseID(ctx context.Context, courseID int64) ([]Le
&i.DisplayOrder, &i.DisplayOrder,
&i.IsActive, &i.IsActive,
&i.CreatedAt, &i.CreatedAt,
&i.Title,
&i.Description,
&i.Thumbnail,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@ -1217,6 +1250,51 @@ func (q *Queries) GetSubModulesByModuleID(ctx context.Context, moduleID int64) (
return items, nil return items, nil
} }
const UpdateLevel = `-- name: UpdateLevel :one
UPDATE levels
SET
title = $1,
description = $2,
thumbnail = $3,
display_order = $4,
is_active = $5
WHERE id = $6
RETURNING id, course_id, cefr_level, display_order, is_active, created_at, title, description, thumbnail
`
type UpdateLevelParams struct {
Title string `json:"title"`
Description pgtype.Text `json:"description"`
Thumbnail pgtype.Text `json:"thumbnail"`
DisplayOrder int32 `json:"display_order"`
IsActive bool `json:"is_active"`
ID int64 `json:"id"`
}
func (q *Queries) UpdateLevel(ctx context.Context, arg UpdateLevelParams) (Level, error) {
row := q.db.QueryRow(ctx, UpdateLevel,
arg.Title,
arg.Description,
arg.Thumbnail,
arg.DisplayOrder,
arg.IsActive,
arg.ID,
)
var i Level
err := row.Scan(
&i.ID,
&i.CourseID,
&i.CefrLevel,
&i.DisplayOrder,
&i.IsActive,
&i.CreatedAt,
&i.Title,
&i.Description,
&i.Thumbnail,
)
return i, err
}
const UpdateSubModuleLesson = `-- name: UpdateSubModuleLesson :one const UpdateSubModuleLesson = `-- name: UpdateSubModuleLesson :one
UPDATE sub_module_lessons UPDATE sub_module_lessons
SET SET

View File

@ -76,6 +76,9 @@ type Level struct {
DisplayOrder int32 `json:"display_order"` DisplayOrder int32 `json:"display_order"`
IsActive bool `json:"is_active"` IsActive bool `json:"is_active"`
CreatedAt pgtype.Timestamptz `json:"created_at"` CreatedAt pgtype.Timestamptz `json:"created_at"`
Title string `json:"title"`
Description pgtype.Text `json:"description"`
Thumbnail pgtype.Text `json:"thumbnail"`
} }
type LevelToSubCourse struct { type LevelToSubCourse struct {

View File

@ -48,6 +48,17 @@ type updateCourseThumbnailReq struct {
type createLevelReq struct { type createLevelReq struct {
CourseID int64 `json:"course_id"` CourseID int64 `json:"course_id"`
CEFRLevel string `json:"cefr_level"` CEFRLevel string `json:"cefr_level"`
Title *string `json:"title"`
Description *string `json:"description"`
Thumbnail *string `json:"thumbnail"`
DisplayOrder *int32 `json:"display_order"`
IsActive *bool `json:"is_active"`
}
type updateLevelReq struct {
Title *string `json:"title"`
Description *string `json:"description"`
Thumbnail *string `json:"thumbnail"`
DisplayOrder *int32 `json:"display_order"` DisplayOrder *int32 `json:"display_order"`
IsActive *bool `json:"is_active"` IsActive *bool `json:"is_active"`
} }
@ -1146,6 +1157,9 @@ func (h *Handler) UnifiedHierarchyByCourse(c *fiber.Ctx) error {
"course_title": course.Title, "course_title": course.Title,
"level_id": nil, "level_id": nil,
"cefr_level": nil, "cefr_level": nil,
"level_title": nil,
"level_description": nil,
"level_thumbnail": nil,
"module_id": nil, "module_id": nil,
"module_title": nil, "module_title": nil,
"sub_module_id": nil, "sub_module_id": nil,
@ -1203,7 +1217,9 @@ func (h *Handler) CourseLearningPath(c *fiber.Ctx) error {
title = row.SubModuleTitle.String title = row.SubModuleTitle.String
} }
level := "" level := ""
if row.CefrLevel.Valid { if row.LevelTitle.Valid && strings.TrimSpace(row.LevelTitle.String) != "" {
level = strings.TrimSpace(row.LevelTitle.String)
} else if row.CefrLevel.Valid {
level = row.CefrLevel.String level = row.CefrLevel.String
} }
subCourseByID[subModuleID] = &domain.LearningPathSubCourse{ subCourseByID[subModuleID] = &domain.LearningPathSubCourse{
@ -1381,7 +1397,7 @@ func (h *Handler) DeleteCourseSubCategory(c *fiber.Ctx) error {
// CreateLevel godoc // CreateLevel godoc
// @Summary Create level // @Summary Create level
// @Description Creates a CEFR level under a course // @Description Creates a CEFR level under a course with optional title (defaults to cefr_level), description, and thumbnail
// @Tags course-management // @Tags course-management
// @Accept json // @Accept json
// @Produce json // @Produce json
@ -1400,11 +1416,20 @@ func (h *Handler) CreateLevel(c *fiber.Ctx) error {
if req.CourseID <= 0 || !validCEFR[req.CEFRLevel] { if req.CourseID <= 0 || !validCEFR[req.CEFRLevel] {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "course_id and valid cefr_level are required"}) return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "course_id and valid cefr_level are required"})
} }
title := req.CEFRLevel
if req.Title != nil {
if t := strings.TrimSpace(*req.Title); t != "" {
title = t
}
}
created, err := h.analyticsDB.CreateLevel(c.Context(), dbgen.CreateLevelParams{ created, err := h.analyticsDB.CreateLevel(c.Context(), dbgen.CreateLevelParams{
CourseID: req.CourseID, CourseID: req.CourseID,
CefrLevel: req.CEFRLevel, CefrLevel: req.CEFRLevel,
Column3: intOrNil(req.DisplayOrder), Title: title,
Column4: boolOrNil(req.IsActive), Description: toText(req.Description),
Thumbnail: toText(req.Thumbnail),
Column6: intOrNil(req.DisplayOrder),
Column7: boolOrNil(req.IsActive),
}) })
if err != nil { if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to create level", Error: err.Error()}) return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{Message: "Failed to create level", Error: err.Error()})
@ -1412,6 +1437,71 @@ func (h *Handler) CreateLevel(c *fiber.Ctx) error {
return c.Status(fiber.StatusCreated).JSON(domain.Response{Message: "Level created", Data: created}) return c.Status(fiber.StatusCreated).JSON(domain.Response{Message: "Level created", Data: created})
} }
// UpdateLevel godoc
// @Summary Update level
// @Description Updates level title, description, thumbnail, display order, and active flag
// @Tags course-management
// @Accept json
// @Produce json
// @Param levelId path int true "Level ID"
// @Param body body updateLevelReq true "Update level payload"
// @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/levels/{levelId} [put]
func (h *Handler) UpdateLevel(c *fiber.Ctx) error {
levelID, err := strconv.ParseInt(c.Params("levelId"), 10, 64)
if err != nil || levelID <= 0 {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "Invalid level ID", Error: "levelId must be a positive integer"})
}
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()})
}
current, err := h.analyticsDB.GetLevelByID(c.Context(), levelID)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{Message: "Level not found", Error: err.Error()})
}
targetTitle := current.Title
if req.Title != nil {
t := strings.TrimSpace(*req.Title)
if t == "" {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{Message: "title cannot be empty"})
}
targetTitle = t
}
targetDescription := mergeTextField(current.Description, req.Description)
targetThumbnail := mergeTextField(current.Thumbnail, req.Thumbnail)
targetDisplayOrder := current.DisplayOrder
if req.DisplayOrder != nil {
targetDisplayOrder = *req.DisplayOrder
}
targetIsActive := current.IsActive
if req.IsActive != nil {
targetIsActive = *req.IsActive
}
updated, err := h.analyticsDB.UpdateLevel(c.Context(), dbgen.UpdateLevelParams{
Title: targetTitle,
Description: targetDescription,
Thumbnail: targetThumbnail,
DisplayOrder: targetDisplayOrder,
IsActive: targetIsActive,
ID: levelID,
})
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", Data: updated})
}
// CreateModule godoc // CreateModule godoc
// @Summary Create module // @Summary Create module
// @Description Creates a module under a level // @Description Creates a module under a level

View File

@ -108,6 +108,7 @@ func (a *App) initAppRoutes() {
groupV1.Post("/course-management/sub-categories", a.authMiddleware, a.RequirePermission("course_categories.create"), h.CreateCourseSubCategory) groupV1.Post("/course-management/sub-categories", a.authMiddleware, a.RequirePermission("course_categories.create"), h.CreateCourseSubCategory)
groupV1.Delete("/course-management/sub-categories/:subCategoryId", a.authMiddleware, a.RequirePermission("course_categories.delete"), h.DeleteCourseSubCategory) groupV1.Delete("/course-management/sub-categories/:subCategoryId", a.authMiddleware, a.RequirePermission("course_categories.delete"), h.DeleteCourseSubCategory)
groupV1.Post("/course-management/levels", a.authMiddleware, a.RequirePermission("subcourses.create"), h.CreateLevel) groupV1.Post("/course-management/levels", a.authMiddleware, a.RequirePermission("subcourses.create"), h.CreateLevel)
groupV1.Put("/course-management/levels/:levelId", a.authMiddleware, a.RequirePermission("subcourses.update"), h.UpdateLevel)
groupV1.Post("/course-management/modules", a.authMiddleware, a.RequirePermission("subcourses.create"), h.CreateModule) groupV1.Post("/course-management/modules", a.authMiddleware, a.RequirePermission("subcourses.create"), h.CreateModule)
groupV1.Delete("/course-management/modules/:moduleId", a.authMiddleware, a.RequirePermission("subcourses.delete"), h.DeleteModule) groupV1.Delete("/course-management/modules/:moduleId", a.authMiddleware, a.RequirePermission("subcourses.delete"), h.DeleteModule)
groupV1.Post("/course-management/sub-modules", a.authMiddleware, a.RequirePermission("subcourses.create"), h.CreateSubModule) groupV1.Post("/course-management/sub-modules", a.authMiddleware, a.RequirePermission("subcourses.create"), h.CreateSubModule)