const { Game } = require('../models'); const { cacheUtils } = require('../config/redis'); const { sequelize } = require('../config/database'); const { Op, Sequelize } = require('sequelize'); /** * Game Controller - Quản lý trò chơi giáo dục */ class GameController { /** * Get all games with pagination */ async getAllGames(req, res, next) { try { const { page = 1, limit = 20, type, is_active, is_premium, difficulty_level } = req.query; const offset = (page - 1) * limit; const cacheKey = `games:list:${page}:${limit}:${type || 'all'}:${is_active || 'all'}:${is_premium || 'all'}:${difficulty_level || 'all'}`; const cached = await cacheUtils.get(cacheKey); /* if (cached) { return res.json({ success: true, data: cached, cached: true, }); } */ const where = {}; if (type) where.type = type; if (is_active !== undefined) where.is_active = is_active === 'true'; if (is_premium !== undefined) where.is_premium = is_premium === 'true'; if (difficulty_level) where.difficulty_level = difficulty_level; const { count, rows } = await Game.findAndCountAll({ where, limit: parseInt(limit), offset: parseInt(offset), order: [['display_order', 'ASC'], ['rating', 'DESC']], }); const result = { games: 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 game by ID */ async getGameById(req, res, next) { try { const { id } = req.params; const cacheKey = `game:${id}`; const cached = await cacheUtils.get(cacheKey); /* if (cached) { return res.json({ success: true, data: cached, cached: true, }); } */ const game = await Game.findByPk(id); if (!game) { return res.status(404).json({ success: false, message: 'Game not found', }); } await cacheUtils.set(cacheKey, game, 3600); res.json({ success: true, data: game, cached: false, }); } catch (error) { next(error); } } /** * Create new game */ async createGame(req, res, next) { try { const { title, description, url, thumbnail, type, config, is_active, is_premium, min_grade, max_grade, difficulty_level, display_order, } = req.body; // Validate required fields if (!title || !url || !type) { return res.status(400).json({ success: false, message: 'title, url, and type are required', }); } const game = await Game.create({ title, description, url, thumbnail, type, config: config || {}, is_active: is_active !== undefined ? is_active : true, is_premium: is_premium !== undefined ? is_premium : false, min_grade, max_grade, difficulty_level, display_order: display_order || 0, play_count: 0, }); // Clear cache await cacheUtils.deletePattern('games:*'); res.status(201).json({ success: true, data: game, message: 'Game created successfully', }); } catch (error) { next(error); } } /** * Update game */ async updateGame(req, res, next) { try { const { id } = req.params; const { title, description, url, thumbnail, type, config, is_active, is_premium, min_grade, max_grade, difficulty_level, display_order, rating, } = req.body; const game = await Game.findByPk(id); if (!game) { return res.status(404).json({ success: false, message: 'Game not found', }); } await game.update({ ...(title !== undefined && { title }), ...(description !== undefined && { description }), ...(url !== undefined && { url }), ...(thumbnail !== undefined && { thumbnail }), ...(type !== undefined && { type }), ...(config !== undefined && { config }), ...(is_active !== undefined && { is_active }), ...(is_premium !== undefined && { is_premium }), ...(min_grade !== undefined && { min_grade }), ...(max_grade !== undefined && { max_grade }), ...(difficulty_level !== undefined && { difficulty_level }), ...(display_order !== undefined && { display_order }), ...(rating !== undefined && { rating }), }); // Clear cache await cacheUtils.deletePattern('games:*'); await cacheUtils.deletePattern(`game:${id}`); res.json({ success: true, data: game, message: 'Game updated successfully', }); } catch (error) { next(error); } } /** * Delete game */ async deleteGame(req, res, next) { try { const { id } = req.params; const game = await Game.findByPk(id); if (!game) { return res.status(404).json({ success: false, message: 'Game not found', }); } await game.destroy(); // Clear cache await cacheUtils.deletePattern('games:*'); await cacheUtils.deletePattern(`game:${id}`); res.json({ success: true, message: 'Game deleted successfully', }); } catch (error) { next(error); } } /** * Increment play count */ async incrementPlayCount(req, res, next) { try { const { id } = req.params; const game = await Game.findByPk(id); if (!game) { return res.status(404).json({ success: false, message: 'Game not found', }); } await game.increment('play_count'); await game.reload(); // Clear cache await cacheUtils.deletePattern(`game:${id}`); res.json({ success: true, data: { id: game.id, play_count: game.play_count, }, message: 'Play count incremented', }); } catch (error) { next(error); } } /** * Get games by type (for lesson matching) */ async getGamesByType(req, res, next) { try { const { type } = req.params; const { only_active = 'true' } = req.query; const cacheKey = `games:type:${type}:${only_active}`; const cached = await cacheUtils.get(cacheKey); /* if (cached) { return res.json({ success: true, data: cached, cached: true, }); } */ const where = { type }; if (only_active === 'true') where.is_active = true; const games = await Game.findAll({ where, order: [['display_order', 'ASC'], ['rating', 'DESC']], }); await cacheUtils.set(cacheKey, games, 3600); res.json({ success: true, data: games, cached: false, }); } catch (error) { next(error); } } /** * Create or Update game with URL check * Nếu body có id: update game đó * Nếu body không có id: kiểm tra URL có tồn tại trong DB không, chưa có thì lưu mới */ async createGameWithUrlCheck(req, res, next) { try { const { id, title, description, url, thumbnail, type, config, is_active, is_premium, min_grade, max_grade, difficulty_level, display_order, rating, } = req.body; // Validate required fields if (!title || !url || !type) { return res.status(400).json({ success: false, message: 'title, url, and type are required', }); } // Check if URL already exists in DB const existingGame = await Game.findOne({ where: { url } }); if (existingGame) { return res.status(200).json({ success: true, data: null, message: 'URL đã tồn tại, game không được lưu', }); } // Create new game (with id if provided, or auto-generate) const game = await Game.create({ ...(id && { id }), title, description, url, thumbnail, type, config: config || {}, is_active: is_active !== undefined ? is_active : true, is_premium: is_premium !== undefined ? is_premium : false, min_grade, max_grade, difficulty_level, display_order: display_order || 0, play_count: 0, }); // Clear cache await cacheUtils.deletePattern('games:*'); res.status(201).json({ success: true, data: game, message: 'Game created successfully', }); } catch (error) { next(error); } } /** * Get all unique game types */ async getGameTypes(req, res, next) { try { const cacheKey = 'games:types'; const cached = await cacheUtils.get(cacheKey); if (cached) { return res.json({ success: true, data: cached, cached: true, }); } // Get all games with only type field const games = await Game.findAll({ attributes: [[Sequelize.fn('DISTINCT', Sequelize.col('type')), 'type']], where: { type: { [Op.and]: [ { [Op.ne]: null }, { [Op.notIn]: ['', ' '] } ] } }, raw: true, }); // Get distinct types const result = games.filter(g => g.type); await cacheUtils.set(cacheKey, result, 3600); res.json({ success: true, data: result, cached: false, }); } catch (error) { next(error); } } /** * Get game statistics */ async getGameStats(req, res, next) { try { const cacheKey = 'games:stats'; const cached = await cacheUtils.get(cacheKey); if (cached) { return res.json({ success: true, data: cached, cached: true, }); } const [totalGames, activeGames, premiumGames, totalPlays] = await Promise.all([ Game.count(), Game.count({ where: { is_active: true } }), Game.count({ where: { is_premium: true } }), Game.sum('play_count'), ]); const topGames = await Game.findAll({ where: { is_active: true }, order: [['play_count', 'DESC']], limit: 10, attributes: ['id', 'title', 'type', 'play_count', 'rating'], }); const stats = { totalGames, activeGames, premiumGames, totalPlays: totalPlays || 0, topGames, }; await cacheUtils.set(cacheKey, stats, 600); res.json({ success: true, data: stats, cached: false, }); } catch (error) { next(error); } } } module.exports = new GameController();