This commit is contained in:
634
API_STRUCTURE_GUIDE.md
Normal file
634
API_STRUCTURE_GUIDE.md
Normal file
@@ -0,0 +1,634 @@
|
||||
# API Structure Guide - Curriculum Management
|
||||
|
||||
## 📋 Tổng quan
|
||||
|
||||
API được tổ chức theo cấu trúc phân cấp (hierarchy) và quan hệ nhiều-nhiều:
|
||||
|
||||
### Cấu trúc phân cấp (1:N):
|
||||
```
|
||||
Category (Danh mục)
|
||||
└── Subject (Môn học)
|
||||
└── Chapter (Chương)
|
||||
└── Lesson (Bài học)
|
||||
```
|
||||
|
||||
### Quan hệ nhiều-nhiều (N:N):
|
||||
```
|
||||
Lesson (Bài học) ←→ Story (Câu chuyện)
|
||||
- Một lesson có thể có nhiều stories
|
||||
- Một story có thể được sử dụng trong nhiều lessons
|
||||
- Quản lý qua bảng pivot: lesson_stories
|
||||
```
|
||||
|
||||
## 🎯 Nguyên tắc thiết kế
|
||||
|
||||
### ✅ Điều NÊN làm:
|
||||
1. **Giữ nguyên cấu trúc phân tách**: Mỗi resource (category, subject, chapter, lesson) có router và controller riêng
|
||||
2. **Sử dụng nested routes**: Cho các thao tác liên quan đến parent-child relationship
|
||||
3. **RESTful naming**: Đặt tên routes theo chuẩn REST
|
||||
4. **Validation đầy đủ**: Kiểm tra parent resource tồn tại trước khi thao tác
|
||||
|
||||
### ❌ Điều KHÔNG NÊN làm:
|
||||
1. **Không gộp chung tất cả vào 1 file**: Vi phạm Single Responsibility Principle
|
||||
2. **Không skip validation**: Luôn kiểm tra parent resource
|
||||
3. **Không quên clear cache**: Cache phải được xóa khi có thay đổi
|
||||
|
||||
---
|
||||
|
||||
## 📚 API Endpoints
|
||||
|
||||
### 1. Categories API
|
||||
|
||||
#### CRUD Operations
|
||||
```http
|
||||
GET /api/categories # Lấy danh sách categories
|
||||
POST /api/categories # Tạo category mới
|
||||
GET /api/categories/:id # Lấy chi tiết category
|
||||
PUT /api/categories/:id # Cập nhật category
|
||||
DELETE /api/categories/:id # Xóa category
|
||||
```
|
||||
|
||||
#### Nested Subject Operations
|
||||
```http
|
||||
GET /api/categories/:categoryId/subjects # Lấy subjects trong category
|
||||
POST /api/categories/:categoryId/subjects # Thêm subject vào category
|
||||
DELETE /api/categories/:categoryId/subjects/:subjectId # Xóa subject khỏi category
|
||||
```
|
||||
|
||||
**Ví dụ sử dụng:**
|
||||
```bash
|
||||
# Tạo subject mới trong category
|
||||
curl -X POST http://localhost:3000/api/categories/123e4567-e89b-12d3-a456-426614174000/subjects \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"subject_code": "ENG101",
|
||||
"subject_name": "English Basics",
|
||||
"description": "Basic English course"
|
||||
}'
|
||||
|
||||
# Xóa subject khỏi category
|
||||
curl -X DELETE http://localhost:3000/api/categories/123e4567-e89b-12d3-a456-426614174000/subjects/456e7890-e89b-12d3-a456-426614174001
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Subjects API
|
||||
|
||||
#### CRUD Operations
|
||||
```http
|
||||
GET /api/subjects # Lấy danh sách subjects
|
||||
POST /api/subjects # Tạo subject mới
|
||||
GET /api/subjects/:id # Lấy chi tiết subject
|
||||
PUT /api/subjects/:id # Cập nhật subject
|
||||
DELETE /api/subjects/:id # Xóa subject
|
||||
```
|
||||
|
||||
#### Nested Chapter Operations
|
||||
```http
|
||||
GET /api/subjects/:subjectId/chapters # Lấy chapters trong subject
|
||||
POST /api/subjects/:subjectId/chapters # Thêm chapter vào subject
|
||||
DELETE /api/subjects/:subjectId/chapters/:chapterId # Xóa chapter khỏi subject
|
||||
```
|
||||
|
||||
**Ví dụ sử dụng:**
|
||||
```bash
|
||||
# Tạo chapter mới trong subject
|
||||
curl -X POST http://localhost:3000/api/subjects/456e7890-e89b-12d3-a456-426614174001/chapters \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"chapter_number": 1,
|
||||
"chapter_title": "Introduction",
|
||||
"chapter_description": "Getting started with English",
|
||||
"display_order": 1
|
||||
}'
|
||||
|
||||
# Xóa chapter khỏi subject
|
||||
curl -X DELETE http://localhost:3000/api/subjects/456e7890-e89b-12d3-a456-426614174001/chapters/789e0123-e89b-12d3-a456-426614174002
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Chapters API
|
||||
|
||||
#### CRUD Operations
|
||||
```http
|
||||
GET /api/chapters # Lấy danh sách chapters
|
||||
POST /api/chapters # Tạo chapter mới
|
||||
GET /api/chapters/:id # Lấy chi tiết chapter
|
||||
PUT /api/chapters/:id # Cập nhật chapter
|
||||
DELETE /api/chapters/:id # Xóa chapter
|
||||
```
|
||||
|
||||
#### Nested Lesson Operations
|
||||
```http
|
||||
GET /api/chapters/:chapterId/lessons # Lấy lessons trong chapter
|
||||
POST /api/chapters/:chapterId/lessons # Thêm lesson vào chapter
|
||||
DELETE /api/chapters/:chapterId/lessons/:lessonId # Xóa lesson khỏi chapter
|
||||
```
|
||||
|
||||
**Ví dụ sử dụng:**
|
||||
```bash
|
||||
# Tạo lesson mới trong chapter
|
||||
curl -X POST http://localhost:3000/api/chapters/789e0123-e89b-12d3-a456-426614174002/lessons \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"lesson_number": 1,
|
||||
"lesson_title": "Alphabet A-F",
|
||||
"lesson_type": "json_content",
|
||||
"display_order": 1
|
||||
}'
|
||||
|
||||
# Xóa lesson khỏi chapter
|
||||
curl -X DELETE http://localhost:3000/api/chapters/789e0123-e89b-12d3-a456-426614174002/lessons/012e3456-e89b-12d3-a456-426614174003
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. Lessons API
|
||||
|
||||
#### CRUD Operations
|
||||
```http
|
||||
GET /api/lessons # Lấy danh sách lessons
|
||||
POST /api/lessons # Tạo lesson mới (cần chỉ định chapter_id)
|
||||
GET /api/lessons/:id # Lấy chi tiết lesson
|
||||
PUT /api/lessons/:id # Cập nhật lesson
|
||||
DELETE /api/lessons/:id # Xóa lesson
|
||||
```
|
||||
|
||||
#### Nested Story Operations (N:N Relationship)
|
||||
```http
|
||||
GET /api/lessons/:lessonId/stories # Lấy stories trong lesson
|
||||
POST /api/lessons/:lessonId/stories # Thêm story vào lesson
|
||||
PUT /api/lessons/:lessonId/stories/:storyId # Cập nhật story trong lesson
|
||||
DELETE /api/lessons/:lessonId/stories/:storyId # Xóa story khỏi lesson
|
||||
```
|
||||
|
||||
**Ví dụ sử dụng:**
|
||||
```bash
|
||||
# Thêm story vào lesson
|
||||
curl -X POST http://localhost:3000/api/lessons/012e3456-e89b-12d3-a456-426614174003/stories \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"story_id": "234e5678-e89b-12d3-a456-426614174004",
|
||||
"display_order": 1,
|
||||
"is_required": true
|
||||
}'
|
||||
|
||||
# Cập nhật story trong lesson
|
||||
curl -X PUT http://localhost:3000/api/lessons/012e3456-e89b-12d3-a456-426614174003/stories/234e5678-e89b-12d3-a456-426614174004 \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"display_order": 2,
|
||||
"is_required": false
|
||||
}'
|
||||
|
||||
# Xóa story khỏi lesson
|
||||
curl -X DELETE http://localhost:3000/api/lessons/012e3456-e89b-12d3-a456-426614174003/stories/234e5678-e89b-12d3-a456-426614174004
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. Stories API
|
||||
|
||||
#### CRUD Operations
|
||||
```http
|
||||
GET /api/stories # Lấy danh sách stories
|
||||
POST /api/stories # Tạo story mới
|
||||
GET /api/stories/:id # Lấy chi tiết story
|
||||
PUT /api/stories/:id # Cập nhật story
|
||||
DELETE /api/stories/:id # Xóa story
|
||||
```
|
||||
|
||||
#### Nested Lesson Operations (N:N Relationship - Alternative Way)
|
||||
```http
|
||||
GET /api/stories/:storyId/lessons # Lấy lessons sử dụng story này
|
||||
POST /api/stories/:storyId/lessons # Thêm lesson vào story
|
||||
DELETE /api/stories/:storyId/lessons/:lessonId # Xóa lesson khỏi story
|
||||
```
|
||||
|
||||
**Ví dụ sử dụng:**
|
||||
```bash
|
||||
# Lấy danh sách lessons sử dụng story
|
||||
curl -X GET http://localhost:3000/api/stories/234e5678-e89b-12d3-a456-426614174004/lessons
|
||||
|
||||
# Thêm lesson vào story (cách alternative)
|
||||
curl -X POST http://localhost:3000/api/stories/234e5678-e89b-12d3-a456-426614174004/lessons \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"lesson_id": "012e3456-e89b-12d3-a456-426614174003",
|
||||
"display_order": 1,
|
||||
"is_required": true
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 So sánh 2 cách tiếp cận
|
||||
|
||||
### Cách 1: Nested Routes (✅ Được áp dụng)
|
||||
|
||||
#### Quan hệ 1:N (Parent-Child)
|
||||
```http
|
||||
POST /api/categories/:categoryId/subjects
|
||||
POST /api/subjects/:subjectId/chapters
|
||||
POST /api/chapters/:chapterId/lessons
|
||||
```
|
||||
|
||||
#### Quan hệ N:N (Many-to-Many)
|
||||
```http
|
||||
# Từ phía Lesson
|
||||
POST /api/lessons/:lessonId/stories
|
||||
GET /api/lessons/:lessonId/stories
|
||||
PUT /api/lessons/:lessonId/stories/:storyId
|
||||
DELETE /api/lessons/:lessonId/stories/:storyId
|
||||
|
||||
# Từ phía Story (alternative)
|
||||
POST /api/stories/:storyId/lessons
|
||||
GET /api/stories/:storyId/lessons
|
||||
DELETE /api/stories/:storyId/lessons/:lessonId
|
||||
```
|
||||
|
||||
**Ưu điểm:**
|
||||
- ✅ Rõ ràng về mối quan hệ parent-child và many-to-many
|
||||
- ✅ Tự động validate parent tồn tại
|
||||
- ✅ RESTful và semantic
|
||||
- ✅ Dễ hiểu cho developers
|
||||
- ✅ Hỗ trợ quản lý pivot table metadata (display_order, is_required)
|
||||
|
||||
**Nhược điểm:**
|
||||
- ⚠️ URL có thể dài hơn
|
||||
- ⚠️ Cần nhiều routes hơn
|
||||
|
||||
### Cách 2: Flat Routes
|
||||
```http
|
||||
POST /api/subjects (với category_id trong body)
|
||||
POST /api/chapters (với subject_id trong body)
|
||||
POST /api/lessons (với chapter_id trong body)
|
||||
```
|
||||
|
||||
**Ưu điểm:**
|
||||
- ✅ URL ngắn hơn
|
||||
- ✅ Ít routes hơn
|
||||
|
||||
**Nhược điểm:**
|
||||
- ❌ Không rõ ràng về mối quan hệ
|
||||
- ❌ Phải validate parent manually trong body
|
||||
- ❌ Kém RESTful
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Patterns được áp dụng
|
||||
|
||||
### 1. Controller Pattern
|
||||
Mỗi resource có controller riêng:
|
||||
```
|
||||
controllers/
|
||||
├── categoryController.js # Quản lý Categories + nested Subjects
|
||||
├── subjectController.js # Quản lý Subjects + nested Chapters
|
||||
├── chapterController.js # Quản lý Chapters + nested Lessons
|
||||
├── lessonController.js # Quản lý Lessons + nested Stories (N:N)
|
||||
└── storyController.js # Quản lý Stories + nested Lessons (N:N)
|
||||
```
|
||||
|
||||
### 2. Route Organization
|
||||
Mỗi resource có route file riêng:
|
||||
```
|
||||
routes/
|
||||
├── categoryRoutes.js # Category routes + nested Subject routes
|
||||
├── subjectRoutes.js # Subject routes + nested Chapter routes
|
||||
├── chapterRoutes.js # Chapter routes + nested Lesson routes
|
||||
├── lessonRoutes.js # Lesson routes + nested Story routes
|
||||
└── storyRoutes.js # Story routes + nested Lesson routes
|
||||
```
|
||||
|
||||
### 3. Model Relationships
|
||||
```javascript
|
||||
// 1:N Relationships
|
||||
Category.hasMany(Subject)
|
||||
Subject.hasMany(Chapter)
|
||||
Chapter.hasMany(Lesson)
|
||||
|
||||
// N:N Relationship
|
||||
Lesson.belongsToMany(Story, { through: 'LessonStory' })
|
||||
Story.belongsToMany(Lesson, { through: 'LessonStory' })
|
||||
```
|
||||
|
||||
### 4. Validation Pattern
|
||||
```javascript
|
||||
// Luôn validate parent trước khi thao tác child
|
||||
const parent = await ParentModel.findByPk(parentId);
|
||||
if (!parent) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Parent not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Validate cả 2 phía trong quan hệ N:N
|
||||
const lesson = await Lesson.findByPk(lessonId);
|
||||
const story = await Story.findByPk(storyId);
|
||||
if (!lesson || !story) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Lesson or Story not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Kiểm tra duplicate trong quan hệ N:N
|
||||
const existing = await LessonStory.findOne({
|
||||
where: { lesson_id: lessonId, story_id: storyId }
|
||||
});
|
||||
if (existing) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Relationship already exists'
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Cache Invalidation Pattern
|
||||
```javascript
|
||||
// Xóa cache của cả parent và child (1:N)
|
||||
await cacheUtils.deletePattern(`parent:${parentId}:children:*`);
|
||||
await cacheUtils.deletePattern('children:list:*');
|
||||
|
||||
// Xóa cache của cả 2 phía (N:N)
|
||||
await cacheUtils.deletePattern(`lesson:${lessonId}:stories:*`);
|
||||
await cacheUtils.deletePattern(`story:${storyId}:lessons:*`);
|
||||
```
|
||||
|
||||
### 6. Pivot Table Pattern (N:N)
|
||||
```javascript
|
||||
// Bảng LessonStory với metadata
|
||||
{
|
||||
lesson_id: UUID,
|
||||
story_id: UUID,
|
||||
display_order: INTEGER, // Thứ tự hiển thị
|
||||
is_required: BOOLEAN, // Có bắt buộc không
|
||||
}
|
||||
|
||||
// Truy vấn với pivot data
|
||||
const stories = await lesson.getStories({
|
||||
joinTableAttributes: ['display_order', 'is_required']
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Best Practices
|
||||
|
||||
### 1. Đặt tên Routes
|
||||
```javascript
|
||||
// ✅ Đúng: RESTful naming
|
||||
router.post('/:categoryId/subjects', ...)
|
||||
router.delete('/:categoryId/subjects/:subjectId', ...)
|
||||
|
||||
// ❌ Sai: Naming không chuẩn
|
||||
router.post('/:categoryId/addSubject', ...)
|
||||
router.delete('/:categoryId/removeSubject/:subjectId', ...)
|
||||
```
|
||||
|
||||
### 2. Response Structure
|
||||
```javascript
|
||||
// ✅ Đúng: Consistent response structure
|
||||
{
|
||||
"success": true,
|
||||
"message": "Subject added to category successfully",
|
||||
"data": { ...subject }
|
||||
}
|
||||
|
||||
// ❌ Sai: Inconsistent
|
||||
{
|
||||
"subject": { ...subject },
|
||||
"msg": "OK"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Error Handling
|
||||
```javascript
|
||||
// ✅ Đúng: Descriptive error messages
|
||||
if (!chapter) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Chapter not found in this subject'
|
||||
});
|
||||
}
|
||||
|
||||
// ❌ Sai: Generic error
|
||||
if (!chapter) {
|
||||
return res.status(404).json({ error: 'Not found' });
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Request/Response Examples
|
||||
|
||||
### Example 1: Tạo Subject trong Category
|
||||
|
||||
**Request:**
|
||||
```http
|
||||
POST /api/categories/123e4567-e89b-12d3-a456-426614174000/subjects
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"subject_code": "ENG101",
|
||||
"subject_name": "English Basics",
|
||||
"subject_name_en": "English Basics",
|
||||
"description": "Basic English for beginners",
|
||||
"is_active": true,
|
||||
"is_premium": false
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Subject added to category successfully",
|
||||
"data": {
|
||||
"id": "456e7890-e89b-12d3-a456-426614174001",
|
||||
"category_id": "123e4567-e89b-12d3-a456-426614174000",
|
||||
"subject_code": "ENG101",
|
||||
"subject_name": "English Basics",
|
||||
"subject_name_en": "English Basics",
|
||||
"description": "Basic English for beginners",
|
||||
"is_active": true,
|
||||
"is_premium": false,
|
||||
"created_at": "2026-02-26T10:00:00.000Z",
|
||||
"updated_at": "2026-02-26T10:00:00.000Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 2: Xóa Chapter khỏi Subject
|
||||
|
||||
**Request:**
|
||||
```http
|
||||
DELETE /api/subjects/456e7890-e89b-12d3-a456-426614174001/chapters/789e0123-e89b-12d3-a456-426614174002
|
||||
```
|
||||
|
||||
**Response (Success):**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Chapter removed from subject successfully"
|
||||
}
|
||||
```
|
||||
|
||||
**Response (Error - Chapter có lessons):**
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "Cannot delete chapter. It has 5 lesson(s). Delete lessons first."
|
||||
}
|
||||
```
|
||||
|
||||
### Example 3: Thêm Story vào Lesson (N:N)
|
||||
|
||||
**Request:**
|
||||
```http
|
||||
POST /api/lessons/012e3456-e89b-12d3-a456-426614174003/stories
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"story_id": "234e5678-e89b-12d3-a456-426614174004",
|
||||
"display_order": 1,
|
||||
"is_required": true
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Story đã được thêm vào bài học",
|
||||
"data": {
|
||||
"id": "345e6789-e89b-12d3-a456-426614174005",
|
||||
"lesson_id": "012e3456-e89b-12d3-a456-426614174003",
|
||||
"story_id": "234e5678-e89b-12d3-a456-426614174004",
|
||||
"display_order": 1,
|
||||
"is_required": true,
|
||||
"created_at": "2026-02-26T11:00:00.000Z",
|
||||
"updated_at": "2026-02-26T11:00:00.000Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 4: Lấy Stories trong Lesson
|
||||
|
||||
**Request:**
|
||||
```http
|
||||
GET /api/lessons/012e3456-e89b-12d3-a456-426614174003/stories?page=1&limit=10
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"lesson": {
|
||||
"id": "012e3456-e89b-12d3-a456-426614174003",
|
||||
"lesson_title": "Alphabet A-F",
|
||||
"lesson_number": 1
|
||||
},
|
||||
"stories": [
|
||||
{
|
||||
"id": "234e5678-e89b-12d3-a456-426614174004",
|
||||
"name": "The Greedy Cat",
|
||||
"type": "story",
|
||||
"thumbnail": "https://cdn.sena.tech/thumbs/greedy-cat.jpg",
|
||||
"display_order": 1,
|
||||
"is_required": true
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"limit": 10,
|
||||
"totalPages": 1
|
||||
}
|
||||
},
|
||||
"cached": false
|
||||
}
|
||||
```
|
||||
|
||||
### Example 5: Cập nhật Story trong Lesson
|
||||
|
||||
**Request:**
|
||||
```http
|
||||
PUT /api/lessons/012e3456-e89b-12d3-a456-426614174003/stories/234e5678-e89b-12d3-a456-426614174004
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"display_order": 2,
|
||||
"is_required": false
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Cập nhật thành công",
|
||||
"data": {
|
||||
"id": "345e6789-e89b-12d3-a456-426614174005",
|
||||
"lesson_id": "012e3456-e89b-12d3-a456-426614174003",
|
||||
"story_id": "234e5678-e89b-12d3-a456-426614174004",
|
||||
"display_order": 2,
|
||||
"is_required": false,
|
||||
"updated_at": "2026-02-26T12:00:00.000Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Migration Guide
|
||||
|
||||
Nếu bạn đang sử dụng cấu trúc cũ, đây là cách migrate:
|
||||
|
||||
### Before (Cũ)
|
||||
```javascript
|
||||
// Tạo subject
|
||||
POST /api/subjects
|
||||
Body: { category_id: "xxx", subject_code: "ENG101", ... }
|
||||
|
||||
// Tạo chapter
|
||||
POST /api/chapters
|
||||
Body: { subject_id: "yyy", chapter_number: 1, ... }
|
||||
```
|
||||
|
||||
### After (Mới)
|
||||
```javascript
|
||||
// Cách 1: Sử dụng nested routes (khuyến nghị)
|
||||
POST /api/categories/:categoryId/subjects
|
||||
Body: { subject_code: "ENG101", ... } // Không cần category_id
|
||||
|
||||
POST /api/subjects/:subjectId/chapters
|
||||
Body: { chapter_number: 1, ... } // Không cần subject_id
|
||||
|
||||
// Cách 2: Vẫn dùng flat routes (vẫn hỗ trợ)
|
||||
POST /api/subjects
|
||||
Body: { category_id: "xxx", subject_code: "ENG101", ... }
|
||||
|
||||
POST /api/chapters
|
||||
Body: { subject_id: "yyy", chapter_number: 1, ... }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 Notes
|
||||
|
||||
1. **Backward Compatibility**: Flat routes vẫn hoạt động bình thường
|
||||
2. **Nested routes**: Được khuyến nghị cho các thao tác mới
|
||||
3. **Cache Strategy**: Automatic cache invalidation được implement ở tất cả endpoints
|
||||
4. **Validation**: Parent resource luôn được validate trước khi thao tác
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Related Documents
|
||||
|
||||
- [SUBJECT_CHAPTER_LESSON_GUIDE.md](SUBJECT_CHAPTER_LESSON_GUIDE.md)
|
||||
- [LEARNING_CONTENT_GUIDE.md](LEARNING_CONTENT_GUIDE.md)
|
||||
- [SWAGGER_GUIDE.md](SWAGGER_GUIDE.md)
|
||||
Reference in New Issue
Block a user