README update

This commit is contained in:
Yared Yemane 2026-04-16 01:59:32 -07:00
parent a9c6966820
commit 1c8d041747
7 changed files with 1943 additions and 294 deletions

View File

@ -1,4 +1,4 @@
# Yimaru Backend API # Yimaru Backend
Yimaru Backend is the server-side application that powers the Yimaru online learning system. It manages courses, lessons, quizzes, student progress, instructor content, and administrative operations for institutions and users on the platform. Yimaru Backend is the server-side application that powers the Yimaru online learning system. It manages courses, lessons, quizzes, student progress, instructor content, and administrative operations for institutions and users on the platform.

View File

@ -5,7 +5,7 @@ services:
container_name: yimaru-backend-postgres-1 container_name: yimaru-backend-postgres-1
image: postgres:16-alpine image: postgres:16-alpine
ports: ports:
- "5592:5422" - "5692:5422"
environment: environment:
- POSTGRES_PASSWORD=secret - POSTGRES_PASSWORD=secret
- POSTGRES_USER=root - POSTGRES_USER=root

View File

@ -0,0 +1,825 @@
{
"info": {
"_postman_id": "d8d17a29-5f9c-4f06-95fd-12e9862f97f8",
"name": "Yimaru Backend - Course Management APIs",
"description": "Fully documented Postman collection for all course-management related endpoints in Yimaru Backend.\n\nAuthentication:\n- All endpoints require `Authorization: Bearer {{accessToken}}`.\n\nBase URL:\n- `{{baseUrl}}/api/{{apiVersion}}`\n\nNotes:\n- IDs in path params must be positive integers.\n- Some update endpoints support partial updates.\n- Endpoint-level permission requirements are documented in each request description.",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"variable": [
{
"key": "baseUrl",
"value": "https://api.yimaruacademy.com",
"type": "string"
},
{
"key": "apiVersion",
"value": "v1",
"type": "string"
},
{
"key": "accessToken",
"value": "",
"type": "string"
},
{
"key": "categoryId",
"value": "1",
"type": "string"
},
{
"key": "subCategoryId",
"value": "1",
"type": "string"
},
{
"key": "courseId",
"value": "1",
"type": "string"
},
{
"key": "levelId",
"value": "1",
"type": "string"
},
{
"key": "moduleId",
"value": "1",
"type": "string"
},
{
"key": "subModuleId",
"value": "1",
"type": "string"
},
{
"key": "videoId",
"value": "1",
"type": "string"
},
{
"key": "questionSetId",
"value": "1",
"type": "string"
},
{
"key": "practiceId",
"value": "1",
"type": "string"
}
],
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "{{accessToken}}",
"type": "string"
}
]
},
"item": [
{
"name": "Categories & Courses",
"item": [
{
"name": "List Course Categories",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/{{apiVersion}}/course-management/categories",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"{{apiVersion}}",
"course-management",
"categories"
]
},
"description": "Returns all course categories.\n\nPermission: `learning_tree.get`."
}
},
{
"name": "Create Course Category",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"name\": \"Grammar\",\n \"is_active\": true\n}"
},
"url": {
"raw": "{{baseUrl}}/api/{{apiVersion}}/course-management/categories",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"{{apiVersion}}",
"course-management",
"categories"
]
},
"description": "Creates a course category.\n\nRequired fields:\n- `name` (string)\n\nOptional fields:\n- `is_active` (boolean)\n\nPermission: `course_categories.create`."
}
},
{
"name": "Delete Course Category",
"request": {
"method": "DELETE",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/{{apiVersion}}/course-management/categories/{{categoryId}}",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"{{apiVersion}}",
"course-management",
"categories",
"{{categoryId}}"
]
},
"description": "Deletes a category by ID.\n\nPath params:\n- `categoryId` (int, required)\n\nPermission: `course_categories.delete`."
}
},
{
"name": "List Courses By Category",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/{{apiVersion}}/course-management/categories/{{categoryId}}/courses?offset=0&limit=50",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"{{apiVersion}}",
"course-management",
"categories",
"{{categoryId}}",
"courses"
],
"query": [
{
"key": "offset",
"value": "0"
},
{
"key": "limit",
"value": "50"
}
]
},
"description": "Returns all courses under a category.\n\nPath params:\n- `categoryId` (int, required)\n\nQuery params:\n- `offset` (int, optional, default 0)\n- `limit` (int, optional, default 10000)\n\nPermission: `learning_tree.get`."
}
},
{
"name": "Create Course",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"category_id\": 1,\n \"sub_category_id\": 1,\n \"title\": \"English Basics\",\n \"description\": \"Beginner-level course\",\n \"thumbnail\": \"https://cdn.example.com/course-thumb.jpg\",\n \"intro_video_url\": \"https://cdn.example.com/intro.mp4\",\n \"is_active\": true\n}"
},
"url": {
"raw": "{{baseUrl}}/api/{{apiVersion}}/course-management/courses",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"{{apiVersion}}",
"course-management",
"courses"
]
},
"description": "Creates a course.\n\nRequired fields:\n- `category_id` (int)\n- `title` (string)\n\nOptional fields:\n- `sub_category_id` (int)\n- `description` (string)\n- `thumbnail` (string URL)\n- `intro_video_url` (string URL)\n- `is_active` (boolean)\n\nPermission: `courses.create`."
}
},
{
"name": "Update Course",
"request": {
"method": "PUT",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"title\": \"English Basics - Updated\",\n \"description\": \"Updated course description\",\n \"thumbnail\": \"https://cdn.example.com/new-thumb.jpg\",\n \"intro_video_url\": \"https://cdn.example.com/new-intro.mp4\",\n \"is_active\": true\n}"
},
"url": {
"raw": "{{baseUrl}}/api/{{apiVersion}}/course-management/courses/{{courseId}}",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"{{apiVersion}}",
"course-management",
"courses",
"{{courseId}}"
]
},
"description": "Updates an existing course (partial update accepted).\n\nPath params:\n- `courseId` (int, required)\n\nOptional fields:\n- `title`, `description`, `thumbnail`, `intro_video_url`, `is_active`\n\nPermission: `courses.update`."
}
},
{
"name": "Delete Course",
"request": {
"method": "DELETE",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/{{apiVersion}}/course-management/courses/{{courseId}}",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"{{apiVersion}}",
"course-management",
"courses",
"{{courseId}}"
]
},
"description": "Deletes a course.\n\nPath params:\n- `courseId` (int, required)\n\nPermission: `courses.delete`."
}
},
{
"name": "Update Course Thumbnail",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"thumbnail_url\": \"https://cdn.example.com/new-thumbnail.jpg\"\n}"
},
"url": {
"raw": "{{baseUrl}}/api/{{apiVersion}}/course-management/courses/{{courseId}}/thumbnail",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"{{apiVersion}}",
"course-management",
"courses",
"{{courseId}}",
"thumbnail"
]
},
"description": "Updates only the thumbnail of a course.\n\nPath params:\n- `courseId` (int, required)\n\nRequired fields:\n- `thumbnail_url` (string)\n\nPermission: `courses.upload_thumbnail`."
}
}
]
},
{
"name": "Hierarchy & Learning Path",
"item": [
{
"name": "Get Unified Hierarchy",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/{{apiVersion}}/course-management/hierarchy",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"{{apiVersion}}",
"course-management",
"hierarchy"
]
},
"description": "Returns global hierarchy data.\n\nPermission: `learning_tree.get`."
}
},
{
"name": "Get Human Language Hierarchy",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/{{apiVersion}}/course-management/human-language/hierarchy",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"{{apiVersion}}",
"course-management",
"human-language",
"hierarchy"
]
},
"description": "Alias endpoint for unified hierarchy under human-language path.\n\nPermission: `learning_tree.get`."
}
},
{
"name": "Get Course Hierarchy",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/{{apiVersion}}/course-management/courses/{{courseId}}/hierarchy",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"{{apiVersion}}",
"course-management",
"courses",
"{{courseId}}",
"hierarchy"
]
},
"description": "Returns hierarchy nodes for one course.\n\nPath params:\n- `courseId` (int, required)\n\nPermission: `learning_tree.get`."
}
},
{
"name": "Get Course Learning Path",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/{{apiVersion}}/course-management/courses/{{courseId}}/learning-path",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"{{apiVersion}}",
"course-management",
"courses",
"{{courseId}}",
"learning-path"
]
},
"description": "Returns learning-path projection for a course including sub-modules, videos, and practices.\n\nPath params:\n- `courseId` (int, required)\n\nPermission: `learning_tree.get`."
}
}
]
},
{
"name": "Sub-Categories, Levels, Modules, Sub-Modules",
"item": [
{
"name": "Create Course Sub-Category",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"category_id\": 1,\n \"name\": \"Everyday Conversation\",\n \"description\": \"Spoken communication track\",\n \"display_order\": 1,\n \"is_active\": true\n}"
},
"url": {
"raw": "{{baseUrl}}/api/{{apiVersion}}/course-management/sub-categories",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"{{apiVersion}}",
"course-management",
"sub-categories"
]
},
"description": "Creates a sub-category under a course category.\n\nRequired fields:\n- `category_id` (int)\n- `name` (string)\n\nOptional fields:\n- `description` (string)\n- `display_order` (int)\n- `is_active` (boolean)\n\nPermission: `course_categories.create`."
}
},
{
"name": "Delete Course Sub-Category",
"request": {
"method": "DELETE",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/{{apiVersion}}/course-management/sub-categories/{{subCategoryId}}",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"{{apiVersion}}",
"course-management",
"sub-categories",
"{{subCategoryId}}"
]
},
"description": "Deletes a sub-category.\n\nPath params:\n- `subCategoryId` (int, required)\n\nPermission: `course_categories.delete`."
}
},
{
"name": "Create Level",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"course_id\": 1,\n \"cefr_level\": \"A1\",\n \"display_order\": 1,\n \"is_active\": true\n}"
},
"url": {
"raw": "{{baseUrl}}/api/{{apiVersion}}/course-management/levels",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"{{apiVersion}}",
"course-management",
"levels"
]
},
"description": "Creates a CEFR level under a course.\n\nRequired fields:\n- `course_id` (int)\n- `cefr_level` (one of: A1, A2, A3, B1, B2, B3, C1, C2, C3)\n\nOptional fields:\n- `display_order` (int)\n- `is_active` (boolean)\n\nPermission: `subcourses.create`."
}
},
{
"name": "Create Module",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"level_id\": 1,\n \"title\": \"Module 1: Introductions\",\n \"description\": \"Core introduction module\",\n \"display_order\": 1,\n \"is_active\": true\n}"
},
"url": {
"raw": "{{baseUrl}}/api/{{apiVersion}}/course-management/modules",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"{{apiVersion}}",
"course-management",
"modules"
]
},
"description": "Creates a module under a level.\n\nRequired fields:\n- `level_id` (int)\n- `title` (string)\n\nOptional fields:\n- `description`, `display_order`, `is_active`\n\nPermission: `subcourses.create`."
}
},
{
"name": "Delete Module",
"request": {
"method": "DELETE",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/{{apiVersion}}/course-management/modules/{{moduleId}}",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"{{apiVersion}}",
"course-management",
"modules",
"{{moduleId}}"
]
},
"description": "Deletes a module.\n\nPath params:\n- `moduleId` (int, required)\n\nPermission: `subcourses.delete`."
}
},
{
"name": "Create Sub-Module",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"module_id\": 1,\n \"title\": \"Sub-Module 1: Greetings\",\n \"description\": \"Greetings and polite expressions\",\n \"display_order\": 1,\n \"is_active\": true\n}"
},
"url": {
"raw": "{{baseUrl}}/api/{{apiVersion}}/course-management/sub-modules",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"{{apiVersion}}",
"course-management",
"sub-modules"
]
},
"description": "Creates a sub-module under a module.\n\nRequired fields:\n- `module_id` (int)\n- `title` (string)\n\nOptional fields:\n- `description`, `display_order`, `is_active`\n\nPermission: `subcourses.create`."
}
},
{
"name": "Update Sub-Module",
"request": {
"method": "PUT",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"title\": \"Sub-Module 1: Greetings (Updated)\",\n \"description\": \"Updated sub-module description\",\n \"is_active\": true\n}"
},
"url": {
"raw": "{{baseUrl}}/api/{{apiVersion}}/course-management/sub-modules/{{subModuleId}}",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"{{apiVersion}}",
"course-management",
"sub-modules",
"{{subModuleId}}"
]
},
"description": "Updates a sub-module.\n\nPath params:\n- `subModuleId` (int, required)\n\nOptional fields:\n- `title`, `description`, `is_active`\n\nPermission: `subcourses.update`."
}
},
{
"name": "Delete Sub-Module",
"request": {
"method": "DELETE",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/{{apiVersion}}/course-management/sub-modules/{{subModuleId}}",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"{{apiVersion}}",
"course-management",
"sub-modules",
"{{subModuleId}}"
]
},
"description": "Deletes a sub-module.\n\nPath params:\n- `subModuleId` (int, required)\n\nPermission: `subcourses.delete`."
}
}
]
},
{
"name": "Sub-Module Videos",
"item": [
{
"name": "Get Sub-Module Videos",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/{{apiVersion}}/course-management/sub-modules/{{subModuleId}}/videos",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"{{apiVersion}}",
"course-management",
"sub-modules",
"{{subModuleId}}",
"videos"
]
},
"description": "Lists videos for a given sub-module.\n\nPath params:\n- `subModuleId` (int, required)\n\nPermission: `videos.list`."
}
},
{
"name": "Create Sub-Module Video",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"sub_module_id\": 1,\n \"title\": \"Greeting Expressions\",\n \"description\": \"Main lesson video\",\n \"video_url\": \"https://cdn.example.com/videos/greetings.mp4\",\n \"duration\": 480,\n \"resolution\": \"1080p\",\n \"visibility\": \"public\",\n \"instructor_id\": \"instructor-123\",\n \"thumbnail\": \"https://cdn.example.com/thumbs/greetings.jpg\",\n \"display_order\": 1,\n \"status\": \"published\"\n}"
},
"url": {
"raw": "{{baseUrl}}/api/{{apiVersion}}/course-management/sub-module-videos",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"{{apiVersion}}",
"course-management",
"sub-module-videos"
]
},
"description": "Creates a video under a sub-module.\n\nRequired fields:\n- `sub_module_id` (int)\n- `title` (string)\n- `video_url` (string URL)\n\nOptional fields:\n- `description`, `duration`, `resolution`, `visibility`, `instructor_id`, `thumbnail`, `display_order`, `status`\n\nPermission: `videos.create`."
}
},
{
"name": "Update Sub-Module Video",
"request": {
"method": "PUT",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"title\": \"Greeting Expressions - Updated\",\n \"description\": \"Updated video description\",\n \"video_url\": \"https://cdn.example.com/videos/greetings-v2.mp4\"\n}"
},
"url": {
"raw": "{{baseUrl}}/api/{{apiVersion}}/course-management/sub-module-videos/{{videoId}}",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"{{apiVersion}}",
"course-management",
"sub-module-videos",
"{{videoId}}"
]
},
"description": "Updates a sub-module video.\n\nPath params:\n- `videoId` (int, required)\n\nRequired fields:\n- `title` (string)\n- `video_url` (string URL)\n\nOptional fields:\n- `description`\n\nPermission: `videos.update`."
}
},
{
"name": "Delete Sub-Module Video",
"request": {
"method": "DELETE",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/{{apiVersion}}/course-management/sub-module-videos/{{videoId}}",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"{{apiVersion}}",
"course-management",
"sub-module-videos",
"{{videoId}}"
]
},
"description": "Deletes a sub-module video.\n\nPath params:\n- `videoId` (int, required)\n\nPermission: `videos.delete`."
}
}
]
},
{
"name": "Lessons & Practices",
"item": [
{
"name": "Attach Sub-Module Lesson (Question Set)",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"sub_module_id\": 1,\n \"question_set_id\": 1,\n \"intro_video_url\": \"https://cdn.example.com/intro-lesson.mp4\",\n \"display_order\": 1,\n \"is_active\": true\n}"
},
"url": {
"raw": "{{baseUrl}}/api/{{apiVersion}}/course-management/sub-module-lessons",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"{{apiVersion}}",
"course-management",
"sub-module-lessons"
]
},
"description": "Links a question set lesson to a sub-module.\n\nRequired fields:\n- `sub_module_id` (int)\n- `question_set_id` (int)\n\nOptional fields:\n- `intro_video_url`, `display_order`, `is_active`\n\nPermission: `question_sets.update`."
}
},
{
"name": "Create Sub-Module Practice",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"sub_module_id\": 1,\n \"title\": \"Practice: Basic Greetings\",\n \"description\": \"Practice set for greetings\",\n \"thumbnail\": \"https://cdn.example.com/practice-thumb.jpg\",\n \"intro_video_url\": \"https://cdn.example.com/practice-intro.mp4\",\n \"question_set_id\": 1,\n \"display_order\": 1,\n \"is_active\": true\n}"
},
"url": {
"raw": "{{baseUrl}}/api/{{apiVersion}}/course-management/sub-module-practices",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"{{apiVersion}}",
"course-management",
"sub-module-practices"
]
},
"description": "Creates a practice under a sub-module.\n\nRequired fields:\n- `sub_module_id` (int)\n- `title` (string)\n- `question_set_id` (int)\n\nOptional fields:\n- `description`, `thumbnail`, `intro_video_url`, `display_order`, `is_active`\n\nPermission: `question_sets.update`."
}
},
{
"name": "Update Practice",
"request": {
"method": "PUT",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"title\": \"Practice: Basic Greetings - Updated\",\n \"description\": \"Updated practice details\",\n \"persona\": \"student\",\n \"is_active\": true\n}"
},
"url": {
"raw": "{{baseUrl}}/api/{{apiVersion}}/course-management/practices/{{practiceId}}",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"{{apiVersion}}",
"course-management",
"practices",
"{{practiceId}}"
]
},
"description": "Updates a practice.\n\nPath params:\n- `practiceId` (int, required)\n\nBehavior:\n- If `is_active` is provided, this endpoint updates practice status.\n- Otherwise, `title` is required and metadata update is applied.\n\nPermission: `question_sets.update`."
}
},
{
"name": "Delete Practice",
"request": {
"method": "DELETE",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/{{apiVersion}}/course-management/practices/{{practiceId}}",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"{{apiVersion}}",
"course-management",
"practices",
"{{practiceId}}"
]
},
"description": "Deletes a practice.\n\nPath params:\n- `practiceId` (int, required)\n\nPermission: `question_sets.delete`."
}
}
]
}
]
}

