This commit is contained in:
silverpro89
2026-01-26 20:23:08 +07:00
parent 53d97ba5db
commit 2c7b4675a7
49 changed files with 12668 additions and 1 deletions

View File

@@ -0,0 +1,858 @@
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: "<subject_uuid>",
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: "<chapter_uuid>",
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: "<chapter_uuid>",
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: "<chapter_uuid>",
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: "<uuid>", 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
});
}
};