const { Subject, Chapter } = require('../models'); const { cacheUtils } = require('../config/redis'); const { addDatabaseWriteJob } = require('../config/bullmq'); /** * 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 via BullMQ) */ async createSubject(req, res, next) { try { const subjectData = req.body; // Add to job queue for async processing const job = await addDatabaseWriteJob('create', 'Subject', subjectData); // Note: Cache will be invalidated by worker after successful creation res.status(202).json({ success: true, message: 'Subject creation job queued', jobId: job.id, data: subjectData, }); } catch (error) { next(error); } } /** * Update subject (async via BullMQ) */ async updateSubject(req, res, next) { try { const { id } = req.params; const updates = req.body; // Add to job queue for async processing const job = await addDatabaseWriteJob('update', 'Subject', { id, updates, }); // Note: Cache will be invalidated by worker after successful update res.status(202).json({ success: true, message: 'Subject update job queued', jobId: job.id, }); } catch (error) { next(error); } } /** * Delete subject (async via BullMQ) */ async deleteSubject(req, res, next) { try { const { id } = req.params; // Add to job queue for async processing const job = await addDatabaseWriteJob('delete', 'Subject', { id }); // Note: Cache will be invalidated by worker after successful deletion res.status(202).json({ success: true, message: 'Subject deletion job queued', jobId: job.id, }); } 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); } } } module.exports = new SubjectController();