update
This commit is contained in:
381
controllers/lessonController.js
Normal file
381
controllers/lessonController.js
Normal file
@@ -0,0 +1,381 @@
|
||||
const { Lesson, Chapter, Subject, Game } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
/**
|
||||
* Lesson Controller
|
||||
* Quản lý các bài học trong chương
|
||||
*/
|
||||
class LessonController {
|
||||
/**
|
||||
* Lấy danh sách tất cả bài học
|
||||
*/
|
||||
async getAllLessons(req, res, next) {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 20,
|
||||
is_published,
|
||||
is_free,
|
||||
lesson_type,
|
||||
search
|
||||
} = req.query;
|
||||
|
||||
const offset = (page - 1) * limit;
|
||||
const where = {};
|
||||
|
||||
// Filters
|
||||
if (is_published !== undefined) {
|
||||
where.is_published = is_published === 'true';
|
||||
}
|
||||
if (is_free !== undefined) {
|
||||
where.is_free = is_free === 'true';
|
||||
}
|
||||
if (lesson_type) {
|
||||
where.lesson_type = lesson_type;
|
||||
}
|
||||
if (search) {
|
||||
where.lesson_title = { [Op.like]: `%${search}%` };
|
||||
}
|
||||
|
||||
const { rows: lessons, count } = 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']
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
order: [['display_order', 'ASC'], ['lesson_number', 'ASC']],
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset)
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
lessons,
|
||||
pagination: {
|
||||
total: count,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total_pages: Math.ceil(count / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lấy chi tiết bài học
|
||||
*/
|
||||
async getLessonById(req, res, next) {
|
||||
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: 'Không tìm thấy bài học'
|
||||
});
|
||||
}
|
||||
|
||||
// Nếu là composite lesson, lấy thông tin games cho từng component
|
||||
if (lesson.lesson_type === 'json_content' &&
|
||||
lesson.content_json?.type === 'composite_lesson') {
|
||||
const components = lesson.content_json.components || [];
|
||||
|
||||
// Tìm tất cả game_type trong components
|
||||
const gameTypes = components
|
||||
.filter(c => c.type === 'game' && c.config?.game_type)
|
||||
.map(c => c.config.game_type);
|
||||
|
||||
if (gameTypes.length > 0) {
|
||||
const games = await Game.findAll({
|
||||
where: {
|
||||
type: { [Op.in]: gameTypes },
|
||||
is_active: true
|
||||
},
|
||||
attributes: ['id', 'title', 'type', 'url', 'thumbnail', 'config']
|
||||
});
|
||||
|
||||
// Gắn game info vào lesson
|
||||
lesson.dataValues.available_games = games;
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: lesson
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lấy danh sách bài học của một chương
|
||||
*/
|
||||
async getLessonsByChapter(req, res, next) {
|
||||
try {
|
||||
const { chapter_id } = req.params;
|
||||
const { include_unpublished = false } = req.query;
|
||||
|
||||
const where = { chapter_id };
|
||||
|
||||
if (!include_unpublished || include_unpublished === 'false') {
|
||||
where.is_published = true;
|
||||
}
|
||||
|
||||
const lessons = await Lesson.findAll({
|
||||
where,
|
||||
order: [['display_order', 'ASC'], ['lesson_number', 'ASC']],
|
||||
attributes: {
|
||||
exclude: ['content_json', 'content_url'] // Không trả full content trong list
|
||||
}
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: lessons
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tạo bài học mới
|
||||
*/
|
||||
async createLesson(req, res, next) {
|
||||
try {
|
||||
const lessonData = req.body;
|
||||
|
||||
// Validate chapter exists
|
||||
const chapter = await Chapter.findByPk(lessonData.chapter_id);
|
||||
if (!chapter) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Không tìm thấy chương học'
|
||||
});
|
||||
}
|
||||
|
||||
// Auto increment lesson_number if not provided
|
||||
if (!lessonData.lesson_number) {
|
||||
const maxLesson = await Lesson.findOne({
|
||||
where: { chapter_id: lessonData.chapter_id },
|
||||
order: [['lesson_number', 'DESC']]
|
||||
});
|
||||
lessonData.lesson_number = maxLesson ? maxLesson.lesson_number + 1 : 1;
|
||||
}
|
||||
|
||||
// Auto increment display_order if not provided
|
||||
if (!lessonData.display_order) {
|
||||
const maxOrder = await Lesson.findOne({
|
||||
where: { chapter_id: lessonData.chapter_id },
|
||||
order: [['display_order', 'DESC']]
|
||||
});
|
||||
lessonData.display_order = maxOrder ? maxOrder.display_order + 1 : 1;
|
||||
}
|
||||
|
||||
const lesson = await Lesson.create(lessonData);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: 'Tạo bài học thành công',
|
||||
data: lesson
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cập nhật bài học
|
||||
*/
|
||||
async updateLesson(req, res, next) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const updateData = req.body;
|
||||
|
||||
const lesson = await Lesson.findByPk(id);
|
||||
if (!lesson) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Không tìm thấy bài học'
|
||||
});
|
||||
}
|
||||
|
||||
// Validate chapter if changing
|
||||
if (updateData.chapter_id && updateData.chapter_id !== lesson.chapter_id) {
|
||||
const chapter = await Chapter.findByPk(updateData.chapter_id);
|
||||
if (!chapter) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Không tìm thấy chương học'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await lesson.update(updateData);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Cập nhật bài học thành công',
|
||||
data: lesson
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Xóa bài học
|
||||
*/
|
||||
async deleteLesson(req, res, next) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const lesson = await Lesson.findByPk(id);
|
||||
if (!lesson) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Không tìm thấy bài học'
|
||||
});
|
||||
}
|
||||
|
||||
await lesson.destroy();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Xóa bài học thành công'
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Đánh dấu hoàn thành bài học
|
||||
*/
|
||||
async completeLesson(req, res, next) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const userId = req.user.id; // From auth middleware
|
||||
const { score, time_spent, results_data } = req.body;
|
||||
|
||||
const lesson = await Lesson.findByPk(id);
|
||||
if (!lesson) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Không tìm thấy bài học'
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Save to UserLessonProgress table
|
||||
// This would track completion, score, etc.
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Đã hoàn thành bài học',
|
||||
data: {
|
||||
lesson_id: id,
|
||||
user_id: userId,
|
||||
score,
|
||||
time_spent,
|
||||
completed_at: new Date()
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lấy game phù hợp cho lesson
|
||||
*/
|
||||
async getMatchingGames(req, res, next) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const lesson = await Lesson.findByPk(id);
|
||||
if (!lesson) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Không tìm thấy bài học'
|
||||
});
|
||||
}
|
||||
|
||||
if (lesson.lesson_type !== 'json_content' || !lesson.content_json?.type) {
|
||||
return res.json({
|
||||
success: true,
|
||||
data: [],
|
||||
message: 'Bài học này không sử dụng game engine'
|
||||
});
|
||||
}
|
||||
|
||||
const lessonType = lesson.content_json.type;
|
||||
|
||||
// Handle composite lesson
|
||||
if (lessonType === 'composite_lesson') {
|
||||
const components = lesson.content_json.components || [];
|
||||
const gameTypes = components
|
||||
.filter(c => c.type === 'game' && c.config?.game_type)
|
||||
.map(c => c.config.game_type);
|
||||
|
||||
const games = await Game.findAll({
|
||||
where: {
|
||||
type: { [Op.in]: gameTypes },
|
||||
is_active: true
|
||||
}
|
||||
});
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: games
|
||||
});
|
||||
}
|
||||
|
||||
// Handle single component lesson
|
||||
const games = await Game.findAll({
|
||||
where: {
|
||||
type: lessonType,
|
||||
is_active: true
|
||||
}
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: games
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new LessonController();
|
||||
Reference in New Issue
Block a user