# 📚 Hướng Dẫn Tạo và Quản Lý Subject, Chapter, Lesson ## Tổng Quan Hệ thống nội dung được tổ chức theo mô hình phân cấp 3 tầng: ``` Subject (Môn học) ├── Chapter 1 (Chương 1) │ ├── Lesson 1 (Bài học 1) │ ├── Lesson 2 (Bài học 2) │ └── Lesson 3 (Bài học 3) ├── Chapter 2 (Chương 2) │ ├── Lesson 4 (Bài học 4) │ └── Lesson 5 (Bài học 5) └── Chapter 3 (Chương 3) └── ... ``` ### Mối quan hệ - **Subject** (1) → **Chapter** (nhiều): Một môn học có nhiều chương - **Chapter** (1) → **Lesson** (nhiều): Một chương có nhiều bài học - **Lesson** → **Game** (optional): Bài học có thể tham chiếu đến Game engine --- ## 📊 Cấu Trúc Dữ Liệu ### 1. Subject (Môn học) **Bảng**: `subjects` **Mục đích**: Quản lý các môn học/giáo trình | Trường | Kiểu | Mô tả | |--------|------|-------| | `id` | UUID | ID duy nhất | | `subject_code` | VARCHAR(50) | Mã môn học (unique) | | `subject_name` | VARCHAR(255) | Tên môn học (tiếng Việt) | | `subject_name_en` | VARCHAR(255) | Tên tiếng Anh | | `description` | TEXT | Mô tả chi tiết | | `is_active` | BOOLEAN | Đang hoạt động | | `is_premium` | BOOLEAN | Nội dung premium | | `is_training` | BOOLEAN | Đào tạo nhân sự | | `is_public` | BOOLEAN | Tự học công khai | | `required_role` | VARCHAR(50) | Role yêu cầu | | `min_subscription_tier` | VARCHAR(50) | Gói tối thiểu | **Ví dụ**: ```json { "subject_code": "MATH_G1", "subject_name": "Toán lớp 1", "subject_name_en": "Math Grade 1", "description": "Chương trình Toán học lớp 1 theo SGK mới", "is_active": true, "is_premium": false, "is_training": false, "is_public": true, "required_role": "student", "min_subscription_tier": "basic" } ``` ### 2. Chapter (Chương học) **Bảng**: `chapters` **Mục đích**: Chia môn học thành các chương | Trường | Kiểu | Mô tả | |--------|------|-------| | `id` | UUID | ID duy nhất | | `subject_id` | UUID | FK → subjects.id | | `chapter_number` | INTEGER | Số thứ tự chương | | `chapter_title` | VARCHAR(255) | Tiêu đề chương | | `chapter_description` | TEXT | Mô tả nội dung | | `duration_minutes` | INTEGER | Thời lượng ước tính | | `is_published` | BOOLEAN | Đã xuất bản | | `display_order` | INTEGER | Thứ tự hiển thị | **Ví dụ**: ```json { "subject_id": "abc-123-xyz", "chapter_number": 1, "chapter_title": "Số và chữ số", "chapter_description": "Làm quen với các số từ 1 đến 10", "duration_minutes": 180, "is_published": true, "display_order": 1 } ``` ### 3. Lesson (Bài học) **Bảng**: `lessons` **Mục đích**: Nội dung giảng dạy cụ thể | Trường | Kiểu | Mô tả | |--------|------|-------| | `id` | UUID | ID duy nhất | | `chapter_id` | UUID | FK → chapters.id | | `lesson_number` | INTEGER | Số thứ tự bài học | | `lesson_title` | VARCHAR(255) | Tiêu đề bài học | | `lesson_type` | ENUM | `json_content` hoặc `url_content` | | `lesson_description` | TEXT | Mô tả bài học | | `content_json` | JSON | Nội dung tương tác | | `content_url` | VARCHAR(500) | Link video/PDF | | `content_type` | VARCHAR(50) | youtube, pdf, audio | | `duration_minutes` | INTEGER | Thời lượng | | `is_published` | BOOLEAN | Đã xuất bản | | `is_free` | BOOLEAN | Học thử miễn phí | | `display_order` | INTEGER | Thứ tự hiển thị | | `thumbnail_url` | VARCHAR(500) | Ảnh thumbnail | **Ví dụ Lesson JSON Content**: ```json { "chapter_id": "def-456-uvw", "lesson_number": 1, "lesson_title": "Đếm từ 1 đến 5", "lesson_type": "json_content", "lesson_description": "Học đếm các số từ 1 đến 5 qua trò chơi", "content_json": { "type": "counting_quiz", "questions": [ { "id": 1, "question": "Có bao nhiêu quả táo?", "image": "https://cdn.senaai.tech/images/apples-3.png", "correct_answer": 3, "options": [2, 3, 4, 5] } ], "pass_score": 70 }, "duration_minutes": 15, "is_published": true, "is_free": true, "display_order": 1 } ``` **Ví dụ Lesson URL Content**: ```json { "chapter_id": "def-456-uvw", "lesson_number": 2, "lesson_title": "Video: Hướng dẫn đếm số", "lesson_type": "url_content", "content_url": "https://www.youtube.com/watch?v=abc123", "content_type": "youtube", "duration_minutes": 10, "is_published": true, "is_free": false, "display_order": 2 } ``` --- ## 🚀 Hướng Dẫn Tạo Nội Dung ### Quy trình 3 bước ``` Bước 1: Tạo Subject (Môn học) ↓ Bước 2: Tạo Chapter (Chương) - liên kết với Subject ↓ Bước 3: Tạo Lesson (Bài học) - liên kết với Chapter ``` --- ## 📝 Bước 1: Tạo Subject (Môn học) ### API Endpoint ``` POST /api/subjects Content-Type: application/json Authorization: Bearer ``` ### Request Body ```json { "subject_code": "MATH_G1", "subject_name": "Toán lớp 1", "subject_name_en": "Math Grade 1", "description": "Chương trình Toán học lớp 1 theo SGK mới, bao gồm số học cơ bản, phép cộng trừ, hình học đơn giản", "is_active": true, "is_premium": false, "is_training": false, "is_public": true, "required_role": "student", "min_subscription_tier": "basic" } ``` ### Response (201 Created) ```json { "success": true, "message": "Subject created successfully", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "subject_code": "MATH_G1", "subject_name": "Toán lớp 1", "subject_name_en": "Math Grade 1", "description": "Chương trình Toán học lớp 1...", "is_active": true, "is_premium": false, "is_training": false, "is_public": true, "required_role": "student", "min_subscription_tier": "basic", "created_at": "2026-01-20T10:00:00.000Z", "updated_at": "2026-01-20T10:00:00.000Z" } } ``` ### Ví dụ với cURL ```bash curl -X POST http://localhost:3000/api/subjects \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_TOKEN" \ -d '{ "subject_code": "MATH_G1", "subject_name": "Toán lớp 1", "subject_name_en": "Math Grade 1", "description": "Chương trình Toán học lớp 1 theo SGK mới", "is_active": true, "is_premium": false, "is_public": true }' ``` ### Ví dụ với Node.js (Sequelize) ```javascript const { Subject } = require('./models'); const mathSubject = await Subject.create({ subject_code: 'MATH_G1', subject_name: 'Toán lớp 1', subject_name_en: 'Math Grade 1', description: 'Chương trình Toán học lớp 1 theo SGK mới', is_active: true, is_premium: false, is_training: false, is_public: true, required_role: 'student', min_subscription_tier: 'basic' }); console.log('Subject ID:', mathSubject.id); // Lưu ID này để tạo Chapter ``` ### Các Subject mẫu khác **Tiếng Việt lớp 1**: ```json { "subject_code": "VIET_G1", "subject_name": "Tiếng Việt lớp 1", "subject_name_en": "Vietnamese Grade 1", "description": "Học chữ cái, đọc, viết và hiểu văn bản đơn giản", "is_active": true, "is_premium": false, "is_public": true } ``` **Tiếng Anh lớp 1**: ```json { "subject_code": "ENG_G1", "subject_name": "Tiếng Anh lớp 1", "subject_name_en": "English Grade 1", "description": "Làm quen với bảng chữ cái, từ vựng và câu giao tiếp cơ bản", "is_active": true, "is_premium": true, "is_public": false, "min_subscription_tier": "premium" } ``` --- ## 📖 Bước 2: Tạo Chapter (Chương) ### API Endpoint ``` POST /api/chapters Content-Type: application/json Authorization: Bearer ``` ### Request Body ```json { "subject_id": "550e8400-e29b-41d4-a716-446655440000", "chapter_number": 1, "chapter_title": "Số và chữ số", "chapter_description": "Làm quen với các số từ 1 đến 10, học đếm và nhận biết chữ số", "duration_minutes": 180, "is_published": true, "display_order": 1 } ``` ### Response (201 Created) ```json { "success": true, "message": "Chapter created successfully", "data": { "id": "660e8400-e29b-41d4-a716-446655440001", "subject_id": "550e8400-e29b-41d4-a716-446655440000", "chapter_number": 1, "chapter_title": "Số và chữ số", "chapter_description": "Làm quen với các số từ 1 đến 10...", "duration_minutes": 180, "is_published": true, "display_order": 1, "created_at": "2026-01-20T10:05:00.000Z", "updated_at": "2026-01-20T10:05:00.000Z" } } ``` ### Ví dụ với Node.js ```javascript const { Chapter } = require('./models'); // Sử dụng subject_id từ bước 1 const chapter1 = await Chapter.create({ subject_id: mathSubject.id, // ID từ bước 1 chapter_number: 1, chapter_title: 'Số và chữ số', chapter_description: 'Làm quen với các số từ 1 đến 10, học đếm và nhận biết chữ số', duration_minutes: 180, is_published: true, display_order: 1 }); console.log('Chapter ID:', chapter1.id); // Tạo Chapter 2 const chapter2 = await Chapter.create({ subject_id: mathSubject.id, chapter_number: 2, chapter_title: 'Phép cộng trong phạm vi 10', chapter_description: 'Học cách cộng các số từ 1 đến 10', duration_minutes: 240, is_published: true, display_order: 2 }); // Tạo Chapter 3 const chapter3 = await Chapter.create({ subject_id: mathSubject.id, chapter_number: 3, chapter_title: 'Phép trừ trong phạm vi 10', chapter_description: 'Học cách trừ các số từ 1 đến 10', duration_minutes: 240, is_published: false, // Chưa xuất bản display_order: 3 }); ``` ### Tạo nhiều Chapter cùng lúc ```javascript const chapters = await Chapter.bulkCreate([ { subject_id: mathSubject.id, chapter_number: 1, chapter_title: 'Số và chữ số', chapter_description: 'Làm quen với các số từ 1 đến 10', duration_minutes: 180, is_published: true, display_order: 1 }, { subject_id: mathSubject.id, chapter_number: 2, chapter_title: 'Phép cộng trong phạm vi 10', chapter_description: 'Học cộng các số từ 1 đến 10', duration_minutes: 240, is_published: true, display_order: 2 }, { subject_id: mathSubject.id, chapter_number: 3, chapter_title: 'Phép trừ trong phạm vi 10', chapter_description: 'Học trừ các số từ 1 đến 10', duration_minutes: 240, is_published: true, display_order: 3 } ]); console.log(`Created ${chapters.length} chapters`); ``` --- ## 📚 Bước 3: Tạo Lesson (Bài học) ### 3.1. Lesson với JSON Content (Tương tác) #### API Endpoint ``` POST /api/lessons Content-Type: application/json Authorization: Bearer ``` #### Request Body ```json { "chapter_id": "660e8400-e29b-41d4-a716-446655440001", "lesson_number": 1, "lesson_title": "Đếm từ 1 đến 5", "lesson_type": "json_content", "lesson_description": "Học đếm các số từ 1 đến 5 qua trò chơi tương tác", "content_json": { "type": "counting_quiz", "theme": "fruits", "questions": [ { "id": 1, "question": "Có bao nhiêu quả táo?", "image": "https://cdn.senaai.tech/images/apples-3.png", "correct_answer": 3, "options": [2, 3, 4, 5] }, { "id": 2, "question": "Có bao nhiêu quả cam?", "image": "https://cdn.senaai.tech/images/oranges-5.png", "correct_answer": 5, "options": [3, 4, 5, 6] } ], "instructions": "Nhìn vào hình và đếm số lượng vật, sau đó chọn đáp án đúng", "pass_score": 70, "max_attempts": 3, "show_hints": true }, "duration_minutes": 15, "is_published": true, "is_free": true, "display_order": 1, "thumbnail_url": "https://cdn.senaai.tech/thumbnails/lesson-counting.jpg" } ``` #### Response (201 Created) ```json { "success": true, "message": "Lesson created successfully", "data": { "id": "770e8400-e29b-41d4-a716-446655440002", "chapter_id": "660e8400-e29b-41d4-a716-446655440001", "lesson_number": 1, "lesson_title": "Đếm từ 1 đến 5", "lesson_type": "json_content", "lesson_description": "Học đếm các số từ 1 đến 5...", "content_json": { /* ... */ }, "content_url": null, "content_type": null, "duration_minutes": 15, "is_published": true, "is_free": true, "display_order": 1, "thumbnail_url": "https://cdn.senaai.tech/thumbnails/lesson-counting.jpg", "created_at": "2026-01-20T10:10:00.000Z", "updated_at": "2026-01-20T10:10:00.000Z" } } ``` #### Ví dụ với Node.js ```javascript const { Lesson } = require('./models'); const lesson1 = await Lesson.create({ chapter_id: chapter1.id, // ID từ bước 2 lesson_number: 1, lesson_title: 'Đếm từ 1 đến 5', lesson_type: 'json_content', lesson_description: 'Học đếm các số từ 1 đến 5 qua trò chơi tương tác', content_json: { type: 'counting_quiz', // Khớp với Game.type theme: 'fruits', questions: [ { id: 1, question: 'Có bao nhiêu quả táo?', image: 'https://cdn.senaai.tech/images/apples-3.png', correct_answer: 3, options: [2, 3, 4, 5] }, { id: 2, question: 'Có bao nhiêu quả cam?', image: 'https://cdn.senaai.tech/images/oranges-5.png', correct_answer: 5, options: [3, 4, 5, 6] } ], instructions: 'Nhìn vào hình và đếm số lượng vật, sau đó chọn đáp án đúng', pass_score: 70, max_attempts: 3, show_hints: true }, duration_minutes: 15, is_published: true, is_free: true, display_order: 1, thumbnail_url: 'https://cdn.senaai.tech/thumbnails/lesson-counting.jpg' }); console.log('Lesson ID:', lesson1.id); ``` ### 3.2. Lesson với URL Content (Video/PDF) #### Video YouTube ```javascript const lesson2 = await Lesson.create({ chapter_id: chapter1.id, lesson_number: 2, lesson_title: 'Video: Hướng dẫn đếm số', lesson_type: 'url_content', lesson_description: 'Video hướng dẫn cách đếm số từ 1 đến 10', content_url: 'https://www.youtube.com/watch?v=abc123xyz', content_type: 'youtube', duration_minutes: 10, is_published: true, is_free: false, display_order: 2, thumbnail_url: 'https://img.youtube.com/vi/abc123xyz/maxresdefault.jpg' }); ``` #### PDF Document ```javascript const lesson3 = await Lesson.create({ chapter_id: chapter1.id, lesson_number: 3, lesson_title: 'Tài liệu: Bảng số từ 1-10', lesson_type: 'url_content', lesson_description: 'Tài liệu PDF hướng dẫn nhận biết và viết các số', content_url: 'https://cdn.senaai.tech/docs/number-chart-1-10.pdf', content_type: 'pdf', duration_minutes: 5, is_published: true, is_free: true, display_order: 3 }); ``` #### Audio File ```javascript const lesson4 = await Lesson.create({ chapter_id: chapter1.id, lesson_number: 4, lesson_title: 'Nghe: Phát âm các số', lesson_type: 'url_content', lesson_description: 'Học cách phát âm đúng các số từ 1 đến 10', content_url: 'https://cdn.senaai.tech/audio/numbers-pronunciation.mp3', content_type: 'audio', duration_minutes: 8, is_published: true, is_free: true, display_order: 4 }); ``` ### 3.3. Tạo nhiều Lesson cùng lúc ```javascript const lessons = await Lesson.bulkCreate([ { chapter_id: chapter1.id, lesson_number: 1, lesson_title: 'Đếm từ 1 đến 5', lesson_type: 'json_content', content_json: { type: 'counting_quiz', questions: [/* ... */] }, duration_minutes: 15, is_published: true, is_free: true, display_order: 1 }, { chapter_id: chapter1.id, lesson_number: 2, lesson_title: 'Đếm từ 6 đến 10', lesson_type: 'json_content', content_json: { type: 'counting_quiz', questions: [/* ... */] }, duration_minutes: 15, is_published: true, is_free: true, display_order: 2 }, { chapter_id: chapter1.id, lesson_number: 3, lesson_title: 'Video: Hướng dẫn đếm số', lesson_type: 'url_content', content_url: 'https://www.youtube.com/watch?v=abc123', content_type: 'youtube', duration_minutes: 10, is_published: true, is_free: false, display_order: 3 } ]); console.log(`Created ${lessons.length} lessons`); ``` --- ## 🔍 Truy Vấn và Quản Lý ### Lấy danh sách Subject ``` GET /api/subjects ``` **Response**: ```json { "success": true, "data": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "subject_code": "MATH_G1", "subject_name": "Toán lớp 1", "is_active": true, "chapter_count": 3 } ] } ``` ### Lấy chi tiết Subject ``` GET /api/subjects/:id ``` **Response**: ```json { "success": true, "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "subject_code": "MATH_G1", "subject_name": "Toán lớp 1", "description": "Chương trình Toán học lớp 1...", "chapters": [ { "id": "660e8400-e29b-41d4-a716-446655440001", "chapter_number": 1, "chapter_title": "Số và chữ số", "lesson_count": 5 } ] } } ``` ### Lấy danh sách Chapter của Subject ``` GET /api/subjects/:subject_id/chapters ``` ### Lấy danh sách Lesson của Chapter ``` GET /api/chapters/:chapter_id/lessons ``` **Response**: ```json { "success": true, "data": [ { "id": "770e8400-e29b-41d4-a716-446655440002", "lesson_number": 1, "lesson_title": "Đếm từ 1 đến 5", "lesson_type": "json_content", "duration_minutes": 15, "is_free": true, "thumbnail_url": "https://cdn.senaai.tech/thumbnails/lesson-counting.jpg" }, { "id": "770e8400-e29b-41d4-a716-446655440003", "lesson_number": 2, "lesson_title": "Video: Hướng dẫn đếm số", "lesson_type": "url_content", "duration_minutes": 10, "is_free": false } ] } ``` ### Lấy chi tiết Lesson ``` GET /api/lessons/:id ``` **Response**: ```json { "success": true, "data": { "id": "770e8400-e29b-41d4-a716-446655440002", "chapter_id": "660e8400-e29b-41d4-a716-446655440001", "lesson_number": 1, "lesson_title": "Đếm từ 1 đến 5", "lesson_type": "json_content", "lesson_description": "Học đếm các số từ 1 đến 5...", "content_json": { "type": "counting_quiz", "questions": [/* ... */], "pass_score": 70 }, "duration_minutes": 15, "is_published": true, "is_free": true, "chapter": { "id": "660e8400-e29b-41d4-a716-446655440001", "chapter_title": "Số và chữ số", "subject": { "id": "550e8400-e29b-41d4-a716-446655440000", "subject_name": "Toán lớp 1" } } } } ``` --- ## ✏️ Cập Nhật Nội Dung ### Cập nhật Subject ``` PUT /api/subjects/:id Content-Type: application/json ``` **Request**: ```json { "subject_name": "Toán lớp 1 - Chương trình nâng cao", "description": "Chương trình Toán học lớp 1 nâng cao với nhiều bài tập thực hành", "is_premium": true, "min_subscription_tier": "premium" } ``` ### Cập nhật Chapter ``` PUT /api/chapters/:id Content-Type: application/json ``` **Request**: ```json { "chapter_title": "Số và chữ số (cập nhật)", "chapter_description": "Làm quen với các số từ 1 đến 20", "duration_minutes": 240, "is_published": true } ``` ### Cập nhật Lesson ``` PUT /api/lessons/:id Content-Type: application/json ``` **Request**: ```json { "lesson_title": "Đếm từ 1 đến 10 (mở rộng)", "content_json": { "type": "counting_quiz", "questions": [ /* Thêm câu hỏi mới */ ], "pass_score": 80 }, "duration_minutes": 20, "is_free": false } ``` --- ## 🗑️ Xóa Nội Dung ### Xóa Subject ``` DELETE /api/subjects/:id ``` **Lưu ý**: Xóa Subject sẽ xóa tất cả Chapter và Lesson liên quan (nếu có cascade) ### Xóa Chapter ``` DELETE /api/chapters/:id ``` **Lưu ý**: Xóa Chapter sẽ xóa tất cả Lesson liên quan ### Xóa Lesson ``` DELETE /api/lessons/:id ``` --- ## 📊 Script Tạo Dữ Liệu Mẫu Đầy Đủ ```javascript // scripts/seed-sample-content.js const { Subject, Chapter, Lesson } = require('../models'); async function seedSampleContent() { try { console.log('🌱 Seeding sample content...'); // 1. TẠO SUBJECT: Toán lớp 1 const mathSubject = await Subject.create({ subject_code: 'MATH_G1', subject_name: 'Toán lớp 1', subject_name_en: 'Math Grade 1', description: 'Chương trình Toán học lớp 1 theo SGK mới', is_active: true, is_premium: false, is_training: false, is_public: true, required_role: 'student', min_subscription_tier: 'basic' }); console.log('✅ Created Subject: Toán lớp 1'); // 2. TẠO CHAPTER 1: Số và chữ số const chapter1 = await Chapter.create({ subject_id: mathSubject.id, chapter_number: 1, chapter_title: 'Số và chữ số', chapter_description: 'Làm quen với các số từ 1 đến 10', duration_minutes: 180, is_published: true, display_order: 1 }); console.log('✅ Created Chapter 1: Số và chữ số'); // 3. TẠO LESSON cho Chapter 1 await Lesson.bulkCreate([ { chapter_id: chapter1.id, lesson_number: 1, lesson_title: 'Đếm từ 1 đến 5', lesson_type: 'json_content', lesson_description: 'Học đếm các số từ 1 đến 5 qua trò chơi', content_json: { type: 'counting_quiz', theme: 'fruits', questions: [ { id: 1, question: 'Có bao nhiêu quả táo?', image: 'https://cdn.senaai.tech/images/apples-3.png', correct_answer: 3, options: [2, 3, 4, 5] }, { id: 2, question: 'Có bao nhiêu quả cam?', image: 'https://cdn.senaai.tech/images/oranges-5.png', correct_answer: 5, options: [3, 4, 5, 6] } ], pass_score: 70 }, duration_minutes: 15, is_published: true, is_free: true, display_order: 1 }, { chapter_id: chapter1.id, lesson_number: 2, lesson_title: 'Video: Hướng dẫn đếm số', lesson_type: 'url_content', lesson_description: 'Video hướng dẫn cách đếm số từ 1 đến 10', content_url: 'https://www.youtube.com/watch?v=sample123', content_type: 'youtube', duration_minutes: 10, is_published: true, is_free: false, display_order: 2 }, { chapter_id: chapter1.id, lesson_number: 3, lesson_title: 'Đếm từ 6 đến 10', lesson_type: 'json_content', lesson_description: 'Tiếp tục học đếm các số từ 6 đến 10', content_json: { type: 'counting_quiz', theme: 'animals', questions: [ { id: 1, question: 'Có bao nhiêu con mèo?', image: 'https://cdn.senaai.tech/images/cats-7.png', correct_answer: 7, options: [6, 7, 8, 9] }, { id: 2, question: 'Có bao nhiêu con chó?', image: 'https://cdn.senaai.tech/images/dogs-10.png', correct_answer: 10, options: [8, 9, 10, 11] } ], pass_score: 70 }, duration_minutes: 15, is_published: true, is_free: true, display_order: 3 } ]); console.log('✅ Created 3 Lessons for Chapter 1'); // 4. TẠO CHAPTER 2: Phép cộng const chapter2 = await Chapter.create({ subject_id: mathSubject.id, chapter_number: 2, chapter_title: 'Phép cộng trong phạm vi 10', chapter_description: 'Học cộng các số từ 1 đến 10', duration_minutes: 240, is_published: true, display_order: 2 }); console.log('✅ Created Chapter 2: Phép cộng'); // 5. TẠO LESSON cho Chapter 2 await Lesson.bulkCreate([ { chapter_id: chapter2.id, lesson_number: 1, lesson_title: 'Phép cộng cơ bản', lesson_type: 'json_content', lesson_description: 'Học phép cộng 2 số trong phạm vi 5', content_json: { type: 'math_practice', operation: 'addition', range: { min: 1, max: 5 }, question_count: 10, show_solution_steps: true }, duration_minutes: 20, is_published: true, is_free: true, display_order: 1 }, { chapter_id: chapter2.id, lesson_number: 2, lesson_title: 'Thực hành phép cộng', lesson_type: 'json_content', lesson_description: 'Luyện tập phép cộng trong phạm vi 10', content_json: { type: 'math_practice', operation: 'addition', range: { min: 1, max: 10 }, question_count: 20, show_solution_steps: true, allow_calculator: false }, duration_minutes: 25, is_published: true, is_free: false, display_order: 2 } ]); console.log('✅ Created 2 Lessons for Chapter 2'); // 6. TẠO SUBJECT: Tiếng Việt lớp 1 const vietnameseSubject = await Subject.create({ subject_code: 'VIET_G1', subject_name: 'Tiếng Việt lớp 1', subject_name_en: 'Vietnamese Grade 1', description: 'Học chữ cái, đọc, viết và hiểu văn bản đơn giản', is_active: true, is_premium: false, is_public: true }); console.log('✅ Created Subject: Tiếng Việt lớp 1'); // 7. TẠO CHAPTER cho Tiếng Việt const vnChapter1 = await Chapter.create({ subject_id: vietnameseSubject.id, chapter_number: 1, chapter_title: 'Bảng chữ cái tiếng Việt', chapter_description: 'Học 29 chữ cái tiếng Việt', duration_minutes: 300, is_published: true, display_order: 1 }); console.log('✅ Created Chapter: Bảng chữ cái tiếng Việt'); // 8. TẠO LESSON cho Tiếng Việt await Lesson.create({ chapter_id: vnChapter1.id, lesson_number: 1, lesson_title: 'Học chữ A, B, C', lesson_type: 'json_content', lesson_description: 'Làm quen với 3 chữ cái đầu tiên', content_json: { type: 'alphabet_learning', letters: ['A', 'B', 'C'], activities: ['recognize', 'write', 'pronounce'] }, duration_minutes: 30, is_published: true, is_free: true, display_order: 1 }); console.log('✅ Created Lesson: Học chữ A, B, C'); console.log('\n🎉 Sample content seeded successfully!'); console.log('\nSummary:'); console.log('- Subjects: 2 (Toán lớp 1, Tiếng Việt lớp 1)'); console.log('- Chapters: 3 (Số và chữ số, Phép cộng, Bảng chữ cái)'); console.log('- Lessons: 6'); } catch (error) { console.error('❌ Error seeding data:', error); throw error; } } // Chạy script seedSampleContent() .then(() => { console.log('\n✅ Done!'); process.exit(0); }) .catch(err => { console.error('\n❌ Failed:', err); process.exit(1); }); ``` ### Chạy script ```bash node scripts/seed-sample-content.js ``` --- ## 🎯 Workflow Thực Tế ### Scenario 1: Thêm môn học mới hoàn chỉnh ```javascript // 1. Tạo Subject const englishSubject = await Subject.create({ subject_code: 'ENG_G1', subject_name: 'Tiếng Anh lớp 1', is_active: true, is_premium: true, is_public: false }); // 2. Tạo nhiều Chapter const chapters = await Chapter.bulkCreate([ { subject_id: englishSubject.id, chapter_number: 1, chapter_title: 'Alphabet', is_published: true, display_order: 1 }, { subject_id: englishSubject.id, chapter_number: 2, chapter_title: 'Greetings', is_published: true, display_order: 2 } ]); // 3. Tạo Lesson cho mỗi Chapter for (const chapter of chapters) { await Lesson.create({ chapter_id: chapter.id, lesson_number: 1, lesson_title: `${chapter.chapter_title} - Lesson 1`, lesson_type: 'json_content', content_json: { /* ... */ }, is_published: true, display_order: 1 }); } ``` ### Scenario 2: Sao chép cấu trúc từ môn khác ```javascript // Sao chép cấu trúc từ Toán lớp 1 sang Toán lớp 2 const mathG1 = await Subject.findOne({ where: { subject_code: 'MATH_G1' }, include: [{ model: Chapter, include: [Lesson] }] }); const mathG2 = await Subject.create({ subject_code: 'MATH_G2', subject_name: 'Toán lớp 2', // ... các trường khác }); // Copy chapters và lessons for (const chapter of mathG1.Chapters) { const newChapter = await Chapter.create({ subject_id: mathG2.id, chapter_number: chapter.chapter_number, chapter_title: chapter.chapter_title, // ... các trường khác }); for (const lesson of chapter.Lessons) { await Lesson.create({ chapter_id: newChapter.id, lesson_number: lesson.lesson_number, lesson_title: lesson.lesson_title, // ... các trường khác }); } } ``` --- ## ✅ Checklist Tạo Nội Dung ### Khi tạo Subject: - [ ] `subject_code` là duy nhất (unique) - [ ] `subject_name` rõ ràng, dễ hiểu - [ ] Chọn đúng `is_premium`, `is_public`, `is_training` - [ ] Thiết lập `required_role` và `min_subscription_tier` phù hợp ### Khi tạo Chapter: - [ ] Liên kết đúng `subject_id` - [ ] `chapter_number` theo thứ tự 1, 2, 3... - [ ] `display_order` phản ánh thứ tự hiển thị - [ ] `duration_minutes` hợp lý với số lượng lesson - [ ] Đặt `is_published = true` khi sẵn sàng ### Khi tạo Lesson: - [ ] Liên kết đúng `chapter_id` - [ ] Chọn đúng `lesson_type`: `json_content` hoặc `url_content` - [ ] Nếu `json_content`: `content_json.type` phải khớp với `Game.type` - [ ] Nếu `url_content`: Kiểm tra URL hợp lệ - [ ] `lesson_number` và `display_order` đúng thứ tự - [ ] Thiết lập `is_free` cho bài học thử miễn phí - [ ] Thêm `thumbnail_url` để hiển thị đẹp --- ## 🚨 Lỗi Thường Gặp ### 1. Lỗi Foreign Key ``` Error: Cannot create chapter: subject_id does not exist ``` **Giải pháp**: Kiểm tra `subject_id` có tồn tại trong bảng `subjects` ### 2. Lỗi Unique Constraint ``` Error: subject_code must be unique ``` **Giải pháp**: Chọn `subject_code` khác hoặc kiểm tra Subject đã tồn tại ### 3. Lỗi JSON Content Type ``` Warning: No game found for type: xxx ``` **Giải pháp**: Tạo Game với `type` khớp với `content_json.type` ### 4. Lỗi Validation ``` Error: lesson_type must be either 'json_content' or 'url_content' ``` **Giải pháp**: Kiểm tra giá trị `lesson_type` hợp lệ --- ## 📚 Tài Liệu Liên Quan - [CONTENT_MANAGEMENT_GUIDE.md](./CONTENT_MANAGEMENT_GUIDE.md) - Hướng dẫn tổng thể chi tiết - [API Documentation](http://localhost:3000/api-docs) - Swagger UI - `models/Subject.js` - Model Subject - `models/Chapter.js` - Model Chapter - `models/Lesson.js` - Model Lesson - `controllers/lessonController.js` - Lesson Controller - `scripts/seed-sample-content.js` - Script tạo dữ liệu mẫu --- ## 💡 Tips & Best Practices 1. **Luôn tạo theo thứ tự**: Subject → Chapter → Lesson 2. **Sử dụng `display_order`**: Kiểm soát thứ tự hiển thị độc lập với số thứ tự 3. **Draft trước, publish sau**: Tạo với `is_published: false`, kiểm tra kỹ rồi mới publish 4. **Consistent naming**: Đặt tên theo quy ước rõ ràng (`MATH_G1`, `VIET_G2`) 5. **Free content**: Luôn có một số bài học miễn phí để học viên trải nghiệm 6. **Thumbnail**: Thêm thumbnail cho lesson để tăng trải nghiệm UI 7. **Metadata**: Sử dụng `description` để mô tả đầy đủ nội dung 8. **Testing**: Test kỹ từng lesson trước khi publish --- **Chúc bạn tạo nội dung thành công! 🎉**