const { Lesson, Chapter, Subject, Vocab, Grammar, sequelize } = require('../models'); const { Op } = require('sequelize'); /** * CREATE: Add new lesson */ exports.createLesson = async (req, res) => { const transaction = await sequelize.transaction(); try { const { chapter_id, lesson_number, lesson_title, lesson_type = 'json_content', lesson_description, content_json, content_url, content_type, lesson_content_type, duration_minutes, is_published = false, is_free = false, display_order = 0, thumbnail_url } = req.body; // Validate required fields if (!chapter_id || !lesson_number || !lesson_title) { await transaction.rollback(); return res.status(400).json({ success: false, message: 'Missing required fields: chapter_id, lesson_number, lesson_title' }); } // Validate content based on lesson_type if (lesson_type === 'json_content' && !content_json) { await transaction.rollback(); return res.status(400).json({ success: false, message: 'content_json is required when lesson_type is json_content' }); } if (lesson_type === 'url_content' && !content_url) { await transaction.rollback(); return res.status(400).json({ success: false, message: 'content_url is required when lesson_type is url_content' }); } // Check if chapter exists const chapter = await Chapter.findByPk(chapter_id); if (!chapter) { await transaction.rollback(); return res.status(404).json({ success: false, message: 'Chapter not found' }); } // Create lesson const lesson = await Lesson.create({ chapter_id, lesson_number, lesson_title, lesson_type, lesson_description, content_json, content_url, content_type, lesson_content_type, duration_minutes, is_published, is_free, display_order, thumbnail_url }, { transaction }); await transaction.commit(); res.status(201).json({ success: true, message: 'Lesson created successfully', data: lesson }); } catch (error) { await transaction.rollback(); console.error('Error creating lesson:', error); res.status(500).json({ success: false, message: 'Failed to create lesson', error: error.message }); } }; /** * READ: Get all lessons with pagination and filters */ exports.getAllLessons = async (req, res) => { try { const { page = 1, limit = 20, chapter_id, lesson_content_type, lesson_type, is_published, is_free, search } = req.query; const offset = (parseInt(page) - 1) * parseInt(limit); const where = {}; if (chapter_id) where.chapter_id = chapter_id; if (lesson_content_type) where.lesson_content_type = lesson_content_type; if (lesson_type) where.lesson_type = lesson_type; if (is_published !== undefined) where.is_published = is_published === 'true'; if (is_free !== undefined) where.is_free = is_free === 'true'; if (search) { where[Op.or] = [ { lesson_title: { [Op.like]: `%${search}%` } }, { lesson_description: { [Op.like]: `%${search}%` } } ]; } const { count, rows } = await Lesson.findAndCountAll({ where, include: [ { model: Chapter, as: 'chapter', attributes: ['id', 'chapter_title', 'chapter_number'], include: [ { model: Subject, as: 'subject', attributes: ['id', 'subject_name', 'subject_code'] } ] } ], limit: parseInt(limit), offset, order: [['display_order', 'ASC'], ['lesson_number', 'ASC']] }); res.json({ success: true, data: rows, pagination: { total: count, page: parseInt(page), limit: parseInt(limit), totalPages: Math.ceil(count / parseInt(limit)) } }); } catch (error) { console.error('Error fetching lessons:', error); res.status(500).json({ success: false, message: 'Failed to fetch lessons', error: error.message }); } }; /** * READ: Get lesson by ID */ exports.getLessonById = async (req, res) => { try { const { id } = req.params; const lesson = await Lesson.findByPk(id, { include: [ { model: Chapter, as: 'chapter', include: [ { model: Subject, as: 'subject' } ] } ] }); if (!lesson) { return res.status(404).json({ success: false, message: 'Lesson not found' }); } res.json({ success: true, data: lesson }); } catch (error) { console.error('Error fetching lesson:', error); res.status(500).json({ success: false, message: 'Failed to fetch lesson', error: error.message }); } }; /** * UPDATE: Update lesson */ exports.updateLesson = async (req, res) => { const transaction = await sequelize.transaction(); try { const { id } = req.params; const updateData = req.body; const lesson = await Lesson.findByPk(id); if (!lesson) { await transaction.rollback(); return res.status(404).json({ success: false, message: 'Lesson not found' }); } await lesson.update(updateData, { transaction }); await transaction.commit(); res.json({ success: true, message: 'Lesson updated successfully', data: lesson }); } catch (error) { await transaction.rollback(); console.error('Error updating lesson:', error); res.status(500).json({ success: false, message: 'Failed to update lesson', error: error.message }); } }; /** * DELETE: Delete lesson */ exports.deleteLesson = async (req, res) => { try { const { id } = req.params; const lesson = await Lesson.findByPk(id); if (!lesson) { return res.status(404).json({ success: false, message: 'Lesson not found' }); } await lesson.destroy(); res.json({ success: true, message: 'Lesson deleted successfully' }); } catch (error) { console.error('Error deleting lesson:', error); res.status(500).json({ success: false, message: 'Failed to delete lesson', error: error.message }); } }; /** * READ: Get lessons by chapter */ exports.getLessonsByChapter = async (req, res) => { try { const { chapter_id } = req.params; const lessons = await Lesson.findAll({ where: { chapter_id }, order: [['display_order', 'ASC'], ['lesson_number', 'ASC']] }); res.json({ success: true, data: lessons, count: lessons.length }); } catch (error) { console.error('Error fetching lessons by chapter:', error); res.status(500).json({ success: false, message: 'Failed to fetch lessons by chapter', error: error.message }); } }; /** * GET GUIDE: Comprehensive guide for AI to create learning content */ exports.getLearningContentGuide = async (req, res) => { try { const guide = { guide_version: "1.0.0", last_updated: new Date().toISOString(), hierarchy: { description: "Learning content hierarchy: Subject → Chapter → Lesson", structure: { subject: "Top-level course/curriculum (e.g., 'English Grade 1')", chapter: "Main topic within subject (e.g., 'Unit 1: My Family')", lesson: "Individual learning activity (e.g., 'Lesson 1: Family Vocabulary')" } }, subject_structure: { description: "Subject represents a complete course or curriculum", required_fields: { subject_code: { type: "string(20)", example: "ENG-G1", description: "Unique code for subject" }, subject_name: { type: "string(100)", example: "English Grade 1", description: "Subject name" } }, optional_fields: { subject_name_en: "English name", description: "Subject description", is_active: "Active status (default: true)", is_premium: "Premium content flag", is_training: "Training content flag", is_public: "Public self-learning flag" } }, chapter_structure: { description: "Chapter represents a major topic within a subject", required_fields: { subject_id: { type: "UUID", example: "550e8400-e29b-41d4-a716-446655440000", description: "Parent subject ID" }, chapter_number: { type: "integer", example: 1, description: "Sequential chapter number" }, chapter_title: { type: "string(200)", example: "Unit 1: My Family", description: "Chapter title" } }, optional_fields: { chapter_description: "Chapter description", duration_minutes: "Estimated duration", is_published: "Published status (default: false)", display_order: "Custom display order" } }, lesson_structure: { description: "Lesson represents individual learning activity", required_fields: { chapter_id: { type: "UUID", example: "550e8400-e29b-41d4-a716-446655440000", description: "Parent chapter ID" }, lesson_number: { type: "integer", example: 1, description: "Sequential lesson number" }, lesson_title: { type: "string(200)", example: "Family Vocabulary", description: "Lesson title" }, lesson_type: { type: "enum", options: ["json_content", "url_content"], default: "json_content", description: "Content type: JSON or URL" } }, optional_fields: { lesson_description: "Lesson description", lesson_content_type: "Content category: vocabulary, grammar, phonics, review, mixed", content_json: "JSON content structure (see lesson_content_types below)", content_url: "URL for external content", content_type: "URL content type: video, audio, pdf, etc.", duration_minutes: "Estimated duration", is_published: "Published status", is_free: "Free trial access", display_order: "Custom display order", thumbnail_url: "Lesson thumbnail" } }, lesson_content_types: { vocabulary_lesson: { description: "Vocabulary learning lesson with word list and exercises. System will lookup words in Vocab table when needed.", structure: { type: "vocabulary", words: { type: "array of strings OR array of objects", description: "List of vocabulary words. System will search in Vocab table by base_word. Can be simple array of words or detailed objects.", example_simple: ["mother", "father", "sister", "brother"], example_detailed: [ { word: "mother", translation: "mẹ", image: "https://cdn.sena.tech/vocab/mother.jpg", audio: "https://cdn.sena.tech/audio/mother.mp3", phonetic: "/ˈmʌð.ər/" } ], note: "Use simple array for words already in Vocab table. Use detailed objects for new/custom vocabulary." }, exercises: { type: "array of objects", description: "Practice exercises", example: [ { type: "match", question: "Match the words with pictures", items: [ { word: "mother", image: "..." }, { word: "father", image: "..." } ] }, { type: "fill_blank", question: "This is my ___", answer: "mother", options: ["mother", "father", "sister"] } ] } }, example: { type: "vocabulary", words: ["mother", "father", "sister", "brother"], exercises: [ { type: "match", question: "Match family members", items: [ { word: "mother", image: "https://cdn.sena.tech/img/mother.jpg" }, { word: "father", image: "https://cdn.sena.tech/img/father.jpg" } ] } ], note: "System will lookup these words in Vocab table when rendering lesson" } }, grammar_lesson: { description: "Grammar lesson with sentences and examples. System will lookup grammar patterns when needed.", structure: { type: "grammar", grammar_points: { type: "array of strings OR array of objects", description: "Grammar points to teach. Can be grammar names or sentence patterns. System will search in Grammar table.", example_simple: ["Present Simple", "Present Continuous"], example_sentences: ["I eat an apple", "She eats an apple", "They are eating"], note: "Use grammar names (e.g., 'Present Simple') or example sentences. System will find matching patterns in Grammar table." }, sentences: { type: "array of strings OR array of objects", description: "Example sentences for this lesson", example: ["I eat an apple", "She drinks water", "He plays football"] }, examples: { type: "array of objects", description: "Example sentences", example: [ { sentence: "I eat an apple.", translation: "Tôi ăn một quả táo.", audio: "https://cdn.sena.tech/audio/example1.mp3" } ] }, exercises: { type: "array of objects", description: "Practice exercises", example: [ { type: "fill_blank", question: "She ___ (eat) an apple every day.", answer: "eats", options: ["eat", "eats", "eating"] }, { type: "arrange_words", question: "Arrange: apple / eat / I / an", answer: "I eat an apple" } ] } }, example: { type: "grammar", grammar_points: ["Present Simple"], sentences: [ "I eat an apple.", "She eats an apple.", "They eat apples." ], exercises: [ { type: "fill_blank", question: "She ___ an apple.", answer: "eats", options: ["eat", "eats", "eating"] } ] } }, phonics_lesson: { description: "Phonics/pronunciation lesson with IPA and sound practice", structure: { type: "phonics", phonics_rules: { type: "array of objects", description: "Phonics rules with IPA and example words", example: [ { ipa: "/æ/", sound_name: "short a", description: "As in 'cat', 'hat'", audio: "https://cdn.sena.tech/phonics/ae.mp3", words: [ { word: "cat", phonetic: "/kæt/", audio: "..." }, { word: "hat", phonetic: "/hæt/", audio: "..." } ] } ] }, exercises: { type: "array of objects", description: "Pronunciation practice", example: [ { type: "listen_repeat", question: "Listen and repeat", audio: "https://cdn.sena.tech/audio/cat.mp3", word: "cat" }, { type: "identify_sound", question: "Which word has the /æ/ sound?", options: ["cat", "cut", "cot"], answer: "cat" } ] } }, example: { type: "phonics", phonics_rules: [ { ipa: "/æ/", sound_name: "short a", audio: "https://cdn.sena.tech/phonics/ae.mp3", words: [ { word: "cat", phonetic: "/kæt/" }, { word: "hat", phonetic: "/hæt/" } ] } ], exercises: [ { type: "listen_repeat", audio: "https://cdn.sena.tech/audio/cat.mp3", word: "cat" } ] } }, review_lesson: { description: "Review lesson combining vocabulary, grammar, and phonics", structure: { type: "review", sections: { type: "array of objects", description: "Array containing vocabulary, grammar, and/or phonics sections", example: [ { section_type: "vocabulary", title: "Family Vocabulary Review", vocabulary_ids: [], exercises: [] }, { section_type: "grammar", title: "Present Simple Review", grammar_ids: [], exercises: [] }, { section_type: "phonics", title: "Short A Sound Review", phonics_rules: [], exercises: [] } ] }, overall_exercises: { type: "array of objects", description: "Mixed exercises covering all sections", example: [ { type: "mixed_quiz", questions: [ { type: "vocabulary", question: "...", answer: "..." }, { type: "grammar", question: "...", answer: "..." } ] } ] } }, example: { type: "review", sections: [ { section_type: "vocabulary", title: "Family Words", words: ["mother", "father", "sister", "brother"] }, { section_type: "grammar", title: "Present Simple", grammar_points: ["Present Simple"], sentences: ["I eat an apple", "She eats an apple"] }, { section_type: "phonics", title: "Short A Sound", phonics_rules: [ { ipa: "/æ/", words: ["cat", "hat", "bat"] } ] } ], overall_exercises: [ { type: "mixed_quiz", questions: [ { type: "vocabulary", question: "What is 'mẹ' in English?", answer: "mother" }, { type: "grammar", question: "She ___ an apple", answer: "eats" } ] } ], note: "System will lookup words and grammar patterns from database when rendering" } } }, exercise_types: { description: "Common exercise types across all lesson types", types: { match: "Match items (words to images, words to translations)", fill_blank: "Fill in the blank", multiple_choice: "Multiple choice question", arrange_words: "Arrange words to form sentence", listen_repeat: "Listen and repeat (for phonics)", identify_sound: "Identify the correct sound/word", true_false: "True or false question", mixed_quiz: "Mixed questions from different types" } }, api_workflow: { description: "Step-by-step workflow to create learning content. No need to create Vocab/Grammar entries first - just use word lists and sentences directly.", steps: [ { step: 1, action: "Create Subject", endpoint: "POST /api/subjects", example: { subject_code: "ENG-G1", subject_name: "English Grade 1" } }, { step: 2, action: "Create Chapter", endpoint: "POST /api/chapters", example: { subject_id: "", chapter_number: 1, chapter_title: "Unit 1: My Family" } }, { step: 3, action: "Create Vocabulary Lesson (using word list)", endpoint: "POST /api/learning-content/lessons", example: { chapter_id: "", lesson_number: 1, lesson_title: "Family Vocabulary", lesson_type: "json_content", lesson_content_type: "vocabulary", content_json: { type: "vocabulary", words: ["mother", "father", "sister", "brother"], exercises: [] } }, note: "System will search for these words in Vocab table when rendering lesson" }, { step: 4, action: "Create Grammar Lesson (using sentences)", endpoint: "POST /api/learning-content/lessons", example: { chapter_id: "", lesson_number: 2, lesson_title: "Present Simple", lesson_type: "json_content", lesson_content_type: "grammar", content_json: { type: "grammar", grammar_points: ["Present Simple"], sentences: ["I eat an apple", "She eats an apple"], exercises: [] } }, note: "System will find matching grammar patterns from Grammar table" }, { step: 5, action: "Create Review Lesson", endpoint: "POST /api/learning-content/lessons", example: { chapter_id: "", lesson_number: 3, lesson_title: "Unit Review", lesson_type: "json_content", lesson_content_type: "review", content_json: { type: "review", sections: [ { section_type: "vocabulary", words: ["mother", "father"] }, { section_type: "grammar", sentences: ["I eat an apple"] } ], overall_exercises: [] } } } ] }, validation_checklist: [ "✓ Subject, Chapter, Lesson hierarchy is maintained", "✓ lesson_type matches content (json_content has content_json, url_content has content_url)", "✓ lesson_content_type is set correctly (vocabulary, grammar, phonics, review)", "✓ content_json structure matches lesson_content_type", "✓ For vocabulary lessons: words array is provided (system will lookup in Vocab table)", "✓ For grammar lessons: sentences or grammar_points are provided (system will lookup in Grammar table)", "✓ Exercise types are valid", "✓ URLs are accessible (audio, images, videos)", "✓ Lesson numbers are sequential within chapter" ], common_mistakes: [ { mistake: "Missing parent references", example: { lesson_title: "Vocab" }, fix: { chapter_id: "", lesson_title: "Vocab" }, explanation: "Always provide chapter_id for lessons" }, { mistake: "Wrong content type", example: { lesson_type: "json_content", content_url: "..." }, fix: { lesson_type: "url_content", content_url: "..." }, explanation: "lesson_type must match content field" }, { mistake: "Using old vocabulary_ids format", example: { vocabulary_ids: ["uuid1", "uuid2"] }, fix: { words: ["mother", "father", "sister"] }, explanation: "Use words array instead of vocabulary_ids. System will lookup words in Vocab table." }, { mistake: "Using old grammar_ids format", example: { grammar_ids: ["uuid1"] }, fix: { grammar_points: ["Present Simple"], sentences: ["I eat an apple"] }, explanation: "Use grammar_points or sentences instead of grammar_ids. System will find patterns in Grammar table." }, { mistake: "Missing content_json type", example: { content_json: { exercises:[]} }, fix: { content_json: { type: "vocabulary", exercises: [] } }, explanation: "content_json must have type field" } ], ai_tips: { planning: "Plan subject → chapter → lesson hierarchy before creating", word_lists: "Use simple word arrays like ['mother', 'father']. System will automatically lookup in Vocab table when rendering.", sentences: "Use sentence arrays like ['I eat an apple', 'She eats an apple']. System will find matching grammar patterns.", consistency: "Keep lesson_content_type and content_json.type consistent", exercises: "Include diverse exercise types for better engagement", multimedia: "Provide audio and images for better learning experience", no_ids_needed: "No need to create Vocab/Grammar entries first - just use word lists and sentences directly!" } }; res.json({ success: true, data: guide }); } catch (error) { console.error('Error generating learning content guide:', error); res.status(500).json({ success: false, message: 'Failed to generate learning content guide', error: error.message }); } };