added more structure to submodules

This commit is contained in:
Yared Yemane 2026-04-17 09:07:25 -07:00
parent c5d3935062
commit 7ff0b639cf
18 changed files with 3843 additions and 79 deletions

View File

@ -0,0 +1,12 @@
DROP INDEX IF EXISTS idx_sub_module_capstones_sub_module_id;
DROP TABLE IF EXISTS sub_module_capstones;
ALTER TABLE question_sets DROP CONSTRAINT IF EXISTS question_sets_set_type_check;
ALTER TABLE question_sets ADD CONSTRAINT question_sets_set_type_check
CHECK (set_type IN (
'PRACTICE',
'INITIAL_ASSESSMENT',
'QUIZ',
'EXAM',
'SURVEY'
));

View File

@ -0,0 +1,29 @@
-- Capstone assessments: sub-module scoped, backed by question_sets (type CAPSTONE).
ALTER TABLE question_sets DROP CONSTRAINT IF EXISTS question_sets_set_type_check;
ALTER TABLE question_sets ADD CONSTRAINT question_sets_set_type_check
CHECK (set_type IN (
'PRACTICE',
'INITIAL_ASSESSMENT',
'QUIZ',
'EXAM',
'SURVEY',
'CAPSTONE'
));
CREATE TABLE IF NOT EXISTS sub_module_capstones (
id BIGSERIAL PRIMARY KEY,
sub_module_id BIGINT NOT NULL REFERENCES sub_modules(id) ON DELETE CASCADE,
title VARCHAR(255) NOT NULL,
description TEXT,
tips TEXT,
thumbnail TEXT,
question_set_id BIGINT NOT NULL REFERENCES question_sets(id) ON DELETE CASCADE,
display_order INT NOT NULL DEFAULT 0,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (question_set_id)
);
CREATE INDEX IF NOT EXISTS idx_sub_module_capstones_sub_module_id
ON sub_module_capstones (sub_module_id);

View File

@ -0,0 +1,4 @@
DROP INDEX IF EXISTS idx_module_capstones_module_id;
DROP TABLE IF EXISTS module_capstones;
ALTER TABLE modules DROP COLUMN IF EXISTS icon_url;

View File

@ -0,0 +1,19 @@
ALTER TABLE modules
ADD COLUMN IF NOT EXISTS icon_url TEXT;
CREATE TABLE IF NOT EXISTS module_capstones (
id BIGSERIAL PRIMARY KEY,
module_id BIGINT NOT NULL REFERENCES modules(id) ON DELETE CASCADE,
title VARCHAR(255) NOT NULL,
description TEXT,
tips TEXT,
thumbnail TEXT,
question_set_id BIGINT NOT NULL REFERENCES question_sets(id) ON DELETE CASCADE,
display_order INT NOT NULL DEFAULT 0,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (question_set_id)
);
CREATE INDEX IF NOT EXISTS idx_module_capstones_module_id
ON module_capstones (module_id);

View File

@ -0,0 +1,3 @@
ALTER TABLE sub_modules
DROP COLUMN IF EXISTS tips,
DROP COLUMN IF EXISTS thumbnail;

View File

@ -0,0 +1,3 @@
ALTER TABLE sub_modules
ADD COLUMN IF NOT EXISTS thumbnail TEXT,
ADD COLUMN IF NOT EXISTS tips TEXT;

View File

