375 lines
8.6 KiB
JavaScript
375 lines
8.6 KiB
JavaScript
const { Game } = require('../models');
|
|
const { cacheUtils } = require('../config/redis');
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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();
|