14 KiB
Course Management API Integration Guide
This document describes the Course Management related APIs used by the admin frontend (Yimaru-Admin) and how to integrate them safely.
It is based on:
src/api/courses.api.tssrc/api/files.api.tssrc/types/course.types.tssrc/api/http.ts
1) Base setup and auth behavior
Base URL
- All requests use
VITE_API_BASE_URLfrom environment.
Authentication
- Access token is sent automatically as
Authorization: Bearer <access_token>. - On
401, the frontend attempts token refresh via:POST /auth/refresh- payload:
{ access_token, refresh_token, role, member_id }
- If refresh fails, auth data is cleared and user is redirected to
/login.
Transport notes
- Axios automatically handles
multipart/form-databoundaries for file upload. - Any network failure without response also redirects to
/login(current client behavior).
2) Core domain model used by frontend
Current hierarchy used by content management:
CategorySub-categoryCourseLevel (CEFR)ModuleSub-moduleVideosLessons(question sets withset_type = QUIZ)Practices(question sets withset_type = PRACTICE)
Important migration note:
- Some APIs/types are marked as legacy (
Program, oldLevel/Moduleflows). - Current frontend mostly uses unified hierarchy endpoints under
/course-management/...plus/question-setsand/questions.
3) File/media APIs (used by course management)
3.1 Upload media
Endpoint
POST /files/upload
Supports
media_type:"image" | "audio" | "video"- File upload via multipart (
file) or URL import via JSON (source_url).
For video uploads
- Can send optional
titleanddescription.
Typical response fields used by frontend
data.object_keydata.urldata.provider(MINIOorVIMEO)data.vimeo_iddata.embed_url
Frontend wrapper functions
uploadAudioFile(fileOrUrl)uploadImageFile(fileOrUrl)uploadVideoFile(fileOrUrl, { title?, description? })
3.2 Resolve object key to URL
Endpoint
GET /files/url?key=<object_key>
Use case
- Resolve media object key when only key is stored.
4) Category and course APIs
4.1 Get categories (normalized in frontend)
Endpoint called
GET /course-management/hierarchy
Frontend behavior
- Client transforms flat hierarchy rows into category list.
- Duplicated category names are merged client-side by "richest" record.
Wrapper
getCourseCategories()
4.2 Create category or sub-category
Category
POST /course-management/categories- body:
{ name }
Sub-category
POST /course-management/sub-categories- body:
{ category_id, name }
Wrapper
createCourseCategory({ name, parent_id? })- if
parent_idexists, creates sub-category; else category.
- if
4.3 Delete category/sub-category
DELETE /course-management/categories/:categoryIdDELETE /course-management/sub-categories/:subCategoryId
Wrappers:
deleteCourseCategory(categoryId)deleteCourseSubCategory(subCategoryId)
4.4 Courses by category
Endpoint called
GET /course-management/hierarchy
Frontend behavior
- Filters and maps rows to courses client-side.
- If duplicate category names exist, it includes rows matching requested category name.
Wrapper:
getCoursesByCategory(categoryId)
4.5 Course CRUD
POST /course-management/coursesPUT /course-management/courses/:courseIdPUT /course-management/courses/:courseId(status toggle viais_active)DELETE /course-management/courses/:courseIdPOST /course-management/courses/:courseId/thumbnail
Wrappers:
createCourse(data)updateCourse(courseId, data)updateCourseStatus(courseId, isActive)deleteCourse(courseId)updateCourseThumbnail(courseId, thumbnailUrl)
5) Course hierarchy (levels/modules/sub-modules)
5.1 Get full hierarchy for one course
Endpoint
GET /course-management/courses/:courseId/hierarchy
Wrapper
getSubModulesByCourse(courseId)
Frontend behavior
- Maps hierarchy rows into
sub_coursesshape (compatibility naming). - This is the primary source for module/sub-module tree rendering.
5.2 Create sub-module flow (composed)
createSubModule(data) is a multi-step client workflow:
POST /course-management/levelsPOST /course-management/modulesPOST /course-management/sub-modules
Use this when creating a new sub-module from minimal info.
5.3 Direct level/module/sub-module creation
createModuleInLevel(levelId, title, description, displayOrder?)POST /course-management/modules
createSubModuleInModule(moduleId, title, description, displayOrder?)POST /course-management/sub-modules
5.4 Update/delete sub-module
PUT /course-management/sub-modules/:subModuleIdPUT /course-management/sub-modules/:subModuleId(status payload)DELETE /course-management/sub-modules/:subModuleIdPOST /course-management/sub-courses/:subModuleId/thumbnail(compat endpoint)
Wrappers:
updateSubModule(...)updateSubModuleStatus(...)deleteSubModule(...)updateSubModuleThumbnail(...)
6) Video APIs (sub-module videos)
6.1 List videos for sub-module
GET /course-management/sub-modules/:subModuleId/videos- wrapper:
getVideosBySubModule(subModuleId)
6.2 Create video
Two wrapper variants, same endpoint:
POST /course-management/sub-module-videos
Minimal variant
createSubCourseVideo({ sub_module_id|sub_course_id, title, description, video_url })
Extended variant
createCourseVideo({ sub_module_id|sub_course_id, title, description, video_url, duration, resolution?, visibility?, display_order?, status? })
6.3 Update/delete video
PUT /course-management/sub-module-videos/:videoIdDELETE /course-management/sub-module-videos/:videoId
Wrappers:
updateSubCourseVideo(videoId, data)deleteSubCourseVideo(videoId)
7) Practices and lessons
7.1 Practices by sub-module
GET /question-sets/by-owner?owner_type=SUB_MODULE&owner_id=:subModuleId- wrapper:
getPracticesBySubModule(subModuleId)
7.2 Create practice (composed)
createPractice(data) does:
POST /question-setsset_type: "PRACTICE"owner_type: "SUB_MODULE"owner_id: sub_module_id
- If step 1 succeeds, links to sub-module practice:
POST /course-management/sub-module-practices- includes
question_set_idand intro metadata
7.3 Create lesson (composed)
createLesson(data) does:
POST /question-setsset_type: "QUIZ"owner_type: "SUB_MODULE"
- Link question set as lesson:
POST /course-management/sub-module-lessons
7.4 Practice update/delete/status
PUT /course-management/practices/:practiceIdPUT /course-management/practices/:practiceId(status)DELETE /course-management/practices/:practiceId
Wrappers:
updatePractice(...)updatePracticeStatus(...)deletePractice(...)
8) Question sets and questions
8.1 Question sets
GET /question-setswith optional query paramsGET /question-sets/by-ownerGET /question-sets/:idPUT /question-sets/:idDELETE /question-sets/:idPOST /question-sets
Wrappers:
getQuestionSets(params?)getQuestionSetsByOwner(ownerType, ownerId)getQuestionSetById(questionSetId)createQuestionSet(data)updateQuestionSet(questionSetId, partialData)deleteQuestionSet(questionSetId)
8.2 Question list within set
GET /question-sets/:questionSetId/questionsPOST /question-sets/:questionSetId/questions(add by question id)
Wrappers:
getQuestionSetQuestions(questionSetId)addQuestionToSet(questionSetId, { question_id, display_order? })
8.3 Questions CRUD
GET /questions(filters)GET /questions/:questionIdPOST /questionsPUT /questions/:questionIdDELETE /questions/:questionId
Wrappers:
getQuestions(params)getQuestionById(questionId)createQuestion(data)updateQuestion(questionId, data)deleteQuestion(questionId)
8.4 Practice-question convenience wrappers
createPracticeQuestion(data):
- Creates question via
POST /questions - Adds it to practice set via
POST /question-sets/:practiceId/questions
updatePracticeQuestion(questionId, data):
- maps to
PUT /questions/:questionId
deletePracticeQuestion(questionId):
DELETE /questions/:questionId
8.5 Practice question listing endpoint variants
getPracticeQuestions(practiceId)->GET /question-sets/:practiceId/questionsgetPracticeQuestionsByPractice(practiceId, params)->GET /practices/:practiceId/questions
Use the second when you need pagination/filtering by question type.
9) Human language specific APIs
9.1 Human language hierarchy
getHumanLanguageHierarchy()- Calls
GET /course-management/hierarchy - If backend already returns nested
sub_categories, uses it directly. - If backend returns flat rows, client builds nested structure and enriches each course by:
- requesting
/course-management/courses/:courseId/hierarchy - requesting
/question-sets/by-ownerper sub-module - deriving lessons from question sets where
set_type = "QUIZ"
- requesting
This method is heavier than basic endpoints and can issue many requests.
9.2 Human language lessons by course+level
GET /course-management/human-language/courses/:courseId/lessons?cefr_level=...- wrapper:
getHumanLanguageLessonsByCourse(courseId, cefrLevel)
9.3 Create human language lesson structure
createHumanLanguageLesson(data) is composed:
POST /course-management/levelsPOST /course-management/modulesPOST /course-management/sub-modules
10) Learning path and assessments
-
GET /course-management/courses/:courseId/learning-path- wrapper:
getLearningPath(courseId)
- wrapper:
-
GET /question-sets/sub-courses/:subModuleId/entry-assessment- wrapper:
getSubModuleEntryAssessment(subModuleId)
- wrapper:
11) Unsupported or stubbed features in current frontend API layer
The following wrappers are intentionally stubbed in frontend and return resolved promises (no real backend call):
getSubModulePrerequisitesaddSubModulePrerequisiteremoveSubModulePrerequisitereorderCategoriesreorderCoursesreorderSubModulesreorderVideosreorderPractices
Implication:
- UI may appear to support these flows, but persistence is not implemented through backend yet.
12) Legacy endpoints still exposed (backward compatibility)
These are still present in courses.api.ts but marked deprecated in types:
- Programs APIs
- Old levels APIs
- Old modules APIs
- Practices by level/module APIs
Prefer unified hierarchy/sub-module/question-set APIs for new work.
13) Integration patterns and recommendations
13.1 Safe creation flows
- For practice/lesson creation, keep composed behavior:
- create question set first
- then link to sub-module entity
- Handle partial failure:
- if link step fails after question set creation, frontend should show recoverable error and optionally support manual relink.
13.2 Request normalization
getQuestionSetsResponse.datacan be either:- raw array
- object with
question_sets
- Normalize before rendering.
13.3 Question type mapping
- UI uses
"SHORT"; backend commonly expects"SHORT_ANSWER". - Existing wrappers already map
"SHORT"to"SHORT_ANSWER"on create/update practice question.
13.4 Media handling
- Prefer using
/files/uploadwrappers for all media. - For Vimeo-backed responses, frontend typically consumes
embed_url(and may append hash from page URL where applicable).
13.5 Retry behavior
- Some hierarchy fetches use single retry (
withSingleRetry) for resiliency against transient auth/network race conditions.
14) Quick endpoint index
Course management
GET /course-management/hierarchyPOST /course-management/categoriesPOST /course-management/sub-categoriesDELETE /course-management/categories/:idDELETE /course-management/sub-categories/:idPOST /course-management/coursesPUT /course-management/courses/:idDELETE /course-management/courses/:idPOST /course-management/courses/:id/thumbnailGET /course-management/courses/:courseId/hierarchyPOST /course-management/levelsPOST /course-management/modulesPUT /course-management/levels/:idDELETE /course-management/levels/:idPUT /course-management/modules/:idDELETE /course-management/modules/:idPOST /course-management/sub-modulesPUT /course-management/sub-modules/:idDELETE /course-management/sub-modules/:idGET /course-management/sub-modules/:subModuleId/videosPOST /course-management/sub-module-videosPUT /course-management/sub-module-videos/:idDELETE /course-management/sub-module-videos/:idPOST /course-management/sub-module-practicesPOST /course-management/sub-module-lessonsGET /course-management/courses/:courseId/learning-pathGET /course-management/human-language/courses/:courseId/lessons
Question sets and questions
GET /question-setsGET /question-sets/by-ownerGET /question-sets/:idPOST /question-setsPUT /question-sets/:idDELETE /question-sets/:idGET /question-sets/:id/questionsPOST /question-sets/:id/questionsGET /practices/:practiceId/questionsGET /questionsGET /questions/:idPOST /questionsPUT /questions/:idDELETE /questions/:idPOST /questions/audio-answer
File/media
POST /files/uploadGET /files/urlGET /vimeo/samplePOST /vimeo/uploads/pull
15) Suggested frontend service contract shape
For any new frontend module, follow this contract:
- Input DTOs: UI-friendly types (can include UI aliases like
SHORT) - Mapper layer: convert UI DTOs to backend DTOs
- Transport layer: pure API calls
- Normalizer layer: normalize polymorphic responses (
arrayvsobject) - Error policy:
- show user-actionable toast
- preserve enough context to retry failed composed steps
This keeps integration robust even with mixed legacy/unified backend surfaces.