const { Subject, Chapter } = require('../models'); const { cacheUtils } = require('../config/redis'); /** * Subject Controller - Quản lý môn học */ class SubjectController { /** * Get all subjects with pagination and caching */ async getAllSubjects(req, res, next) { try { const { page = 1, limit = 50, is_active } = req.query; const offset = (page - 1) * limit; // Generate cache key const cacheKey = `subjects:list:${page}:${limit}:${is_active || 'all'}`; // Try to get from cache first const cached = await cacheUtils.get(cacheKey); if (cached) { return res.json({ success: true, data: cached, cached: true, }); } // Build query conditions const where = {}; if (is_active !== undefined) where.is_active = is_active === 'true'; // Query from database (through ProxySQL) const { count, rows } = await Subject.findAndCountAll({ where, limit: parseInt(limit), offset: parseInt(offset), order: [['subject_code', 'ASC']], }); const result = { subjects: rows, pagination: { total: count, page: parseInt(page), limit: parseInt(limit), totalPages: Math.ceil(count / limit), }, }; // Cache the result await cacheUtils.set(cacheKey, result, 7200); // Cache for 2 hours res.json({ success: true, data: result, cached: false, }); } catch (error) { next(error); } } /** * Get subject by ID with caching */ async getSubjectById(req, res, next) { try { const { id } = req.params; const cacheKey = `subject:${id}`; // Try cache first const cached = await cacheUtils.get(cacheKey); if (cached) { return res.json({ success: true, data: cached, cached: true, }); } // Query from database const subject = await Subject.findByPk(id); if (!subject) { return res.status(404).json({ success: false, message: 'Subject not found', }); } // Cache the result await cacheUtils.set(cacheKey, subject, 7200); // Cache for 2 hours res.json({ success: true, data: subject, cached: false, }); } catch (error) { next(error); } } /** * Get subject by code with caching */ async getSubjectByCode(req, res, next) { try { const { code } = req.params; const cacheKey = `subject:code:${code}`; // Try cache first const cached = await cacheUtils.get(cacheKey); if (cached) { return res.json({ success: true, data: cached, cached: true, }); } // Query from database const subject = await Subject.findOne({ where: { subject_code: code }, }); if (!subject) { return res.status(404).json({ success: false, message: 'Subject not found', }); } // Cache the result await cacheUtils.set(cacheKey, subject, 7200); // Cache for 2 hours res.json({ success: true, data: subject, cached: false, }); } catch (error) { next(error); } } /** * Create new subject */ async createSubject(req, res, next) { try { const subjectData = req.body; const subject = await Subject.create(subjectData); await cacheUtils.deletePattern('subjects:list:*'); res.status(201).json({ success: true, message: 'Subject created successfully', data: subject, }); } catch (error) { next(error); } } /** * Update subject */ async updateSubject(req, res, next) { try { const { id } = req.params; const updates = req.body; const subject = await Subject.findByPk(id); if (!subject) { return res.status(404).json({ success: false, message: 'Subject not found', }); } await subject.update(updates); await cacheUtils.delete(`subject:${id}`); await cacheUtils.deletePattern('subjects:list:*'); res.json({ success: true, message: 'Subject updated successfully', data: subject, }); } catch (error) { next(error); } } /** * Delete subject */ async deleteSubject(req, res, next) { try { const { id } = req.params; const subject = await Subject.findByPk(id); if (!subject) { return res.status(404).json({ success: false, message: 'Subject not found', }); } await subject.destroy(); await cacheUtils.delete(`subject:${id}`); await cacheUtils.deletePattern('subjects:list:*'); res.json({ success: true, message: 'Subject deleted successfully', }); } catch (error) { next(error); } } /** * Get active subjects (frequently used) */ async getActiveSubjects(req, res, next) { try { const cacheKey = 'subjects:active'; // Try cache first const cached = await cacheUtils.get(cacheKey); if (cached) { return res.json({ success: true, data: cached, cached: true, }); } // Query from database const subjects = await Subject.findAll({ where: { is_active: true }, order: [['subject_code', 'ASC']], }); // Cache for 4 hours (this data changes infrequently) await cacheUtils.set(cacheKey, subjects, 14400); res.json({ success: true, data: subjects, cached: false, }); } catch (error) { next(error); } } /** * Get subject datatypes */ async getSubjectDatatypes(req, res, next) { try { const datatypes = Subject.rawAttributes; res.json({ success: true, data: datatypes, }); } catch (error) { next(error); } } /** * Get chapters by subject ID */ async getChaptersBySubject(req, res, next) { try { const { id } = req.params; const { page = 1, limit = 50, is_published } = req.query; const offset = (page - 1) * limit; // Generate cache key const cacheKey = `subject:${id}:chapters:${page}:${limit}:${is_published || 'all'}`; // Try cache first const cached = await cacheUtils.get(cacheKey); if (cached) { return res.json({ success: true, data: cached, cached: true, }); } // Check if subject exists const subject = await Subject.findByPk(id); if (!subject) { return res.status(404).json({ success: false, message: 'Subject not found', }); } // Build query conditions const where = { subject_id: id }; if (is_published !== undefined) where.is_published = is_published === 'true'; // Query chapters const { count, rows } = await Chapter.findAndCountAll({ where, limit: parseInt(limit), offset: parseInt(offset), order: [['display_order', 'ASC'], ['chapter_number', 'ASC']], }); const result = { subject: { id: subject.id, subject_code: subject.subject_code, subject_name: subject.subject_name, }, chapters: rows, pagination: { total: count, page: parseInt(page), limit: parseInt(limit), totalPages: Math.ceil(count / limit), }, }; // Cache the result await cacheUtils.set(cacheKey, result, 3600); // Cache for 1 hour res.json({ success: true, data: result, cached: false, }); } catch (error) { next(error); } } /** * Add chapter to subject (Create chapter within subject context) */ async addChapterToSubject(req, res, next) { try { const { subjectId } = req.params; const chapterData = req.body; // Check if subject exists const subject = await Subject.findByPk(subjectId); if (!subject) { return res.status(404).json({ success: false, message: 'Subject not found', }); } // Validate required fields if (!chapterData.chapter_number || !chapterData.chapter_title) { return res.status(400).json({ success: false, message: 'chapter_number and chapter_title are required', }); } // Create chapter with subject_id const chapter = await Chapter.create({ ...chapterData, subject_id: subjectId, }); // Clear cache await cacheUtils.deletePattern('chapters:*'); await cacheUtils.deletePattern(`subject:${subjectId}:chapters:*`); res.status(201).json({ success: true, message: 'Chapter added to subject successfully', data: chapter, }); } catch (error) { next(error); } } /** * Remove chapter from subject (Delete chapter within subject context) */ async removeChapterFromSubject(req, res, next) { try { const { subjectId, chapterId } = req.params; // Check if subject exists const subject = await Subject.findByPk(subjectId); if (!subject) { return res.status(404).json({ success: false, message: 'Subject not found', }); } // Find chapter const chapter = await Chapter.findOne({ where: { id: chapterId, subject_id: subjectId, }, }); if (!chapter) { return res.status(404).json({ success: false, message: 'Chapter not found in this subject', }); } // Check if chapter has lessons const Lesson = require('../models').Lesson; const lessonsCount = await Lesson.count({ where: { chapter_id: chapterId } }); 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:${chapterId}*`); await cacheUtils.deletePattern(`subject:${subjectId}:chapters:*`); res.json({ success: true, message: 'Chapter removed from subject successfully', }); } catch (error) { next(error); } } } module.exports = new SubjectController();