added more structure to levels
This commit is contained in:
parent
518c3ee751
commit
c5d3935062
|
|
@ -0,0 +1,4 @@
|
|||
ALTER TABLE levels
|
||||
DROP COLUMN IF EXISTS title,
|
||||
DROP COLUMN IF EXISTS description,
|
||||
DROP COLUMN IF EXISTS thumbnail;
|
||||
|
|
@ -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;
|
||||
|
|
@ -142,6 +142,9 @@ SELECT
|
|||
c.title AS course_title,
|
||||
l.id AS level_id,
|
||||
l.cefr_level,
|
||||
l.title AS level_title,
|
||||
l.description AS level_description,
|
||||
l.thumbnail AS level_thumbnail,
|
||||
m.id AS module_id,
|
||||
m.title AS module_title,
|
||||
sm.id AS sub_module_id,
|
||||
|
|
@ -206,10 +209,24 @@ OFFSET sqlc.narg('offset')::INT;
|
|||
INSERT INTO levels (
|
||||
course_id,
|
||||
cefr_level,
|
||||
title,
|
||||
description,
|
||||
thumbnail,
|
||||
display_order,
|
||||
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 *;
|
||||
|
||||
-- name: CreateModule :one
|
||||
|
|
|
|||
88
docs/docs.go
88
docs/docs.go
|
|
@ -1379,7 +1379,7 @@ const docTemplate = `{
|
|||
}
|
||||
},
|
||||
"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": [
|
||||
"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": {
|
||||
|
|
@ -10358,11 +10415,20 @@ const docTemplate = `{
|
|||
"course_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"display_order": {
|
||||
"type": "integer"
|
||||
},
|
||||
"is_active": {
|
||||
"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": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
|
|
@ -1371,7 +1371,7 @@
|
|||
}
|
||||
},
|
||||
"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": [
|
||||
"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": {
|
||||
|
|
@ -10350,11 +10407,20 @@
|
|||
"course_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"display_order": {
|
||||
"type": "integer"
|
||||
},
|
||||
"is_active": {
|
||||
"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": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
|
|
@ -993,10 +993,16 @@ definitions:
|
|||
type: string
|
||||
course_id:
|
||||
type: integer
|
||||
description:
|
||||
type: string
|
||||
display_order:
|
||||
type: integer
|
||||
is_active:
|
||||
type: boolean
|
||||
thumbnail:
|
||||
type: string
|
||||
title:
|
||||
type: string
|
||||
type: object
|
||||
handlers.createModuleReq:
|
||||
properties:
|
||||
|
|
@ -1418,6 +1424,19 @@ definitions:
|
|||
required:
|
||||
- status
|
||||
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:
|
||||
properties:
|
||||
currency:
|
||||
|
|
@ -2839,7 +2858,8 @@ paths:
|
|||
post:
|
||||
consumes:
|
||||
- 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:
|
||||
- description: Create level payload
|
||||
in: body
|
||||
|
|
@ -2896,6 +2916,45 @@ paths:
|
|||
summary: Get level detail
|
||||
tags:
|
||||
- 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:
|
||||
get:
|
||||
description: Returns all active modules for one level
|
||||
|
|
|
|||
|
|
@ -56,26 +56,35 @@ const CreateLevel = `-- name: CreateLevel :one
|
|||
INSERT INTO levels (
|
||||
course_id,
|
||||
cefr_level,
|
||||
title,
|
||||
description,
|
||||
thumbnail,
|
||||
display_order,
|
||||
is_active
|
||||
)
|
||||
VALUES ($1, $2, COALESCE($3, 0), COALESCE($4, TRUE))
|
||||
RETURNING id, course_id, cefr_level, display_order, is_active, created_at
|
||||
VALUES ($1, $2, $3, $4, $5, COALESCE($6, 0), COALESCE($7, TRUE))
|
||||
RETURNING id, course_id, cefr_level, display_order, is_active, created_at, title, description, thumbnail
|
||||
`
|
||||
|
||||
type CreateLevelParams struct {
|
||||
CourseID int64 `json:"course_id"`
|
||||
CefrLevel string `json:"cefr_level"`
|
||||
Column3 interface{} `json:"column_3"`
|
||||
Column4 interface{} `json:"column_4"`
|
||||
CourseID int64 `json:"course_id"`
|
||||
CefrLevel string `json:"cefr_level"`
|
||||
Title string `json:"title"`
|
||||
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) {
|
||||
row := q.db.QueryRow(ctx, CreateLevel,
|
||||
arg.CourseID,
|
||||
arg.CefrLevel,
|
||||
arg.Column3,
|
||||
arg.Column4,
|
||||
arg.Title,
|
||||
arg.Description,
|
||||
arg.Thumbnail,
|
||||
arg.Column6,
|
||||
arg.Column7,
|
||||
)
|
||||
var i Level
|
||||
err := row.Scan(
|
||||
|
|
@ -85,6 +94,9 @@ func (q *Queries) CreateLevel(ctx context.Context, arg CreateLevelParams) (Level
|
|||
&i.DisplayOrder,
|
||||
&i.IsActive,
|
||||
&i.CreatedAt,
|
||||
&i.Title,
|
||||
&i.Description,
|
||||
&i.Thumbnail,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
@ -398,7 +410,7 @@ func (q *Queries) CreateSubModuleVideo(ctx context.Context, arg CreateSubModuleV
|
|||
const GetAllLevels = `-- name: GetAllLevels :many
|
||||
SELECT
|
||||
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
|
||||
ORDER BY l.display_order ASC, l.id ASC
|
||||
LIMIT $2::INT
|
||||
|
|
@ -418,6 +430,9 @@ type GetAllLevelsRow struct {
|
|||
DisplayOrder int32 `json:"display_order"`
|
||||
IsActive bool `json:"is_active"`
|
||||
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) {
|
||||
|
|
@ -437,6 +452,9 @@ func (q *Queries) GetAllLevels(ctx context.Context, arg GetAllLevelsParams) ([]G
|
|||
&i.DisplayOrder,
|
||||
&i.IsActive,
|
||||
&i.CreatedAt,
|
||||
&i.Title,
|
||||
&i.Description,
|
||||
&i.Thumbnail,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -683,6 +701,9 @@ SELECT
|
|||
c.title AS course_title,
|
||||
l.id AS level_id,
|
||||
l.cefr_level,
|
||||
l.title AS level_title,
|
||||
l.description AS level_description,
|
||||
l.thumbnail AS level_thumbnail,
|
||||
m.id AS module_id,
|
||||
m.title AS module_title,
|
||||
sm.id AS sub_module_id,
|
||||
|
|
@ -696,14 +717,17 @@ ORDER BY l.display_order, l.id, m.display_order, m.id, sm.display_order, sm.id
|
|||
`
|
||||
|
||||
type GetFullHierarchyByCourseIDRow struct {
|
||||
CourseID int64 `json:"course_id"`
|
||||
CourseTitle string `json:"course_title"`
|
||||
LevelID pgtype.Int8 `json:"level_id"`
|
||||
CefrLevel pgtype.Text `json:"cefr_level"`
|
||||
ModuleID pgtype.Int8 `json:"module_id"`
|
||||
ModuleTitle pgtype.Text `json:"module_title"`
|
||||
SubModuleID pgtype.Int8 `json:"sub_module_id"`
|
||||
SubModuleTitle pgtype.Text `json:"sub_module_title"`
|
||||
CourseID int64 `json:"course_id"`
|
||||
CourseTitle string `json:"course_title"`
|
||||
LevelID pgtype.Int8 `json:"level_id"`
|
||||
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"`
|
||||
ModuleTitle pgtype.Text `json:"module_title"`
|
||||
SubModuleID pgtype.Int8 `json:"sub_module_id"`
|
||||
SubModuleTitle pgtype.Text `json:"sub_module_title"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetFullHierarchyByCourseID(ctx context.Context, id int64) ([]GetFullHierarchyByCourseIDRow, error) {
|
||||
|
|
@ -720,6 +744,9 @@ func (q *Queries) GetFullHierarchyByCourseID(ctx context.Context, id int64) ([]G
|
|||
&i.CourseTitle,
|
||||
&i.LevelID,
|
||||
&i.CefrLevel,
|
||||
&i.LevelTitle,
|
||||
&i.LevelDescription,
|
||||
&i.LevelThumbnail,
|
||||
&i.ModuleID,
|
||||
&i.ModuleTitle,
|
||||
&i.SubModuleID,
|
||||
|
|
@ -804,7 +831,7 @@ func (q *Queries) GetHumanLanguageCourseSubCategories(ctx context.Context, arg G
|
|||
}
|
||||
|
||||
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
|
||||
WHERE id = $1
|
||||
`
|
||||
|
|
@ -819,12 +846,15 @@ func (q *Queries) GetLevelByID(ctx context.Context, id int64) (Level, error) {
|
|||
&i.DisplayOrder,
|
||||
&i.IsActive,
|
||||
&i.CreatedAt,
|
||||
&i.Title,
|
||||
&i.Description,
|
||||
&i.Thumbnail,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
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
|
||||
WHERE course_id = $1
|
||||
AND is_active = TRUE
|
||||
|
|
@ -847,6 +877,9 @@ func (q *Queries) GetLevelsByCourseID(ctx context.Context, courseID int64) ([]Le
|
|||
&i.DisplayOrder,
|
||||
&i.IsActive,
|
||||
&i.CreatedAt,
|
||||
&i.Title,
|
||||
&i.Description,
|
||||
&i.Thumbnail,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1217,6 +1250,51 @@ func (q *Queries) GetSubModulesByModuleID(ctx context.Context, moduleID int64) (
|
|||
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
|
||||
UPDATE sub_module_lessons
|
||||
SET
|
||||
|
|
|
|||
|
|
@ -76,6 +76,9 @@ type Level struct {
|
|||
DisplayOrder int32 `json:"display_order"`
|
||||
IsActive bool `json:"is_active"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
Title string `json:"title"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Thumbnail pgtype.Text `json:"thumbnail"`
|
||||
}
|
||||
|
||||
type LevelToSubCourse struct {
|
||||
|
|
|
|||
|
|
@ -46,10 +46,21 @@ type updateCourseThumbnailReq struct {
|
|||
}
|
||||
|
||||
type createLevelReq struct {
|
||||
CourseID int64 `json:"course_id"`
|
||||
CEFRLevel string `json:"cefr_level"`
|
||||
DisplayOrder *int32 `json:"display_order"`
|
||||
IsActive *bool `json:"is_active"`
|
||||
CourseID int64 `json:"course_id"`
|
||||
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"`
|
||||
IsActive *bool `json:"is_active"`
|
||||
}
|
||||
|
||||
type createModuleReq struct {
|
||||
|
|
@ -1142,14 +1153,17 @@ func (h *Handler) UnifiedHierarchyByCourse(c *fiber.Ctx) error {
|
|||
Message: "Course hierarchy retrieved successfully",
|
||||
Data: []map[string]interface{}{
|
||||
{
|
||||
"course_id": course.ID,
|
||||
"course_title": course.Title,
|
||||
"level_id": nil,
|
||||
"cefr_level": nil,
|
||||
"module_id": nil,
|
||||
"module_title": nil,
|
||||
"sub_module_id": nil,
|
||||
"sub_module_title": nil,
|
||||
"course_id": course.ID,
|
||||
"course_title": course.Title,
|
||||
"level_id": nil,
|
||||
"cefr_level": nil,
|
||||
"level_title": nil,
|
||||
"level_description": nil,
|
||||
"level_thumbnail": nil,
|
||||
"module_id": nil,
|
||||
"module_title": nil,
|
||||
"sub_module_id": nil,
|
||||
"sub_module_title": nil,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
@ -1203,7 +1217,9 @@ func (h *Handler) CourseLearningPath(c *fiber.Ctx) error {
|
|||
title = row.SubModuleTitle.String
|
||||
}
|
||||
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
|
||||
}
|
||||
subCourseByID[subModuleID] = &domain.LearningPathSubCourse{
|
||||
|
|
@ -1381,7 +1397,7 @@ func (h *Handler) DeleteCourseSubCategory(c *fiber.Ctx) error {
|
|||
|
||||
// CreateLevel godoc
|
||||
// @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
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
|
|
@ -1400,11 +1416,20 @@ func (h *Handler) CreateLevel(c *fiber.Ctx) error {
|
|||
if req.CourseID <= 0 || !validCEFR[req.CEFRLevel] {
|
||||
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{
|
||||
CourseID: req.CourseID,
|
||||
CefrLevel: req.CEFRLevel,
|
||||
Column3: intOrNil(req.DisplayOrder),
|
||||
Column4: boolOrNil(req.IsActive),
|
||||
CourseID: req.CourseID,
|
||||
CefrLevel: req.CEFRLevel,
|
||||
Title: title,
|
||||
Description: toText(req.Description),
|
||||
Thumbnail: toText(req.Thumbnail),
|
||||
Column6: intOrNil(req.DisplayOrder),
|
||||
Column7: boolOrNil(req.IsActive),
|
||||
})
|
||||
if err != nil {
|
||||
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})
|
||||
}
|
||||
|
||||
// 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
|
||||
// @Summary Create module
|
||||
// @Description Creates a module under a level
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ func (a *App) initAppRoutes() {
|
|||
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.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.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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user