View File

@ -1,292 +0,0 @@
# Human Language Mobile Integration Guide
This guide explains how to integrate the new **Human Language** feature in the **Yimaru learner mobile app** (not admin).
It is designed to keep the existing non-language hierarchy intact while introducing a dedicated CEFR-based flow for language learning.
---
## 1) Scope and Goals
### What is new
- A dedicated backend API namespace for Human Language:
- `GET /api/v1/course-management/human-language/courses/:courseId/lessons?cefr_level=A1..C3`
- `POST /api/v1/course-management/human-language/lessons` (admin/instructor side)
- `PATCH /api/v1/course-management/human-language/lessons/:id` (admin/instructor side)
- CEFR levels are fixed to:
- `A1, A2, A3, B1, B2, B3, C1, C2, C3`
- No custom sub-levels in Human Language flow.
### What remains unchanged
- Existing non-human-language content hierarchy and APIs.
- Existing course/category/sub-course endpoints for non-language domains (programming, etc.).
---
## 2) Target Hierarchy for Human Language
- Course Category (Human Language)
- Course (e.g., English)
- CEFR Lesson Unit (A1..C3 only)
- Intro/lesson videos
- Practices
- Audio questions
Backend implementation stores CEFR lesson units using the existing sub-course model, with CEFR mapped internally and validated strictly.
---
## 3) Authentication and Access
All Human Language endpoints are under `/api/v1` and require bearer token auth.
- Header:
- `Authorization: Bearer <access_token>`
### Permission notes
- **Learner mobile app** typically needs only the `GET` endpoint.
- `POST` and `PATCH` are content-management endpoints and should generally be used by admin/instructor clients, not learner clients.
If learner roles do not currently have `learning_tree.get`, coordinate RBAC assignment for read-only access to the `GET` endpoint.
---
## 4) Endpoint Contracts
## 4.1 Fetch lessons by CEFR level (learner-facing)
### Request
`GET /api/v1/course-management/human-language/courses/{courseId}/lessons?cefr_level={A1|A2|A3|B1|B2|B3|C1|C2|C3}`
### Example
`GET /api/v1/course-management/human-language/courses/12/lessons?cefr_level=B1`
### Response (success)
```json
{
"message": "Human-language lessons retrieved successfully",
"data": {
"course_id": 12,
"course_title": "English",
"cefr_level": "B1",
"lessons": [
{
"id": 201,
"course_id": 12,
"title": "B1 Module 1",
"description": "Intermediate conversational patterns",
"thumbnail": "https://.../thumb.jpg",
"display_order": 1,
"level": "B1",
"video_count": 3,
"practice_count": 2,
"videos": [
{
"id": 9001,
"title": "B1 Intro",
"description": "Lesson intro",
"video_url": "https://...",
"duration": 420,
"resolution": "1080p",
"display_order": 1
}
],
"practices": [
{
"id": 4401,
"title": "B1 Speaking Practice",
"status": "PUBLISHED",
"question_count": 10
}
]
}
]
},
"success": true,
"status_code": 200,
"metadata": null
}
```
### Validation errors
- Invalid CEFR level:
- `400` with error message: `Use one of: A1,A2,A3,B1,B2,B3,C1,C2,C3`
- Course not in human language category:
- `400` with error message indicating invalid human-language course.
---
## 4.2 Create Human Language lesson unit (admin/instructor)
### Request
`POST /api/v1/course-management/human-language/lessons`
```json
{
"course_id": 12,
"title": "A2 Module 1",
"description": "A2 speaking fundamentals",
"thumbnail": "https://...",
"display_order": 1,
"cefr_level": "A2"
}
```
### Response
Returns created lesson metadata with CEFR-normalized level behavior.
---
## 4.3 Update Human Language lesson unit (admin/instructor)
### Request
`PATCH /api/v1/course-management/human-language/lessons/{lessonId}`
```json
{
"title": "A2 Module 1 - Updated",
"description": "Updated description",
"cefr_level": "A3",
"is_active": true
}
```
---
## 5) Recommended Mobile App Integration Flow
## Step 1: Discover courses under Human Language category
Use existing endpoints:
1. `GET /course-management/categories`
2. Pick category where name indicates human language.
3. `GET /course-management/categories/:categoryId/courses`
## Step 2: Select CEFR level in UI
Use static list in app:
- `A1, A2, A3, B1, B2, B3, C1, C2, C3`
Do not allow custom levels in mobile UI.
## Step 3: Fetch lessons by selected CEFR level
Call:
- `GET /course-management/human-language/courses/:courseId/lessons?cefr_level=<selected>`
Render grouped lessons with nested videos/practices from response.
## Step 4: Navigate to learning/practice screens
- Lesson video playback uses returned `videos[]`.
- Practice entry uses returned `practices[]` IDs and existing practice-question endpoints.
---
## 6) Mobile UI/UX Recommendations
- Use CEFR tabs/segmented control (`A1`...`C3`) at top.
- Cache last selected level per course.
- Show empty state per level:
- "No lessons available for this level yet."
- Sort by `display_order` and maintain backend order.
- Show badges:
- video count
- practice count
---
## 7) Error Handling and Resilience
- `400` invalid level: reset selection to previous valid CEFR value and show toast/snackbar.
- `401/403`: trigger token refresh / re-login flow.
- `5xx`: show retry UI with exponential backoff.
- If category/course fetch succeeds but level fetch fails, keep course visible and allow manual retry.
---
## 8) Data Model Mapping (Mobile DTO)
Suggested DTO for learner app:
```ts
type CefrLevel = "A1"|"A2"|"A3"|"B1"|"B2"|"B3"|"C1"|"C2"|"C3";
interface HumanLanguageLessonDTO {
id: number;
courseId: number;
title: string;
description?: string;
thumbnail?: string;
order: number;
level: CefrLevel;
videoCount: number;
practiceCount: number;
videos: {
id: number;
title: string;
url: string;
duration: number;
order: number;
}[];
practices: {
id: number;
title: string;
status: string;
questionCount: number;
}[];
}
```
---
## 9) Backward Compatibility
- Existing non-language content (programming and other categories) continues to use current APIs and hierarchy unchanged.
- New Human Language endpoint is additive and isolated.
- Mobile app can progressively enable the new flow for language categories only.
---
## 10) QA Checklist for Mobile Team
- [ ] Category discovery correctly identifies Human Language category.
- [ ] CEFR selector only allows A1..C3.
- [ ] Fetch by CEFR level returns only matching lessons.
- [ ] Video/practice counts match rendered lists.
- [ ] Empty-level state works.
- [ ] Unauthorized/session-expired flow works.
- [ ] Non-language courses still load via existing app flow.
---
## 11) Example Call Matrix
- Load language categories/courses:
- `GET /course-management/categories`
- `GET /course-management/categories/:id/courses`
- Load A1 lessons:
- `GET /course-management/human-language/courses/:courseId/lessons?cefr_level=A1`
- Load B2 lessons:
- `GET /course-management/human-language/courses/:courseId/lessons?cefr_level=B2`
---
For backend ownership questions, refer to:
- `internal/web_server/handlers/course_management.go`
- `internal/web_server/routes.go`

