diff --git a/app.js b/app.js index ba154e7..858a2f0 100644 --- a/app.js +++ b/app.js @@ -32,6 +32,7 @@ const trainingRoutes = require('./routes/trainingRoutes'); const parentTaskRoutes = require('./routes/parentTaskRoutes'); const chapterRoutes = require('./routes/chapterRoutes'); const gameRoutes = require('./routes/gameRoutes'); +const gameTypeRoutes = require('./routes/gameTypeRoutes'); const lessonRoutes = require('./routes/lessonRoutes'); const chapterLessonRoutes = require('./routes/chapterLessonRoutes'); const vocabRoutes = require('./routes/vocabRoutes'); @@ -150,6 +151,7 @@ app.get('/api', (req, res) => { chapters: '/api/chapters', lessons: '/api/lessons', games: '/api/games', + gameTypes: '/api/game-types', vocab: '/api/vocab', }, documentation: '/api-docs', @@ -202,6 +204,7 @@ app.use('/api/parent-tasks', parentTaskRoutes); app.use('/api/chapters', chapterRoutes); app.use('/api/chapters', chapterLessonRoutes); // Nested route: /api/chapters/:id/lessons app.use('/api/games', gameRoutes); +app.use('/api/game-types', gameTypeRoutes); app.use('/api/lessons', lessonRoutes); app.use('/api/vocab', vocabRoutes); app.use('/api/grammar', grammarRoutes); diff --git a/controllers/academicYearController.js b/controllers/academicYearController.js index 8ae7bbe..3336069 100644 --- a/controllers/academicYearController.js +++ b/controllers/academicYearController.js @@ -1,6 +1,6 @@ const { AcademicYear } = require('../models'); const { cacheUtils } = require('../config/redis'); -const { addDatabaseWriteJob } = require('../config/bullmq'); + /** * Academic Year Controller - Quản lý năm học @@ -146,24 +146,23 @@ class AcademicYearController { } /** - * Create new academic year (async via BullMQ) + * Create new academic year */ async createAcademicYear(req, res, next) { try { const academicYearData = req.body; - // Add to job queue for async processing - const job = await addDatabaseWriteJob('create', 'AcademicYear', academicYearData); + // Create academic year directly + const academicYear = await AcademicYear.create(academicYearData); // Invalidate related caches await cacheUtils.deletePattern('academic_years:list:*'); await cacheUtils.delete('academic_year:current'); - res.status(202).json({ + res.status(201).json({ success: true, - message: 'Academic year creation job queued', - jobId: job.id, - data: academicYearData, + message: 'Academic year created successfully', + data: academicYear, }); } catch (error) { next(error); @@ -171,28 +170,34 @@ class AcademicYearController { } /** - * Update academic year (async via BullMQ) + * Update academic year */ async updateAcademicYear(req, res, next) { try { const { id } = req.params; const updates = req.body; - // Add to job queue for async processing - const job = await addDatabaseWriteJob('update', 'AcademicYear', { - id, - updates, - }); + const academicYear = await AcademicYear.findByPk(id); + + if (!academicYear) { + return res.status(404).json({ + success: false, + message: 'Academic year not found', + }); + } + + // Update academic year directly + await academicYear.update(updates); // Invalidate related caches await cacheUtils.delete(`academic_year:${id}`); await cacheUtils.deletePattern('academic_years:list:*'); await cacheUtils.delete('academic_year:current'); - res.status(202).json({ + res.json({ success: true, - message: 'Academic year update job queued', - jobId: job.id, + message: 'Academic year updated successfully', + data: academicYear, }); } catch (error) { next(error); @@ -200,24 +205,32 @@ class AcademicYearController { } /** - * Delete academic year (async via BullMQ) + * Delete academic year */ async deleteAcademicYear(req, res, next) { try { const { id } = req.params; - // Add to job queue for async processing - const job = await addDatabaseWriteJob('delete', 'AcademicYear', { id }); + const academicYear = await AcademicYear.findByPk(id); + + if (!academicYear) { + return res.status(404).json({ + success: false, + message: 'Academic year not found', + }); + } + + // Delete academic year directly + await academicYear.destroy(); // Invalidate related caches await cacheUtils.delete(`academic_year:${id}`); await cacheUtils.deletePattern('academic_years:list:*'); await cacheUtils.delete('academic_year:current'); - res.status(202).json({ + res.json({ success: true, - message: 'Academic year deletion job queued', - jobId: job.id, + message: 'Academic year deleted successfully', }); } catch (error) { next(error); @@ -231,20 +244,31 @@ class AcademicYearController { try { const { id } = req.params; - // Add to job queue to update current year - const job = await addDatabaseWriteJob('update', 'AcademicYear', { - id, - updates: { is_current: true }, - // Will also set all other years to is_current: false - }); + const academicYear = await AcademicYear.findByPk(id); + + if (!academicYear) { + return res.status(404).json({ + success: false, + message: 'Academic year not found', + }); + } + + // Set all other years to not current + await AcademicYear.update( + { is_current: false }, + { where: {} } + ); + + // Set this year as current + await academicYear.update({ is_current: true }); // Invalidate all academic year caches await cacheUtils.deletePattern('academic_year*'); - res.status(202).json({ + res.json({ success: true, - message: 'Set current academic year job queued', - jobId: job.id, + message: 'Academic year set as current successfully', + data: academicYear, }); } catch (error) { next(error); diff --git a/controllers/attendanceController.js b/controllers/attendanceController.js index 60d795e..99ed3e0 100644 --- a/controllers/attendanceController.js +++ b/controllers/attendanceController.js @@ -1,6 +1,6 @@ const { AttendanceLog, AttendanceDaily, UsersAuth, School } = require('../models'); const { cacheUtils } = require('../config/redis'); -const { addDatabaseWriteJob, addAttendanceProcessJob } = require('../config/bullmq'); +const { addAttendanceProcessJob } = require('../config/bullmq'); /** * Attendance Controller - Quản lý điểm danh @@ -82,15 +82,15 @@ class AttendanceController { try { const logData = req.body; - const job = await addDatabaseWriteJob('create', 'AttendanceLog', logData); + const newLog = await AttendanceLog.create(logData); await cacheUtils.deletePattern('attendance:logs:*'); await cacheUtils.deletePattern('attendance:daily:*'); - res.status(202).json({ + res.status(201).json({ success: true, - message: 'Attendance log created', - jobId: job.id, + message: 'Attendance log created successfully', + data: newLog, }); } catch (error) { next(error); diff --git a/controllers/classController.js b/controllers/classController.js index 5837da7..aa75d36 100644 --- a/controllers/classController.js +++ b/controllers/classController.js @@ -1,7 +1,6 @@ const { Class } = require('../models'); const { StudentDetail, UserProfile, UsersAuth, School } = require('../models'); const { cacheUtils } = require('../config/redis'); -const { addDatabaseWriteJob } = require('../config/bullmq'); /** * Class Controller - Quản lý thông tin lớp học @@ -107,22 +106,55 @@ class ClassController { } /** - * Create new class (async via BullMQ) + * Create new class */ async createClass(req, res, next) { try { const classData = req.body; - // Add to job queue for async processing - const job = await addDatabaseWriteJob('create', 'Class', classData); + // Create class directly + const newClass = await Class.create(classData); // Invalidate related caches await cacheUtils.deletePattern('classes:list:*'); - res.status(202).json({ + res.status(201).json({ success: true, - message: 'Class creation job queued', - jobId: job.id, + message: 'Class created successfully', + data: newClass, + }); + } catch (error) { + next(error); + } + } + + /** + * Update class + */ + async updateClass(req, res, next) { + try { + const { id } = req.params; + const updates = req.body; + + const classData = await Class.findByPk(id); + + if (!classData) { + return res.status(404).json({ + success: false, + message: 'Class not found', + }); + } + + // Update class directly + await classData.update(updates); + + // Invalidate related caches + await cacheUtils.delete(`class:${id}`); + await cacheUtils.deletePattern('classes:list:*'); + + res.json({ + success: true, + message: 'Class updated successfully', data: classData, }); } catch (error) { @@ -131,51 +163,31 @@ class ClassController { } /** - * Update class (async via BullMQ) - */ - async updateClass(req, res, next) { - try { - const { id } = req.params; - const updates = req.body; - - // Add to job queue for async processing - const job = await addDatabaseWriteJob('update', 'Class', { - id, - updates, - }); - - // Invalidate related caches - await cacheUtils.delete(`class:${id}`); - await cacheUtils.deletePattern('classes:list:*'); - - res.status(202).json({ - success: true, - message: 'Class update job queued', - jobId: job.id, - }); - } catch (error) { - next(error); - } - } - - /** - * Delete class (async via BullMQ) + * Delete class */ async deleteClass(req, res, next) { try { const { id } = req.params; - // Add to job queue for async processing - const job = await addDatabaseWriteJob('delete', 'Class', { id }); + const classData = await Class.findByPk(id); + + if (!classData) { + return res.status(404).json({ + success: false, + message: 'Class not found', + }); + } + + // Delete class directly + await classData.destroy(); // Invalidate related caches await cacheUtils.delete(`class:${id}`); await cacheUtils.deletePattern('classes:list:*'); - res.status(202).json({ + res.json({ success: true, - message: 'Class deletion job queued', - jobId: job.id, + message: 'Class deleted successfully', }); } catch (error) { next(error); diff --git a/controllers/gameTypeController.js b/controllers/gameTypeController.js new file mode 100644 index 0000000..fac98be --- /dev/null +++ b/controllers/gameTypeController.js @@ -0,0 +1,343 @@ +const { GameType } = require('../models'); +const { cacheUtils } = require('../config/redis'); + +/** + * GameType Controller - Quản lý loại trò chơi giáo dục + */ +class GameTypeController { + /** + * Get all game types with pagination + */ + async getAllGameTypes(req, res, next) { + try { + const { page = 1, limit = 20, is_active, is_premium, difficulty_level } = req.query; + const offset = (page - 1) * limit; + + const cacheKey = `game_types:list:${page}:${limit}:${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 (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 GameType.findAndCountAll({ + where, + limit: parseInt(limit), + offset: parseInt(offset), + order: [['display_order', 'ASC'], ['rating', 'DESC']], + }); + + const result = { + gameTypes: 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 type by ID + */ + async getGameTypeById(req, res, next) { + try { + const { id } = req.params; + const cacheKey = `game_type:${id}`; + + const cached = await cacheUtils.get(cacheKey); + if (cached) { + return res.json({ + success: true, + data: cached, + cached: true, + }); + } + + const gameType = await GameType.findByPk(id); + + if (!gameType) { + return res.status(404).json({ + success: false, + error: 'Game type not found', + }); + } + + await cacheUtils.set(cacheKey, gameType, 3600); + + res.json({ + success: true, + data: gameType, + cached: false, + }); + } catch (error) { + next(error); + } + } + + /** + * Get game type by type code + */ + async getGameTypeByType(req, res, next) { + try { + const { type } = req.params; + const cacheKey = `game_type:type:${type}`; + + const cached = await cacheUtils.get(cacheKey); + if (cached) { + return res.json({ + success: true, + data: cached, + cached: true, + }); + } + + const gameType = await GameType.findOne({ + where: { type }, + }); + + if (!gameType) { + return res.status(404).json({ + success: false, + error: 'Game type not found', + }); + } + + await cacheUtils.set(cacheKey, gameType, 3600); + + res.json({ + success: true, + data: gameType, + cached: false, + }); + } catch (error) { + next(error); + } + } + + /** + * Create new game type + */ + async createGameType(req, res, next) { + try { + const gameTypeData = req.body; + + // Check if type already exists + const existingType = await GameType.findOne({ + where: { type: gameTypeData.type }, + }); + + if (existingType) { + return res.status(400).json({ + success: false, + error: 'Game type with this type code already exists', + }); + } + + const gameType = await GameType.create(gameTypeData); + + // Invalidate cache + await cacheUtils.deletePattern('game_types:*'); + + res.status(201).json({ + success: true, + data: gameType, + message: 'Game type created successfully', + }); + } catch (error) { + next(error); + } + } + + /** + * Update game type + */ + async updateGameType(req, res, next) { + try { + const { id } = req.params; + const updateData = req.body; + + const gameType = await GameType.findByPk(id); + + if (!gameType) { + return res.status(404).json({ + success: false, + error: 'Game type not found', + }); + } + + // If updating type code, check uniqueness + if (updateData.type && updateData.type !== gameType.type) { + const existingType = await GameType.findOne({ + where: { type: updateData.type }, + }); + + if (existingType) { + return res.status(400).json({ + success: false, + error: 'Game type with this type code already exists', + }); + } + } + + await gameType.update(updateData); + + // Invalidate cache + await cacheUtils.del(`game_type:${id}`); + await cacheUtils.del(`game_type:type:${gameType.type}`); + await cacheUtils.deletePattern('game_types:*'); + + res.json({ + success: true, + data: gameType, + message: 'Game type updated successfully', + }); + } catch (error) { + next(error); + } + } + + /** + * Delete game type + */ + async deleteGameType(req, res, next) { + try { + const { id } = req.params; + + const gameType = await GameType.findByPk(id); + + if (!gameType) { + return res.status(404).json({ + success: false, + error: 'Game type not found', + }); + } + + const typeCode = gameType.type; + + await gameType.destroy(); + + // Invalidate cache + await cacheUtils.del(`game_type:${id}`); + await cacheUtils.del(`game_type:type:${typeCode}`); + await cacheUtils.deletePattern('game_types:*'); + + res.json({ + success: true, + message: 'Game type deleted successfully', + }); + } catch (error) { + next(error); + } + } + + /** + * Get active game types only + */ + async getActiveGameTypes(req, res, next) { + try { + const cacheKey = 'game_types:active'; + + const cached = await cacheUtils.get(cacheKey); + if (cached) { + return res.json({ + success: true, + data: cached, + cached: true, + }); + } + + const gameTypes = await GameType.findAll({ + where: { is_active: true }, + order: [['display_order', 'ASC'], ['rating', 'DESC']], + }); + + await cacheUtils.set(cacheKey, gameTypes, 3600); + + res.json({ + success: true, + data: gameTypes, + cached: false, + }); + } catch (error) { + next(error); + } + } + + /** + * Get game types statistics + */ + async getGameTypeStats(req, res, next) { + try { + const cacheKey = 'game_types:stats'; + + const cached = await cacheUtils.get(cacheKey); + if (cached) { + return res.json({ + success: true, + data: cached, + cached: true, + }); + } + + const [total, active, premium, byDifficulty] = await Promise.all([ + GameType.count(), + GameType.count({ where: { is_active: true } }), + GameType.count({ where: { is_premium: true } }), + GameType.findAll({ + attributes: [ + 'difficulty_level', + [GameType.sequelize.fn('COUNT', GameType.sequelize.col('id')), 'count'], + ], + group: ['difficulty_level'], + }), + ]); + + const stats = { + total, + active, + premium, + inactive: total - active, + byDifficulty: byDifficulty.reduce((acc, item) => { + if (item.difficulty_level) { + acc[item.difficulty_level] = parseInt(item.dataValues.count); + } + return acc; + }, {}), + }; + + await cacheUtils.set(cacheKey, stats, 1800); + + res.json({ + success: true, + data: stats, + cached: false, + }); + } catch (error) { + next(error); + } + } +} + +module.exports = new GameTypeController(); diff --git a/controllers/gradeController.js b/controllers/gradeController.js index ca41300..51a812e 100644 --- a/controllers/gradeController.js +++ b/controllers/gradeController.js @@ -1,6 +1,6 @@ const { Grade, GradeItem, GradeSummary, StudentDetail, Subject, AcademicYear } = require('../models'); const { cacheUtils } = require('../config/redis'); -const { addDatabaseWriteJob, addGradeCalculationJob } = require('../config/bullmq'); +const { addGradeCalculationJob } = require('../config/bullmq'); /** * Grade Controller - Quản lý điểm số @@ -121,22 +121,21 @@ class GradeController { } /** - * Create new grade (async via BullMQ) + * Create new grade */ async createGrade(req, res, next) { try { const gradeData = req.body; - const job = await addDatabaseWriteJob('create', 'Grade', gradeData); + const grade = await Grade.create(gradeData); await cacheUtils.deletePattern('grades:list:*'); await cacheUtils.deletePattern(`grade:student:${gradeData.student_id}:*`); - res.status(202).json({ + res.status(201).json({ success: true, - message: 'Grade creation job queued', - jobId: job.id, - data: gradeData, + message: 'Grade created successfully', + data: grade, }); } catch (error) { next(error); @@ -144,25 +143,31 @@ class GradeController { } /** - * Update grade (async via BullMQ) + * Update grade */ async updateGrade(req, res, next) { try { const { id } = req.params; const updates = req.body; - const job = await addDatabaseWriteJob('update', 'Grade', { - id, - updates, - }); + const grade = await Grade.findByPk(id); + + if (!grade) { + return res.status(404).json({ + success: false, + message: 'Grade not found', + }); + } + + await grade.update(updates); await cacheUtils.delete(`grade:${id}`); await cacheUtils.deletePattern('grades:list:*'); - res.status(202).json({ + res.status(200).json({ success: true, - message: 'Grade update job queued', - jobId: job.id, + message: 'Grade updated successfully', + data: grade, }); } catch (error) { next(error); @@ -170,21 +175,29 @@ class GradeController { } /** - * Delete grade (async via BullMQ) + * Delete grade */ async deleteGrade(req, res, next) { try { const { id } = req.params; - const job = await addDatabaseWriteJob('delete', 'Grade', { id }); + const grade = await Grade.findByPk(id); + + if (!grade) { + return res.status(404).json({ + success: false, + message: 'Grade not found', + }); + } + + await grade.destroy(); await cacheUtils.delete(`grade:${id}`); await cacheUtils.deletePattern('grades:list:*'); - res.status(202).json({ + res.json({ success: true, - message: 'Grade deletion job queued', - jobId: job.id, + message: 'Grade deleted successfully', }); } catch (error) { next(error); diff --git a/controllers/parentTaskController.js b/controllers/parentTaskController.js index 0308d63..8ca8c8b 100644 --- a/controllers/parentTaskController.js +++ b/controllers/parentTaskController.js @@ -1,6 +1,6 @@ const { ParentAssignedTask, GradeItem, StudentDetail, UsersAuth, ParentStudentMap } = require('../models'); const { cacheUtils } = require('../config/redis'); -const { addDatabaseWriteJob, addNotificationJob } = require('../config/bullmq'); +const { addNotificationJob } = require('../config/bullmq'); const { v4: uuidv4 } = require('uuid'); /** @@ -64,8 +64,8 @@ class ParentTaskController { priority: priority || 'normal', }; - // Async write to DB via BullMQ - await addDatabaseWriteJob('create', 'ParentAssignedTask', taskData); + // Create task directly in database + const task = await ParentAssignedTask.create(taskData); // Gửi notification cho học sinh await addNotificationJob({ @@ -80,10 +80,10 @@ class ParentTaskController { await cacheUtils.del(`parent:tasks:${parent_id}`); await cacheUtils.del(`student:tasks:${student_id}`); - res.status(202).json({ + res.status(201).json({ success: true, - message: 'Task assignment request queued', - data: taskData, + message: 'Task assigned successfully', + data: task, }); } catch (error) { next(error); @@ -138,11 +138,7 @@ class ParentTaskController { for (const task of tasks) { if (task.status !== 'completed' && new Date(task.due_date) < now) { if (task.status !== 'overdue') { - await addDatabaseWriteJob('update', 'ParentAssignedTask', { - id: task.id, - status: 'overdue', - }); - task.status = 'overdue'; + await task.update({ status: 'overdue' }); } } } @@ -235,8 +231,7 @@ class ParentTaskController { }); } - await addDatabaseWriteJob('update', 'ParentAssignedTask', { - id, + await task.update({ status: 'completed', completion_date: new Date(), student_notes: student_notes || null, @@ -255,9 +250,10 @@ class ParentTaskController { await cacheUtils.del(`parent:tasks:${task.parent_id}:*`); await cacheUtils.del(`student:tasks:${task.student_id}:*`); - res.status(202).json({ + res.status(200).json({ success: true, - message: 'Task completion request queued', + message: 'Task completed successfully', + data: task, }); } catch (error) { next(error); @@ -288,18 +284,16 @@ class ParentTaskController { }); } - await addDatabaseWriteJob('update', 'ParentAssignedTask', { - id, - status, - }); + await task.update({ status }); // Clear cache await cacheUtils.del(`parent:tasks:${task.parent_id}:*`); await cacheUtils.del(`student:tasks:${task.student_id}:*`); - res.status(202).json({ + res.status(200).json({ success: true, - message: 'Task status update queued', + message: 'Task status updated successfully', + data: task, }); } catch (error) { next(error); @@ -330,15 +324,15 @@ class ParentTaskController { }); } - await addDatabaseWriteJob('delete', 'ParentAssignedTask', { id }); + await task.destroy(); // Clear cache await cacheUtils.del(`parent:tasks:${task.parent_id}:*`); await cacheUtils.del(`student:tasks:${task.student_id}:*`); - res.status(202).json({ + res.status(200).json({ success: true, - message: 'Task deletion request queued', + message: 'Task deleted successfully', }); } catch (error) { next(error); diff --git a/controllers/roomController.js b/controllers/roomController.js index 4f06ee9..9918531 100644 --- a/controllers/roomController.js +++ b/controllers/roomController.js @@ -1,6 +1,5 @@ const { Room, School } = require('../models'); const { cacheUtils } = require('../config/redis'); -const { addDatabaseWriteJob } = require('../config/bullmq'); /** * Room Controller - Quản lý phòng học @@ -97,21 +96,20 @@ class RoomController { } /** - * Create new room (async via BullMQ) + * Create new room */ async createRoom(req, res, next) { try { const roomData = req.body; - const job = await addDatabaseWriteJob('create', 'Room', roomData); + const room = await Room.create(roomData); await cacheUtils.deletePattern('rooms:list:*'); - res.status(202).json({ + res.status(201).json({ success: true, - message: 'Room creation job queued', - jobId: job.id, - data: roomData, + message: 'Room created successfully', + data: room, }); } catch (error) { next(error); @@ -119,25 +117,31 @@ class RoomController { } /** - * Update room (async via BullMQ) + * Update room */ async updateRoom(req, res, next) { try { const { id } = req.params; const updates = req.body; - const job = await addDatabaseWriteJob('update', 'Room', { - id, - updates, - }); + const room = await Room.findByPk(id); + + if (!room) { + return res.status(404).json({ + success: false, + message: 'Room not found', + }); + } + + await room.update(updates); await cacheUtils.delete(`room:${id}`); await cacheUtils.deletePattern('rooms:list:*'); - res.status(202).json({ + res.json({ success: true, - message: 'Room update job queued', - jobId: job.id, + message: 'Room updated successfully', + data: room, }); } catch (error) { next(error); @@ -145,21 +149,29 @@ class RoomController { } /** - * Delete room (async via BullMQ) + * Delete room */ async deleteRoom(req, res, next) { try { const { id } = req.params; - const job = await addDatabaseWriteJob('delete', 'Room', { id }); + const room = await Room.findByPk(id); + + if (!room) { + return res.status(404).json({ + success: false, + message: 'Room not found', + }); + } + + await room.destroy(); await cacheUtils.delete(`room:${id}`); await cacheUtils.deletePattern('rooms:list:*'); - res.status(202).json({ + res.json({ success: true, - message: 'Room deletion job queued', - jobId: job.id, + message: 'Room deleted successfully', }); } catch (error) { next(error); diff --git a/controllers/schoolController.js b/controllers/schoolController.js index 654cc2b..0347f62 100644 --- a/controllers/schoolController.js +++ b/controllers/schoolController.js @@ -1,6 +1,5 @@ const { School } = require('../models'); const { cacheUtils } = require('../config/redis'); -const { addDatabaseWriteJob } = require('../config/bullmq'); /** * School Controller - Quản lý thông tin trường học @@ -108,12 +107,11 @@ class SchoolController { } /** - * Create new school - Push to BullMQ + * Create new school */ async createSchool(req, res, next) { try { const schoolData = req.body; - const userId = req.user?.id; // From auth middleware // Validate required fields (you can use Joi for this) if (!schoolData.school_code || !schoolData.school_name || !schoolData.school_type) { @@ -123,22 +121,14 @@ class SchoolController { }); } - // Add job to BullMQ queue - const job = await addDatabaseWriteJob( - 'create', - 'School', - schoolData, - { userId, priority: 3 } - ); + const newSchool = await School.create(schoolData); - res.status(202).json({ + await cacheUtils.deletePattern('schools:list:*'); + + res.status(201).json({ success: true, - message: 'School creation queued', - jobId: job.id, - data: { - school_code: schoolData.school_code, - school_name: schoolData.school_name, - }, + message: 'School created successfully', + data: newSchool, }); } catch (error) { next(error); @@ -146,44 +136,31 @@ class SchoolController { } /** - * Update school - Push to BullMQ + * Update school */ async updateSchool(req, res, next) { try { const { id } = req.params; const updateData = req.body; - const userId = req.user?.id; - - // Check if school exists (from cache or DB) - const cacheKey = `school:${id}`; - let school = await cacheUtils.get(cacheKey); + const school = await School.findByPk(id); if (!school) { - school = await School.findByPk(id); - if (!school) { - return res.status(404).json({ - success: false, - message: 'School not found', - }); - } + return res.status(404).json({ + success: false, + message: 'School not found', + }); } - // Add update job to BullMQ - const job = await addDatabaseWriteJob( - 'update', - 'School', - { id, ...updateData }, - { userId, priority: 3 } - ); + await school.update(updateData); // Invalidate cache - await cacheUtils.delete(cacheKey); + await cacheUtils.delete(`school:${id}`); await cacheUtils.deletePattern('schools:list:*'); - res.json({ + res.status(200).json({ success: true, - message: 'School update queued', - jobId: job.id, + message: 'School updated successfully', + data: school, }); } catch (error) { next(error); @@ -191,12 +168,11 @@ class SchoolController { } /** - * Delete school - Push to BullMQ + * Delete school (soft delete) */ async deleteSchool(req, res, next) { try { const { id } = req.params; - const userId = req.user?.id; // Check if school exists const school = await School.findByPk(id); @@ -208,21 +184,16 @@ class SchoolController { } // Soft delete - just mark as inactive - const job = await addDatabaseWriteJob( - 'update', - 'School', - { id, is_active: false }, - { userId, priority: 3 } - ); + await school.update({ is_active: false }); // Invalidate cache await cacheUtils.delete(`school:${id}`); await cacheUtils.deletePattern('schools:list:*'); - res.json({ + res.status(200).json({ success: true, - message: 'School deletion queued', - jobId: job.id, + message: 'School deactivated successfully', + data: school, }); } catch (error) { next(error); diff --git a/controllers/studentController.js b/controllers/studentController.js index 3d42652..1240dd4 100644 --- a/controllers/studentController.js +++ b/controllers/studentController.js @@ -1,6 +1,5 @@ const { StudentDetail, UserProfile, UsersAuth, Class } = require('../models'); const { cacheUtils } = require('../config/redis'); -const { addDatabaseWriteJob } = require('../config/bullmq'); /** * Student Controller - Quản lý học sinh @@ -120,21 +119,20 @@ class StudentController { } /** - * Create new student (async via BullMQ) + * Create new student */ async createStudent(req, res, next) { try { const studentData = req.body; - const job = await addDatabaseWriteJob('create', 'StudentDetail', studentData); + const newStudent = await StudentDetail.create(studentData); await cacheUtils.deletePattern('students:list:*'); - res.status(202).json({ + res.status(201).json({ success: true, - message: 'Student creation job queued', - jobId: job.id, - data: studentData, + message: 'Student created successfully', + data: newStudent, }); } catch (error) { next(error); @@ -142,25 +140,30 @@ class StudentController { } /** - * Update student (async via BullMQ) + * Update student */ async updateStudent(req, res, next) { try { const { id } = req.params; const updates = req.body; - const job = await addDatabaseWriteJob('update', 'StudentDetail', { - id, - updates, - }); + const student = await StudentDetail.findByPk(id); + if (!student) { + return res.status(404).json({ + success: false, + message: 'Student not found', + }); + } + + await student.update(updates); await cacheUtils.delete(`student:${id}`); await cacheUtils.deletePattern('students:list:*'); - res.status(202).json({ + res.status(200).json({ success: true, - message: 'Student update job queued', - jobId: job.id, + message: 'Student updated successfully', + data: student, }); } catch (error) { next(error); @@ -174,18 +177,23 @@ class StudentController { try { const { id } = req.params; - const job = await addDatabaseWriteJob('update', 'StudentDetail', { - id, - updates: { status: 'dropped' }, - }); + const student = await StudentDetail.findByPk(id); + if (!student) { + return res.status(404).json({ + success: false, + message: 'Student not found', + }); + } + + await student.update({ status: 'dropped' }); await cacheUtils.delete(`student:${id}`); await cacheUtils.deletePattern('students:list:*'); - res.status(202).json({ + res.status(200).json({ success: true, - message: 'Student deletion job queued', - jobId: job.id, + message: 'Student status updated to dropped', + data: student, }); } catch (error) { next(error); diff --git a/controllers/subjectController.js b/controllers/subjectController.js index 6ac13b7..2bb0441 100644 --- a/controllers/subjectController.js +++ b/controllers/subjectController.js @@ -1,6 +1,5 @@ const { Subject, Chapter } = require('../models'); const { cacheUtils } = require('../config/redis'); -const { addDatabaseWriteJob } = require('../config/bullmq'); /** * Subject Controller - Quản lý môn học @@ -147,22 +146,20 @@ class SubjectController { } /** - * Create new subject (async via BullMQ) + * Create new subject */ async createSubject(req, res, next) { try { const subjectData = req.body; - // Add to job queue for async processing - const job = await addDatabaseWriteJob('create', 'Subject', subjectData); + const subject = await Subject.create(subjectData); - // Note: Cache will be invalidated by worker after successful creation + await cacheUtils.deletePattern('subjects:list:*'); - res.status(202).json({ + res.status(201).json({ success: true, - message: 'Subject creation job queued', - jobId: job.id, - data: subjectData, + message: 'Subject created successfully', + data: subject, }); } catch (error) { next(error); @@ -170,25 +167,31 @@ class SubjectController { } /** - * Update subject (async via BullMQ) + * Update subject */ 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, - }); + const subject = await Subject.findByPk(id); - // Note: Cache will be invalidated by worker after successful update + if (!subject) { + return res.status(404).json({ + success: false, + message: 'Subject not found', + }); + } - res.status(202).json({ + await subject.update(updates); + + await cacheUtils.delete(`subject:${id}`); + await cacheUtils.deletePattern('subjects:list:*'); + + res.json({ success: true, - message: 'Subject update job queued', - jobId: job.id, + message: 'Subject updated successfully', + data: subject, }); } catch (error) { next(error); @@ -196,21 +199,29 @@ class SubjectController { } /** - * Delete subject (async via BullMQ) + * Delete subject */ async deleteSubject(req, res, next) { try { const { id } = req.params; - // Add to job queue for async processing - const job = await addDatabaseWriteJob('delete', 'Subject', { id }); + const subject = await Subject.findByPk(id); - // Note: Cache will be invalidated by worker after successful deletion + if (!subject) { + return res.status(404).json({ + success: false, + message: 'Subject not found', + }); + } - res.status(202).json({ + await subject.destroy(); + + await cacheUtils.delete(`subject:${id}`); + await cacheUtils.deletePattern('subjects:list:*'); + + res.json({ success: true, - message: 'Subject deletion job queued', - jobId: job.id, + message: 'Subject deleted successfully', }); } catch (error) { next(error); diff --git a/controllers/subscriptionController.js b/controllers/subscriptionController.js index c285341..a69801a 100644 --- a/controllers/subscriptionController.js +++ b/controllers/subscriptionController.js @@ -1,6 +1,5 @@ const { SubscriptionPlan, UserSubscription, UsersAuth } = require('../models'); const { cacheUtils } = require('../config/redis'); -const { addDatabaseWriteJob } = require('../config/bullmq'); const { v4: uuidv4 } = require('uuid'); /** @@ -98,16 +97,15 @@ class SubscriptionController { auto_renew: req.body.auto_renew || false, }; - // Async write to DB via BullMQ - await addDatabaseWriteJob('create', 'UserSubscription', subscriptionData); + const newSubscription = await UserSubscription.create(subscriptionData); // Clear cache await cacheUtils.del(`subscription:user:${user_id}`); - res.status(202).json({ + res.status(201).json({ success: true, - message: 'Subscription purchase request queued', - data: subscriptionData, + message: 'Subscription purchased successfully', + data: newSubscription, }); } catch (error) { next(error); @@ -153,11 +151,7 @@ class SubscriptionController { // Check nếu subscription đã hết hạn const now = new Date(); if (subscription.status === 'active' && new Date(subscription.end_date) < now) { - subscription.status = 'expired'; - await addDatabaseWriteJob('update', 'UserSubscription', { - id: subscription.id, - status: 'expired', - }); + await subscription.update({ status: 'expired' }); } await cacheUtils.set(cacheKey, subscription, 900); // Cache 15 min @@ -195,9 +189,8 @@ class SubscriptionController { }); } - // Async update via BullMQ - await addDatabaseWriteJob('update', 'UserSubscription', { - id, + // Update subscription + await subscription.update({ status: 'cancelled', auto_renew: false, }); @@ -205,9 +198,10 @@ class SubscriptionController { // Clear cache await cacheUtils.del(`subscription:user:${subscription.user_id}`); - res.status(202).json({ + res.status(200).json({ success: true, - message: 'Subscription cancellation request queued', + message: 'Subscription cancelled successfully', + data: subscription, }); } catch (error) { next(error); diff --git a/controllers/teacherController.js b/controllers/teacherController.js index 4d81a83..997a31c 100644 --- a/controllers/teacherController.js +++ b/controllers/teacherController.js @@ -1,6 +1,5 @@ const { TeacherDetail, UserProfile, UsersAuth } = require('../models'); const { cacheUtils } = require('../config/redis'); -const { addDatabaseWriteJob } = require('../config/bullmq'); const teacherProfileService = require('../services/teacherProfileService'); /** @@ -205,25 +204,30 @@ class TeacherController { } /** - * Update teacher (async via BullMQ) + * Update teacher */ async updateTeacher(req, res, next) { try { const { id } = req.params; const updates = req.body; - const job = await addDatabaseWriteJob('update', 'TeacherDetail', { - id, - updates, - }); + const teacher = await TeacherDetail.findByPk(id); + if (!teacher) { + return res.status(404).json({ + success: false, + message: 'Teacher not found', + }); + } + + await teacher.update(updates); await cacheUtils.delete(`teacher:${id}`); await cacheUtils.deletePattern('teachers:list:*'); - res.status(202).json({ + res.status(200).json({ success: true, - message: 'Teacher update job queued', - jobId: job.id, + message: 'Teacher updated successfully', + data: teacher, }); } catch (error) { next(error); @@ -237,17 +241,28 @@ class TeacherController { try { const { id } = req.params; - const job = await addDatabaseWriteJob('update', 'TeacherDetail', { - id, - updates: { status: 'resigned' }, - }); + const teacher = await TeacherDetail.findByPk(id); + if (!teacher) { + return res.status(404).json({ + success: false, + message: 'Teacher not found', + }); + } + + await teacher.update({ status: 'resigned' }); await cacheUtils.delete(`teacher:${id}`); await cacheUtils.deletePattern('teachers:list:*'); - res.status(202).json({ + res.status(200).json({ success: true, - message: 'Teacher deletion job queued', + message: 'Teacher status updated to resigned', + data: teacher, + }); + } catch (error) { + next(error); + } + } jobId: job.id, }); } catch (error) { diff --git a/controllers/trainingController.js b/controllers/trainingController.js index 083467c..1aa6661 100644 --- a/controllers/trainingController.js +++ b/controllers/trainingController.js @@ -1,6 +1,5 @@ const { StaffTrainingAssignment, StaffAchievement, UsersAuth, Subject } = require('../models'); const { cacheUtils } = require('../config/redis'); -const { addDatabaseWriteJob } = require('../config/bullmq'); const { v4: uuidv4 } = require('uuid'); /** @@ -55,11 +54,7 @@ class TrainingController { for (const assignment of assignments) { if (assignment.status !== 'completed' && new Date(assignment.deadline) < now) { if (assignment.status !== 'overdue') { - await addDatabaseWriteJob('update', 'StaffTrainingAssignment', { - id: assignment.id, - status: 'overdue', - }); - assignment.status = 'overdue'; + await assignment.update({ status: 'overdue' }); } } } @@ -102,16 +97,15 @@ class TrainingController { notes: notes || null, }; - // Async write to DB via BullMQ - await addDatabaseWriteJob('create', 'StaffTrainingAssignment', assignmentData); + const newAssignment = await StaffTrainingAssignment.create(assignmentData); // Clear cache await cacheUtils.del(`training:assignments:${staff_id}:*`); - res.status(202).json({ + res.status(201).json({ success: true, - message: 'Training assignment request queued', - data: assignmentData, + message: 'Training assignment created successfully', + data: newAssignment, }); } catch (error) { next(error); @@ -142,17 +136,15 @@ class TrainingController { }); } - await addDatabaseWriteJob('update', 'StaffTrainingAssignment', { - id, - status, - }); + await assignment.update({ status }); // Clear cache await cacheUtils.del(`training:assignments:${assignment.staff_id}:*`); - res.status(202).json({ + res.status(200).json({ success: true, - message: 'Assignment status update queued', + message: 'Assignment status updated successfully', + data: assignment, }); } catch (error) { next(error); @@ -199,16 +191,15 @@ class TrainingController { verified_at: verified_by ? new Date() : null, }; - // Async write to DB via BullMQ - await addDatabaseWriteJob('create', 'StaffAchievement', achievementData); + const newAchievement = await StaffAchievement.create(achievementData); // Clear cache await cacheUtils.del(`training:achievements:${staff_id}`); - res.status(202).json({ + res.status(201).json({ success: true, - message: 'Achievement creation request queued', - data: achievementData, + message: 'Achievement created successfully', + data: newAchievement, }); } catch (error) { next(error); diff --git a/controllers/userController.js b/controllers/userController.js index 4d366f6..ec7b186 100644 --- a/controllers/userController.js +++ b/controllers/userController.js @@ -1,6 +1,5 @@ const { UsersAuth, UserProfile } = require('../models'); const { cacheUtils } = require('../config/redis'); -const { addDatabaseWriteJob } = require('../config/bullmq'); const bcrypt = require('bcrypt'); const jwt = require('jsonwebtoken'); const crypto = require('crypto'); @@ -404,21 +403,20 @@ class UserController { } /** - * Create new user (async via BullMQ) + * Create new user */ async createUser(req, res, next) { try { const userData = req.body; - const job = await addDatabaseWriteJob('create', 'UsersAuth', userData); + const newUser = await UsersAuth.create(userData); await cacheUtils.deletePattern('users:list:*'); - res.status(202).json({ + res.status(201).json({ success: true, - message: 'User creation job queued', - jobId: job.id, - data: userData, + message: 'User created successfully', + data: newUser, }); } catch (error) { next(error); @@ -426,25 +424,30 @@ class UserController { } /** - * Update user (async via BullMQ) + * Update user */ async updateUser(req, res, next) { try { const { id } = req.params; const updates = req.body; - const job = await addDatabaseWriteJob('update', 'UsersAuth', { - id, - updates, - }); + const user = await UsersAuth.findByPk(id); + if (!user) { + return res.status(404).json({ + success: false, + message: 'User not found', + }); + } + + await user.update(updates); await cacheUtils.delete(`user:${id}`); await cacheUtils.deletePattern('users:list:*'); - res.status(202).json({ + res.status(200).json({ success: true, - message: 'User update job queued', - jobId: job.id, + message: 'User updated successfully', + data: user, }); } catch (error) { next(error); @@ -458,18 +461,23 @@ class UserController { try { const { id } = req.params; - const job = await addDatabaseWriteJob('update', 'UsersAuth', { - id, - updates: { is_active: false }, - }); + const user = await UsersAuth.findByPk(id); + if (!user) { + return res.status(404).json({ + success: false, + message: 'User not found', + }); + } + + await user.update({ is_active: false }); await cacheUtils.delete(`user:${id}`); await cacheUtils.deletePattern('users:list:*'); - res.status(202).json({ + res.status(200).json({ success: true, - message: 'User deletion job queued', - jobId: job.id, + message: 'User deactivated successfully', + data: user, }); } catch (error) { next(error); diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..618d108 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +# Script tự động deploy và fix bcrypt trên server +# Usage: ./deploy.sh + +SERVER="root@senaai.tech" +PROJECT_PATH="/var/www/services/sena_db_api" + +echo "════════════════════════════════════════════════════════" +echo " 🚀 Auto Deploy & Fix Bcrypt Script" +echo " Server: $SERVER" +echo " Path: $PROJECT_PATH" +echo "════════════════════════════════════════════════════════" +echo "" + +# Execute commands on remote server +ssh $SERVER << 'ENDSSH' +set -e # Exit on error + +echo "📁 Navigating to project directory..." +cd /var/www/services/sena_db_api + +echo "" +echo "🔧 Rebuilding bcrypt module..." +npm rebuild bcrypt --build-from-source + +echo "" +echo "🔄 Rebuilding all native modules..." +npm rebuild + +echo "" +echo "📂 Creating logs directory..." +mkdir -p logs + +echo "" +echo "✅ Build completed successfully!" +echo "" +echo "════════════════════════════════════════════════════════" +echo " PM2 Status:" +echo "════════════════════════════════════════════════════════" +pm2 list + +echo "" +echo "Do you want to restart PM2 processes? (y/n)" +read -r response +if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then + echo "" + echo "🔄 Restarting PM2 processes..." + pm2 restart all 2>/dev/null || pm2 start start.json + echo "" + echo "✅ PM2 processes restarted!" + echo "" + pm2 list +else + echo "" + echo "⏭️ Skipped PM2 restart" +fi + +echo "" +echo "════════════════════════════════════════════════════════" +echo " ✅ Deployment completed!" +echo "════════════════════════════════════════════════════════" + +ENDSSH + +echo "" +echo "Done! Check the output above for any errors." diff --git a/fix-bcrypt-complete.bat b/fix-bcrypt-complete.bat new file mode 100644 index 0000000..5a00d2b --- /dev/null +++ b/fix-bcrypt-complete.bat @@ -0,0 +1,17 @@ +@echo off +REM Complete bcrypt fix for Windows +REM This will remove node_modules and reinstall everything + +echo ================================================================ +echo Complete Bcrypt Fix Script +echo Server: root@senaai.tech +echo ================================================================ +echo. + +ssh root@senaai.tech "cd /var/www/services/sena_db_api && echo 'Removing node_modules...' && rm -rf node_modules package-lock.json pnpm-lock.yaml yarn.lock && echo 'Installing with npm...' && npm install && echo 'Rebuilding bcrypt...' && npm rebuild bcrypt --build-from-source && echo 'Restarting PM2...' && pm2 restart all && pm2 list" + +echo. +echo ================================================================ +echo Done! Check the output above. +echo ================================================================ +pause diff --git a/fix-bcrypt-complete.sh b/fix-bcrypt-complete.sh new file mode 100644 index 0000000..a2642d6 --- /dev/null +++ b/fix-bcrypt-complete.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# Fix bcrypt on server - Complete reinstall +# Run this script: bash fix-bcrypt-complete.sh + +echo "════════════════════════════════════════════════════════" +echo " 🔧 Complete Bcrypt Fix Script" +echo " Server: root@senaai.tech" +echo "════════════════════════════════════════════════════════" +echo "" + +ssh root@senaai.tech << 'ENDSSH' +set -e + +echo "📁 Navigating to project..." +cd /var/www/services/sena_db_api + +echo "" +echo "🗑️ Step 1: Removing old node_modules and lock files..." +rm -rf node_modules package-lock.json pnpm-lock.yaml yarn.lock + +echo "" +echo "📦 Step 2: Installing dependencies with npm..." +npm install + +echo "" +echo "🔨 Step 3: Rebuilding bcrypt from source..." +npm rebuild bcrypt --build-from-source + +echo "" +echo "✅ Step 4: Build completed!" + +echo "" +echo "🔄 Step 5: Restarting PM2 processes..." +pm2 restart sena-api +pm2 restart worker-db-write +pm2 restart worker-lesson-fill +pm2 restart worker-process-data + +echo "" +echo "📊 PM2 Status:" +pm2 list + +echo "" +echo "════════════════════════════════════════════════════════" +echo " ✅ All done! Check logs with: pm2 logs sena-api" +echo "════════════════════════════════════════════════════════" + +ENDSSH diff --git a/models/GameType.js b/models/GameType.js new file mode 100644 index 0000000..0db23e5 --- /dev/null +++ b/models/GameType.js @@ -0,0 +1,97 @@ +const { DataTypes } = require('sequelize'); +const { sequelize } = require('../config/database'); + +/** + * GameType Model - Loại trò chơi giáo dục + * Định nghĩa các loại game template/engine khác nhau + * Một GameType có thể có nhiều Game instances + */ +const GameType = sequelize.define('game_types', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + en_title: { + type: DataTypes.TEXT, + allowNull: false, + comment: 'Tên loại game (tiếng Anh)' + }, + vi_title: { + type: DataTypes.TEXT, + allowNull: false, + comment: 'Tên loại game (tiếng Việt)' + }, + en_desc: { + type: DataTypes.TEXT, + comment: 'Mô tả loại game (tiếng Anh)' + }, + vi_desc: { + type: DataTypes.TEXT, + comment: 'Mô tả loại game (tiếng Việt)' + }, + type: { + type: DataTypes.TEXT, + allowNull: false, + unique: true, + comment: 'Mã định danh loại game: counting_quiz, math_game, word_puzzle, etc.' + }, + thumb: { + type: DataTypes.TEXT, + comment: 'URL ảnh thumbnail đại diện cho loại game' + }, + data: { + type: DataTypes.JSON, + comment: 'Dữ liệu mẫu hoặc template data cho loại game này' + }, + config: { + type: DataTypes.JSON, + comment: 'Cấu hình mặc định cho loại game: controls, settings, features, etc.' + }, + is_premium: { + type: DataTypes.BOOLEAN, + defaultValue: false, + comment: 'Yêu cầu premium để sử dụng loại game này' + }, + is_active: { + type: DataTypes.BOOLEAN, + defaultValue: true, + comment: 'Loại game có đang hoạt động không' + }, + difficulty_level: { + type: DataTypes.ENUM('easy', 'medium', 'hard'), + comment: 'Độ khó mặc định' + }, + rating: { + type: DataTypes.DECIMAL(3, 2), + comment: 'Đánh giá trung bình (0.00 - 5.00)' + }, + display_order: { + type: DataTypes.INTEGER, + defaultValue: 0, + comment: 'Thứ tự hiển thị' + }, + created_at: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: DataTypes.NOW + }, + updated_at: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: DataTypes.NOW + }, +}, { + tableName: 'game_types', + timestamps: true, + underscored: true, + indexes: [ + { fields: ['type'], unique: true }, + { fields: ['is_active'] }, + { fields: ['is_premium'] }, + { fields: ['display_order'] }, + { fields: ['difficulty_level'] }, + ], +}); + +module.exports = GameType; diff --git a/models/index.js b/models/index.js index 8f1258b..8408ba2 100644 --- a/models/index.js +++ b/models/index.js @@ -29,6 +29,7 @@ const Room = require('./Room'); const Chapter = require('./Chapter'); const Lesson = require('./Lesson'); const Game = require('./Game'); +const GameType = require('./GameType'); const LessonComponentProgress = require('./LessonComponentProgress'); const LessonLeaderboard = require('./LessonLeaderboard'); @@ -292,6 +293,7 @@ module.exports = { Chapter, Lesson, Game, + GameType, LessonComponentProgress, LessonLeaderboard, diff --git a/quick-fix-bcrypt.bat b/quick-fix-bcrypt.bat new file mode 100644 index 0000000..242a772 --- /dev/null +++ b/quick-fix-bcrypt.bat @@ -0,0 +1,23 @@ +@echo off +REM Windows batch script to fix bcrypt on remote server +REM Usage: quick-fix-bcrypt.bat + +set SERVER=root@senaai.tech +set PROJECT_PATH=/var/www/services/sena_db_api + +echo ================================================================ +echo Quick Fix Bcrypt Script +echo Server: %SERVER% +echo ================================================================ +echo. + +echo Connecting to server and fixing bcrypt... +echo. + +ssh %SERVER% "cd %PROJECT_PATH% && npm rebuild bcrypt --build-from-source && npm rebuild && mkdir -p logs && echo. && echo [SUCCESS] Bcrypt rebuilt! && echo. && pm2 restart all 2>nul || pm2 start start.json && echo. && pm2 list" + +echo. +echo ================================================================ +echo Done! Check the output above. +echo ================================================================ +pause diff --git a/quick-fix-bcrypt.sh b/quick-fix-bcrypt.sh new file mode 100644 index 0000000..673fb8f --- /dev/null +++ b/quick-fix-bcrypt.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# Script tự động rebuild bcrypt và restart PM2 +# Usage: ./quick-fix-bcrypt.sh + +SERVER="root@senaai.tech" +PROJECT_PATH="/var/www/services/sena_db_api" + +echo "🔧 Quick fix bcrypt on $SERVER..." +echo "" + +ssh $SERVER << ENDSSH +cd $PROJECT_PATH && \ +echo "🔨 Rebuilding bcrypt..." && \ +npm rebuild bcrypt --build-from-source && \ +npm rebuild && \ +mkdir -p logs && \ +echo "" && \ +echo "✅ Bcrypt rebuilt successfully!" && \ +echo "" && \ +echo "🔄 Restarting PM2..." && \ +pm2 restart all 2>/dev/null || pm2 start start.json && \ +echo "" && \ +echo "📊 PM2 Status:" && \ +pm2 list && \ +echo "" && \ +echo "✅ All done!" +ENDSSH diff --git a/routes/gameTypeRoutes.js b/routes/gameTypeRoutes.js new file mode 100644 index 0000000..70b0e35 --- /dev/null +++ b/routes/gameTypeRoutes.js @@ -0,0 +1,34 @@ +const express = require('express'); +const router = express.Router(); +const gameTypeController = require('../controllers/gameTypeController'); + +/** + * Game Type Routes + * Base path: /api/game-types + */ + +// Get all game types +router.get('/', gameTypeController.getAllGameTypes); + +// Get active game types only +router.get('/active', gameTypeController.getActiveGameTypes); + +// Get game type statistics +router.get('/stats', gameTypeController.getGameTypeStats); + +// Get game type by type code +router.get('/type/:type', gameTypeController.getGameTypeByType); + +// Get game type by ID +router.get('/:id', gameTypeController.getGameTypeById); + +// Create new game type +router.post('/', gameTypeController.createGameType); + +// Update game type +router.put('/:id', gameTypeController.updateGameType); + +// Delete game type +router.delete('/:id', gameTypeController.deleteGameType); + +module.exports = router;