diff --git a/README.md b/README.md index a1cd102..ec9253a 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/docker-compose.yml b/docker-compose.yml index 831bb74..ea71a59 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,7 @@ services: container_name: yimaru-backend-postgres-1 image: postgres:16-alpine ports: - - "5592:5422" + - "5692:5422" environment: - POSTGRES_PASSWORD=secret - POSTGRES_USER=root diff --git a/docs/COURSE_MANAGEMENT_POSTMAN_COLLECTION.json b/docs/COURSE_MANAGEMENT_POSTMAN_COLLECTION.json new file mode 100644 index 0000000..7e3515f --- /dev/null +++ b/docs/COURSE_MANAGEMENT_POSTMAN_COLLECTION.json @@ -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`." + } + } + ] + } + ] +} diff --git a/docs/HUMAN_LANGUAGE_MOBILE_INTEGRATION_GUIDE.md b/docs/HUMAN_LANGUAGE_MOBILE_INTEGRATION_GUIDE.md deleted file mode 100644 index 9f7cde3..0000000 --- a/docs/HUMAN_LANGUAGE_MOBILE_INTEGRATION_GUIDE.md +++ /dev/null @@ -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 ` - -### 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=` - -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` - diff --git a/docs/docs.go b/docs/docs.go index ebce5d4..0ed88fd 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -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": { "get": { "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": { "get": { "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": { "type": "object", "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": { "type": "object", "required": [ diff --git a/docs/swagger.json b/docs/swagger.json index 10ef9ed..f856779 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -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": { "get": { "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": { "get": { "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": { "type": "object", "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": { "type": "object", "required": [ diff --git a/docs/swagger.yaml b/docs/swagger.yaml index bb8af8c..ed61ae4 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -947,6 +947,30 @@ definitions: confirm: type: string 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: properties: category_id: @@ -1354,6 +1378,24 @@ definitions: example: false type: boolean 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: properties: status: @@ -2323,6 +2365,178 @@ paths: summary: Refresh token tags: - 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: get: description: Returns hierarchy nodes for one course including levels/modules/sub-modules @@ -2350,6 +2564,68 @@ paths: summary: Get hierarchy for a course tags: - 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: get: description: 'Returns full hierarchy: category -> sub-category -> course'