View File

@ -713,6 +713,267 @@ const docTemplate = `{
} }
} }
}, },
"/api/v1/course-management/categories": {
"get": {
"description": "Legacy-compatible endpoint for listing course categories",
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "List course categories",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
},
"post": {
"description": "Legacy-compatible endpoint for creating a course category",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Create course category",
"parameters": [
{
"description": "Create category payload",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.createCourseCategoryReq"
}
}
],
"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/categories/{categoryId}/courses": {
"get": {
"description": "Legacy-compatible endpoint that returns courses for one category",
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "List courses by category",
"parameters": [
{
"type": "integer",
"description": "Category ID",
"name": "categoryId",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "Offset",
"name": "offset",
"in": "query"
},
{
"type": "integer",
"description": "Limit",
"name": "limit",
"in": "query"
}
],
"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/courses": {
"post": {
"description": "Legacy-compatible endpoint for creating a course",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Create course",
"parameters": [
{
"description": "Create course payload",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.createCourseReq"
}
}
],
"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/courses/{courseId}": {
"put": {
"description": "Legacy-compatible endpoint for updating a course",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Update course",
"parameters": [
{
"type": "integer",
"description": "Course ID",
"name": "courseId",
"in": "path",
"required": true
},
{
"description": "Update course payload",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.updateCourseReq"
}
}
],
"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"
}
}
}
},
"delete": {
"description": "Legacy-compatible endpoint for deleting a course",
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Delete course",
"parameters": [
{
"type": "integer",
"description": "Course ID",
"name": "courseId",
"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/courses/{courseId}/hierarchy": { "/api/v1/course-management/courses/{courseId}/hierarchy": {
"get": { "get": {
"description": "Returns hierarchy nodes for one course including levels/modules/sub-modules", "description": "Returns hierarchy nodes for one course including levels/modules/sub-modules",
@ -754,6 +1015,100 @@ const docTemplate = `{
} }
} }
}, },
"/api/v1/course-management/courses/{courseId}/learning-path": {
"get": {
"description": "Legacy-compatible endpoint for course learning path",
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Get course learning path",
"parameters": [
{
"type": "integer",
"description": "Course ID",
"name": "courseId",
"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/courses/{courseId}/thumbnail": {
"post": {
"description": "Legacy-compatible endpoint for updating course thumbnail",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Update course thumbnail",
"parameters": [
{
"type": "integer",
"description": "Course ID",
"name": "courseId",
"in": "path",
"required": true
},
{
"description": "Update course thumbnail payload",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.updateCourseThumbnailReq"
}
}
],
"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/hierarchy": { "/api/v1/course-management/hierarchy": {
"get": { "get": {
"description": "Returns full hierarchy: category -\u003e sub-category -\u003e course", "description": "Returns full hierarchy: category -\u003e sub-category -\u003e course",
@ -9057,6 +9412,43 @@ const docTemplate = `{
} }
} }
}, },
"handlers.createCourseCategoryReq": {
"type": "object",
"properties": {
"is_active": {
"type": "boolean"
},
"name": {
"type": "string"
}
}
},
"handlers.createCourseReq": {
"type": "object",
"properties": {
"category_id": {
"type": "integer"
},
"description": {
"type": "string"
},
"intro_video_url": {
"type": "string"
},
"is_active": {
"type": "boolean"
},
"sub_category_id": {
"type": "integer"
},
"thumbnail": {
"type": "string"
},
"title": {
"type": "string"
}
}
},
"handlers.createCourseSubCategoryReq": { "handlers.createCourseSubCategoryReq": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -9658,6 +10050,34 @@ const docTemplate = `{
} }
} }
}, },
"handlers.updateCourseReq": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"intro_video_url": {
"type": "string"
},
"is_active": {
"type": "boolean"
},
"thumbnail": {
"type": "string"
},
"title": {
"type": "string"
}
}
},
"handlers.updateCourseThumbnailReq": {
"type": "object",
"properties": {
"thumbnail_url": {
"type": "string"
}
}
},
"handlers.updateIssueStatusReq": { "handlers.updateIssueStatusReq": {
"type": "object", "type": "object",
"required": [ "required": [

View File

@ -705,6 +705,267 @@
} }
} }
}, },
"/api/v1/course-management/categories": {
"get": {
"description": "Legacy-compatible endpoint for listing course categories",
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "List course categories",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
},
"post": {
"description": "Legacy-compatible endpoint for creating a course category",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Create course category",
"parameters": [
{
"description": "Create category payload",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.createCourseCategoryReq"
}
}
],
"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/categories/{categoryId}/courses": {
"get": {
"description": "Legacy-compatible endpoint that returns courses for one category",
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "List courses by category",
"parameters": [
{
"type": "integer",
"description": "Category ID",
"name": "categoryId",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "Offset",
"name": "offset",
"in": "query"
},
{
"type": "integer",
"description": "Limit",
"name": "limit",
"in": "query"
}
],
"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/courses": {
"post": {
"description": "Legacy-compatible endpoint for creating a course",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Create course",
"parameters": [
{
"description": "Create course payload",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.createCourseReq"
}
}
],
"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/courses/{courseId}": {
"put": {
"description": "Legacy-compatible endpoint for updating a course",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Update course",
"parameters": [
{
"type": "integer",
"description": "Course ID",
"name": "courseId",
"in": "path",
"required": true
},
{
"description": "Update course payload",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.updateCourseReq"
}
}
],
"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"
}
}
}
},
"delete": {
"description": "Legacy-compatible endpoint for deleting a course",
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Delete course",
"parameters": [
{
"type": "integer",
"description": "Course ID",
"name": "courseId",
"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/courses/{courseId}/hierarchy": { "/api/v1/course-management/courses/{courseId}/hierarchy": {
"get": { "get": {
"description": "Returns hierarchy nodes for one course including levels/modules/sub-modules", "description": "Returns hierarchy nodes for one course including levels/modules/sub-modules",
@ -746,6 +1007,100 @@
} }
} }
}, },
"/api/v1/course-management/courses/{courseId}/learning-path": {
"get": {
"description": "Legacy-compatible endpoint for course learning path",
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Get course learning path",
"parameters": [
{
"type": "integer",
"description": "Course ID",
"name": "courseId",
"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/courses/{courseId}/thumbnail": {
"post": {
"description": "Legacy-compatible endpoint for updating course thumbnail",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"course-management"
],
"summary": "Update course thumbnail",
"parameters": [
{
"type": "integer",
"description": "Course ID",
"name": "courseId",
"in": "path",
"required": true
},
{
"description": "Update course thumbnail payload",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.updateCourseThumbnailReq"
}
}
],
"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/hierarchy": { "/api/v1/course-management/hierarchy": {
"get": { "get": {
"description": "Returns full hierarchy: category -\u003e sub-category -\u003e course", "description": "Returns full hierarchy: category -\u003e sub-category -\u003e course",
@ -9049,6 +9404,43 @@
} }
} }
}, },
"handlers.createCourseCategoryReq": {
"type": "object",
"properties": {
"is_active": {
"type": "boolean"
},
"name": {
"type": "string"
}
}
},
"handlers.createCourseReq": {
"type": "object",
"properties": {
"category_id": {
"type": "integer"
},
"description": {
"type": "string"
},
"intro_video_url": {
"type": "string"
},
"is_active": {
"type": "boolean"
},
"sub_category_id": {
"type": "integer"
},
"thumbnail": {
"type": "string"
},
"title": {
"type": "string"
}
}
},
"handlers.createCourseSubCategoryReq": { "handlers.createCourseSubCategoryReq": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -9650,6 +10042,34 @@
} }
} }
}, },
"handlers.updateCourseReq": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"intro_video_url": {
"type": "string"
},
"is_active": {
"type": "boolean"
},
"thumbnail": {
"type": "string"
},
"title": {
"type": "string"
}
}
},
"handlers.updateCourseThumbnailReq": {
"type": "object",
"properties": {
"thumbnail_url": {
"type": "string"
}
}
},
"handlers.updateIssueStatusReq": { "handlers.updateIssueStatusReq": {
"type": "object", "type": "object",
"required": [ "required": [

View File

@ -947,6 +947,30 @@ definitions:
confirm: confirm:
type: string type: string
type: object type: object
handlers.createCourseCategoryReq:
properties:
is_active:
type: boolean
name:
type: string
type: object
handlers.createCourseReq:
properties:
category_id:
type: integer
description:
type: string
intro_video_url:
type: string
is_active:
type: boolean
sub_category_id:
type: integer
thumbnail:
type: string
title:
type: string
type: object
handlers.createCourseSubCategoryReq: handlers.createCourseSubCategoryReq:
properties: properties:
category_id: category_id:
@ -1354,6 +1378,24 @@ definitions:
example: false example: false
type: boolean type: boolean
type: object type: object
handlers.updateCourseReq:
properties:
description:
type: string
intro_video_url:
type: string
is_active:
type: boolean
thumbnail:
type: string
title:
type: string
type: object
handlers.updateCourseThumbnailReq:
properties:
thumbnail_url:
type: string
type: object
handlers.updateIssueStatusReq: handlers.updateIssueStatusReq:
properties: properties:
status: status:
@ -2323,6 +2365,178 @@ paths:
summary: Refresh token summary: Refresh token
tags: tags:
- auth - auth
/api/v1/course-management/categories:
get:
description: Legacy-compatible endpoint for listing course categories
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/domain.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/domain.ErrorResponse'
summary: List course categories
tags:
- course-management
post:
consumes:
- application/json
description: Legacy-compatible endpoint for creating a course category
parameters:
- description: Create category payload
in: body
name: body
required: true
schema:
$ref: '#/definitions/handlers.createCourseCategoryReq'
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 course category
tags:
- course-management
/api/v1/course-management/categories/{categoryId}/courses:
get:
description: Legacy-compatible endpoint that returns courses for one category
parameters:
- description: Category ID
in: path
name: categoryId
required: true
type: integer
- description: Offset
in: query
name: offset
type: integer
- description: Limit
in: query
name: limit
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 courses by category
tags:
- course-management
/api/v1/course-management/courses:
post:
consumes:
- application/json
description: Legacy-compatible endpoint for creating a course
parameters:
- description: Create course payload
in: body
name: body
required: true
schema:
$ref: '#/definitions/handlers.createCourseReq'
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 course
tags:
- course-management
/api/v1/course-management/courses/{courseId}:
delete:
description: Legacy-compatible endpoint for deleting a course
parameters:
- description: Course ID
in: path
name: courseId
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 course
tags:
- course-management
put:
consumes:
- application/json
description: Legacy-compatible endpoint for updating a course
parameters:
- description: Course ID
in: path
name: courseId
required: true
type: integer
- description: Update course payload
in: body
name: body
required: true
schema:
$ref: '#/definitions/handlers.updateCourseReq'
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: Update course
tags:
- course-management
/api/v1/course-management/courses/{courseId}/hierarchy: /api/v1/course-management/courses/{courseId}/hierarchy:
get: get:
description: Returns hierarchy nodes for one course including levels/modules/sub-modules description: Returns hierarchy nodes for one course including levels/modules/sub-modules
@ -2350,6 +2564,68 @@ paths:
summary: Get hierarchy for a course summary: Get hierarchy for a course
tags: tags:
- course-management - course-management
/api/v1/course-management/courses/{courseId}/learning-path:
get:
description: Legacy-compatible endpoint for course learning path
parameters:
- description: Course ID
in: path
name: courseId
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: Get course learning path
tags:
- course-management
/api/v1/course-management/courses/{courseId}/thumbnail:
post:
consumes:
- application/json
description: Legacy-compatible endpoint for updating course thumbnail
parameters:
- description: Course ID
in: path
name: courseId
required: true
type: integer
- description: Update course thumbnail payload
in: body
name: body
required: true
schema:
$ref: '#/definitions/handlers.updateCourseThumbnailReq'
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: Update course thumbnail
tags:
- course-management
/api/v1/course-management/hierarchy: /api/v1/course-management/hierarchy:
get: get:
description: 'Returns full hierarchy: category -> sub-category -> course' description: 'Returns full hierarchy: category -> sub-category -> course'