@ -136,6 +136,53 @@ WHERE smp.id = $1
AND smp.is_active = TRUE AND smp.is_active = TRUE
AND qs.set_type = 'PRACTICE'; AND qs.set_type = 'PRACTICE';
-- name: GetSubModuleCapstones :many
SELECT
smc.id,
smc.sub_module_id,
smc.title,
smc.description,
smc.tips,
smc.thumbnail,
smc.question_set_id,
smc.display_order,
smc.is_active,
qs.status,
qs.set_type,
qs.time_limit_minutes,
qs.passing_score,
qs.shuffle_questions,
(SELECT COUNT(*) FROM question_set_items qsi WHERE qsi.set_id = qs.id) AS question_count
FROM sub_module_capstones smc
JOIN question_sets qs ON qs.id = smc.question_set_id
WHERE smc.sub_module_id = $1
AND smc.is_active = TRUE
AND qs.set_type = 'CAPSTONE'
ORDER BY smc.display_order ASC, smc.id ASC;
-- name: GetSubModuleCapstoneByID :one
SELECT
smc.id,
smc.sub_module_id,
smc.title,
smc.description,
smc.tips,
smc.thumbnail,
smc.question_set_id,
smc.display_order,
smc.is_active,
qs.status,
qs.set_type,
qs.time_limit_minutes,
qs.passing_score,
qs.shuffle_questions,
(SELECT COUNT(*) FROM question_set_items qsi WHERE qsi.set_id = qs.id) AS question_count
FROM sub_module_capstones smc
JOIN question_sets qs ON qs.id = smc.question_set_id
WHERE smc.id = $1
AND smc.is_active = TRUE
AND qs.set_type = 'CAPSTONE';
-- name: GetFullHierarchyByCourseID :many -- name: GetFullHierarchyByCourseID :many
SELECT SELECT
c.id AS course_id, c.id AS course_id,
@ -147,8 +194,13 @@ SELECT
l.thumbnail AS level_thumbnail, 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,
m.icon_url AS module_icon_url,
sm.id AS sub_module_id, sm.id AS sub_module_id,
sm.title AS sub_module_title sm.title AS sub_module_title,
sm.description AS sub_module_description,
sm.thumbnail AS sub_module_thumbnail,
sm.tips AS sub_module_tips,
sm.display_order AS sub_module_display_order
FROM courses c FROM courses c
LEFT JOIN levels l ON l.course_id = c.id AND l.is_active = TRUE LEFT JOIN levels l ON l.course_id = c.id AND l.is_active = TRUE
LEFT JOIN modules m ON m.level_id = l.id AND m.is_active = TRUE LEFT JOIN modules m ON m.level_id = l.id AND m.is_active = TRUE
@ -234,10 +286,22 @@ INSERT INTO modules (
level_id, level_id,
title, title,
description, description,
icon_url,
display_order, display_order,
is_active is_active
) )
VALUES ($1, $2, $3, COALESCE($4, 0), COALESCE($5, TRUE)) VALUES ($1, $2, $3, $4, COALESCE($5, 0), COALESCE($6, TRUE))
RETURNING *;
-- name: UpdateModule :one
UPDATE modules
SET
title = $1,
description = $2,
icon_url = $3,
display_order = $4,
is_active = $5
WHERE id = $6
RETURNING *; RETURNING *;
-- name: CreateSubModule :one -- name: CreateSubModule :one
@ -245,10 +309,24 @@ INSERT INTO sub_modules (
module_id, module_id,
title, title,
description, description,
thumbnail,
tips,
display_order, display_order,
is_active is_active
) )
VALUES ($1, $2, $3, COALESCE($4, 0), COALESCE($5, TRUE)) VALUES ($1, $2, $3, $4, $5, COALESCE($6, 0), COALESCE($7, TRUE))
RETURNING *;
-- name: UpdateSubModule :one
UPDATE sub_modules
SET
title = $1,
description = $2,
thumbnail = $3,
tips = $4,
display_order = $5,
is_active = $6
WHERE id = $7
RETURNING *; RETURNING *;
-- name: CreateSubModuleVideo :one -- name: CreateSubModuleVideo :one
@ -337,3 +415,102 @@ INSERT INTO sub_module_practices (
VALUES ($1, $2, $3, $4, $5, $6, COALESCE($7, 0), COALESCE($8, TRUE)) VALUES ($1, $2, $3, $4, $5, $6, COALESCE($7, 0), COALESCE($8, TRUE))
RETURNING *; RETURNING *;
-- name: CreateSubModuleCapstone :one
INSERT INTO sub_module_capstones (
sub_module_id,
title,
description,
tips,
thumbnail,
question_set_id,
display_order,
is_active
)
VALUES ($1, $2, $3, $4, $5, $6, COALESCE($7, 0), COALESCE($8, TRUE))
RETURNING *;
-- name: UpdateSubModuleCapstone :one
UPDATE sub_module_capstones
SET
title = $1,
description = $2,
tips = $3,
thumbnail = $4,
display_order = $5,
is_active = $6
WHERE id = $7
RETURNING *;
-- name: GetModuleCapstones :many
SELECT
mc.id,
mc.module_id,
mc.title,
mc.description,
mc.tips,
mc.thumbnail,
mc.question_set_id,
mc.display_order,
mc.is_active,
qs.status,
qs.set_type,
qs.time_limit_minutes,
qs.passing_score,
qs.shuffle_questions,
(SELECT COUNT(*) FROM question_set_items qsi WHERE qsi.set_id = qs.id) AS question_count
FROM module_capstones mc
JOIN question_sets qs ON qs.id = mc.question_set_id
WHERE mc.module_id = $1
AND mc.is_active = TRUE
AND qs.set_type = 'CAPSTONE'
ORDER BY mc.display_order ASC, mc.id ASC;
-- name: GetModuleCapstoneByID :one
SELECT
mc.id,
mc.module_id,
mc.title,
mc.description,
mc.tips,
mc.thumbnail,
mc.question_set_id,
mc.display_order,
mc.is_active,
qs.status,
qs.set_type,
qs.time_limit_minutes,
qs.passing_score,
qs.shuffle_questions,
(SELECT COUNT(*) FROM question_set_items qsi WHERE qsi.set_id = qs.id) AS question_count
FROM module_capstones mc
JOIN question_sets qs ON qs.id = mc.question_set_id
WHERE mc.id = $1
AND mc.is_active = TRUE
AND qs.set_type = 'CAPSTONE';
-- name: CreateModuleCapstone :one
INSERT INTO module_capstones (
module_id,
title,
description,
tips,
thumbnail,
question_set_id,
display_order,
is_active
)
VALUES ($1, $2, $3, $4, $5, $6, COALESCE($7, 0), COALESCE($8, TRUE))
RETURNING *;
-- name: UpdateModuleCapstone :one
UPDATE module_capstones
SET
title = $1,
description = $2,
tips = $3,
thumbnail = $4,
display_order = $5,
is_active = $6
WHERE id = $7
RETURNING *;

View File

@ -713,6 +713,149 @@ const docTemplate = `{
} }
} }
}, },
"/api/v1/course-management/capstones/{capstoneId}": {
"get": {
"description": "Returns one capstone with question-set fields and the ordered question list",
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Get capstone detail",
"parameters": [
{
"type": "integer",
"description": "Capstone ID",
"name": "capstoneId",
"in": "path",
"required": true
}
],
"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"
}
}
}
},
"put": {
"description": "Updates capstone content, question-set assessment settings, and optionally replaces the question list",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Update capstone",
"parameters": [
{
"type": "integer",
"description": "Capstone ID",
"name": "capstoneId",
"in": "path",
"required": true
},
{
"description": "Update capstone payload",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.updateSubModuleCapstoneReq"
}
}
],
"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"
}
}
}
},
"delete": {
"description": "Deletes the capstone and its backing question set (and question items)",
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Delete capstone",
"parameters": [
{
"type": "integer",
"description": "Capstone ID",
"name": "capstoneId",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
}
},
"/api/v1/course-management/categories": { "/api/v1/course-management/categories": {
"get": { "get": {
"description": "Legacy-compatible endpoint for listing course categories", "description": "Legacy-compatible endpoint for listing course categories",
@ -1568,6 +1711,195 @@ const docTemplate = `{
} }
} }
}, },
"/api/v1/course-management/module-capstones": {
"post": {
"description": "Creates a module-level capstone with a new CAPSTONE question set and ordered questions",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Create module capstone",
"parameters": [
{
"description": "Create module capstone payload",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.createModuleCapstoneReq"
}
}
],
"responses": {
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/domain.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
}
},
"/api/v1/course-management/module-capstones/{moduleCapstoneId}": {
"get": {
"description": "Returns one module capstone with question-set fields and the ordered question list",
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Get module capstone detail",
"parameters": [
{
"type": "integer",
"description": "Module capstone ID",
"name": "moduleCapstoneId",
"in": "path",
"required": true
}
],
"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"
}
}
}
},
"put": {
"description": "Updates module capstone content, question-set assessment settings, and optionally replaces the question list",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Update module capstone",
"parameters": [
{
"type": "integer",
"description": "Module capstone ID",
"name": "moduleCapstoneId",
"in": "path",
"required": true
},
{
"description": "Update module capstone payload",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.updateModuleCapstoneReq"
}
}
],
"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"
}
}
}
},
"delete": {
"description": "Deletes the module capstone and its backing question set",
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Delete module capstone",
"parameters": [
{
"type": "integer",
"description": "Module capstone ID",
"name": "moduleCapstoneId",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
}
},
"/api/v1/course-management/modules": { "/api/v1/course-management/modules": {
"get": { "get": {
"description": "Returns all modules with pagination", "description": "Returns all modules with pagination",
@ -1608,7 +1940,7 @@ const docTemplate = `{
} }
}, },
"post": { "post": {
"description": "Creates a module under a level", "description": "Creates a module under a level; optional icon_url stores a module icon image URL",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@ -1697,6 +2029,104 @@ const docTemplate = `{
} }
} }
} }
},
"put": {
"description": "Updates module title, description, icon URL, display order, and active flag",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Update module",
"parameters": [
{
"type": "integer",
"description": "Module ID",
"name": "moduleId",
"in": "path",
"required": true
},
{
"description": "Update module payload",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.updateModuleReq"
}
}
],
"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/modules/{moduleId}/capstones": {
"get": {
"description": "Returns active module capstones with question-set settings and question counts",
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "List capstones under module",
"parameters": [
{
"type": "integer",
"description": "Module ID",
"name": "moduleId",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
} }
}, },
"/api/v1/course-management/modules/{moduleId}/sub-modules": { "/api/v1/course-management/modules/{moduleId}/sub-modules": {
@ -1927,6 +2357,52 @@ const docTemplate = `{
} }
} }
}, },
"/api/v1/course-management/sub-module-capstones": {
"post": {
"description": "Creates a capstone assessment with a new CAPSTONE question set, metadata, and ordered questions",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Create capstone under sub-module",
"parameters": [
{
"description": "Create capstone payload",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.createSubModuleCapstoneReq"
}
}
],
"responses": {
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/domain.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
}
},
"/api/v1/course-management/sub-module-lessons": { "/api/v1/course-management/sub-module-lessons": {
"post": { "post": {
"description": "Creates a sub-module lesson with teaching content (text, image, audio, video URLs) and optional thumbnail", "description": "Creates a sub-module lesson with teaching content (text, image, audio, video URLs) and optional thumbnail",
@ -2212,7 +2688,7 @@ const docTemplate = `{
} }
}, },
"post": { "post": {
"description": "Creates a sub-module under a module", "description": "Creates a sub-module under a module; optional thumbnail (image URL) and tips text",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@ -2303,6 +2779,47 @@ const docTemplate = `{
} }
} }
}, },
"/api/v1/course-management/sub-modules/{subModuleId}/capstones": {
"get": {
"description": "Returns active capstones for a sub-module with question-set settings and question counts",
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "List capstones under sub-module",
"parameters": [
{
"type": "integer",
"description": "Sub-module ID",
"name": "subModuleId",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
}
},
"/api/v1/course-management/sub-modules/{subModuleId}/lessons": { "/api/v1/course-management/sub-modules/{subModuleId}/lessons": {
"get": { "get": {
"description": "Returns all active lessons for a sub-module (teaching content metadata)", "description": "Returns all active lessons for a sub-module (teaching content metadata)",
@ -4040,7 +4557,7 @@ const docTemplate = `{
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
"description": "Set type (PRACTICE, INITIAL_ASSESSMENT, QUIZ, EXAM, SURVEY)", "description": "Set type (PRACTICE, INITIAL_ASSESSMENT, QUIZ, EXAM, SURVEY, CAPSTONE)",
"name": "set_type", "name": "set_type",
"in": "query", "in": "query",
"required": true "required": true
@ -10300,6 +10817,17 @@ const docTemplate = `{
} }
} }
}, },
"handlers.capstoneQuestionItem": {
"type": "object",
"properties": {
"display_order": {
"type": "integer"
},
"question_id": {
"type": "integer"
}
}
},
"handlers.changePasswordReq": { "handlers.changePasswordReq": {
"type": "object", "type": "object",
"required": [ "required": [
@ -10432,6 +10960,50 @@ const docTemplate = `{
} }
} }
}, },
"handlers.createModuleCapstoneReq": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"display_order": {
"type": "integer"
},
"is_active": {
"type": "boolean"
},
"module_id": {
"type": "integer"
},
"passing_score": {
"type": "integer"
},
"questions": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.capstoneQuestionItem"
}
},
"shuffle_questions": {
"type": "boolean"
},
"status": {
"type": "string"
},
"thumbnail": {
"type": "string"
},
"time_limit_minutes": {
"type": "integer"
},
"tips": {
"type": "string"
},
"title": {
"type": "string"
}
}
},
"handlers.createModuleReq": { "handlers.createModuleReq": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -10441,6 +11013,9 @@ const docTemplate = `{
"display_order": { "display_order": {
"type": "integer" "type": "integer"
}, },
"icon_url": {
"type": "string"
},
"is_active": { "is_active": {
"type": "boolean" "type": "boolean"
}, },
@ -10588,7 +11163,8 @@ const docTemplate = `{
"INITIAL_ASSESSMENT", "INITIAL_ASSESSMENT",
"QUIZ", "QUIZ",
"EXAM", "EXAM",
"SURVEY" "SURVEY",
"CAPSTONE"
] ]
}, },
"shuffle_questions": { "shuffle_questions": {
@ -10608,6 +11184,50 @@ const docTemplate = `{
} }
} }
}, },
"handlers.createSubModuleCapstoneReq": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"display_order": {
"type": "integer"
},
"is_active": {
"type": "boolean"
},
"passing_score": {
"type": "integer"
},
"questions": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.capstoneQuestionItem"
}
},
"shuffle_questions": {
"type": "boolean"
},
"status": {
"type": "string"
},
"sub_module_id": {
"type": "integer"
},
"thumbnail": {
"type": "string"
},
"time_limit_minutes": {
"type": "integer"
},
"tips": {
"type": "string"
},
"title": {
"type": "string"
}
}
},
"handlers.createSubModuleLessonReq": { "handlers.createSubModuleLessonReq": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -10687,6 +11307,12 @@ const docTemplate = `{
"module_id": { "module_id": {
"type": "integer" "type": "integer"
}, },
"thumbnail": {
"type": "string"
},
"tips": {
"type": "string"
},
"title": { "title": {
"type": "string" "type": "string"
} }
@ -11073,6 +11699,67 @@ const docTemplate = `{
} }
} }
}, },
"handlers.updateModuleCapstoneReq": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"display_order": {
"type": "integer"
},
"is_active": {
"type": "boolean"
},
"passing_score": {
"type": "integer"
},
"questions": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.capstoneQuestionItem"
}
},
"shuffle_questions": {
"type": "boolean"
},
"status": {
"type": "string"
},
"thumbnail": {
"type": "string"
},
"time_limit_minutes": {
"type": "integer"
},
"tips": {
"type": "string"
},
"title": {
"type": "string"
}
}
},
"handlers.updateModuleReq": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"display_order": {
"type": "integer"
},
"icon_url": {
"type": "string"
},
"is_active": {
"type": "boolean"
},
"title": {
"type": "string"
}
}
},
"handlers.updatePlanReq": { "handlers.updatePlanReq": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -11195,6 +11882,47 @@ const docTemplate = `{
} }
} }
}, },
"handlers.updateSubModuleCapstoneReq": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"display_order": {
"type": "integer"
},
"is_active": {
"type": "boolean"
},
"passing_score": {
"type": "integer"
},
"questions": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.capstoneQuestionItem"
}
},
"shuffle_questions": {
"type": "boolean"
},
"status": {
"type": "string"
},
"thumbnail": {
"type": "string"
},
"time_limit_minutes": {
"type": "integer"
},
"tips": {
"type": "string"
},
"title": {
"type": "string"
}
}
},
"handlers.updateSubModuleLessonReq": { "handlers.updateSubModuleLessonReq": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -705,6 +705,149 @@
} }
} }
}, },
"/api/v1/course-management/capstones/{capstoneId}": {
"get": {
"description": "Returns one capstone with question-set fields and the ordered question list",
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Get capstone detail",
"parameters": [
{
"type": "integer",
"description": "Capstone ID",
"name": "capstoneId",
"in": "path",
"required": true
}
],
"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"
}
}
}
},
"put": {
"description": "Updates capstone content, question-set assessment settings, and optionally replaces the question list",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Update capstone",
"parameters": [
{
"type": "integer",
"description": "Capstone ID",
"name": "capstoneId",
"in": "path",
"required": true
},
{
"description": "Update capstone payload",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.updateSubModuleCapstoneReq"
}
}
],
"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"
}
}
}
},
"delete": {
"description": "Deletes the capstone and its backing question set (and question items)",
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Delete capstone",
"parameters": [
{
"type": "integer",
"description": "Capstone ID",
"name": "capstoneId",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
}
},
"/api/v1/course-management/categories": { "/api/v1/course-management/categories": {
"get": { "get": {
"description": "Legacy-compatible endpoint for listing course categories", "description": "Legacy-compatible endpoint for listing course categories",
@ -1560,6 +1703,195 @@
} }
} }
}, },
"/api/v1/course-management/module-capstones": {
"post": {
"description": "Creates a module-level capstone with a new CAPSTONE question set and ordered questions",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Create module capstone",
"parameters": [
{
"description": "Create module capstone payload",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.createModuleCapstoneReq"
}
}
],
"responses": {
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/domain.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
}
},
"/api/v1/course-management/module-capstones/{moduleCapstoneId}": {
"get": {
"description": "Returns one module capstone with question-set fields and the ordered question list",
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Get module capstone detail",
"parameters": [
{
"type": "integer",
"description": "Module capstone ID",
"name": "moduleCapstoneId",
"in": "path",
"required": true
}
],
"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"
}
}
}
},
"put": {
"description": "Updates module capstone content, question-set assessment settings, and optionally replaces the question list",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Update module capstone",
"parameters": [
{
"type": "integer",
"description": "Module capstone ID",
"name": "moduleCapstoneId",
"in": "path",
"required": true
},
{
"description": "Update module capstone payload",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.updateModuleCapstoneReq"
}
}
],
"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"
}
}
}
},
"delete": {
"description": "Deletes the module capstone and its backing question set",
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Delete module capstone",
"parameters": [
{
"type": "integer",
"description": "Module capstone ID",
"name": "moduleCapstoneId",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
}
},
"/api/v1/course-management/modules": { "/api/v1/course-management/modules": {
"get": { "get": {
"description": "Returns all modules with pagination", "description": "Returns all modules with pagination",
@ -1600,7 +1932,7 @@
} }
}, },
"post": { "post": {
"description": "Creates a module under a level", "description": "Creates a module under a level; optional icon_url stores a module icon image URL",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@ -1689,6 +2021,104 @@
} }
} }
} }
},
"put": {
"description": "Updates module title, description, icon URL, display order, and active flag",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Update module",
"parameters": [
{
"type": "integer",
"description": "Module ID",
"name": "moduleId",
"in": "path",
"required": true
},
{
"description": "Update module payload",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.updateModuleReq"
}
}
],
"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/modules/{moduleId}/capstones": {
"get": {
"description": "Returns active module capstones with question-set settings and question counts",
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "List capstones under module",
"parameters": [
{
"type": "integer",
"description": "Module ID",
"name": "moduleId",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
} }
}, },
"/api/v1/course-management/modules/{moduleId}/sub-modules": { "/api/v1/course-management/modules/{moduleId}/sub-modules": {
@ -1919,6 +2349,52 @@
} }
} }
}, },
"/api/v1/course-management/sub-module-capstones": {
"post": {
"description": "Creates a capstone assessment with a new CAPSTONE question set, metadata, and ordered questions",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Create capstone under sub-module",
"parameters": [
{
"description": "Create capstone payload",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.createSubModuleCapstoneReq"
}
}
],
"responses": {
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/domain.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
}
},
"/api/v1/course-management/sub-module-lessons": { "/api/v1/course-management/sub-module-lessons": {
"post": { "post": {
"description": "Creates a sub-module lesson with teaching content (text, image, audio, video URLs) and optional thumbnail", "description": "Creates a sub-module lesson with teaching content (text, image, audio, video URLs) and optional thumbnail",
@ -2204,7 +2680,7 @@
} }
}, },
"post": { "post": {
"description": "Creates a sub-module under a module", "description": "Creates a sub-module under a module; optional thumbnail (image URL) and tips text",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@ -2295,6 +2771,47 @@
} }
} }
}, },
"/api/v1/course-management/sub-modules/{subModuleId}/capstones": {
"get": {
"description": "Returns active capstones for a sub-module with question-set settings and question counts",
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "List capstones under sub-module",
"parameters": [
{
"type": "integer",
"description": "Sub-module ID",
"name": "subModuleId",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
}
},
"/api/v1/course-management/sub-modules/{subModuleId}/lessons": { "/api/v1/course-management/sub-modules/{subModuleId}/lessons": {
"get": { "get": {
"description": "Returns all active lessons for a sub-module (teaching content metadata)", "description": "Returns all active lessons for a sub-module (teaching content metadata)",
@ -4032,7 +4549,7 @@
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
"description": "Set type (PRACTICE, INITIAL_ASSESSMENT, QUIZ, EXAM, SURVEY)", "description": "Set type (PRACTICE, INITIAL_ASSESSMENT, QUIZ, EXAM, SURVEY, CAPSTONE)",
"name": "set_type", "name": "set_type",
"in": "query", "in": "query",
"required": true "required": true
@ -10292,6 +10809,17 @@
} }
} }
}, },
"handlers.capstoneQuestionItem": {
"type": "object",
"properties": {
"display_order": {
"type": "integer"
},
"question_id": {
"type": "integer"
}
}
},
"handlers.changePasswordReq": { "handlers.changePasswordReq": {
"type": "object", "type": "object",
"required": [ "required": [
@ -10424,6 +10952,50 @@
} }
} }
}, },
"handlers.createModuleCapstoneReq": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"display_order": {
"type": "integer"
},
"is_active": {
"type": "boolean"
},
"module_id": {
"type": "integer"
},
"passing_score": {
"type": "integer"
},
"questions": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.capstoneQuestionItem"
}
},
"shuffle_questions": {
"type": "boolean"
},
"status": {
"type": "string"
},
"thumbnail": {
"type": "string"
},
"time_limit_minutes": {
"type": "integer"
},
"tips": {
"type": "string"
},
"title": {
"type": "string"
}
}
},
"handlers.createModuleReq": { "handlers.createModuleReq": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -10433,6 +11005,9 @@
"display_order": { "display_order": {
"type": "integer" "type": "integer"
}, },
"icon_url": {
"type": "string"
},
"is_active": { "is_active": {
"type": "boolean" "type": "boolean"
}, },
@ -10580,7 +11155,8 @@
"INITIAL_ASSESSMENT", "INITIAL_ASSESSMENT",
"QUIZ", "QUIZ",
"EXAM", "EXAM",
"SURVEY" "SURVEY",
"CAPSTONE"
] ]
}, },
"shuffle_questions": { "shuffle_questions": {
@ -10600,6 +11176,50 @@
} }
} }
}, },
"handlers.createSubModuleCapstoneReq": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"display_order": {
"type": "integer"
},
"is_active": {
"type": "boolean"
},
"passing_score": {
"type": "integer"
},
"questions": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.capstoneQuestionItem"
}
},
"shuffle_questions": {
"type": "boolean"
},
"status": {
"type": "string"
},
"sub_module_id": {
"type": "integer"
},
"thumbnail": {
"type": "string"
},
"time_limit_minutes": {
"type": "integer"
},
"tips": {
"type": "string"
},
"title": {
"type": "string"
}
}
},
"handlers.createSubModuleLessonReq": { "handlers.createSubModuleLessonReq": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -10679,6 +11299,12 @@
"module_id": { "module_id": {
"type": "integer" "type": "integer"
}, },
"thumbnail": {
"type": "string"
},
"tips": {
"type": "string"
},
"title": { "title": {
"type": "string" "type": "string"
} }
@ -11065,6 +11691,67 @@
} }
} }
}, },
"handlers.updateModuleCapstoneReq": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"display_order": {
"type": "integer"
},
"is_active": {
"type": "boolean"
},
"passing_score": {
"type": "integer"
},
"questions": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.capstoneQuestionItem"
}
},
"shuffle_questions": {
"type": "boolean"
},
"status": {
"type": "string"
},
"thumbnail": {
"type": "string"
},
"time_limit_minutes": {
"type": "integer"
},
"tips": {
"type": "string"
},
"title": {
"type": "string"
}
}
},
"handlers.updateModuleReq": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"display_order": {
"type": "integer"
},
"icon_url": {
"type": "string"
},
"is_active": {
"type": "boolean"
},
"title": {
"type": "string"
}
}
},
"handlers.updatePlanReq": { "handlers.updatePlanReq": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -11187,6 +11874,47 @@
} }
} }
}, },
"handlers.updateSubModuleCapstoneReq": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"display_order": {
"type": "integer"
},
"is_active": {
"type": "boolean"
},
"passing_score": {
"type": "integer"
},
"questions": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.capstoneQuestionItem"
}
},
"shuffle_questions": {
"type": "boolean"
},
"status": {
"type": "string"
},
"thumbnail": {
"type": "string"
},
"time_limit_minutes": {
"type": "integer"
},
"tips": {
"type": "string"
},
"title": {
"type": "string"
}
}
},
"handlers.updateSubModuleLessonReq": { "handlers.updateSubModuleLessonReq": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -916,6 +916,13 @@ definitions:
auto_renew: auto_renew:
type: boolean type: boolean
type: object type: object
handlers.capstoneQuestionItem:
properties:
display_order:
type: integer
question_id:
type: integer
type: object
handlers.changePasswordReq: handlers.changePasswordReq:
properties: properties:
current_password: current_password:
@ -1004,12 +1011,43 @@ definitions:
title: title:
type: string type: string
type: object type: object
handlers.createModuleCapstoneReq:
properties:
description:
type: string
display_order:
type: integer
is_active:
type: boolean
module_id:
type: integer
passing_score:
type: integer
questions:
items:
$ref: '#/definitions/handlers.capstoneQuestionItem'
type: array
shuffle_questions:
type: boolean
status:
type: string
thumbnail:
type: string
time_limit_minutes:
type: integer
tips:
type: string
title:
type: string
type: object
handlers.createModuleReq: handlers.createModuleReq:
properties: properties:
description: description:
type: string type: string
display_order: display_order:
type: integer type: integer
icon_url:
type: string
is_active: is_active:
type: boolean type: boolean
level_id: level_id:
@ -1111,6 +1149,7 @@ definitions:
- QUIZ - QUIZ
- EXAM - EXAM
- SURVEY - SURVEY
- CAPSTONE
type: string type: string
shuffle_questions: shuffle_questions:
type: boolean type: boolean
@ -1126,6 +1165,35 @@ definitions:
- set_type - set_type
- title - title
type: object type: object
handlers.createSubModuleCapstoneReq:
properties:
description:
type: string
display_order:
type: integer
is_active:
type: boolean
passing_score:
type: integer
questions:
items:
$ref: '#/definitions/handlers.capstoneQuestionItem'
type: array
shuffle_questions:
type: boolean
status:
type: string
sub_module_id:
type: integer
thumbnail:
type: string
time_limit_minutes:
type: integer
tips:
type: string
title:
type: string
type: object
handlers.createSubModuleLessonReq: handlers.createSubModuleLessonReq:
properties: properties:
description: description:
@ -1178,6 +1246,10 @@ definitions:
type: boolean type: boolean
module_id: module_id:
type: integer type: integer
thumbnail:
type: string
tips:
type: string
title: title:
type: string type: string
type: object type: object
@ -1437,6 +1509,46 @@ definitions:
title: title:
type: string type: string
type: object type: object
handlers.updateModuleCapstoneReq:
properties:
description:
type: string
display_order:
type: integer
is_active:
type: boolean
passing_score:
type: integer
questions:
items:
$ref: '#/definitions/handlers.capstoneQuestionItem'
type: array
shuffle_questions:
type: boolean
status:
type: string
thumbnail:
type: string
time_limit_minutes:
type: integer
tips:
type: string
title:
type: string
type: object
handlers.updateModuleReq:
properties:
description:
type: string
display_order:
type: integer
icon_url:
type: string
is_active:
type: boolean
title:
type: string
type: object
handlers.updatePlanReq: handlers.updatePlanReq:
properties: properties:
currency: currency:
@ -1517,6 +1629,33 @@ definitions:
title: title:
type: string type: string
type: object type: object
handlers.updateSubModuleCapstoneReq:
properties:
description:
type: string
display_order:
type: integer
is_active:
type: boolean
passing_score:
type: integer
questions:
items:
$ref: '#/definitions/handlers.capstoneQuestionItem'
type: array
shuffle_questions:
type: boolean
status:
type: string
thumbnail:
type: string
time_limit_minutes:
type: integer
tips:
type: string
title:
type: string
type: object
handlers.updateSubModuleLessonReq: handlers.updateSubModuleLessonReq:
properties: properties:
description: description:
@ -2417,6 +2556,104 @@ paths:
summary: Refresh token summary: Refresh token
tags: tags:
- auth - auth
/api/v1/course-management/capstones/{capstoneId}:
delete:
description: Deletes the capstone and its backing question set (and question
items)
parameters:
- description: Capstone ID
in: path
name: capstoneId
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/domain.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/domain.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/domain.ErrorResponse'
summary: Delete capstone
tags:
- course-management
get:
description: Returns one capstone with question-set fields and the ordered question
list
parameters:
- description: Capstone ID
in: path
name: capstoneId
required: true
type: integer
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: Get capstone detail
tags:
- course-management
put:
consumes:
- application/json
description: Updates capstone content, question-set assessment settings, and
optionally replaces the question list
parameters:
- description: Capstone ID
in: path
name: capstoneId
required: true
type: integer
- description: Update capstone payload
in: body
name: body
required: true
schema:
$ref: '#/definitions/handlers.updateSubModuleCapstoneReq'
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 capstone
tags:
- course-management
/api/v1/course-management/categories: /api/v1/course-management/categories:
get: get:
description: Legacy-compatible endpoint for listing course categories description: Legacy-compatible endpoint for listing course categories
@ -2982,6 +3219,134 @@ paths:
summary: List modules by level summary: List modules by level
tags: tags:
- course-management - course-management
/api/v1/course-management/module-capstones:
post:
consumes:
- application/json
description: Creates a module-level capstone with a new CAPSTONE question set
and ordered questions
parameters:
- description: Create module capstone payload
in: body
name: body
required: true
schema:
$ref: '#/definitions/handlers.createModuleCapstoneReq'
produces:
- application/json
responses:
"201":
description: Created
schema:
$ref: '#/definitions/domain.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/domain.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/domain.ErrorResponse'
summary: Create module capstone
tags:
- course-management
/api/v1/course-management/module-capstones/{moduleCapstoneId}:
delete:
description: Deletes the module capstone and its backing question set
parameters:
- description: Module capstone ID
in: path
name: moduleCapstoneId
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/domain.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/domain.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/domain.ErrorResponse'
summary: Delete module capstone
tags:
- course-management
get:
description: Returns one module capstone with question-set fields and the ordered
question list
parameters:
- description: Module capstone ID
in: path
name: moduleCapstoneId
required: true
type: integer
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: Get module capstone detail
tags:
- course-management
put:
consumes:
- application/json
description: Updates module capstone content, question-set assessment settings,
and optionally replaces the question list
parameters:
- description: Module capstone ID
in: path
name: moduleCapstoneId
required: true
type: integer
- description: Update module capstone payload
in: body
name: body
required: true
schema:
$ref: '#/definitions/handlers.updateModuleCapstoneReq'
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 module capstone
tags:
- course-management
/api/v1/course-management/modules: /api/v1/course-management/modules:
get: get:
description: Returns all modules with pagination description: Returns all modules with pagination
@ -3011,7 +3376,8 @@ paths:
post: post:
consumes: consumes:
- application/json - application/json
description: Creates a module under a level description: Creates a module under a level; optional icon_url stores a module
icon image URL
parameters: parameters:
- description: Create module payload - description: Create module payload
in: body in: body
@ -3068,6 +3434,73 @@ paths:
summary: Get module detail summary: Get module detail
tags: tags:
- course-management - course-management
put:
consumes:
- application/json
description: Updates module title, description, icon URL, display order, and
active flag
parameters:
- description: Module ID
in: path
name: moduleId
required: true
type: integer
- description: Update module payload
in: body
name: body
required: true
schema:
$ref: '#/definitions/handlers.updateModuleReq'
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 module
tags:
- course-management
/api/v1/course-management/modules/{moduleId}/capstones:
get:
description: Returns active module capstones with question-set settings and
question counts
parameters:
- description: Module ID
in: path
name: moduleId
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/domain.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/domain.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/domain.ErrorResponse'
summary: List capstones under module
tags:
- course-management
/api/v1/course-management/modules/{moduleId}/sub-modules: /api/v1/course-management/modules/{moduleId}/sub-modules:
get: get:
description: Returns all active sub-modules for one module description: Returns all active sub-modules for one module
@ -3218,6 +3651,37 @@ paths:
summary: List courses by sub-category summary: List courses by sub-category
tags: tags:
- course-management - course-management
/api/v1/course-management/sub-module-capstones:
post:
consumes:
- application/json
description: Creates a capstone assessment with a new CAPSTONE question set,
metadata, and ordered questions
parameters:
- description: Create capstone payload
in: body
name: body
required: true
schema:
$ref: '#/definitions/handlers.createSubModuleCapstoneReq'
produces:
- application/json
responses:
"201":
description: Created
schema:
$ref: '#/definitions/domain.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/domain.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/domain.ErrorResponse'
summary: Create capstone under sub-module
tags:
- course-management
/api/v1/course-management/sub-module-lessons: /api/v1/course-management/sub-module-lessons:
post: post:
consumes: consumes:
@ -3411,7 +3875,8 @@ paths:
post: post:
consumes: consumes:
- application/json - application/json
description: Creates a sub-module under a module description: Creates a sub-module under a module; optional thumbnail (image
URL) and tips text
parameters: parameters:
- description: Create sub-module payload - description: Create sub-module payload
in: body in: body
@ -3468,6 +3933,34 @@ paths:
summary: Get sub-module detail summary: Get sub-module detail
tags: tags:
- course-management - course-management
/api/v1/course-management/sub-modules/{subModuleId}/capstones:
get:
description: Returns active capstones for a sub-module with question-set settings
and question counts
parameters:
- description: Sub-module ID
in: path
name: subModuleId
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/domain.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/domain.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/domain.ErrorResponse'
summary: List capstones under sub-module
tags:
- course-management
/api/v1/course-management/sub-modules/{subModuleId}/lessons: /api/v1/course-management/sub-modules/{subModuleId}/lessons:
get: get:
consumes: consumes:
@ -4597,7 +5090,7 @@ paths:
get: get:
description: Returns a paginated list of question sets filtered by type description: Returns a paginated list of question sets filtered by type
parameters: parameters:
- description: Set type (PRACTICE, INITIAL_ASSESSMENT, QUIZ, EXAM, SURVEY) - description: Set type (PRACTICE, INITIAL_ASSESSMENT, QUIZ, EXAM, SURVEY, CAPSTONE)
in: query in: query
name: set_type name: set_type
required: true required: true

View File

@ -8,7 +8,7 @@ import (
func (q *Queries) GetSubModuleByIDCompat(ctx context.Context, id int64) (SubModule, error) { func (q *Queries) GetSubModuleByIDCompat(ctx context.Context, id int64) (SubModule, error) {
row := q.db.QueryRow(ctx, ` row := q.db.QueryRow(ctx, `
SELECT id, module_id, title, description, display_order, is_active, created_at, legacy_sub_course_id SELECT id, module_id, title, description, display_order, is_active, created_at, legacy_sub_course_id, thumbnail, tips
FROM sub_modules FROM sub_modules
WHERE id = $1 WHERE id = $1
`, id) `, id)
@ -22,19 +22,24 @@ WHERE id = $1
&i.IsActive, &i.IsActive,
&i.CreatedAt, &i.CreatedAt,
&i.LegacySubCourseID, &i.LegacySubCourseID,
&i.Thumbnail,
&i.Tips,
) )
return i, err return i, err
} }
func (q *Queries) UpdateSubModuleCompat(ctx context.Context, id int64, title string, description string, isActive bool) error { func (q *Queries) UpdateSubModuleCompat(ctx context.Context, id int64, title string, description string, thumbnail string, tips string, displayOrder int32, isActive bool) error {
_, err := q.db.Exec(ctx, ` _, err := q.db.Exec(ctx, `
UPDATE sub_modules UPDATE sub_modules
SET SET
title = $1, title = $1,
description = NULLIF($2, ''), description = NULLIF($2, ''),
is_active = $3 thumbnail = NULLIF($3, ''),
WHERE id = $4 tips = NULLIF($4, ''),
`, title, description, isActive, id) display_order = $5,
is_active = $6
WHERE id = $7
`, title, description, thumbnail, tips, displayOrder, isActive, id)
return err return err
} }
@ -119,6 +124,24 @@ func (q *Queries) DeletePracticeCompat(ctx context.Context, id int64) error {
return err return err
} }
// DeleteCapstoneCompat removes the backing question set (and cascades sub_module_capstones).
func (q *Queries) DeleteCapstoneCompat(ctx context.Context, capstoneID int64) error {
_, err := q.db.Exec(ctx, `
DELETE FROM question_sets
WHERE id = (SELECT question_set_id FROM sub_module_capstones WHERE id = $1)
`, capstoneID)
return err
}
// DeleteModuleCapstoneCompat removes the backing question set (and cascades module_capstones).
func (q *Queries) DeleteModuleCapstoneCompat(ctx context.Context, capstoneID int64) error {
_, err := q.db.Exec(ctx, `
DELETE FROM question_sets
WHERE id = (SELECT question_set_id FROM module_capstones WHERE id = $1)
`, capstoneID)
return err
}
func (q *Queries) CreateCourseCompat( func (q *Queries) CreateCourseCompat(
ctx context.Context, ctx context.Context,
categoryID int64, categoryID int64,

View File

@ -106,19 +106,21 @@ INSERT INTO modules (
level_id, level_id,
title, title,
description, description,
icon_url,
display_order, display_order,
is_active is_active
) )
VALUES ($1, $2, $3, COALESCE($4, 0), COALESCE($5, TRUE)) VALUES ($1, $2, $3, $4, COALESCE($5, 0), COALESCE($6, TRUE))
RETURNING id, level_id, title, description, display_order, is_active, created_at RETURNING id, level_id, title, description, display_order, is_active, created_at, icon_url
` `
type CreateModuleParams struct { type CreateModuleParams struct {
LevelID int64 `json:"level_id"` LevelID int64 `json:"level_id"`
Title string `json:"title"` Title string `json:"title"`
Description pgtype.Text `json:"description"` Description pgtype.Text `json:"description"`
Column4 interface{} `json:"column_4"` IconUrl pgtype.Text `json:"icon_url"`
Column5 interface{} `json:"column_5"` Column5 interface{} `json:"column_5"`
Column6 interface{} `json:"column_6"`
} }
func (q *Queries) CreateModule(ctx context.Context, arg CreateModuleParams) (Module, error) { func (q *Queries) CreateModule(ctx context.Context, arg CreateModuleParams) (Module, error) {
@ -126,8 +128,9 @@ func (q *Queries) CreateModule(ctx context.Context, arg CreateModuleParams) (Mod
arg.LevelID, arg.LevelID,
arg.Title, arg.Title,
arg.Description, arg.Description,
arg.Column4, arg.IconUrl,
arg.Column5, arg.Column5,
arg.Column6,
) )
var i Module var i Module
err := row.Scan( err := row.Scan(
@ -138,6 +141,60 @@ func (q *Queries) CreateModule(ctx context.Context, arg CreateModuleParams) (Mod
&i.DisplayOrder, &i.DisplayOrder,
&i.IsActive, &i.IsActive,
&i.CreatedAt, &i.CreatedAt,
&i.IconUrl,
)
return i, err
}
const CreateModuleCapstone = `-- name: CreateModuleCapstone :one
INSERT INTO module_capstones (
module_id,
title,
description,
tips,
thumbnail,
question_set_id,
display_order,
is_active
)
VALUES ($1, $2, $3, $4, $5, $6, COALESCE($7, 0), COALESCE($8, TRUE))
RETURNING id, module_id, title, description, tips, thumbnail, question_set_id, display_order, is_active, created_at
`
type CreateModuleCapstoneParams struct {
ModuleID int64 `json:"module_id"`
Title string `json:"title"`
Description pgtype.Text `json:"description"`
Tips pgtype.Text `json:"tips"`
Thumbnail pgtype.Text `json:"thumbnail"`
QuestionSetID int64 `json:"question_set_id"`
Column7 interface{} `json:"column_7"`
Column8 interface{} `json:"column_8"`
}
func (q *Queries) CreateModuleCapstone(ctx context.Context, arg CreateModuleCapstoneParams) (ModuleCapstone, error) {
row := q.db.QueryRow(ctx, CreateModuleCapstone,
arg.ModuleID,
arg.Title,
arg.Description,
arg.Tips,
arg.Thumbnail,
arg.QuestionSetID,
arg.Column7,
arg.Column8,
)
var i ModuleCapstone
err := row.Scan(
&i.ID,
&i.ModuleID,
&i.Title,
&i.Description,
&i.Tips,
&i.Thumbnail,
&i.QuestionSetID,
&i.DisplayOrder,
&i.IsActive,
&i.CreatedAt,
) )
return i, err return i, err
} }
@ -147,19 +204,23 @@ INSERT INTO sub_modules (
module_id, module_id,
title, title,
description, description,
thumbnail,
tips,
display_order, display_order,
is_active is_active
) )
VALUES ($1, $2, $3, COALESCE($4, 0), COALESCE($5, TRUE)) VALUES ($1, $2, $3, $4, $5, COALESCE($6, 0), COALESCE($7, TRUE))
RETURNING id, module_id, title, description, display_order, is_active, created_at, legacy_sub_course_id RETURNING id, module_id, title, description, display_order, is_active, created_at, legacy_sub_course_id, thumbnail, tips
` `
type CreateSubModuleParams struct { type CreateSubModuleParams struct {
ModuleID int64 `json:"module_id"` ModuleID int64 `json:"module_id"`
Title string `json:"title"` Title string `json:"title"`
Description pgtype.Text `json:"description"` Description pgtype.Text `json:"description"`
Column4 interface{} `json:"column_4"` Thumbnail pgtype.Text `json:"thumbnail"`
Column5 interface{} `json:"column_5"` Tips pgtype.Text `json:"tips"`
Column6 interface{} `json:"column_6"`
Column7 interface{} `json:"column_7"`
} }
func (q *Queries) CreateSubModule(ctx context.Context, arg CreateSubModuleParams) (SubModule, error) { func (q *Queries) CreateSubModule(ctx context.Context, arg CreateSubModuleParams) (SubModule, error) {
@ -167,8 +228,10 @@ func (q *Queries) CreateSubModule(ctx context.Context, arg CreateSubModuleParams
arg.ModuleID, arg.ModuleID,
arg.Title, arg.Title,
arg.Description, arg.Description,
arg.Column4, arg.Thumbnail,
arg.Column5, arg.Tips,
arg.Column6,
arg.Column7,
) )
var i SubModule var i SubModule
err := row.Scan( err := row.Scan(
@ -180,6 +243,61 @@ func (q *Queries) CreateSubModule(ctx context.Context, arg CreateSubModuleParams
&i.IsActive, &i.IsActive,
&i.CreatedAt, &i.CreatedAt,
&i.LegacySubCourseID, &i.LegacySubCourseID,
&i.Thumbnail,
&i.Tips,
)
return i, err
}
const CreateSubModuleCapstone = `-- name: CreateSubModuleCapstone :one
INSERT INTO sub_module_capstones (
sub_module_id,
title,
description,
tips,
thumbnail,
question_set_id,
display_order,
is_active
)
VALUES ($1, $2, $3, $4, $5, $6, COALESCE($7, 0), COALESCE($8, TRUE))
RETURNING id, sub_module_id, title, description, tips, thumbnail, question_set_id, display_order, is_active, created_at
`
type CreateSubModuleCapstoneParams struct {
SubModuleID int64 `json:"sub_module_id"`
Title string `json:"title"`
Description pgtype.Text `json:"description"`
Tips pgtype.Text `json:"tips"`
Thumbnail pgtype.Text `json:"thumbnail"`
QuestionSetID int64 `json:"question_set_id"`
Column7 interface{} `json:"column_7"`
Column8 interface{} `json:"column_8"`
}
func (q *Queries) CreateSubModuleCapstone(ctx context.Context, arg CreateSubModuleCapstoneParams) (SubModuleCapstone, error) {
row := q.db.QueryRow(ctx, CreateSubModuleCapstone,
arg.SubModuleID,
arg.Title,
arg.Description,
arg.Tips,
arg.Thumbnail,
arg.QuestionSetID,
arg.Column7,
arg.Column8,
)
var i SubModuleCapstone
err := row.Scan(
&i.ID,
&i.SubModuleID,
&i.Title,
&i.Description,
&i.Tips,
&i.Thumbnail,
&i.QuestionSetID,
&i.DisplayOrder,
&i.IsActive,
&i.CreatedAt,
) )
return i, err return i, err
} }
@ -469,7 +587,7 @@ func (q *Queries) GetAllLevels(ctx context.Context, arg GetAllLevelsParams) ([]G
const GetAllModules = `-- name: GetAllModules :many const GetAllModules = `-- name: GetAllModules :many
SELECT SELECT
COUNT(*) OVER () AS total_count, COUNT(*) OVER () AS total_count,
m.id, m.level_id, m.title, m.description, m.display_order, m.is_active, m.created_at m.id, m.level_id, m.title, m.description, m.display_order, m.is_active, m.created_at, m.icon_url
FROM modules m FROM modules m
ORDER BY m.display_order ASC, m.id ASC ORDER BY m.display_order ASC, m.id ASC
LIMIT $2::INT LIMIT $2::INT
@ -490,6 +608,7 @@ type GetAllModulesRow 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"`
IconUrl pgtype.Text `json:"icon_url"`
} }
func (q *Queries) GetAllModules(ctx context.Context, arg GetAllModulesParams) ([]GetAllModulesRow, error) { func (q *Queries) GetAllModules(ctx context.Context, arg GetAllModulesParams) ([]GetAllModulesRow, error) {
@ -510,6 +629,7 @@ func (q *Queries) GetAllModules(ctx context.Context, arg GetAllModulesParams) ([
&i.DisplayOrder, &i.DisplayOrder,
&i.IsActive, &i.IsActive,
&i.CreatedAt, &i.CreatedAt,
&i.IconUrl,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@ -524,7 +644,7 @@ func (q *Queries) GetAllModules(ctx context.Context, arg GetAllModulesParams) ([
const GetAllSubModules = `-- name: GetAllSubModules :many const GetAllSubModules = `-- name: GetAllSubModules :many
SELECT SELECT
COUNT(*) OVER () AS total_count, COUNT(*) OVER () AS total_count,
sm.id, sm.module_id, sm.title, sm.description, sm.display_order, sm.is_active, sm.created_at, sm.legacy_sub_course_id sm.id, sm.module_id, sm.title, sm.description, sm.display_order, sm.is_active, sm.created_at, sm.legacy_sub_course_id, sm.thumbnail, sm.tips
FROM sub_modules sm FROM sub_modules sm
ORDER BY sm.display_order ASC, sm.id ASC ORDER BY sm.display_order ASC, sm.id ASC
LIMIT $2::INT LIMIT $2::INT
@ -546,6 +666,8 @@ type GetAllSubModulesRow struct {
IsActive bool `json:"is_active"` IsActive bool `json:"is_active"`
CreatedAt pgtype.Timestamptz `json:"created_at"` CreatedAt pgtype.Timestamptz `json:"created_at"`
LegacySubCourseID pgtype.Int8 `json:"legacy_sub_course_id"` LegacySubCourseID pgtype.Int8 `json:"legacy_sub_course_id"`
Thumbnail pgtype.Text `json:"thumbnail"`
Tips pgtype.Text `json:"tips"`
} }
func (q *Queries) GetAllSubModules(ctx context.Context, arg GetAllSubModulesParams) ([]GetAllSubModulesRow, error) { func (q *Queries) GetAllSubModules(ctx context.Context, arg GetAllSubModulesParams) ([]GetAllSubModulesRow, error) {
@ -567,6 +689,8 @@ func (q *Queries) GetAllSubModules(ctx context.Context, arg GetAllSubModulesPara
&i.IsActive, &i.IsActive,
&i.CreatedAt, &i.CreatedAt,
&i.LegacySubCourseID, &i.LegacySubCourseID,
&i.Thumbnail,
&i.Tips,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@ -706,8 +830,13 @@ SELECT
l.thumbnail AS level_thumbnail, 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,
m.icon_url AS module_icon_url,
sm.id AS sub_module_id, sm.id AS sub_module_id,
sm.title AS sub_module_title sm.title AS sub_module_title,
sm.description AS sub_module_description,
sm.thumbnail AS sub_module_thumbnail,
sm.tips AS sub_module_tips,
sm.display_order AS sub_module_display_order
FROM courses c FROM courses c
LEFT JOIN levels l ON l.course_id = c.id AND l.is_active = TRUE LEFT JOIN levels l ON l.course_id = c.id AND l.is_active = TRUE
LEFT JOIN modules m ON m.level_id = l.id AND m.is_active = TRUE LEFT JOIN modules m ON m.level_id = l.id AND m.is_active = TRUE
@ -717,17 +846,22 @@ 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"`
LevelTitle pgtype.Text `json:"level_title"` LevelTitle pgtype.Text `json:"level_title"`
LevelDescription pgtype.Text `json:"level_description"` LevelDescription pgtype.Text `json:"level_description"`
LevelThumbnail pgtype.Text `json:"level_thumbnail"` 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"` ModuleIconUrl pgtype.Text `json:"module_icon_url"`
SubModuleTitle pgtype.Text `json:"sub_module_title"` SubModuleID pgtype.Int8 `json:"sub_module_id"`
SubModuleTitle pgtype.Text `json:"sub_module_title"`
SubModuleDescription pgtype.Text `json:"sub_module_description"`
SubModuleThumbnail pgtype.Text `json:"sub_module_thumbnail"`
SubModuleTips pgtype.Text `json:"sub_module_tips"`
SubModuleDisplayOrder pgtype.Int4 `json:"sub_module_display_order"`
} }
func (q *Queries) GetFullHierarchyByCourseID(ctx context.Context, id int64) ([]GetFullHierarchyByCourseIDRow, error) { func (q *Queries) GetFullHierarchyByCourseID(ctx context.Context, id int64) ([]GetFullHierarchyByCourseIDRow, error) {
@ -749,8 +883,13 @@ func (q *Queries) GetFullHierarchyByCourseID(ctx context.Context, id int64) ([]G
&i.LevelThumbnail, &i.LevelThumbnail,
&i.ModuleID, &i.ModuleID,
&i.ModuleTitle, &i.ModuleTitle,
&i.ModuleIconUrl,
&i.SubModuleID, &i.SubModuleID,
&i.SubModuleTitle, &i.SubModuleTitle,
&i.SubModuleDescription,
&i.SubModuleThumbnail,
&i.SubModuleTips,
&i.SubModuleDisplayOrder,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@ -892,7 +1031,7 @@ func (q *Queries) GetLevelsByCourseID(ctx context.Context, courseID int64) ([]Le
} }
const GetModuleByID = `-- name: GetModuleByID :one const GetModuleByID = `-- name: GetModuleByID :one
SELECT id, level_id, title, description, display_order, is_active, created_at SELECT id, level_id, title, description, display_order, is_active, created_at, icon_url
FROM modules FROM modules
WHERE id = $1 WHERE id = $1
` `
@ -908,12 +1047,157 @@ func (q *Queries) GetModuleByID(ctx context.Context, id int64) (Module, error) {
&i.DisplayOrder, &i.DisplayOrder,
&i.IsActive, &i.IsActive,
&i.CreatedAt, &i.CreatedAt,
&i.IconUrl,
) )
return i, err return i, err
} }
const GetModuleCapstoneByID = `-- name: GetModuleCapstoneByID :one
SELECT
mc.id,
mc.module_id,
mc.title,
mc.description,
mc.tips,
mc.thumbnail,
mc.question_set_id,
mc.display_order,
mc.is_active,
qs.status,
qs.set_type,
qs.time_limit_minutes,
qs.passing_score,
qs.shuffle_questions,
(SELECT COUNT(*) FROM question_set_items qsi WHERE qsi.set_id = qs.id) AS question_count
FROM module_capstones mc
JOIN question_sets qs ON qs.id = mc.question_set_id
WHERE mc.id = $1
AND mc.is_active = TRUE
AND qs.set_type = 'CAPSTONE'
`
type GetModuleCapstoneByIDRow struct {
ID int64 `json:"id"`
ModuleID int64 `json:"module_id"`
Title string `json:"title"`
Description pgtype.Text `json:"description"`
Tips pgtype.Text `json:"tips"`
Thumbnail pgtype.Text `json:"thumbnail"`
QuestionSetID int64 `json:"question_set_id"`
DisplayOrder int32 `json:"display_order"`
IsActive bool `json:"is_active"`
Status string `json:"status"`
SetType string `json:"set_type"`
TimeLimitMinutes pgtype.Int4 `json:"time_limit_minutes"`
PassingScore pgtype.Int4 `json:"passing_score"`
ShuffleQuestions bool `json:"shuffle_questions"`
QuestionCount int64 `json:"question_count"`
}
func (q *Queries) GetModuleCapstoneByID(ctx context.Context, id int64) (GetModuleCapstoneByIDRow, error) {
row := q.db.QueryRow(ctx, GetModuleCapstoneByID, id)
var i GetModuleCapstoneByIDRow
err := row.Scan(
&i.ID,
&i.ModuleID,
&i.Title,
&i.Description,
&i.Tips,
&i.Thumbnail,
&i.QuestionSetID,
&i.DisplayOrder,
&i.IsActive,
&i.Status,
&i.SetType,
&i.TimeLimitMinutes,
&i.PassingScore,
&i.ShuffleQuestions,
&i.QuestionCount,
)
return i, err
}
const GetModuleCapstones = `-- name: GetModuleCapstones :many
SELECT
mc.id,
mc.module_id,
mc.title,
mc.description,
mc.tips,
mc.thumbnail,
mc.question_set_id,
mc.display_order,
mc.is_active,
qs.status,
qs.set_type,
qs.time_limit_minutes,
qs.passing_score,
qs.shuffle_questions,
(SELECT COUNT(*) FROM question_set_items qsi WHERE qsi.set_id = qs.id) AS question_count
FROM module_capstones mc
JOIN question_sets qs ON qs.id = mc.question_set_id
WHERE mc.module_id = $1
AND mc.is_active = TRUE
AND qs.set_type = 'CAPSTONE'
ORDER BY mc.display_order ASC, mc.id ASC
`
type GetModuleCapstonesRow struct {
ID int64 `json:"id"`
ModuleID int64 `json:"module_id"`
Title string `json:"title"`
Description pgtype.Text `json:"description"`
Tips pgtype.Text `json:"tips"`
Thumbnail pgtype.Text `json:"thumbnail"`
QuestionSetID int64 `json:"question_set_id"`
DisplayOrder int32 `json:"display_order"`
IsActive bool `json:"is_active"`
Status string `json:"status"`
SetType string `json:"set_type"`
TimeLimitMinutes pgtype.Int4 `json:"time_limit_minutes"`
PassingScore pgtype.Int4 `json:"passing_score"`
ShuffleQuestions bool `json:"shuffle_questions"`
QuestionCount int64 `json:"question_count"`
}
func (q *Queries) GetModuleCapstones(ctx context.Context, moduleID int64) ([]GetModuleCapstonesRow, error) {
rows, err := q.db.Query(ctx, GetModuleCapstones, moduleID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetModuleCapstonesRow
for rows.Next() {
var i GetModuleCapstonesRow
if err := rows.Scan(
&i.ID,
&i.ModuleID,
&i.Title,
&i.Description,
&i.Tips,
&i.Thumbnail,
&i.QuestionSetID,
&i.DisplayOrder,
&i.IsActive,
&i.Status,
&i.SetType,
&i.TimeLimitMinutes,
&i.PassingScore,
&i.ShuffleQuestions,
&i.QuestionCount,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const GetModulesByLevelID = `-- name: GetModulesByLevelID :many const GetModulesByLevelID = `-- name: GetModulesByLevelID :many
SELECT id, level_id, title, description, display_order, is_active, created_at SELECT id, level_id, title, description, display_order, is_active, created_at, icon_url
FROM modules FROM modules
WHERE level_id = $1 WHERE level_id = $1
AND is_active = TRUE AND is_active = TRUE
@ -937,6 +1221,7 @@ func (q *Queries) GetModulesByLevelID(ctx context.Context, levelID int64) ([]Mod
&i.DisplayOrder, &i.DisplayOrder,
&i.IsActive, &i.IsActive,
&i.CreatedAt, &i.CreatedAt,
&i.IconUrl,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@ -949,7 +1234,7 @@ func (q *Queries) GetModulesByLevelID(ctx context.Context, levelID int64) ([]Mod
} }
const GetSubModuleByID = `-- name: GetSubModuleByID :one const GetSubModuleByID = `-- name: GetSubModuleByID :one
SELECT id, module_id, title, description, display_order, is_active, created_at, legacy_sub_course_id SELECT id, module_id, title, description, display_order, is_active, created_at, legacy_sub_course_id, thumbnail, tips
FROM sub_modules FROM sub_modules
WHERE id = $1 WHERE id = $1
` `
@ -966,10 +1251,156 @@ func (q *Queries) GetSubModuleByID(ctx context.Context, id int64) (SubModule, er
&i.IsActive, &i.IsActive,
&i.CreatedAt, &i.CreatedAt,
&i.LegacySubCourseID, &i.LegacySubCourseID,
&i.Thumbnail,
&i.Tips,
) )
return i, err return i, err
} }
const GetSubModuleCapstoneByID = `-- name: GetSubModuleCapstoneByID :one
SELECT
smc.id,
smc.sub_module_id,
smc.title,
smc.description,
smc.tips,
smc.thumbnail,
smc.question_set_id,
smc.display_order,
smc.is_active,
qs.status,
qs.set_type,
qs.time_limit_minutes,
qs.passing_score,
qs.shuffle_questions,
(SELECT COUNT(*) FROM question_set_items qsi WHERE qsi.set_id = qs.id) AS question_count
FROM sub_module_capstones smc
JOIN question_sets qs ON qs.id = smc.question_set_id
WHERE smc.id = $1
AND smc.is_active = TRUE
AND qs.set_type = 'CAPSTONE'
`
type GetSubModuleCapstoneByIDRow struct {
ID int64 `json:"id"`
SubModuleID int64 `json:"sub_module_id"`
Title string `json:"title"`
Description pgtype.Text `json:"description"`
Tips pgtype.Text `json:"tips"`
Thumbnail pgtype.Text `json:"thumbnail"`
QuestionSetID int64 `json:"question_set_id"`
DisplayOrder int32 `json:"display_order"`
IsActive bool `json:"is_active"`
Status string `json:"status"`
SetType string `json:"set_type"`
TimeLimitMinutes pgtype.Int4 `json:"time_limit_minutes"`
PassingScore pgtype.Int4 `json:"passing_score"`
ShuffleQuestions bool `json:"shuffle_questions"`
QuestionCount int64 `json:"question_count"`
}
func (q *Queries) GetSubModuleCapstoneByID(ctx context.Context, id int64) (GetSubModuleCapstoneByIDRow, error) {
row := q.db.QueryRow(ctx, GetSubModuleCapstoneByID, id)
var i GetSubModuleCapstoneByIDRow
err := row.Scan(
&i.ID,
&i.SubModuleID,
&i.Title,
&i.Description,
&i.Tips,
&i.Thumbnail,
&i.QuestionSetID,
&i.DisplayOrder,
&i.IsActive,
&i.Status,
&i.SetType,
&i.TimeLimitMinutes,
&i.PassingScore,
&i.ShuffleQuestions,
&i.QuestionCount,
)
return i, err
}
const GetSubModuleCapstones = `-- name: GetSubModuleCapstones :many
SELECT
smc.id,
smc.sub_module_id,
smc.title,
smc.description,
smc.tips,
smc.thumbnail,
smc.question_set_id,
smc.display_order,
smc.is_active,
qs.status,
qs.set_type,
qs.time_limit_minutes,
qs.passing_score,
qs.shuffle_questions,
(SELECT COUNT(*) FROM question_set_items qsi WHERE qsi.set_id = qs.id) AS question_count
FROM sub_module_capstones smc
JOIN question_sets qs ON qs.id = smc.question_set_id
WHERE smc.sub_module_id = $1
AND smc.is_active = TRUE
AND qs.set_type = 'CAPSTONE'
ORDER BY smc.display_order ASC, smc.id ASC
`
type GetSubModuleCapstonesRow struct {
ID int64 `json:"id"`
SubModuleID int64 `json:"sub_module_id"`
Title string `json:"title"`
Description pgtype.Text `json:"description"`
Tips pgtype.Text `json:"tips"`
Thumbnail pgtype.Text `json:"thumbnail"`
QuestionSetID int64 `json:"question_set_id"`
DisplayOrder int32 `json:"display_order"`
IsActive bool `json:"is_active"`
Status string `json:"status"`
SetType string `json:"set_type"`
TimeLimitMinutes pgtype.Int4 `json:"time_limit_minutes"`
PassingScore pgtype.Int4 `json:"passing_score"`
ShuffleQuestions bool `json:"shuffle_questions"`
QuestionCount int64 `json:"question_count"`
}
func (q *Queries) GetSubModuleCapstones(ctx context.Context, subModuleID int64) ([]GetSubModuleCapstonesRow, error) {
rows, err := q.db.Query(ctx, GetSubModuleCapstones, subModuleID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetSubModuleCapstonesRow
for rows.Next() {
var i GetSubModuleCapstonesRow
if err := rows.Scan(
&i.ID,
&i.SubModuleID,
&i.Title,
&i.Description,
&i.Tips,
&i.Thumbnail,
&i.QuestionSetID,
&i.DisplayOrder,
&i.IsActive,
&i.Status,
&i.SetType,
&i.TimeLimitMinutes,
&i.PassingScore,
&i.ShuffleQuestions,
&i.QuestionCount,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const GetSubModuleLessonByID = `-- name: GetSubModuleLessonByID :one const GetSubModuleLessonByID = `-- name: GetSubModuleLessonByID :one
SELECT id, sub_module_id, display_order, is_active, created_at, title, description, thumbnail, teaching_text, teaching_image_url, teaching_audio_url, teaching_video_url SELECT id, sub_module_id, display_order, is_active, created_at, title, description, thumbnail, teaching_text, teaching_image_url, teaching_audio_url, teaching_video_url
FROM sub_module_lessons FROM sub_module_lessons
@ -1214,7 +1645,7 @@ func (q *Queries) GetSubModuleVideos(ctx context.Context, subModuleID int64) ([]
} }
const GetSubModulesByModuleID = `-- name: GetSubModulesByModuleID :many const GetSubModulesByModuleID = `-- name: GetSubModulesByModuleID :many
SELECT id, module_id, title, description, display_order, is_active, created_at, legacy_sub_course_id SELECT id, module_id, title, description, display_order, is_active, created_at, legacy_sub_course_id, thumbnail, tips
FROM sub_modules FROM sub_modules
WHERE module_id = $1 WHERE module_id = $1
AND is_active = TRUE AND is_active = TRUE
@ -1239,6 +1670,8 @@ func (q *Queries) GetSubModulesByModuleID(ctx context.Context, moduleID int64) (
&i.IsActive, &i.IsActive,
&i.CreatedAt, &i.CreatedAt,
&i.LegacySubCourseID, &i.LegacySubCourseID,
&i.Thumbnail,
&i.Tips,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@ -1295,6 +1728,197 @@ func (q *Queries) UpdateLevel(ctx context.Context, arg UpdateLevelParams) (Level
return i, err return i, err
} }
const UpdateModule = `-- name: UpdateModule :one
UPDATE modules
SET
title = $1,
description = $2,
icon_url = $3,
display_order = $4,
is_active = $5
WHERE id = $6
RETURNING id, level_id, title, description, display_order, is_active, created_at, icon_url
`
type UpdateModuleParams struct {
Title string `json:"title"`
Description pgtype.Text `json:"description"`
IconUrl pgtype.Text `json:"icon_url"`
DisplayOrder int32 `json:"display_order"`
IsActive bool `json:"is_active"`
ID int64 `json:"id"`
}
func (q *Queries) UpdateModule(ctx context.Context, arg UpdateModuleParams) (Module, error) {
row := q.db.QueryRow(ctx, UpdateModule,
arg.Title,
arg.Description,
arg.IconUrl,
arg.DisplayOrder,
arg.IsActive,
arg.ID,
)
var i Module
err := row.Scan(
&i.ID,
&i.LevelID,
&i.Title,
&i.Description,
&i.DisplayOrder,
&i.IsActive,
&i.CreatedAt,
&i.IconUrl,
)
return i, err
}
const UpdateModuleCapstone = `-- name: UpdateModuleCapstone :one
UPDATE module_capstones
SET
title = $1,
description = $2,
tips = $3,
thumbnail = $4,
display_order = $5,
is_active = $6
WHERE id = $7
RETURNING id, module_id, title, description, tips, thumbnail, question_set_id, display_order, is_active, created_at
`
type UpdateModuleCapstoneParams struct {
Title string `json:"title"`
Description pgtype.Text `json:"description"`
Tips pgtype.Text `json:"tips"`
Thumbnail pgtype.Text `json:"thumbnail"`
DisplayOrder int32 `json:"display_order"`
IsActive bool `json:"is_active"`
ID int64 `json:"id"`
}
func (q *Queries) UpdateModuleCapstone(ctx context.Context, arg UpdateModuleCapstoneParams) (ModuleCapstone, error) {
row := q.db.QueryRow(ctx, UpdateModuleCapstone,
arg.Title,
arg.Description,
arg.Tips,
arg.Thumbnail,
arg.DisplayOrder,
arg.IsActive,
arg.ID,
)
var i ModuleCapstone
err := row.Scan(
&i.ID,
&i.ModuleID,
&i.Title,
&i.Description,
&i.Tips,
&i.Thumbnail,
&i.QuestionSetID,
&i.DisplayOrder,
&i.IsActive,
&i.CreatedAt,
)
return i, err
}
const UpdateSubModule = `-- name: UpdateSubModule :one
UPDATE sub_modules
SET
title = $1,
description = $2,
thumbnail = $3,
tips = $4,
display_order = $5,
is_active = $6
WHERE id = $7
RETURNING id, module_id, title, description, display_order, is_active, created_at, legacy_sub_course_id, thumbnail, tips
`
type UpdateSubModuleParams struct {
Title string `json:"title"`
Description pgtype.Text `json:"description"`
Thumbnail pgtype.Text `json:"thumbnail"`
Tips pgtype.Text `json:"tips"`
DisplayOrder int32 `json:"display_order"`
IsActive bool `json:"is_active"`
ID int64 `json:"id"`
}
func (q *Queries) UpdateSubModule(ctx context.Context, arg UpdateSubModuleParams) (SubModule, error) {
row := q.db.QueryRow(ctx, UpdateSubModule,
arg.Title,
arg.Description,
arg.Thumbnail,
arg.Tips,
arg.DisplayOrder,
arg.IsActive,
arg.ID,
)
var i SubModule
err := row.Scan(
&i.ID,
&i.ModuleID,
&i.Title,
&i.Description,
&i.DisplayOrder,
&i.IsActive,
&i.CreatedAt,
&i.LegacySubCourseID,
&i.Thumbnail,
&i.Tips,
)
return i, err
}
const UpdateSubModuleCapstone = `-- name: UpdateSubModuleCapstone :one
UPDATE sub_module_capstones
SET
title = $1,
description = $2,
tips = $3,
thumbnail = $4,
display_order = $5,
is_active = $6
WHERE id = $7
RETURNING id, sub_module_id, title, description, tips, thumbnail, question_set_id, display_order, is_active, created_at
`
type UpdateSubModuleCapstoneParams struct {
Title string `json:"title"`
Description pgtype.Text `json:"description"`
Tips pgtype.Text `json:"tips"`
Thumbnail pgtype.Text `json:"thumbnail"`
DisplayOrder int32 `json:"display_order"`
IsActive bool `json:"is_active"`
ID int64 `json:"id"`
}
func (q *Queries) UpdateSubModuleCapstone(ctx context.Context, arg UpdateSubModuleCapstoneParams) (SubModuleCapstone, error) {
row := q.db.QueryRow(ctx, UpdateSubModuleCapstone,
arg.Title,
arg.Description,
arg.Tips,
arg.Thumbnail,
arg.DisplayOrder,
arg.IsActive,
arg.ID,
)
var i SubModuleCapstone
err := row.Scan(
&i.ID,
&i.SubModuleID,
&i.Title,
&i.Description,
&i.Tips,
&i.Thumbnail,
&i.QuestionSetID,
&i.DisplayOrder,
&i.IsActive,
&i.CreatedAt,
)
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

@ -94,6 +94,20 @@ type Module 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"`
IconUrl pgtype.Text `json:"icon_url"`
}
type ModuleCapstone struct {
ID int64 `json:"id"`
ModuleID int64 `json:"module_id"`
Title string `json:"title"`
Description pgtype.Text `json:"description"`
Tips pgtype.Text `json:"tips"`
Thumbnail pgtype.Text `json:"thumbnail"`
QuestionSetID int64 `json:"question_set_id"`
DisplayOrder int32 `json:"display_order"`
IsActive bool `json:"is_active"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
} }
type ModuleToSubCourse struct { type ModuleToSubCourse struct {
@ -356,6 +370,21 @@ type SubModule struct {
IsActive bool `json:"is_active"` IsActive bool `json:"is_active"`
CreatedAt pgtype.Timestamptz `json:"created_at"` CreatedAt pgtype.Timestamptz `json:"created_at"`
LegacySubCourseID pgtype.Int8 `json:"legacy_sub_course_id"` LegacySubCourseID pgtype.Int8 `json:"legacy_sub_course_id"`
Thumbnail pgtype.Text `json:"thumbnail"`
Tips pgtype.Text `json:"tips"`
}
type SubModuleCapstone struct {
ID int64 `json:"id"`
SubModuleID int64 `json:"sub_module_id"`
Title string `json:"title"`
Description pgtype.Text `json:"description"`
Tips pgtype.Text `json:"tips"`
Thumbnail pgtype.Text `json:"thumbnail"`
QuestionSetID int64 `json:"question_set_id"`
DisplayOrder int32 `json:"display_order"`
IsActive bool `json:"is_active"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
} }
type SubModuleLesson struct { type SubModuleLesson struct {

View File

@ -146,6 +146,7 @@ type LearningPathSubCourse struct {
Title string `json:"title"` Title string `json:"title"`
Description *string `json:"description,omitempty"` Description *string `json:"description,omitempty"`
Thumbnail *string `json:"thumbnail,omitempty"` Thumbnail *string `json:"thumbnail,omitempty"`
Tips *string `json:"tips,omitempty"`
DisplayOrder int32 `json:"display_order"` DisplayOrder int32 `json:"display_order"`
Level string `json:"level"` Level string `json:"level"`
SubLevel string `json:"sub_level"` SubLevel string `json:"sub_level"`

View File

@ -27,6 +27,7 @@ const (
QuestionSetTypeQuiz QuestionSetType = "QUIZ" QuestionSetTypeQuiz QuestionSetType = "QUIZ"
QuestionSetTypeExam QuestionSetType = "EXAM" QuestionSetTypeExam QuestionSetType = "EXAM"
QuestionSetTypeSurvey QuestionSetType = "SURVEY" QuestionSetTypeSurvey QuestionSetType = "SURVEY"
QuestionSetTypeCapstone QuestionSetType = "CAPSTONE"
) )
type PracticeAccessBlock struct { type PracticeAccessBlock struct {

File diff suppressed because it is too large Load Diff

View File

@ -515,7 +515,7 @@ func (h *Handler) DeleteQuestion(c *fiber.Ctx) error {
type createQuestionSetReq struct { type createQuestionSetReq struct {
Title string `json:"title" validate:"required"` Title string `json:"title" validate:"required"`
Description *string `json:"description"` Description *string `json:"description"`
SetType string `json:"set_type" validate:"required,oneof=PRACTICE INITIAL_ASSESSMENT QUIZ EXAM SURVEY"` SetType string `json:"set_type" validate:"required,oneof=PRACTICE INITIAL_ASSESSMENT QUIZ EXAM SURVEY CAPSTONE"`
OwnerType *string `json:"owner_type"` OwnerType *string `json:"owner_type"`
OwnerID *int64 `json:"owner_id"` OwnerID *int64 `json:"owner_id"`
BannerImage *string `json:"banner_image"` BannerImage *string `json:"banner_image"`
@ -791,7 +791,7 @@ func (h *Handler) GetQuestionSetByID(c *fiber.Ctx) error {
// @Description Returns a paginated list of question sets filtered by type // @Description Returns a paginated list of question sets filtered by type
// @Tags question-sets // @Tags question-sets
// @Produce json // @Produce json
// @Param set_type query string true "Set type (PRACTICE, INITIAL_ASSESSMENT, QUIZ, EXAM, SURVEY)" // @Param set_type query string true "Set type (PRACTICE, INITIAL_ASSESSMENT, QUIZ, EXAM, SURVEY, CAPSTONE)"
// @Param limit query int false "Limit" default(10) // @Param limit query int false "Limit" default(10)
// @Param offset query int false "Offset" default(0) // @Param offset query int false "Offset" default(0)
// @Success 200 {object} domain.Response // @Success 200 {object} domain.Response

View File

@ -110,6 +110,7 @@ func (a *App) initAppRoutes() {
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.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.Put("/course-management/modules/:moduleId", a.authMiddleware, a.RequirePermission("subcourses.update"), h.UpdateModule)
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)
groupV1.Put("/course-management/sub-modules/:subModuleId", a.authMiddleware, a.RequirePermission("subcourses.update"), h.UpdateSubModule) groupV1.Put("/course-management/sub-modules/:subModuleId", a.authMiddleware, a.RequirePermission("subcourses.update"), h.UpdateSubModule)
@ -126,6 +127,16 @@ func (a *App) initAppRoutes() {
groupV1.Post("/course-management/sub-module-practices", a.authMiddleware, a.RequirePermission("question_sets.update"), h.CreateSubModulePractice) 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.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) groupV1.Delete("/course-management/practices/:practiceId", a.authMiddleware, a.RequirePermission("question_sets.delete"), h.DeletePractice)
groupV1.Get("/course-management/sub-modules/:subModuleId/capstones", a.authMiddleware, a.RequirePermission("question_sets.list"), h.GetSubModuleCapstones)
groupV1.Get("/course-management/capstones/:capstoneId", a.authMiddleware, a.RequirePermission("question_sets.get"), h.GetSubModuleCapstoneByID)
groupV1.Post("/course-management/sub-module-capstones", a.authMiddleware, a.RequirePermission("question_sets.update"), h.CreateSubModuleCapstone)
groupV1.Put("/course-management/capstones/:capstoneId", a.authMiddleware, a.RequirePermission("question_sets.update"), h.UpdateSubModuleCapstone)
groupV1.Delete("/course-management/capstones/:capstoneId", a.authMiddleware, a.RequirePermission("question_sets.delete"), h.DeleteCapstone)
groupV1.Get("/course-management/modules/:moduleId/capstones", a.authMiddleware, a.RequirePermission("question_sets.list"), h.GetModuleCapstones)
groupV1.Get("/course-management/module-capstones/:moduleCapstoneId", a.authMiddleware, a.RequirePermission("question_sets.get"), h.GetModuleCapstoneByID)
groupV1.Post("/course-management/module-capstones", a.authMiddleware, a.RequirePermission("question_sets.update"), h.CreateModuleCapstone)
groupV1.Put("/course-management/module-capstones/:moduleCapstoneId", a.authMiddleware, a.RequirePermission("question_sets.update"), h.UpdateModuleCapstone)
groupV1.Delete("/course-management/module-capstones/:moduleCapstoneId", a.authMiddleware, a.RequirePermission("question_sets.delete"), h.DeleteModuleCapstone)
// Questions // Questions
groupV1.Post("/questions", a.authMiddleware, a.RequirePermission("questions.create"), h.CreateQuestion) groupV1.Post("/questions", a.authMiddleware, a.RequirePermission("questions.create"), h.CreateQuestion)