const { Categories, Subject } = require('../models'); const { cacheUtils } = require('../config/redis'); /** * Categories Controller - Quản lý danh mục */ class CategoryController { /** * Get all categories with pagination and caching */ async getAllCategories(req, res, next) { try { const { page = 1, limit = 50, is_active } = req.query; const offset = (page - 1) * limit; // Generate cache key const cacheKey = `categories: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 Categories.findAndCountAll({ where, limit: parseInt(limit), offset: parseInt(offset), order: [['category_code', 'ASC']], }); const result = { categories: 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 category by ID with caching */ async getCategoryById(req, res, next) { try { const { id } = req.params; const cacheKey = `category:${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 category = await Categories.findByPk(id); if (!category) { return res.status(404).json({ success: false, message: 'Category not found', }); } // Cache the result await cacheUtils.set(cacheKey, category, 7200); // Cache for 2 hours res.json({ success: true, data: category, cached: false, }); } catch (error) { next(error); } } /** * Get category by code with caching */ async getCategoryByCode(req, res, next) { try { const { code } = req.params; const cacheKey = `category: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 category = await Categories.findOne({ where: { category_code: code }, }); if (!category) { return res.status(404).json({ success: false, message: 'Category not found', }); } // Cache the result await cacheUtils.set(cacheKey, category, 7200); // Cache for 2 hours res.json({ success: true, data: category, cached: false, }); } catch (error) { next(error); } } /** * Create new category */ async createCategory(req, res, next) { try { const categoryData = req.body; const category = await Categories.create(categoryData); await cacheUtils.deletePattern('categories:list:*'); res.status(201).json({ success: true, message: 'Category created successfully', data: category, }); } catch (error) { next(error); } } /** * Update category */ async updateCategory(req, res, next) { try { const { id } = req.params; const updates = req.body; const category = await Categories.findByPk(id); if (!category) { return res.status(404).json({ success: false, message: 'Category not found', }); } await category.update(updates); await cacheUtils.delete(`category:${id}`); await cacheUtils.deletePattern('categories:list:*'); res.json({ success: true, message: 'Category updated successfully', data: category, }); } catch (error) { next(error); } } /** * Delete category */ async deleteCategory(req, res, next) { try { const { id } = req.params; const category = await Categories.findByPk(id); if (!category) { return res.status(404).json({ success: false, message: 'Category not found', }); } await category.destroy(); await cacheUtils.delete(`category:${id}`); await cacheUtils.deletePattern('categories:list:*'); res.json({ success: true, message: 'Category deleted successfully', }); } catch (error) { next(error); } } /** * Get active categories (frequently used) */ async getActiveCategories(req, res, next) { try { const cacheKey = 'categories: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 categories = await Categories.findAll({ where: { is_active: true }, order: [['category_code', 'ASC']], }); // Cache for 4 hours (this data changes infrequently) await cacheUtils.set(cacheKey, categories, 14400); res.json({ success: true, data: categories, cached: false, }); } catch (error) { next(error); } } /** * Get category datatypes */ async getCategoryDatatypes(req, res, next) { try { const datatypes = Categories.rawAttributes; res.json({ success: true, data: datatypes, }); } catch (error) { next(error); } } /** * Get subjects by category ID */ async getSubjectsByCategory(req, res, next) { try { const { id } = req.params; const { page = 1, limit = 50, is_active } = req.query; const offset = (page - 1) * limit; // Generate cache key const cacheKey = `category:${id}:subjects:${page}:${limit}:${is_active || 'all'}`; // Try cache first const cached = await cacheUtils.get(cacheKey); if (cached) { return res.json({ success: true, data: cached, cached: true, }); } // Check if category exists const category = await Categories.findByPk(id); if (!category) { return res.status(404).json({ success: false, message: 'Category not found', }); } // Build query conditions const where = { category_id: id }; if (is_active !== undefined) where.is_active = is_active === 'true'; // Query subjects const { count, rows } = await Subject.findAndCountAll({ where, limit: parseInt(limit), offset: parseInt(offset), order: [['subject_code', 'ASC']], }); const result = { category: { id: category.id, category_code: category.category_code, category_name: category.category_name, }, subjects: 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 CategoryController();