From 53d97ba5db759ba36ecbff99f04216eb39df0563 Mon Sep 17 00:00:00 2001 From: silverpro89 Date: Tue, 20 Jan 2026 20:29:07 +0700 Subject: [PATCH] update --- CONTENT_MANAGEMENT_GUIDE.md | 671 +++++++++++++++- SUBJECT_CHAPTER_LESSON_GUIDE.md | 1251 +++++++++++++++++++++++++++++ app.js | 5 + config/swagger.js | 111 +++ controllers/lessonController.js | 381 +++++++++ middleware/auth.js | 210 +++++ models/LessonComponentProgress.js | 100 +++ models/LessonLeaderboard.js | 88 ++ models/index.js | 14 + routes/chapterLessonRoutes.js | 36 + routes/lessonRoutes.js | 224 ++++++ scripts/seed-sample-content.js | 390 +++++++++ 12 files changed, 3461 insertions(+), 20 deletions(-) create mode 100644 SUBJECT_CHAPTER_LESSON_GUIDE.md create mode 100644 controllers/lessonController.js create mode 100644 middleware/auth.js create mode 100644 models/LessonComponentProgress.js create mode 100644 models/LessonLeaderboard.js create mode 100644 routes/chapterLessonRoutes.js create mode 100644 routes/lessonRoutes.js create mode 100644 scripts/seed-sample-content.js diff --git a/CONTENT_MANAGEMENT_GUIDE.md b/CONTENT_MANAGEMENT_GUIDE.md index 46ac00b..50b1495 100644 --- a/CONTENT_MANAGEMENT_GUIDE.md +++ b/CONTENT_MANAGEMENT_GUIDE.md @@ -247,7 +247,325 @@ const lesson4 = await Lesson.create({ }); ``` -### Bước 3c: Các loại Content JSON khác +### Bước 3c: Composite Lesson (Nhiều thành phần) + +```javascript +// Lesson có nhiều components: story, game, results, leaderboard +const compositeLesson = await Lesson.create({ + chapter_id: chapter1.id, + lesson_number: 5, + lesson_title: 'Bài học tổng hợp: Đếm số và so sánh', + lesson_type: 'json_content', + lesson_description: 'Bài học tích hợp nhiều hoạt động: xem câu chuyện, chơi game, xem kết quả', + content_json: { + type: 'composite_lesson', // Type đặc biệt cho multi-component + version: '2.0', + layout: 'vertical', // vertical, horizontal, tabs + components: [ + // Component 1: Story Game (Câu chuyện tương tác) + { + id: 'story-1', + type: 'story_game', + order: 1, + title: 'Câu chuyện: Gấu đếm quả táo', + config: { + skippable: true, + auto_play: false, + show_subtitles: true + }, + content: { + story_id: 'bear-counting-story', + scenes: [ + { + scene_id: 1, + background: 'https://cdn.senaai.tech/scenes/forest.jpg', + character: 'bear', + dialogue: 'Chào các em! Hôm nay chú gấu sẽ dạy các em đếm táo.', + audio: 'https://cdn.senaai.tech/audio/scene-1.mp3', + duration: 5 + }, + { + scene_id: 2, + background: 'https://cdn.senaai.tech/scenes/apple-tree.jpg', + character: 'bear', + dialogue: 'Nhìn kìa! Có bao nhiêu quả táo trên cây?', + interactive: true, + question: { + type: 'counting', + correct_answer: 5, + hint: 'Đếm từ trái sang phải nhé!' + } + }, + { + scene_id: 3, + background: 'https://cdn.senaai.tech/scenes/celebration.jpg', + dialogue: 'Chính xác! Có 5 quả táo. Giỏi lắm!', + reward_stars: 3 + } + ] + } + }, + + // Component 2: Main Game (Game chính) + { + id: 'game-1', + type: 'game', + order: 2, + title: 'Trò chơi: Thử thách đếm số', + required: true, // Bắt buộc phải hoàn thành + unlock_after: 'story-1', // Mở khóa sau khi xem story + config: { + game_type: 'counting_quiz', // Tham chiếu tới Game.type + difficulty: 'medium', + time_limit: 300, + max_attempts: 3, + show_hints: true, + save_progress: true + }, + content: { + questions: [ + { + id: 1, + question: 'Đếm số con vịt', + image: 'https://cdn.senaai.tech/images/ducks-7.png', + correct_answer: 7, + options: [5, 6, 7, 8], + points: 10, + time_limit: 30 + }, + { + id: 2, + question: 'Đếm số bông hoa', + image: 'https://cdn.senaai.tech/images/flowers-9.png', + correct_answer: 9, + options: [7, 8, 9, 10], + points: 10, + time_limit: 30 + }, + { + id: 3, + question: 'So sánh: Bên nào nhiều hơn?', + images: [ + 'https://cdn.senaai.tech/images/group-a.png', + 'https://cdn.senaai.tech/images/group-b.png' + ], + correct_answer: 'left', + options: ['left', 'right', 'equal'], + points: 15, + time_limit: 45 + } + ], + pass_score: 70, + perfect_score: 100 + } + }, + + // Component 3: Results Board (Bảng kết quả) + { + id: 'results-1', + type: 'results_board', + order: 3, + title: 'Kết quả của bạn', + unlock_after: 'game-1', + config: { + show_comparison: true, // So sánh với lần trước + show_statistics: true, // Thống kê chi tiết + show_recommendations: true // Gợi ý học tiếp + }, + content: { + display_fields: [ + { + field: 'score', + label: 'Điểm số', + format: 'number', + show_progress_bar: true + }, + { + field: 'accuracy', + label: 'Độ chính xác', + format: 'percentage', + color_coded: true // Đỏ/Vàng/Xanh theo % + }, + { + field: 'time_spent', + label: 'Thời gian', + format: 'duration', + unit: 'seconds' + }, + { + field: 'stars_earned', + label: 'Số sao đạt được', + format: 'stars', + max: 5 + }, + { + field: 'correct_answers', + label: 'Câu đúng/Tổng số', + format: 'fraction' + } + ], + achievements: [ + { + id: 'perfect_score', + name: 'Hoàn hảo', + condition: 'score === 100', + icon: 'trophy', + unlocked: false + }, + { + id: 'speed_demon', + name: 'Thần tốc', + condition: 'time_spent < 120', + icon: 'lightning', + unlocked: false + } + ], + recommendations: { + next_lesson_id: 'uuid-next-lesson', + practice_areas: ['counting_10_20', 'comparison'], + difficulty_adjustment: 'increase' // increase, maintain, decrease + } + } + }, + + // Component 4: Leaderboard (Bảng xếp hạng) + { + id: 'leaderboard-1', + type: 'leaderboard', + order: 4, + title: 'Bảng xếp hạng', + config: { + scope: 'class', // class, school, global + time_range: 'week', // day, week, month, all_time + max_entries: 50, + show_user_rank: true, + show_avatars: true, + realtime_updates: true + }, + content: { + ranking_criteria: [ + { + field: 'total_score', + weight: 0.5, + label: 'Tổng điểm' + }, + { + field: 'completion_time', + weight: 0.3, + label: 'Thời gian hoàn thành', + sort: 'asc' // Thời gian ngắn = tốt hơn + }, + { + field: 'accuracy', + weight: 0.2, + label: 'Độ chính xác' + } + ], + display_columns: [ + { field: 'rank', label: '#', width: '10%' }, + { field: 'avatar', label: '', width: '10%' }, + { field: 'name', label: 'Tên', width: '30%' }, + { field: 'score', label: 'Điểm', width: '15%' }, + { field: 'time', label: 'Thời gian', width: '15%' }, + { field: 'accuracy', label: 'Độ chính xác', width: '20%' } + ], + filters: [ + { field: 'class_id', label: 'Lớp học' }, + { field: 'date_range', label: 'Thời gian' } + ], + badges: [ + { rank: 1, icon: 'gold-medal', color: '#FFD700' }, + { rank: 2, icon: 'silver-medal', color: '#C0C0C0' }, + { rank: 3, icon: 'bronze-medal', color: '#CD7F32' } + ] + } + } + ], + + // Global config cho toàn bộ lesson + global_config: { + enable_navigation: true, + allow_skip: false, // Phải làm theo thứ tự + save_progress: true, + allow_replay: true, + completion_criteria: { + required_components: ['game-1'], // Chỉ cần hoàn thành game + min_score: 60, + require_all: false + } + }, + + // Theme/Styling + theme: { + primary_color: '#4CAF50', + background: 'https://cdn.senaai.tech/backgrounds/forest-theme.jpg', + font_family: 'Quicksand', + sound_enabled: true, + background_music: 'https://cdn.senaai.tech/music/cheerful-learning.mp3' + } + }, + duration_minutes: 25, + is_published: true, + is_free: false, + display_order: 5 +}); +``` + +### Bước 3d: Drag-and-Drop Lesson Builder + +```javascript +// Component Builder - Để giáo viên tự tạo lesson bằng kéo thả +const dragDropLesson = await Lesson.create({ + chapter_id: chapter1.id, + lesson_number: 6, + lesson_title: 'Bài học tùy chỉnh', + lesson_type: 'json_content', + content_json: { + type: 'custom_builder', + version: '1.0', + builder_config: { + editable: true, // Giáo viên có thể chỉnh sửa + allow_reorder: true, // Cho phép sắp xếp lại + component_library: [ // Thư viện components có sẵn + 'story_game', + 'game', + 'quiz', + 'video', + 'reading', + 'results_board', + 'leaderboard', + 'discussion', + 'assignment' + ] + }, + components: [ + // Giáo viên kéo thả components vào đây + { + id: 'comp-1', + type: 'video', + order: 1, + content: { + url: 'https://youtube.com/watch?v=...', + title: 'Video giới thiệu' + } + }, + { + id: 'comp-2', + type: 'quiz', + order: 2, + content: { + questions: [/* ... */] + } + } + // ... thêm components khác + ] + }, + duration_minutes: 30, + is_published: false // Draft +}); +``` + +### Bước 3e: Các loại Content JSON khác ```javascript // Quiz trắc nghiệm @@ -455,26 +773,66 @@ GET /api/chapters/:chapter_id/lessons GET /api/lessons/:lesson_id → Trả về chi tiết Lesson -// 5a. Nếu lesson_type = 'json_content' -const lessonType = lesson.content_json.type; // vd: 'counting_quiz' +// 5. Render lesson theo type +if (lesson.lesson_type === 'json_content') { + const lessonType = lesson.content_json.type; + + // 5a. Single Component Lesson + if (lessonType !== 'composite_lesson') { + // Tìm game engine phù hợp + const game = await fetch(`/api/games?type=${lessonType}`); + + // Render game với content + + } + + // 5b. Composite Lesson (Multi Components) + else if (lessonType === 'composite_lesson') { + const components = lesson.content_json.components; + + // Render từng component theo order + components.sort((a, b) => a.order - b.order).map(comp => { + switch(comp.type) { + case 'story_game': + return ; + + case 'game': + // Tìm game engine theo comp.config.game_type + const game = await fetch(`/api/games?type=${comp.config.game_type}`); + return ; + + case 'results_board': + return ; + + case 'leaderboard': + return ; + + default: + return null; + } + }); + } +} -// Tìm game engine phù hợp -GET /api/games?type=counting_quiz -→ Trả về Game có type khớp - -// Load game và truyền content vào -