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,
|
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
|
||||||
|
|
|
||||||
88
docs/docs.go
88
docs/docs.go
|
|
@ -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": {
|
||||||
|
|
|
||||||
|
|
@ -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": {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
@ -696,14 +717,17 @@ ORDER BY l.display_order, l.id, m.display_order, m.id, sm.display_order, sm.id
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetFullHierarchyByCourseIDRow struct {
|
type GetFullHierarchyByCourseIDRow struct {
|
||||||
CourseID int64 `json:"course_id"`
|
CourseID int64 `json:"course_id"`
|
||||||
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"`
|
||||||
ModuleID pgtype.Int8 `json:"module_id"`
|
LevelTitle pgtype.Text `json:"level_title"`
|
||||||
ModuleTitle pgtype.Text `json:"module_title"`
|
LevelDescription pgtype.Text `json:"level_description"`
|
||||||
SubModuleID pgtype.Int8 `json:"sub_module_id"`
|
LevelThumbnail pgtype.Text `json:"level_thumbnail"`
|
||||||
SubModuleTitle pgtype.Text `json:"sub_module_title"`
|
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) {
|
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.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
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -46,10 +46,21 @@ 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"`
|
||||||
DisplayOrder *int32 `json:"display_order"`
|
Title *string `json:"title"`
|
||||||
IsActive *bool `json:"is_active"`
|
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 {
|
type createModuleReq struct {
|
||||||
|
|
@ -1142,14 +1153,17 @@ func (h *Handler) UnifiedHierarchyByCourse(c *fiber.Ctx) error {
|
||||||
Message: "Course hierarchy retrieved successfully",
|
Message: "Course hierarchy retrieved successfully",
|
||||||
Data: []map[string]interface{}{
|
Data: []map[string]interface{}{
|
||||||
{
|
{
|
||||||
"course_id": course.ID,
|
"course_id": course.ID,
|
||||||
"course_title": course.Title,
|
"course_title": course.Title,
|
||||||
"level_id": nil,
|
"level_id": nil,
|
||||||
"cefr_level": nil,
|
"cefr_level": nil,
|
||||||
"module_id": nil,
|
"level_title": nil,
|
||||||
"module_title": nil,
|
"level_description": nil,
|
||||||
"sub_module_id": nil,
|
"level_thumbnail": nil,
|
||||||
"sub_module_title": 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
|
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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user