Files
sena_db_api_layer/controllers/chapterController.js
2026-01-19 09:33:35 +07:00

336 lines
8.3 KiB
JavaScript

const { Chapter, Subject, Lesson } = require('../models');
const { cacheUtils } = require('../config/redis');
/**
* Chapter Controller - Quản lý chương học
*/
class ChapterController {
/**
* Get all chapters with pagination
*/
async getAllChapters(req, res, next) {
try {
const { page = 1, limit = 20, subject_id, is_published } = req.query;
const offset = (page - 1) * limit;
const cacheKey = `chapters:list:${page}:${limit}:${subject_id || 'all'}:${is_published || 'all'}`;
const cached = await cacheUtils.get(cacheKey);
if (cached) {
return res.json({
success: true,
data: cached,
cached: true,
});
}
const where = {};
if (subject_id) where.subject_id = subject_id;
if (is_published !== undefined) where.is_published = is_published === 'true';
const { count, rows } = await Chapter.findAndCountAll({
where,
include: [
{
model: Subject,
as: 'subject',
attributes: ['id', 'subject_name', 'subject_code'],
},
],
limit: parseInt(limit),
offset: parseInt(offset),
order: [['display_order', 'ASC'], ['chapter_number', 'ASC']],
});
const result = {
chapters: rows,
pagination: {
total: count,
page: parseInt(page),
limit: parseInt(limit),
totalPages: Math.ceil(count / limit),
},
};
await cacheUtils.set(cacheKey, result, 1800);
res.json({
success: true,
data: result,
cached: false,
});
} catch (error) {
next(error);
}
}
/**
* Get chapter by ID
*/
async getChapterById(req, res, next) {
try {
const { id } = req.params;
const { include_lessons } = req.query;
const cacheKey = `chapter:${id}:${include_lessons || 'no'}`;
const cached = await cacheUtils.get(cacheKey);
if (cached) {
return res.json({
success: true,
data: cached,
cached: true,
});
}
const includeOptions = [
{
model: Subject,
as: 'subject',
attributes: ['id', 'subject_name', 'subject_code', 'description'],
},
];
if (include_lessons === 'true') {
includeOptions.push({
model: Lesson,
as: 'lessons',
where: { is_published: true },
required: false,
order: [['display_order', 'ASC']],
});
}
const chapter = await Chapter.findByPk(id, {
include: includeOptions,
});
if (!chapter) {
return res.status(404).json({
success: false,
message: 'Chapter not found',
});
}
await cacheUtils.set(cacheKey, chapter, 3600);
res.json({
success: true,
data: chapter,
cached: false,
});
} catch (error) {
next(error);
}
}
/**
* Create new chapter
*/
async createChapter(req, res, next) {
try {
const {
subject_id,
chapter_number,
chapter_title,
chapter_description,
duration_minutes,
is_published,
display_order,
} = req.body;
// Validate required fields
if (!subject_id || !chapter_number || !chapter_title) {
return res.status(400).json({
success: false,
message: 'subject_id, chapter_number, and chapter_title are required',
});
}
// Check if subject exists
const subject = await Subject.findByPk(subject_id);
if (!subject) {
return res.status(404).json({
success: false,
message: 'Subject not found',
});
}
const chapter = await Chapter.create({
subject_id,
chapter_number,
chapter_title,
chapter_description,
duration_minutes,
is_published: is_published !== undefined ? is_published : false,
display_order: display_order || 0,
});
// Clear cache
await cacheUtils.deletePattern('chapters:*');
res.status(201).json({
success: true,
data: chapter,
message: 'Chapter created successfully',
});
} catch (error) {
next(error);
}
}
/**
* Update chapter
*/
async updateChapter(req, res, next) {
try {
const { id } = req.params;
const {
chapter_number,
chapter_title,
chapter_description,
duration_minutes,
is_published,
display_order,
} = req.body;
const chapter = await Chapter.findByPk(id);
if (!chapter) {
return res.status(404).json({
success: false,
message: 'Chapter not found',
});
}
await chapter.update({
...(chapter_number !== undefined && { chapter_number }),
...(chapter_title !== undefined && { chapter_title }),
...(chapter_description !== undefined && { chapter_description }),
...(duration_minutes !== undefined && { duration_minutes }),
...(is_published !== undefined && { is_published }),
...(display_order !== undefined && { display_order }),
});
// Clear cache
await cacheUtils.deletePattern('chapters:*');
await cacheUtils.deletePattern(`chapter:${id}*`);
res.json({
success: true,
data: chapter,
message: 'Chapter updated successfully',
});
} catch (error) {
next(error);
}
}
/**
* Delete chapter
*/
async deleteChapter(req, res, next) {
try {
const { id } = req.params;
const chapter = await Chapter.findByPk(id);
if (!chapter) {
return res.status(404).json({
success: false,
message: 'Chapter not found',
});
}
// Check if chapter has lessons
const lessonsCount = await Lesson.count({ where: { chapter_id: id } });
if (lessonsCount > 0) {
return res.status(400).json({
success: false,
message: `Cannot delete chapter. It has ${lessonsCount} lesson(s). Delete lessons first.`,
});
}
await chapter.destroy();
// Clear cache
await cacheUtils.deletePattern('chapters:*');
await cacheUtils.deletePattern(`chapter:${id}*`);
res.json({
success: true,
message: 'Chapter deleted successfully',
});
} catch (error) {
next(error);
}
}
/**
* Get lessons in a chapter
*/
async getLessonsByChapter(req, res, next) {
try {
const { id } = req.params;
const { page = 1, limit = 20, is_published } = req.query;
const offset = (page - 1) * limit;
const cacheKey = `chapter:${id}:lessons:${page}:${limit}:${is_published || 'all'}`;
const cached = await cacheUtils.get(cacheKey);
if (cached) {
return res.json({
success: true,
data: cached,
cached: true,
});
}
const chapter = await Chapter.findByPk(id);
if (!chapter) {
return res.status(404).json({
success: false,
message: 'Chapter not found',
});
}
const where = { chapter_id: id };
if (is_published !== undefined) where.is_published = is_published === 'true';
const { count, rows } = await Lesson.findAndCountAll({
where,
limit: parseInt(limit),
offset: parseInt(offset),
order: [['display_order', 'ASC'], ['lesson_number', 'ASC']],
});
const result = {
chapter: {
id: chapter.id,
chapter_title: chapter.chapter_title,
chapter_number: chapter.chapter_number,
},
lessons: rows,
pagination: {
total: count,
page: parseInt(page),
limit: parseInt(limit),
totalPages: Math.ceil(count / limit),
},
};
await cacheUtils.set(cacheKey, result, 1800);
res.json({
success: true,
data: result,
cached: false,
});
} catch (error) {
next(error);
}
}
}
module.exports = new ChapterController();