From 71ba71476a69d9352ba5253761bdbe97b9f01425 Mon Sep 17 00:00:00 2001 From: Yared Yemane Date: Thu, 16 Apr 2026 05:16:30 -0700 Subject: [PATCH] edit lesson form UI adjustment --- docs/course-management-api-integration.md | 479 ++++++++++++++++++++++ 1 file changed, 479 insertions(+) create mode 100644 docs/course-management-api-integration.md diff --git a/docs/course-management-api-integration.md b/docs/course-management-api-integration.md new file mode 100644 index 0000000..ed44e64 --- /dev/null +++ b/docs/course-management-api-integration.md @@ -0,0 +1,479 @@ +# 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.ts` +- `src/api/files.api.ts` +- `src/types/course.types.ts` +- `src/api/http.ts` + +--- + +## 1) Base setup and auth behavior + +### Base URL +- All requests use `VITE_API_BASE_URL` from environment. + +### Authentication +- Access token is sent automatically as `Authorization: Bearer `. +- 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-data` boundaries 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: +- `Category` + - `Sub-category` + - `Course` + - `Level (CEFR)` + - `Module` + - `Sub-module` + - `Videos` + - `Lessons` (question sets with `set_type = QUIZ`) + - `Practices` (question sets with `set_type = PRACTICE`) + +Important migration note: +- Some APIs/types are marked as legacy (`Program`, old `Level/Module` flows). +- Current frontend mostly uses unified hierarchy endpoints under `/course-management/...` plus `/question-sets` and `/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 `title` and `description`. + +### Typical response fields used by frontend +- `data.object_key` +- `data.url` +- `data.provider` (`MINIO` or `VIMEO`) +- `data.vimeo_id` +- `data.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=` + +### 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_id` exists, creates sub-category; else category. + +## 4.3 Delete category/sub-category +- `DELETE /course-management/categories/:categoryId` +- `DELETE /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/courses` +- `PUT /course-management/courses/:courseId` +- `PUT /course-management/courses/:courseId` (status toggle via `is_active`) +- `DELETE /course-management/courses/:courseId` +- `POST /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_courses` shape (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: +1. `POST /course-management/levels` +2. `POST /course-management/modules` +3. `POST /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/:subModuleId` +- `PUT /course-management/sub-modules/:subModuleId` (status payload) +- `DELETE /course-management/sub-modules/:subModuleId` +- `POST /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/:videoId` +- `DELETE /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: +1. `POST /question-sets` + - `set_type: "PRACTICE"` + - `owner_type: "SUB_MODULE"` + - `owner_id: sub_module_id` +2. If step 1 succeeds, links to sub-module practice: + - `POST /course-management/sub-module-practices` + - includes `question_set_id` and intro metadata + +## 7.3 Create lesson (composed) + +`createLesson(data)` does: +1. `POST /question-sets` + - `set_type: "QUIZ"` + - `owner_type: "SUB_MODULE"` +2. Link question set as lesson: + - `POST /course-management/sub-module-lessons` + +## 7.4 Practice update/delete/status +- `PUT /course-management/practices/:practiceId` +- `PUT /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-sets` with optional query params +- `GET /question-sets/by-owner` +- `GET /question-sets/:id` +- `PUT /question-sets/:id` +- `DELETE /question-sets/:id` +- `POST /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/questions` +- `POST /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/:questionId` +- `POST /questions` +- `PUT /questions/:questionId` +- `DELETE /questions/:questionId` + +Wrappers: +- `getQuestions(params)` +- `getQuestionById(questionId)` +- `createQuestion(data)` +- `updateQuestion(questionId, data)` +- `deleteQuestion(questionId)` + +## 8.4 Practice-question convenience wrappers + +`createPracticeQuestion(data)`: +1. Creates question via `POST /questions` +2. 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/questions` +- `getPracticeQuestionsByPractice(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-owner` per sub-module + - deriving lessons from question sets where `set_type = "QUIZ"` + +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: +1. `POST /course-management/levels` +2. `POST /course-management/modules` +3. `POST /course-management/sub-modules` + +--- + +## 10) Learning path and assessments + +- `GET /course-management/courses/:courseId/learning-path` + - wrapper: `getLearningPath(courseId)` + +- `GET /question-sets/sub-courses/:subModuleId/entry-assessment` + - wrapper: `getSubModuleEntryAssessment(subModuleId)` + +--- + +## 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): +- `getSubModulePrerequisites` +- `addSubModulePrerequisite` +- `removeSubModulePrerequisite` +- `reorderCategories` +- `reorderCourses` +- `reorderSubModules` +- `reorderVideos` +- `reorderPractices` + +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.data` can 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/upload` wrappers 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/hierarchy` +- `POST /course-management/categories` +- `POST /course-management/sub-categories` +- `DELETE /course-management/categories/:id` +- `DELETE /course-management/sub-categories/:id` +- `POST /course-management/courses` +- `PUT /course-management/courses/:id` +- `DELETE /course-management/courses/:id` +- `POST /course-management/courses/:id/thumbnail` +- `GET /course-management/courses/:courseId/hierarchy` +- `POST /course-management/levels` +- `POST /course-management/modules` +- `PUT /course-management/levels/:id` +- `DELETE /course-management/levels/:id` +- `PUT /course-management/modules/:id` +- `DELETE /course-management/modules/:id` +- `POST /course-management/sub-modules` +- `PUT /course-management/sub-modules/:id` +- `DELETE /course-management/sub-modules/:id` +- `GET /course-management/sub-modules/:subModuleId/videos` +- `POST /course-management/sub-module-videos` +- `PUT /course-management/sub-module-videos/:id` +- `DELETE /course-management/sub-module-videos/:id` +- `POST /course-management/sub-module-practices` +- `POST /course-management/sub-module-lessons` +- `GET /course-management/courses/:courseId/learning-path` +- `GET /course-management/human-language/courses/:courseId/lessons` + +### Question sets and questions +- `GET /question-sets` +- `GET /question-sets/by-owner` +- `GET /question-sets/:id` +- `POST /question-sets` +- `PUT /question-sets/:id` +- `DELETE /question-sets/:id` +- `GET /question-sets/:id/questions` +- `POST /question-sets/:id/questions` +- `GET /practices/:practiceId/questions` +- `GET /questions` +- `GET /questions/:id` +- `POST /questions` +- `PUT /questions/:id` +- `DELETE /questions/:id` +- `POST /questions/audio-answer` + +### File/media +- `POST /files/upload` +- `GET /files/url` +- `GET /vimeo/sample` +- `POST /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 (`array` vs `object`) +- **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. +