Files
sena_db_api_layer/controllers/learningContentController.js
silverpro89 2c7b4675a7 update
2026-01-26 20:23:08 +07:00

859 lines
26 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
});
}
};