Files
sena_db_api_layer/LESSON_STORY_GUIDE.md
Ken 6287a019e3
All checks were successful
Deploy to Production / deploy (push) Successful in 20s
update
2026-02-27 09:38:39 +07:00

308 lines
6.3 KiB
Markdown

# Lesson-Story N:N Relationship Guide
## 📋 Tổng quan
Quan hệ **Many-to-Many (N:N)** giữa `Lesson``Story`:
- Một lesson có thể chứa 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`
## 🗂️ Database Schema
### Bảng: lesson_stories
```sql
CREATE TABLE lesson_stories (
id CHAR(36) PRIMARY KEY,
lesson_id CHAR(36) NOT NULL,
story_id CHAR(36) NOT NULL,
display_order INT DEFAULT 0,
is_required BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP,
updated_at TIMESTAMP,
UNIQUE(lesson_id, story_id)
);
```
### Metadata trong Pivot Table
- `display_order`: Thứ tự hiển thị story trong lesson
- `is_required`: Story có bắt buộc phải hoàn thành không
## 🔌 API Endpoints
### 1. Quản lý từ phía Lesson
#### Lấy danh sách stories trong lesson
```http
GET /api/lessons/:lessonId/stories?page=1&limit=20
```
**Response:**
```json
{
"success": true,
"data": {
"lesson": {
"id": "xxx",
"lesson_title": "Alphabet A-F",
"lesson_number": 1
},
"stories": [
{
"id": "yyy",
"name": "The Greedy Cat",
"type": "story",
"display_order": 1,
"is_required": true
}
],
"pagination": { ... }
}
}
```
#### Thêm story vào lesson
```http
POST /api/lessons/:lessonId/stories
Content-Type: application/json
{
"story_id": "story-uuid",
"display_order": 1,
"is_required": true
}
```
#### Cập nhật story trong lesson
```http
PUT /api/lessons/:lessonId/stories/:storyId
Content-Type: application/json
{
"display_order": 2,
"is_required": false
}
```
#### Xóa story khỏi lesson
```http
DELETE /api/lessons/:lessonId/stories/:storyId
```
---
### 2. Quản lý từ phía Story (Alternative)
#### Lấy danh sách lessons sử dụng story
```http
GET /api/stories/:storyId/lessons?page=1&limit=20
```
**Response:**
```json
{
"success": true,
"data": {
"story": {
"id": "yyy",
"name": "The Greedy Cat",
"type": "story"
},
"lessons": [
{
"id": "xxx",
"lesson_title": "Alphabet A-F",
"display_order": 1,
"is_required": true
}
],
"pagination": { ... }
}
}
```
#### Thêm lesson vào story
```http
POST /api/stories/:storyId/lessons
Content-Type: application/json
{
"lesson_id": "lesson-uuid",
"display_order": 1,
"is_required": true
}
```
#### Xóa lesson khỏi story
```http
DELETE /api/stories/:storyId/lessons/:lessonId
```
## 💡 Use Cases
### Use Case 1: Tạo lesson với nhiều stories
```bash
# 1. Tạo lesson
POST /api/chapters/:chapterId/lessons
{
"lesson_number": 1,
"lesson_title": "Learning ABC",
"lesson_type": "json_content"
}
# 2. Thêm story thứ nhất
POST /api/lessons/{lesson_id}/stories
{
"story_id": "story-1-id",
"display_order": 1,
"is_required": true
}
# 3. Thêm story thứ hai
POST /api/lessons/{lesson_id}/stories
{
"story_id": "story-2-id",
"display_order": 2,
"is_required": false
}
```
### Use Case 2: Tái sử dụng story cho nhiều lessons
```bash
# Story "The Greedy Cat" được sử dụng trong 3 lessons khác nhau
# Lesson 1 - Grade 1
POST /api/lessons/lesson-1-id/stories
{
"story_id": "greedy-cat-id",
"display_order": 1,
"is_required": true
}
# Lesson 2 - Grade 2
POST /api/lessons/lesson-2-id/stories
{
"story_id": "greedy-cat-id",
"display_order": 2,
"is_required": false
}
# Lesson 3 - Review lesson
POST /api/lessons/lesson-3-id/stories
{
"story_id": "greedy-cat-id",
"display_order": 1,
"is_required": true
}
```
### Use Case 3: Cập nhật thứ tự stories trong lesson
```bash
# Đổi thứ tự từ 1 sang 3
PUT /api/lessons/{lesson_id}/stories/{story_id}
{
"display_order": 3
}
# Đánh dấu không bắt buộc
PUT /api/lessons/{lesson_id}/stories/{story_id}
{
"is_required": false
}
```
## 🎯 Best Practices
### 1. Naming Convention
- `display_order` bắt đầu từ 1, tăng dần
- Để lại khoảng trống giữa các order (1, 5, 10...) để dễ insert sau
### 2. Validation
```javascript
// Check duplicate trước khi thêm
const existing = await LessonStory.findOne({
where: { lesson_id, story_id }
});
if (existing) {
return res.status(400).json({
message: 'Story đã tồn tại trong lesson này'
});
}
```
### 3. Cache Strategy
```javascript
// Clear cache của cả 2 phía khi có thay đổi
await cacheUtils.deletePattern(`lesson:${lessonId}:stories:*`);
await cacheUtils.deletePattern(`story:${storyId}:lessons:*`);
```
### 4. Query Optimization
```javascript
// Sử dụng include để eager load
const lessons = await Lesson.findAll({
include: [{
model: Story,
as: 'stories',
through: {
attributes: ['display_order', 'is_required']
}
}]
});
```
## ⚠️ Important Notes
### Cascade Delete
- Khi xóa lesson → Tự động xóa relationships trong `lesson_stories`
- Khi xóa story → Tự động xóa relationships trong `lesson_stories`
- **KHÔNG** xóa story/lesson khi xóa relationship
### Transaction Safety
```javascript
const transaction = await sequelize.transaction();
try {
// Add multiple stories to lesson
await LessonStory.bulkCreate([
{ lesson_id, story_id: 'story1', display_order: 1 },
{ lesson_id, story_id: 'story2', display_order: 2 },
], { transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
```
### Performance Tips
1. **Index**: lesson_id và story_id đều có index
2. **Pagination**: Luôn dùng limit khi query
3. **Cache**: Cache kết quả 30 phút (1800s)
4. **Eager Loading**: Load stories cùng lesson khi cần
## 🔍 Troubleshooting
### Issue 1: Duplicate Error
```
Error: Unique constraint violation
```
**Solution**: Story đã tồn tại trong lesson, kiểm tra trước khi thêm
### Issue 2: Foreign Key Error
```
Error: Cannot add or update a child row
```
**Solution**: lesson_id hoặc story_id không tồn tại, validate trước
### Issue 3: Order Conflict
```
Warning: Multiple stories have same display_order
```
**Solution**: Cập nhật display_order để unique trong lesson
## 📚 Related Documents
- [API_STRUCTURE_GUIDE.md](../API_STRUCTURE_GUIDE.md)
- [STORY_GUIDE.md](../STORY_GUIDE.md)
- [SUBJECT_CHAPTER_LESSON_GUIDE.md](../SUBJECT_CHAPTER_LESSON_GUIDE.md)