feat: implement complete Phase 2 frontend foundation with React 18
Major milestone: Frontend implementation complete for Shattered Void MMO FRONTEND IMPLEMENTATION: - React 18 + TypeScript + Vite development environment - Tailwind CSS with custom dark theme for sci-fi aesthetic - Zustand state management with authentication persistence - Socket.io WebSocket client with auto-reconnection - Protected routing with authentication guards - Responsive design with mobile-first approach AUTHENTICATION SYSTEM: - Login/register forms with comprehensive validation - JWT token management with localStorage persistence - Password strength validation and user feedback - Protected routes and authentication guards CORE GAME INTERFACE: - Colony management dashboard with real-time updates - Resource display with live production tracking - WebSocket integration for real-time game events - Navigation with connection status indicator - Toast notifications for user feedback BACKEND ENHANCEMENTS: - Complete Research System with technology tree (23 technologies) - Fleet Management System with ship designs and movement - Enhanced Authentication with email verification and password reset - Complete game tick integration for all systems - Advanced WebSocket events for real-time updates ARCHITECTURE FEATURES: - Type-safe TypeScript throughout - Component-based architecture with reusable UI elements - API client with request/response interceptors - Error handling and loading states - Performance optimized builds with code splitting Phase 2 Status: Frontend foundation complete (Week 1-2 objectives met) Ready for: Colony management, fleet operations, research interface Next: Enhanced gameplay features and admin interface 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
8d9ef427be
commit
d41d1e8125
130 changed files with 33588 additions and 14817 deletions
|
|
@ -29,22 +29,22 @@ router.use(rateLimiters.admin);
|
|||
* Admin API Status and Information
|
||||
*/
|
||||
router.get('/', (req, res) => {
|
||||
res.json({
|
||||
name: 'Shattered Void - Admin API',
|
||||
version: process.env.npm_package_version || '0.1.0',
|
||||
status: 'operational',
|
||||
timestamp: new Date().toISOString(),
|
||||
correlationId: req.correlationId,
|
||||
endpoints: {
|
||||
authentication: '/api/admin/auth',
|
||||
players: '/api/admin/players',
|
||||
system: '/api/admin/system',
|
||||
events: '/api/admin/events',
|
||||
analytics: '/api/admin/analytics',
|
||||
combat: '/api/admin/combat'
|
||||
},
|
||||
note: 'Administrative access required for all endpoints'
|
||||
});
|
||||
res.json({
|
||||
name: 'Shattered Void - Admin API',
|
||||
version: process.env.npm_package_version || '0.1.0',
|
||||
status: 'operational',
|
||||
timestamp: new Date().toISOString(),
|
||||
correlationId: req.correlationId,
|
||||
endpoints: {
|
||||
authentication: '/api/admin/auth',
|
||||
players: '/api/admin/players',
|
||||
system: '/api/admin/system',
|
||||
events: '/api/admin/events',
|
||||
analytics: '/api/admin/analytics',
|
||||
combat: '/api/admin/combat',
|
||||
},
|
||||
note: 'Administrative access required for all endpoints',
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
@ -55,50 +55,50 @@ const authRoutes = express.Router();
|
|||
|
||||
// Public admin authentication endpoints
|
||||
authRoutes.post('/login',
|
||||
rateLimiters.auth,
|
||||
validators.validateAdminLogin,
|
||||
auditAdminAction('admin_login'),
|
||||
adminAuthController.login
|
||||
rateLimiters.auth,
|
||||
validators.validateAdminLogin,
|
||||
auditAdminAction('admin_login'),
|
||||
adminAuthController.login,
|
||||
);
|
||||
|
||||
// Protected admin authentication endpoints
|
||||
authRoutes.post('/logout',
|
||||
authenticateAdmin,
|
||||
auditAdminAction('admin_logout'),
|
||||
adminAuthController.logout
|
||||
authenticateAdmin,
|
||||
auditAdminAction('admin_logout'),
|
||||
adminAuthController.logout,
|
||||
);
|
||||
|
||||
authRoutes.get('/me',
|
||||
authenticateAdmin,
|
||||
adminAuthController.getProfile
|
||||
authenticateAdmin,
|
||||
adminAuthController.getProfile,
|
||||
);
|
||||
|
||||
authRoutes.get('/verify',
|
||||
authenticateAdmin,
|
||||
adminAuthController.verifyToken
|
||||
authenticateAdmin,
|
||||
adminAuthController.verifyToken,
|
||||
);
|
||||
|
||||
authRoutes.post('/refresh',
|
||||
rateLimiters.auth,
|
||||
adminAuthController.refresh
|
||||
rateLimiters.auth,
|
||||
adminAuthController.refresh,
|
||||
);
|
||||
|
||||
authRoutes.get('/stats',
|
||||
authenticateAdmin,
|
||||
requirePermissions([ADMIN_PERMISSIONS.ANALYTICS_READ]),
|
||||
auditAdminAction('view_system_stats'),
|
||||
adminAuthController.getSystemStats
|
||||
authenticateAdmin,
|
||||
requirePermissions([ADMIN_PERMISSIONS.ANALYTICS_READ]),
|
||||
auditAdminAction('view_system_stats'),
|
||||
adminAuthController.getSystemStats,
|
||||
);
|
||||
|
||||
authRoutes.post('/change-password',
|
||||
authenticateAdmin,
|
||||
rateLimiters.auth,
|
||||
validateRequest(require('joi').object({
|
||||
currentPassword: require('joi').string().required(),
|
||||
newPassword: require('joi').string().min(8).max(128).required()
|
||||
}), 'body'),
|
||||
auditAdminAction('admin_password_change'),
|
||||
adminAuthController.changePassword
|
||||
authenticateAdmin,
|
||||
rateLimiters.auth,
|
||||
validateRequest(require('joi').object({
|
||||
currentPassword: require('joi').string().required(),
|
||||
newPassword: require('joi').string().min(8).max(128).required(),
|
||||
}), 'body'),
|
||||
auditAdminAction('admin_password_change'),
|
||||
adminAuthController.changePassword,
|
||||
);
|
||||
|
||||
// Mount admin authentication routes
|
||||
|
|
@ -115,125 +115,125 @@ playerRoutes.use(authenticateAdmin);
|
|||
|
||||
// Get players list
|
||||
playerRoutes.get('/',
|
||||
requirePermissions([ADMIN_PERMISSIONS.PLAYER_DATA_READ]),
|
||||
validators.validatePagination,
|
||||
validateRequest(require('joi').object({
|
||||
search: require('joi').string().max(50).optional(),
|
||||
activeOnly: require('joi').boolean().optional(),
|
||||
sortBy: require('joi').string().valid('created_at', 'updated_at', 'username', 'email', 'last_login_at').default('created_at'),
|
||||
sortOrder: require('joi').string().valid('asc', 'desc').default('desc')
|
||||
}), 'query'),
|
||||
auditAdminAction('list_players'),
|
||||
async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 20,
|
||||
search = '',
|
||||
activeOnly = null,
|
||||
sortBy = 'created_at',
|
||||
sortOrder = 'desc'
|
||||
} = req.query;
|
||||
requirePermissions([ADMIN_PERMISSIONS.PLAYER_DATA_READ]),
|
||||
validators.validatePagination,
|
||||
validateRequest(require('joi').object({
|
||||
search: require('joi').string().max(50).optional(),
|
||||
activeOnly: require('joi').boolean().optional(),
|
||||
sortBy: require('joi').string().valid('created_at', 'updated_at', 'username', 'email', 'last_login_at').default('created_at'),
|
||||
sortOrder: require('joi').string().valid('asc', 'desc').default('desc'),
|
||||
}), 'query'),
|
||||
auditAdminAction('list_players'),
|
||||
async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 20,
|
||||
search = '',
|
||||
activeOnly = null,
|
||||
sortBy = 'created_at',
|
||||
sortOrder = 'desc',
|
||||
} = req.query;
|
||||
|
||||
const result = await adminService.getPlayersList({
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
search,
|
||||
activeOnly,
|
||||
sortBy,
|
||||
sortOrder
|
||||
}, req.correlationId);
|
||||
const result = await adminService.getPlayersList({
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
search,
|
||||
activeOnly,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
}, req.correlationId);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Players list retrieved successfully',
|
||||
data: result,
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Players list retrieved successfully',
|
||||
data: result,
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to retrieve players list',
|
||||
message: error.message,
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to retrieve players list',
|
||||
message: error.message,
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Get specific player details
|
||||
playerRoutes.get('/:playerId',
|
||||
requirePlayerAccess('playerId'),
|
||||
validators.validatePlayerId,
|
||||
auditAdminAction('view_player_details'),
|
||||
async (req, res) => {
|
||||
try {
|
||||
const playerId = parseInt(req.params.playerId);
|
||||
const playerDetails = await adminService.getPlayerDetails(playerId, req.correlationId);
|
||||
requirePlayerAccess('playerId'),
|
||||
validators.validatePlayerId,
|
||||
auditAdminAction('view_player_details'),
|
||||
async (req, res) => {
|
||||
try {
|
||||
const playerId = parseInt(req.params.playerId);
|
||||
const playerDetails = await adminService.getPlayerDetails(playerId, req.correlationId);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Player details retrieved successfully',
|
||||
data: {
|
||||
player: playerDetails
|
||||
},
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Player details retrieved successfully',
|
||||
data: {
|
||||
player: playerDetails,
|
||||
},
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
const statusCode = error.name === 'NotFoundError' ? 404 : 500;
|
||||
res.status(statusCode).json({
|
||||
success: false,
|
||||
error: error.name === 'NotFoundError' ? 'Player not found' : 'Failed to retrieve player details',
|
||||
message: error.message,
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
const statusCode = error.name === 'NotFoundError' ? 404 : 500;
|
||||
res.status(statusCode).json({
|
||||
success: false,
|
||||
error: error.name === 'NotFoundError' ? 'Player not found' : 'Failed to retrieve player details',
|
||||
message: error.message,
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Update player status (activate/deactivate)
|
||||
playerRoutes.put('/:playerId/status',
|
||||
requirePermissions([ADMIN_PERMISSIONS.PLAYER_MANAGEMENT]),
|
||||
validators.validatePlayerId,
|
||||
validateRequest(require('joi').object({
|
||||
isActive: require('joi').boolean().required(),
|
||||
reason: require('joi').string().max(200).optional()
|
||||
}), 'body'),
|
||||
auditAdminAction('update_player_status'),
|
||||
async (req, res) => {
|
||||
try {
|
||||
const playerId = parseInt(req.params.playerId);
|
||||
const { isActive, reason } = req.body;
|
||||
requirePermissions([ADMIN_PERMISSIONS.PLAYER_MANAGEMENT]),
|
||||
validators.validatePlayerId,
|
||||
validateRequest(require('joi').object({
|
||||
isActive: require('joi').boolean().required(),
|
||||
reason: require('joi').string().max(200).optional(),
|
||||
}), 'body'),
|
||||
auditAdminAction('update_player_status'),
|
||||
async (req, res) => {
|
||||
try {
|
||||
const playerId = parseInt(req.params.playerId);
|
||||
const { isActive, reason } = req.body;
|
||||
|
||||
const updatedPlayer = await adminService.updatePlayerStatus(
|
||||
playerId,
|
||||
isActive,
|
||||
req.correlationId
|
||||
);
|
||||
const updatedPlayer = await adminService.updatePlayerStatus(
|
||||
playerId,
|
||||
isActive,
|
||||
req.correlationId,
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `Player ${isActive ? 'activated' : 'deactivated'} successfully`,
|
||||
data: {
|
||||
player: updatedPlayer,
|
||||
action: isActive ? 'activated' : 'deactivated',
|
||||
reason: reason || null
|
||||
},
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
res.json({
|
||||
success: true,
|
||||
message: `Player ${isActive ? 'activated' : 'deactivated'} successfully`,
|
||||
data: {
|
||||
player: updatedPlayer,
|
||||
action: isActive ? 'activated' : 'deactivated',
|
||||
reason: reason || null,
|
||||
},
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
const statusCode = error.name === 'NotFoundError' ? 404 : 500;
|
||||
res.status(statusCode).json({
|
||||
success: false,
|
||||
error: error.name === 'NotFoundError' ? 'Player not found' : 'Failed to update player status',
|
||||
message: error.message,
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
const statusCode = error.name === 'NotFoundError' ? 404 : 500;
|
||||
res.status(statusCode).json({
|
||||
success: false,
|
||||
error: error.name === 'NotFoundError' ? 'Player not found' : 'Failed to update player status',
|
||||
message: error.message,
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Mount player management routes
|
||||
|
|
@ -250,88 +250,88 @@ systemRoutes.use(authenticateAdmin);
|
|||
|
||||
// Get detailed system statistics
|
||||
systemRoutes.get('/stats',
|
||||
requirePermissions([ADMIN_PERMISSIONS.SYSTEM_MANAGEMENT]),
|
||||
auditAdminAction('view_detailed_system_stats'),
|
||||
async (req, res) => {
|
||||
try {
|
||||
const stats = await adminService.getSystemStats(req.correlationId);
|
||||
requirePermissions([ADMIN_PERMISSIONS.SYSTEM_MANAGEMENT]),
|
||||
auditAdminAction('view_detailed_system_stats'),
|
||||
async (req, res) => {
|
||||
try {
|
||||
const stats = await adminService.getSystemStats(req.correlationId);
|
||||
|
||||
// Add additional system information
|
||||
const systemInfo = {
|
||||
...stats,
|
||||
server: {
|
||||
version: process.env.npm_package_version || '0.1.0',
|
||||
environment: process.env.NODE_ENV || 'development',
|
||||
uptime: process.uptime(),
|
||||
nodeVersion: process.version,
|
||||
memory: {
|
||||
used: Math.round(process.memoryUsage().heapUsed / 1024 / 1024),
|
||||
total: Math.round(process.memoryUsage().heapTotal / 1024 / 1024),
|
||||
rss: Math.round(process.memoryUsage().rss / 1024 / 1024)
|
||||
}
|
||||
}
|
||||
};
|
||||
// Add additional system information
|
||||
const systemInfo = {
|
||||
...stats,
|
||||
server: {
|
||||
version: process.env.npm_package_version || '0.1.0',
|
||||
environment: process.env.NODE_ENV || 'development',
|
||||
uptime: process.uptime(),
|
||||
nodeVersion: process.version,
|
||||
memory: {
|
||||
used: Math.round(process.memoryUsage().heapUsed / 1024 / 1024),
|
||||
total: Math.round(process.memoryUsage().heapTotal / 1024 / 1024),
|
||||
rss: Math.round(process.memoryUsage().rss / 1024 / 1024),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'System statistics retrieved successfully',
|
||||
data: systemInfo,
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'System statistics retrieved successfully',
|
||||
data: systemInfo,
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to retrieve system statistics',
|
||||
message: error.message,
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to retrieve system statistics',
|
||||
message: error.message,
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// System health check
|
||||
systemRoutes.get('/health',
|
||||
requirePermissions([ADMIN_PERMISSIONS.SYSTEM_MANAGEMENT]),
|
||||
async (req, res) => {
|
||||
try {
|
||||
// TODO: Implement comprehensive health checks
|
||||
// - Database connectivity
|
||||
// - Redis connectivity
|
||||
// - WebSocket server status
|
||||
// - External service connectivity
|
||||
requirePermissions([ADMIN_PERMISSIONS.SYSTEM_MANAGEMENT]),
|
||||
async (req, res) => {
|
||||
try {
|
||||
// TODO: Implement comprehensive health checks
|
||||
// - Database connectivity
|
||||
// - Redis connectivity
|
||||
// - WebSocket server status
|
||||
// - External service connectivity
|
||||
|
||||
const healthStatus = {
|
||||
status: 'healthy',
|
||||
timestamp: new Date().toISOString(),
|
||||
services: {
|
||||
database: 'healthy',
|
||||
redis: 'healthy',
|
||||
websocket: 'healthy'
|
||||
},
|
||||
performance: {
|
||||
uptime: process.uptime(),
|
||||
memory: process.memoryUsage(),
|
||||
cpu: process.cpuUsage()
|
||||
}
|
||||
};
|
||||
const healthStatus = {
|
||||
status: 'healthy',
|
||||
timestamp: new Date().toISOString(),
|
||||
services: {
|
||||
database: 'healthy',
|
||||
redis: 'healthy',
|
||||
websocket: 'healthy',
|
||||
},
|
||||
performance: {
|
||||
uptime: process.uptime(),
|
||||
memory: process.memoryUsage(),
|
||||
cpu: process.cpuUsage(),
|
||||
},
|
||||
};
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'System health check completed',
|
||||
data: healthStatus,
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'System health check completed',
|
||||
data: healthStatus,
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Health check failed',
|
||||
message: error.message,
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Health check failed',
|
||||
message: error.message,
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Mount system routes
|
||||
|
|
@ -348,26 +348,26 @@ router.use('/combat', require('./admin/combat'));
|
|||
* /api/admin/events/*
|
||||
*/
|
||||
router.get('/events',
|
||||
authenticateAdmin,
|
||||
requirePermissions([ADMIN_PERMISSIONS.EVENT_MANAGEMENT]),
|
||||
validators.validatePagination,
|
||||
auditAdminAction('view_events'),
|
||||
(req, res) => {
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Events endpoint - feature not yet implemented',
|
||||
data: {
|
||||
events: [],
|
||||
pagination: {
|
||||
page: 1,
|
||||
limit: 20,
|
||||
total: 0,
|
||||
totalPages: 0
|
||||
}
|
||||
},
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
}
|
||||
authenticateAdmin,
|
||||
requirePermissions([ADMIN_PERMISSIONS.EVENT_MANAGEMENT]),
|
||||
validators.validatePagination,
|
||||
auditAdminAction('view_events'),
|
||||
(req, res) => {
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Events endpoint - feature not yet implemented',
|
||||
data: {
|
||||
events: [],
|
||||
pagination: {
|
||||
page: 1,
|
||||
limit: 20,
|
||||
total: 0,
|
||||
totalPages: 0,
|
||||
},
|
||||
},
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
@ -375,34 +375,34 @@ router.get('/events',
|
|||
* /api/admin/analytics/*
|
||||
*/
|
||||
router.get('/analytics',
|
||||
authenticateAdmin,
|
||||
requirePermissions([ADMIN_PERMISSIONS.ANALYTICS_READ]),
|
||||
auditAdminAction('view_analytics'),
|
||||
(req, res) => {
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Analytics endpoint - feature not yet implemented',
|
||||
data: {
|
||||
analytics: {},
|
||||
timeRange: 'daily',
|
||||
metrics: []
|
||||
},
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
}
|
||||
authenticateAdmin,
|
||||
requirePermissions([ADMIN_PERMISSIONS.ANALYTICS_READ]),
|
||||
auditAdminAction('view_analytics'),
|
||||
(req, res) => {
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Analytics endpoint - feature not yet implemented',
|
||||
data: {
|
||||
analytics: {},
|
||||
timeRange: 'daily',
|
||||
metrics: [],
|
||||
},
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Error handling for admin routes
|
||||
*/
|
||||
router.use('*', (req, res) => {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
error: 'Admin API endpoint not found',
|
||||
message: `The endpoint ${req.method} ${req.originalUrl} does not exist`,
|
||||
correlationId: req.correlationId,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
error: 'Admin API endpoint not found',
|
||||
message: `The endpoint ${req.method} ${req.originalUrl} does not exist`,
|
||||
correlationId: req.correlationId,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router;
|
||||
|
|
|
|||
|
|
@ -8,21 +8,21 @@ const router = express.Router();
|
|||
|
||||
// Import controllers
|
||||
const {
|
||||
getCombatStatistics,
|
||||
getCombatQueue,
|
||||
forceResolveCombat,
|
||||
cancelBattle,
|
||||
getCombatConfigurations,
|
||||
saveCombatConfiguration,
|
||||
deleteCombatConfiguration
|
||||
getCombatStatistics,
|
||||
getCombatQueue,
|
||||
forceResolveCombat,
|
||||
cancelBattle,
|
||||
getCombatConfigurations,
|
||||
saveCombatConfiguration,
|
||||
deleteCombatConfiguration,
|
||||
} = require('../../controllers/admin/combat.controller');
|
||||
|
||||
// Import middleware
|
||||
const { authenticateAdmin } = require('../../middleware/admin.middleware');
|
||||
const {
|
||||
validateCombatQueueQuery,
|
||||
validateParams,
|
||||
logCombatAction
|
||||
validateCombatQueueQuery,
|
||||
validateParams,
|
||||
logCombatAction,
|
||||
} = require('../../middleware/combat.middleware');
|
||||
const { validateCombatConfiguration } = require('../../validators/combat.validators');
|
||||
|
||||
|
|
@ -35,8 +35,8 @@ router.use(authenticateAdmin);
|
|||
* @access Admin
|
||||
*/
|
||||
router.get('/statistics',
|
||||
logCombatAction('admin_get_combat_statistics'),
|
||||
getCombatStatistics
|
||||
logCombatAction('admin_get_combat_statistics'),
|
||||
getCombatStatistics,
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
@ -45,9 +45,9 @@ router.get('/statistics',
|
|||
* @access Admin
|
||||
*/
|
||||
router.get('/queue',
|
||||
logCombatAction('admin_get_combat_queue'),
|
||||
validateCombatQueueQuery,
|
||||
getCombatQueue
|
||||
logCombatAction('admin_get_combat_queue'),
|
||||
validateCombatQueueQuery,
|
||||
getCombatQueue,
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
@ -56,9 +56,9 @@ router.get('/queue',
|
|||
* @access Admin
|
||||
*/
|
||||
router.post('/resolve/:battleId',
|
||||
logCombatAction('admin_force_resolve_combat'),
|
||||
validateParams('battleId'),
|
||||
forceResolveCombat
|
||||
logCombatAction('admin_force_resolve_combat'),
|
||||
validateParams('battleId'),
|
||||
forceResolveCombat,
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
@ -67,20 +67,20 @@ router.post('/resolve/:battleId',
|
|||
* @access Admin
|
||||
*/
|
||||
router.post('/cancel/:battleId',
|
||||
logCombatAction('admin_cancel_battle'),
|
||||
validateParams('battleId'),
|
||||
(req, res, next) => {
|
||||
// Validate cancel reason in request body
|
||||
const { reason } = req.body;
|
||||
if (!reason || typeof reason !== 'string' || reason.trim().length < 5) {
|
||||
return res.status(400).json({
|
||||
error: 'Cancel reason is required and must be at least 5 characters',
|
||||
code: 'INVALID_CANCEL_REASON'
|
||||
});
|
||||
}
|
||||
next();
|
||||
},
|
||||
cancelBattle
|
||||
logCombatAction('admin_cancel_battle'),
|
||||
validateParams('battleId'),
|
||||
(req, res, next) => {
|
||||
// Validate cancel reason in request body
|
||||
const { reason } = req.body;
|
||||
if (!reason || typeof reason !== 'string' || reason.trim().length < 5) {
|
||||
return res.status(400).json({
|
||||
error: 'Cancel reason is required and must be at least 5 characters',
|
||||
code: 'INVALID_CANCEL_REASON',
|
||||
});
|
||||
}
|
||||
next();
|
||||
},
|
||||
cancelBattle,
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
@ -89,8 +89,8 @@ router.post('/cancel/:battleId',
|
|||
* @access Admin
|
||||
*/
|
||||
router.get('/configurations',
|
||||
logCombatAction('admin_get_combat_configurations'),
|
||||
getCombatConfigurations
|
||||
logCombatAction('admin_get_combat_configurations'),
|
||||
getCombatConfigurations,
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
@ -99,25 +99,25 @@ router.get('/configurations',
|
|||
* @access Admin
|
||||
*/
|
||||
router.post('/configurations',
|
||||
logCombatAction('admin_create_combat_configuration'),
|
||||
(req, res, next) => {
|
||||
const { error, value } = validateCombatConfiguration(req.body);
|
||||
if (error) {
|
||||
const details = error.details.map(detail => ({
|
||||
field: detail.path.join('.'),
|
||||
message: detail.message
|
||||
}));
|
||||
|
||||
return res.status(400).json({
|
||||
error: 'Validation failed',
|
||||
code: 'VALIDATION_ERROR',
|
||||
details
|
||||
});
|
||||
}
|
||||
req.body = value;
|
||||
next();
|
||||
},
|
||||
saveCombatConfiguration
|
||||
logCombatAction('admin_create_combat_configuration'),
|
||||
(req, res, next) => {
|
||||
const { error, value } = validateCombatConfiguration(req.body);
|
||||
if (error) {
|
||||
const details = error.details.map(detail => ({
|
||||
field: detail.path.join('.'),
|
||||
message: detail.message,
|
||||
}));
|
||||
|
||||
return res.status(400).json({
|
||||
error: 'Validation failed',
|
||||
code: 'VALIDATION_ERROR',
|
||||
details,
|
||||
});
|
||||
}
|
||||
req.body = value;
|
||||
next();
|
||||
},
|
||||
saveCombatConfiguration,
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
@ -126,26 +126,26 @@ router.post('/configurations',
|
|||
* @access Admin
|
||||
*/
|
||||
router.put('/configurations/:configId',
|
||||
logCombatAction('admin_update_combat_configuration'),
|
||||
validateParams('configId'),
|
||||
(req, res, next) => {
|
||||
const { error, value } = validateCombatConfiguration(req.body);
|
||||
if (error) {
|
||||
const details = error.details.map(detail => ({
|
||||
field: detail.path.join('.'),
|
||||
message: detail.message
|
||||
}));
|
||||
|
||||
return res.status(400).json({
|
||||
error: 'Validation failed',
|
||||
code: 'VALIDATION_ERROR',
|
||||
details
|
||||
});
|
||||
}
|
||||
req.body = value;
|
||||
next();
|
||||
},
|
||||
saveCombatConfiguration
|
||||
logCombatAction('admin_update_combat_configuration'),
|
||||
validateParams('configId'),
|
||||
(req, res, next) => {
|
||||
const { error, value } = validateCombatConfiguration(req.body);
|
||||
if (error) {
|
||||
const details = error.details.map(detail => ({
|
||||
field: detail.path.join('.'),
|
||||
message: detail.message,
|
||||
}));
|
||||
|
||||
return res.status(400).json({
|
||||
error: 'Validation failed',
|
||||
code: 'VALIDATION_ERROR',
|
||||
details,
|
||||
});
|
||||
}
|
||||
req.body = value;
|
||||
next();
|
||||
},
|
||||
saveCombatConfiguration,
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
@ -154,9 +154,9 @@ router.put('/configurations/:configId',
|
|||
* @access Admin
|
||||
*/
|
||||
router.delete('/configurations/:configId',
|
||||
logCombatAction('admin_delete_combat_configuration'),
|
||||
validateParams('configId'),
|
||||
deleteCombatConfiguration
|
||||
logCombatAction('admin_delete_combat_configuration'),
|
||||
validateParams('configId'),
|
||||
deleteCombatConfiguration,
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
@ -165,98 +165,98 @@ router.delete('/configurations/:configId',
|
|||
* @access Admin
|
||||
*/
|
||||
router.get('/battles',
|
||||
logCombatAction('admin_get_battles'),
|
||||
async (req, res, next) => {
|
||||
try {
|
||||
const {
|
||||
status,
|
||||
battle_type,
|
||||
location,
|
||||
limit = 50,
|
||||
offset = 0,
|
||||
start_date,
|
||||
end_date
|
||||
} = req.query;
|
||||
logCombatAction('admin_get_battles'),
|
||||
async (req, res, next) => {
|
||||
try {
|
||||
const {
|
||||
status,
|
||||
battle_type,
|
||||
location,
|
||||
limit = 50,
|
||||
offset = 0,
|
||||
start_date,
|
||||
end_date,
|
||||
} = req.query;
|
||||
|
||||
const db = require('../../database/connection');
|
||||
const logger = require('../../utils/logger');
|
||||
const db = require('../../database/connection');
|
||||
const logger = require('../../utils/logger');
|
||||
|
||||
let query = db('battles')
|
||||
.select([
|
||||
'battles.*',
|
||||
'combat_configurations.config_name',
|
||||
'combat_configurations.combat_type'
|
||||
])
|
||||
.leftJoin('combat_configurations', 'battles.combat_configuration_id', 'combat_configurations.id')
|
||||
.orderBy('battles.started_at', 'desc')
|
||||
.limit(parseInt(limit))
|
||||
.offset(parseInt(offset));
|
||||
let query = db('battles')
|
||||
.select([
|
||||
'battles.*',
|
||||
'combat_configurations.config_name',
|
||||
'combat_configurations.combat_type',
|
||||
])
|
||||
.leftJoin('combat_configurations', 'battles.combat_configuration_id', 'combat_configurations.id')
|
||||
.orderBy('battles.started_at', 'desc')
|
||||
.limit(parseInt(limit))
|
||||
.offset(parseInt(offset));
|
||||
|
||||
if (status) {
|
||||
query = query.where('battles.status', status);
|
||||
}
|
||||
if (status) {
|
||||
query = query.where('battles.status', status);
|
||||
}
|
||||
|
||||
if (battle_type) {
|
||||
query = query.where('battles.battle_type', battle_type);
|
||||
}
|
||||
if (battle_type) {
|
||||
query = query.where('battles.battle_type', battle_type);
|
||||
}
|
||||
|
||||
if (location) {
|
||||
query = query.where('battles.location', location);
|
||||
}
|
||||
if (location) {
|
||||
query = query.where('battles.location', location);
|
||||
}
|
||||
|
||||
if (start_date) {
|
||||
query = query.where('battles.started_at', '>=', new Date(start_date));
|
||||
}
|
||||
if (start_date) {
|
||||
query = query.where('battles.started_at', '>=', new Date(start_date));
|
||||
}
|
||||
|
||||
if (end_date) {
|
||||
query = query.where('battles.started_at', '<=', new Date(end_date));
|
||||
}
|
||||
if (end_date) {
|
||||
query = query.where('battles.started_at', '<=', new Date(end_date));
|
||||
}
|
||||
|
||||
const battles = await query;
|
||||
const battles = await query;
|
||||
|
||||
// Get total count for pagination
|
||||
let countQuery = db('battles').count('* as total');
|
||||
|
||||
if (status) countQuery = countQuery.where('status', status);
|
||||
if (battle_type) countQuery = countQuery.where('battle_type', battle_type);
|
||||
if (location) countQuery = countQuery.where('location', location);
|
||||
if (start_date) countQuery = countQuery.where('started_at', '>=', new Date(start_date));
|
||||
if (end_date) countQuery = countQuery.where('started_at', '<=', new Date(end_date));
|
||||
// Get total count for pagination
|
||||
let countQuery = db('battles').count('* as total');
|
||||
|
||||
const [{ total }] = await countQuery;
|
||||
if (status) countQuery = countQuery.where('status', status);
|
||||
if (battle_type) countQuery = countQuery.where('battle_type', battle_type);
|
||||
if (location) countQuery = countQuery.where('location', location);
|
||||
if (start_date) countQuery = countQuery.where('started_at', '>=', new Date(start_date));
|
||||
if (end_date) countQuery = countQuery.where('started_at', '<=', new Date(end_date));
|
||||
|
||||
// Parse participants JSON for each battle
|
||||
const battlesWithParsedParticipants = battles.map(battle => ({
|
||||
...battle,
|
||||
participants: JSON.parse(battle.participants),
|
||||
battle_data: battle.battle_data ? JSON.parse(battle.battle_data) : null,
|
||||
result: battle.result ? JSON.parse(battle.result) : null
|
||||
}));
|
||||
const [{ total }] = await countQuery;
|
||||
|
||||
logger.info('Admin battles retrieved', {
|
||||
correlationId: req.correlationId,
|
||||
adminUser: req.user.id,
|
||||
count: battles.length,
|
||||
total: parseInt(total)
|
||||
});
|
||||
// Parse participants JSON for each battle
|
||||
const battlesWithParsedParticipants = battles.map(battle => ({
|
||||
...battle,
|
||||
participants: JSON.parse(battle.participants),
|
||||
battle_data: battle.battle_data ? JSON.parse(battle.battle_data) : null,
|
||||
result: battle.result ? JSON.parse(battle.result) : null,
|
||||
}));
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
battles: battlesWithParsedParticipants,
|
||||
pagination: {
|
||||
total: parseInt(total),
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset),
|
||||
hasMore: (parseInt(offset) + parseInt(limit)) < parseInt(total)
|
||||
}
|
||||
}
|
||||
});
|
||||
logger.info('Admin battles retrieved', {
|
||||
correlationId: req.correlationId,
|
||||
adminUser: req.user.id,
|
||||
count: battles.length,
|
||||
total: parseInt(total),
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
battles: battlesWithParsedParticipants,
|
||||
pagination: {
|
||||
total: parseInt(total),
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset),
|
||||
hasMore: (parseInt(offset) + parseInt(limit)) < parseInt(total),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
@ -265,81 +265,81 @@ router.get('/battles',
|
|||
* @access Admin
|
||||
*/
|
||||
router.get('/encounters/:encounterId',
|
||||
logCombatAction('admin_get_combat_encounter'),
|
||||
validateParams('encounterId'),
|
||||
async (req, res, next) => {
|
||||
try {
|
||||
const encounterId = parseInt(req.params.encounterId);
|
||||
const db = require('../../database/connection');
|
||||
const logger = require('../../utils/logger');
|
||||
logCombatAction('admin_get_combat_encounter'),
|
||||
validateParams('encounterId'),
|
||||
async (req, res, next) => {
|
||||
try {
|
||||
const encounterId = parseInt(req.params.encounterId);
|
||||
const db = require('../../database/connection');
|
||||
const logger = require('../../utils/logger');
|
||||
|
||||
// Get encounter with all related data
|
||||
const encounter = await db('combat_encounters')
|
||||
.select([
|
||||
'combat_encounters.*',
|
||||
'battles.battle_type',
|
||||
'battles.participants',
|
||||
'battles.started_at as battle_started',
|
||||
'battles.completed_at as battle_completed',
|
||||
'attacker_fleet.name as attacker_fleet_name',
|
||||
'attacker_player.username as attacker_username',
|
||||
'defender_fleet.name as defender_fleet_name',
|
||||
'defender_player.username as defender_username',
|
||||
'defender_colony.name as defender_colony_name',
|
||||
'colony_player.username as colony_owner_username'
|
||||
])
|
||||
.join('battles', 'combat_encounters.battle_id', 'battles.id')
|
||||
.leftJoin('fleets as attacker_fleet', 'combat_encounters.attacker_fleet_id', 'attacker_fleet.id')
|
||||
.leftJoin('players as attacker_player', 'attacker_fleet.player_id', 'attacker_player.id')
|
||||
.leftJoin('fleets as defender_fleet', 'combat_encounters.defender_fleet_id', 'defender_fleet.id')
|
||||
.leftJoin('players as defender_player', 'defender_fleet.player_id', 'defender_player.id')
|
||||
.leftJoin('colonies as defender_colony', 'combat_encounters.defender_colony_id', 'defender_colony.id')
|
||||
.leftJoin('players as colony_player', 'defender_colony.player_id', 'colony_player.id')
|
||||
.where('combat_encounters.id', encounterId)
|
||||
.first();
|
||||
// Get encounter with all related data
|
||||
const encounter = await db('combat_encounters')
|
||||
.select([
|
||||
'combat_encounters.*',
|
||||
'battles.battle_type',
|
||||
'battles.participants',
|
||||
'battles.started_at as battle_started',
|
||||
'battles.completed_at as battle_completed',
|
||||
'attacker_fleet.name as attacker_fleet_name',
|
||||
'attacker_player.username as attacker_username',
|
||||
'defender_fleet.name as defender_fleet_name',
|
||||
'defender_player.username as defender_username',
|
||||
'defender_colony.name as defender_colony_name',
|
||||
'colony_player.username as colony_owner_username',
|
||||
])
|
||||
.join('battles', 'combat_encounters.battle_id', 'battles.id')
|
||||
.leftJoin('fleets as attacker_fleet', 'combat_encounters.attacker_fleet_id', 'attacker_fleet.id')
|
||||
.leftJoin('players as attacker_player', 'attacker_fleet.player_id', 'attacker_player.id')
|
||||
.leftJoin('fleets as defender_fleet', 'combat_encounters.defender_fleet_id', 'defender_fleet.id')
|
||||
.leftJoin('players as defender_player', 'defender_fleet.player_id', 'defender_player.id')
|
||||
.leftJoin('colonies as defender_colony', 'combat_encounters.defender_colony_id', 'defender_colony.id')
|
||||
.leftJoin('players as colony_player', 'defender_colony.player_id', 'colony_player.id')
|
||||
.where('combat_encounters.id', encounterId)
|
||||
.first();
|
||||
|
||||
if (!encounter) {
|
||||
return res.status(404).json({
|
||||
error: 'Combat encounter not found',
|
||||
code: 'ENCOUNTER_NOT_FOUND'
|
||||
});
|
||||
}
|
||||
if (!encounter) {
|
||||
return res.status(404).json({
|
||||
error: 'Combat encounter not found',
|
||||
code: 'ENCOUNTER_NOT_FOUND',
|
||||
});
|
||||
}
|
||||
|
||||
// Get combat logs
|
||||
const combatLogs = await db('combat_logs')
|
||||
.where('encounter_id', encounterId)
|
||||
.orderBy('round_number')
|
||||
.orderBy('timestamp');
|
||||
// Get combat logs
|
||||
const combatLogs = await db('combat_logs')
|
||||
.where('encounter_id', encounterId)
|
||||
.orderBy('round_number')
|
||||
.orderBy('timestamp');
|
||||
|
||||
const detailedEncounter = {
|
||||
...encounter,
|
||||
participants: JSON.parse(encounter.participants),
|
||||
initial_forces: JSON.parse(encounter.initial_forces),
|
||||
final_forces: JSON.parse(encounter.final_forces),
|
||||
casualties: JSON.parse(encounter.casualties),
|
||||
combat_log: JSON.parse(encounter.combat_log),
|
||||
loot_awarded: JSON.parse(encounter.loot_awarded),
|
||||
detailed_logs: combatLogs.map(log => ({
|
||||
...log,
|
||||
event_data: JSON.parse(log.event_data)
|
||||
}))
|
||||
};
|
||||
const detailedEncounter = {
|
||||
...encounter,
|
||||
participants: JSON.parse(encounter.participants),
|
||||
initial_forces: JSON.parse(encounter.initial_forces),
|
||||
final_forces: JSON.parse(encounter.final_forces),
|
||||
casualties: JSON.parse(encounter.casualties),
|
||||
combat_log: JSON.parse(encounter.combat_log),
|
||||
loot_awarded: JSON.parse(encounter.loot_awarded),
|
||||
detailed_logs: combatLogs.map(log => ({
|
||||
...log,
|
||||
event_data: JSON.parse(log.event_data),
|
||||
})),
|
||||
};
|
||||
|
||||
logger.info('Admin combat encounter retrieved', {
|
||||
correlationId: req.correlationId,
|
||||
adminUser: req.user.id,
|
||||
encounterId
|
||||
});
|
||||
logger.info('Admin combat encounter retrieved', {
|
||||
correlationId: req.correlationId,
|
||||
adminUser: req.user.id,
|
||||
encounterId,
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: detailedEncounter
|
||||
});
|
||||
res.json({
|
||||
success: true,
|
||||
data: detailedEncounter,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router;
|
||||
|
|
|
|||
|
|
@ -39,4 +39,4 @@ router.get('/status', authenticateToken('admin'), asyncHandler(async (req, res)
|
|||
});
|
||||
}));
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router;
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const logger = require('../../utils/logger');
|
||||
const {
|
||||
gameTickService,
|
||||
getGameTickStatus,
|
||||
triggerManualTick
|
||||
const {
|
||||
gameTickService,
|
||||
getGameTickStatus,
|
||||
triggerManualTick,
|
||||
} = require('../../services/game-tick.service');
|
||||
const db = require('../../database/connection');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
|
|
@ -20,22 +20,22 @@ const { v4: uuidv4 } = require('uuid');
|
|||
*/
|
||||
router.get('/tick/status', async (req, res) => {
|
||||
const correlationId = req.correlationId || uuidv4();
|
||||
|
||||
|
||||
try {
|
||||
logger.info('Admin requesting game tick status', {
|
||||
correlationId,
|
||||
adminId: req.user?.id,
|
||||
adminUsername: req.user?.username
|
||||
adminUsername: req.user?.username,
|
||||
});
|
||||
|
||||
const status = getGameTickStatus();
|
||||
|
||||
|
||||
// Get recent tick logs
|
||||
const recentLogs = await db('game_tick_log')
|
||||
.select('*')
|
||||
.orderBy('tick_number', 'desc')
|
||||
.limit(10);
|
||||
|
||||
|
||||
// Get performance statistics
|
||||
const performanceStats = await db('game_tick_log')
|
||||
.select(
|
||||
|
|
@ -43,20 +43,20 @@ router.get('/tick/status', async (req, res) => {
|
|||
db.raw('COUNT(*) as total_ticks'),
|
||||
db.raw('COUNT(*) FILTER (WHERE status = \'completed\') as successful_ticks'),
|
||||
db.raw('COUNT(*) FILTER (WHERE status = \'failed\') as failed_ticks'),
|
||||
db.raw('MAX(tick_number) as latest_tick')
|
||||
db.raw('MAX(tick_number) as latest_tick'),
|
||||
)
|
||||
.where('started_at', '>=', db.raw("NOW() - INTERVAL '24 hours'"))
|
||||
.where('started_at', '>=', db.raw('NOW() - INTERVAL \'24 hours\''))
|
||||
.first();
|
||||
|
||||
|
||||
// Get user group statistics
|
||||
const userGroupStats = await db('game_tick_log')
|
||||
.select(
|
||||
'user_group',
|
||||
db.raw('COUNT(*) as tick_count'),
|
||||
db.raw('AVG(processed_players) as avg_players'),
|
||||
db.raw('COUNT(*) FILTER (WHERE status = \'failed\') as failures')
|
||||
db.raw('COUNT(*) FILTER (WHERE status = \'failed\') as failures'),
|
||||
)
|
||||
.where('started_at', '>=', db.raw("NOW() - INTERVAL '24 hours'"))
|
||||
.where('started_at', '>=', db.raw('NOW() - INTERVAL \'24 hours\''))
|
||||
.groupBy('user_group')
|
||||
.orderBy('user_group');
|
||||
|
||||
|
|
@ -75,11 +75,11 @@ router.get('/tick/status', async (req, res) => {
|
|||
duration: log.performance_metrics?.duration_ms,
|
||||
startedAt: log.started_at,
|
||||
completedAt: log.completed_at,
|
||||
errorMessage: log.error_message
|
||||
}))
|
||||
errorMessage: log.error_message,
|
||||
})),
|
||||
},
|
||||
timestamp: new Date().toISOString(),
|
||||
correlationId
|
||||
correlationId,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
|
|
@ -87,13 +87,13 @@ router.get('/tick/status', async (req, res) => {
|
|||
correlationId,
|
||||
adminId: req.user?.id,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
stack: error.stack,
|
||||
});
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to retrieve game tick status',
|
||||
correlationId
|
||||
correlationId,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -104,16 +104,16 @@ router.get('/tick/status', async (req, res) => {
|
|||
*/
|
||||
router.post('/tick/trigger', async (req, res) => {
|
||||
const correlationId = req.correlationId || uuidv4();
|
||||
|
||||
|
||||
try {
|
||||
logger.info('Admin triggering manual game tick', {
|
||||
correlationId,
|
||||
adminId: req.user?.id,
|
||||
adminUsername: req.user?.username
|
||||
adminUsername: req.user?.username,
|
||||
});
|
||||
|
||||
const result = await triggerManualTick(correlationId);
|
||||
|
||||
|
||||
// Log admin action
|
||||
await db('audit_log').insert({
|
||||
entity_type: 'game_tick',
|
||||
|
|
@ -123,10 +123,10 @@ router.post('/tick/trigger', async (req, res) => {
|
|||
actor_id: req.user?.id,
|
||||
changes: {
|
||||
correlation_id: correlationId,
|
||||
triggered_by: req.user?.username
|
||||
triggered_by: req.user?.username,
|
||||
},
|
||||
ip_address: req.ip,
|
||||
user_agent: req.get('User-Agent')
|
||||
user_agent: req.get('User-Agent'),
|
||||
});
|
||||
|
||||
res.json({
|
||||
|
|
@ -134,7 +134,7 @@ router.post('/tick/trigger', async (req, res) => {
|
|||
message: 'Manual game tick triggered successfully',
|
||||
data: result,
|
||||
timestamp: new Date().toISOString(),
|
||||
correlationId
|
||||
correlationId,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
|
|
@ -142,13 +142,13 @@ router.post('/tick/trigger', async (req, res) => {
|
|||
correlationId,
|
||||
adminId: req.user?.id,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
stack: error.stack,
|
||||
});
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message || 'Failed to trigger manual game tick',
|
||||
correlationId
|
||||
correlationId,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -159,34 +159,34 @@ router.post('/tick/trigger', async (req, res) => {
|
|||
*/
|
||||
router.put('/tick/config', async (req, res) => {
|
||||
const correlationId = req.correlationId || uuidv4();
|
||||
|
||||
|
||||
try {
|
||||
const {
|
||||
tick_interval_ms,
|
||||
user_groups_count,
|
||||
max_retry_attempts,
|
||||
bonus_tick_threshold,
|
||||
retry_delay_ms
|
||||
retry_delay_ms,
|
||||
} = req.body;
|
||||
|
||||
logger.info('Admin updating game tick configuration', {
|
||||
correlationId,
|
||||
adminId: req.user?.id,
|
||||
adminUsername: req.user?.username,
|
||||
newConfig: req.body
|
||||
newConfig: req.body,
|
||||
});
|
||||
|
||||
// Validate configuration values
|
||||
const validationErrors = [];
|
||||
|
||||
|
||||
if (tick_interval_ms && (tick_interval_ms < 10000 || tick_interval_ms > 3600000)) {
|
||||
validationErrors.push('tick_interval_ms must be between 10000 and 3600000 (10 seconds to 1 hour)');
|
||||
}
|
||||
|
||||
|
||||
if (user_groups_count && (user_groups_count < 1 || user_groups_count > 50)) {
|
||||
validationErrors.push('user_groups_count must be between 1 and 50');
|
||||
}
|
||||
|
||||
|
||||
if (max_retry_attempts && (max_retry_attempts < 1 || max_retry_attempts > 10)) {
|
||||
validationErrors.push('max_retry_attempts must be between 1 and 10');
|
||||
}
|
||||
|
|
@ -196,7 +196,7 @@ router.put('/tick/config', async (req, res) => {
|
|||
success: false,
|
||||
error: 'Configuration validation failed',
|
||||
details: validationErrors,
|
||||
correlationId
|
||||
correlationId,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -209,7 +209,7 @@ router.put('/tick/config', async (req, res) => {
|
|||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'No active game tick configuration found',
|
||||
correlationId
|
||||
correlationId,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -222,7 +222,7 @@ router.put('/tick/config', async (req, res) => {
|
|||
max_retry_attempts: max_retry_attempts || currentConfig.max_retry_attempts,
|
||||
bonus_tick_threshold: bonus_tick_threshold || currentConfig.bonus_tick_threshold,
|
||||
retry_delay_ms: retry_delay_ms || currentConfig.retry_delay_ms,
|
||||
updated_at: new Date()
|
||||
updated_at: new Date(),
|
||||
})
|
||||
.returning('*');
|
||||
|
||||
|
|
@ -236,10 +236,10 @@ router.put('/tick/config', async (req, res) => {
|
|||
changes: {
|
||||
before: currentConfig,
|
||||
after: updatedConfig[0],
|
||||
updated_by: req.user?.username
|
||||
updated_by: req.user?.username,
|
||||
},
|
||||
ip_address: req.ip,
|
||||
user_agent: req.get('User-Agent')
|
||||
user_agent: req.get('User-Agent'),
|
||||
});
|
||||
|
||||
// Reload configuration in the service
|
||||
|
|
@ -250,10 +250,10 @@ router.put('/tick/config', async (req, res) => {
|
|||
message: 'Game tick configuration updated successfully',
|
||||
data: {
|
||||
previousConfig: currentConfig,
|
||||
newConfig: updatedConfig[0]
|
||||
newConfig: updatedConfig[0],
|
||||
},
|
||||
timestamp: new Date().toISOString(),
|
||||
correlationId
|
||||
correlationId,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
|
|
@ -261,13 +261,13 @@ router.put('/tick/config', async (req, res) => {
|
|||
correlationId,
|
||||
adminId: req.user?.id,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
stack: error.stack,
|
||||
});
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to update game tick configuration',
|
||||
correlationId
|
||||
correlationId,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -278,7 +278,7 @@ router.put('/tick/config', async (req, res) => {
|
|||
*/
|
||||
router.get('/tick/logs', async (req, res) => {
|
||||
const correlationId = req.correlationId || uuidv4();
|
||||
|
||||
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
|
|
@ -287,7 +287,7 @@ router.get('/tick/logs', async (req, res) => {
|
|||
userGroup,
|
||||
tickNumber,
|
||||
startDate,
|
||||
endDate
|
||||
endDate,
|
||||
} = req.query;
|
||||
|
||||
const pageNum = parseInt(page);
|
||||
|
|
@ -300,19 +300,19 @@ router.get('/tick/logs', async (req, res) => {
|
|||
if (status) {
|
||||
query = query.where('status', status);
|
||||
}
|
||||
|
||||
|
||||
if (userGroup !== undefined) {
|
||||
query = query.where('user_group', parseInt(userGroup));
|
||||
}
|
||||
|
||||
|
||||
if (tickNumber) {
|
||||
query = query.where('tick_number', parseInt(tickNumber));
|
||||
}
|
||||
|
||||
|
||||
if (startDate) {
|
||||
query = query.where('started_at', '>=', new Date(startDate));
|
||||
}
|
||||
|
||||
|
||||
if (endDate) {
|
||||
query = query.where('started_at', '<=', new Date(endDate));
|
||||
}
|
||||
|
|
@ -341,17 +341,17 @@ router.get('/tick/logs', async (req, res) => {
|
|||
errorMessage: log.error_message,
|
||||
performanceMetrics: log.performance_metrics,
|
||||
startedAt: log.started_at,
|
||||
completedAt: log.completed_at
|
||||
completedAt: log.completed_at,
|
||||
})),
|
||||
pagination: {
|
||||
page: pageNum,
|
||||
limit: limitNum,
|
||||
total: parseInt(total),
|
||||
pages: Math.ceil(total / limitNum)
|
||||
}
|
||||
pages: Math.ceil(total / limitNum),
|
||||
},
|
||||
},
|
||||
timestamp: new Date().toISOString(),
|
||||
correlationId
|
||||
correlationId,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
|
|
@ -359,13 +359,13 @@ router.get('/tick/logs', async (req, res) => {
|
|||
correlationId,
|
||||
adminId: req.user?.id,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
stack: error.stack,
|
||||
});
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to retrieve game tick logs',
|
||||
correlationId
|
||||
correlationId,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -376,26 +376,26 @@ router.get('/tick/logs', async (req, res) => {
|
|||
*/
|
||||
router.get('/performance', async (req, res) => {
|
||||
const correlationId = req.correlationId || uuidv4();
|
||||
|
||||
|
||||
try {
|
||||
const { timeRange = '24h' } = req.query;
|
||||
|
||||
|
||||
let interval;
|
||||
switch (timeRange) {
|
||||
case '1h':
|
||||
interval = "1 hour";
|
||||
break;
|
||||
case '24h':
|
||||
interval = "24 hours";
|
||||
break;
|
||||
case '7d':
|
||||
interval = "7 days";
|
||||
break;
|
||||
case '30d':
|
||||
interval = "30 days";
|
||||
break;
|
||||
default:
|
||||
interval = "24 hours";
|
||||
case '1h':
|
||||
interval = '1 hour';
|
||||
break;
|
||||
case '24h':
|
||||
interval = '24 hours';
|
||||
break;
|
||||
case '7d':
|
||||
interval = '7 days';
|
||||
break;
|
||||
case '30d':
|
||||
interval = '30 days';
|
||||
break;
|
||||
default:
|
||||
interval = '24 hours';
|
||||
}
|
||||
|
||||
// Get tick performance metrics
|
||||
|
|
@ -406,7 +406,7 @@ router.get('/performance', async (req, res) => {
|
|||
db.raw('COUNT(*) FILTER (WHERE status = \'completed\') as successful_ticks'),
|
||||
db.raw('COUNT(*) FILTER (WHERE status = \'failed\') as failed_ticks'),
|
||||
db.raw('AVG(processed_players) as avg_players_processed'),
|
||||
db.raw('AVG(EXTRACT(EPOCH FROM (completed_at - started_at)) * 1000) as avg_duration_ms')
|
||||
db.raw('AVG(EXTRACT(EPOCH FROM (completed_at - started_at)) * 1000) as avg_duration_ms'),
|
||||
)
|
||||
.where('started_at', '>=', db.raw(`NOW() - INTERVAL '${interval}'`))
|
||||
.groupBy(db.raw('DATE_TRUNC(\'hour\', started_at)'))
|
||||
|
|
@ -433,7 +433,7 @@ router.get('/performance', async (req, res) => {
|
|||
.select(
|
||||
db.raw('COUNT(*) FILTER (WHERE is_active = true) as active_players'),
|
||||
db.raw('COUNT(*) FILTER (WHERE last_login >= NOW() - INTERVAL \'24 hours\') as recent_players'),
|
||||
db.raw('COUNT(*) as total_players')
|
||||
db.raw('COUNT(*) as total_players'),
|
||||
)
|
||||
.first();
|
||||
|
||||
|
|
@ -446,16 +446,16 @@ router.get('/performance', async (req, res) => {
|
|||
totalTicks: parseInt(metric.total_ticks),
|
||||
successfulTicks: parseInt(metric.successful_ticks),
|
||||
failedTicks: parseInt(metric.failed_ticks),
|
||||
successRate: metric.total_ticks > 0 ?
|
||||
successRate: metric.total_ticks > 0 ?
|
||||
((metric.successful_ticks / metric.total_ticks) * 100).toFixed(2) : 0,
|
||||
avgPlayersProcessed: parseFloat(metric.avg_players_processed || 0).toFixed(1),
|
||||
avgDurationMs: parseFloat(metric.avg_duration_ms || 0).toFixed(2)
|
||||
avgDurationMs: parseFloat(metric.avg_duration_ms || 0).toFixed(2),
|
||||
})),
|
||||
databaseMetrics: dbMetrics.rows,
|
||||
playerStats
|
||||
playerStats,
|
||||
},
|
||||
timestamp: new Date().toISOString(),
|
||||
correlationId
|
||||
correlationId,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
|
|
@ -463,13 +463,13 @@ router.get('/performance', async (req, res) => {
|
|||
correlationId,
|
||||
adminId: req.user?.id,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
stack: error.stack,
|
||||
});
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to retrieve performance metrics',
|
||||
correlationId
|
||||
correlationId,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -480,16 +480,16 @@ router.get('/performance', async (req, res) => {
|
|||
*/
|
||||
router.post('/tick/stop', async (req, res) => {
|
||||
const correlationId = req.correlationId || uuidv4();
|
||||
|
||||
|
||||
try {
|
||||
logger.warn('Admin stopping game tick service', {
|
||||
correlationId,
|
||||
adminId: req.user?.id,
|
||||
adminUsername: req.user?.username
|
||||
adminUsername: req.user?.username,
|
||||
});
|
||||
|
||||
gameTickService.stop();
|
||||
|
||||
|
||||
// Log admin action
|
||||
await db('audit_log').insert({
|
||||
entity_type: 'game_tick',
|
||||
|
|
@ -500,30 +500,30 @@ router.post('/tick/stop', async (req, res) => {
|
|||
changes: {
|
||||
correlation_id: correlationId,
|
||||
stopped_by: req.user?.username,
|
||||
timestamp: new Date().toISOString()
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
ip_address: req.ip,
|
||||
user_agent: req.get('User-Agent')
|
||||
user_agent: req.get('User-Agent'),
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Game tick service stopped successfully',
|
||||
timestamp: new Date().toISOString(),
|
||||
correlationId
|
||||
correlationId,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Failed to stop game tick service', {
|
||||
correlationId,
|
||||
adminId: req.user?.id,
|
||||
error: error.message
|
||||
error: error.message,
|
||||
});
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to stop game tick service',
|
||||
correlationId
|
||||
correlationId,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -534,16 +534,16 @@ router.post('/tick/stop', async (req, res) => {
|
|||
*/
|
||||
router.post('/tick/start', async (req, res) => {
|
||||
const correlationId = req.correlationId || uuidv4();
|
||||
|
||||
|
||||
try {
|
||||
logger.info('Admin starting game tick service', {
|
||||
correlationId,
|
||||
adminId: req.user?.id,
|
||||
adminUsername: req.user?.username
|
||||
adminUsername: req.user?.username,
|
||||
});
|
||||
|
||||
await gameTickService.initialize();
|
||||
|
||||
|
||||
// Log admin action
|
||||
await db('audit_log').insert({
|
||||
entity_type: 'game_tick',
|
||||
|
|
@ -554,10 +554,10 @@ router.post('/tick/start', async (req, res) => {
|
|||
changes: {
|
||||
correlation_id: correlationId,
|
||||
started_by: req.user?.username,
|
||||
timestamp: new Date().toISOString()
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
ip_address: req.ip,
|
||||
user_agent: req.get('User-Agent')
|
||||
user_agent: req.get('User-Agent'),
|
||||
});
|
||||
|
||||
res.json({
|
||||
|
|
@ -565,22 +565,22 @@ router.post('/tick/start', async (req, res) => {
|
|||
message: 'Game tick service started successfully',
|
||||
data: gameTickService.getStatus(),
|
||||
timestamp: new Date().toISOString(),
|
||||
correlationId
|
||||
correlationId,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Failed to start game tick service', {
|
||||
correlationId,
|
||||
adminId: req.user?.id,
|
||||
error: error.message
|
||||
error: error.message,
|
||||
});
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message || 'Failed to start game tick service',
|
||||
correlationId
|
||||
correlationId,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router;
|
||||
|
|
|
|||
|
|
@ -8,10 +8,33 @@ const router = express.Router();
|
|||
|
||||
// Import middleware
|
||||
const { authenticatePlayer, optionalPlayerAuth, requireOwnership, injectPlayerId } = require('../middleware/auth.middleware');
|
||||
const { authenticateToken } = require('../middleware/auth'); // Standardized auth
|
||||
const { rateLimiters } = require('../middleware/rateLimit.middleware');
|
||||
const { validators, validateRequest } = require('../middleware/validation.middleware');
|
||||
const {
|
||||
accountLockoutProtection,
|
||||
rateLimiter,
|
||||
passwordStrengthValidator,
|
||||
requireEmailVerification,
|
||||
sanitizeInput
|
||||
} = require('../middleware/security.middleware');
|
||||
const {
|
||||
validateRequest: validateAuthRequest,
|
||||
validateRegistrationUniqueness,
|
||||
registerPlayerSchema,
|
||||
loginPlayerSchema,
|
||||
verifyEmailSchema,
|
||||
resendVerificationSchema,
|
||||
requestPasswordResetSchema,
|
||||
resetPasswordSchema,
|
||||
changePasswordSchema
|
||||
} = require('../validators/auth.validators');
|
||||
const corsMiddleware = require('../middleware/cors.middleware');
|
||||
|
||||
// Use standardized authentication for players
|
||||
const authenticatePlayerToken = authenticateToken('player');
|
||||
const optionalPlayerToken = require('../middleware/auth').optionalAuth('player');
|
||||
|
||||
// Import controllers
|
||||
const authController = require('../controllers/api/auth.controller');
|
||||
const playerController = require('../controllers/api/player.controller');
|
||||
|
|
@ -54,20 +77,25 @@ const authRoutes = express.Router();
|
|||
|
||||
// Public authentication endpoints (with stricter rate limiting)
|
||||
authRoutes.post('/register',
|
||||
rateLimiters.auth,
|
||||
validators.validatePlayerRegistration,
|
||||
rateLimiter({ maxRequests: 3, windowMinutes: 60, action: 'registration' }),
|
||||
sanitizeInput(['email', 'username']),
|
||||
validateAuthRequest(registerPlayerSchema),
|
||||
validateRegistrationUniqueness(),
|
||||
passwordStrengthValidator('password'),
|
||||
authController.register
|
||||
);
|
||||
|
||||
authRoutes.post('/login',
|
||||
rateLimiters.auth,
|
||||
validators.validatePlayerLogin,
|
||||
rateLimiter({ maxRequests: 5, windowMinutes: 15, action: 'login' }),
|
||||
accountLockoutProtection,
|
||||
sanitizeInput(['email']),
|
||||
validateAuthRequest(loginPlayerSchema),
|
||||
authController.login
|
||||
);
|
||||
|
||||
// Protected authentication endpoints
|
||||
authRoutes.post('/logout',
|
||||
authenticatePlayer,
|
||||
authenticatePlayerToken,
|
||||
authController.logout
|
||||
);
|
||||
|
||||
|
|
@ -77,33 +105,76 @@ authRoutes.post('/refresh',
|
|||
);
|
||||
|
||||
authRoutes.get('/me',
|
||||
authenticatePlayer,
|
||||
authenticatePlayerToken,
|
||||
authController.getProfile
|
||||
);
|
||||
|
||||
authRoutes.put('/me',
|
||||
authenticatePlayer,
|
||||
authenticatePlayerToken,
|
||||
requireEmailVerification,
|
||||
rateLimiter({ maxRequests: 5, windowMinutes: 60, action: 'profile_update' }),
|
||||
sanitizeInput(['username', 'displayName', 'bio']),
|
||||
validateRequest(require('joi').object({
|
||||
username: require('joi').string().alphanum().min(3).max(20).optional()
|
||||
username: require('joi').string().alphanum().min(3).max(20).optional(),
|
||||
displayName: require('joi').string().min(1).max(50).optional(),
|
||||
bio: require('joi').string().max(500).optional()
|
||||
}), 'body'),
|
||||
authController.updateProfile
|
||||
);
|
||||
|
||||
authRoutes.get('/verify',
|
||||
authenticatePlayer,
|
||||
authenticatePlayerToken,
|
||||
authController.verifyToken
|
||||
);
|
||||
|
||||
authRoutes.post('/change-password',
|
||||
authenticatePlayer,
|
||||
rateLimiters.auth,
|
||||
validateRequest(require('joi').object({
|
||||
currentPassword: require('joi').string().required(),
|
||||
newPassword: require('joi').string().min(8).max(128).required()
|
||||
}), 'body'),
|
||||
authenticatePlayerToken,
|
||||
rateLimiter({ maxRequests: 3, windowMinutes: 60, action: 'password_change' }),
|
||||
validateAuthRequest(changePasswordSchema),
|
||||
passwordStrengthValidator('newPassword'),
|
||||
authController.changePassword
|
||||
);
|
||||
|
||||
// Email verification endpoints
|
||||
authRoutes.post('/verify-email',
|
||||
rateLimiter({ maxRequests: 5, windowMinutes: 15, action: 'email_verification' }),
|
||||
validateAuthRequest(verifyEmailSchema),
|
||||
authController.verifyEmail
|
||||
);
|
||||
|
||||
authRoutes.post('/resend-verification',
|
||||
rateLimiter({ maxRequests: 3, windowMinutes: 60, action: 'resend_verification' }),
|
||||
sanitizeInput(['email']),
|
||||
validateAuthRequest(resendVerificationSchema),
|
||||
authController.resendVerification
|
||||
);
|
||||
|
||||
// Password reset endpoints
|
||||
authRoutes.post('/request-password-reset',
|
||||
rateLimiter({ maxRequests: 3, windowMinutes: 60, action: 'password_reset_request' }),
|
||||
sanitizeInput(['email']),
|
||||
validateAuthRequest(requestPasswordResetSchema),
|
||||
authController.requestPasswordReset
|
||||
);
|
||||
|
||||
authRoutes.post('/reset-password',
|
||||
rateLimiter({ maxRequests: 3, windowMinutes: 60, action: 'password_reset' }),
|
||||
validateAuthRequest(resetPasswordSchema),
|
||||
passwordStrengthValidator('newPassword'),
|
||||
authController.resetPassword
|
||||
);
|
||||
|
||||
// Security utility endpoints
|
||||
authRoutes.post('/check-password-strength',
|
||||
rateLimiter({ maxRequests: 10, windowMinutes: 5, action: 'password_check' }),
|
||||
authController.checkPasswordStrength
|
||||
);
|
||||
|
||||
authRoutes.get('/security-status',
|
||||
authenticatePlayerToken,
|
||||
authController.getSecurityStatus
|
||||
);
|
||||
|
||||
// Mount authentication routes
|
||||
router.use('/auth', authRoutes);
|
||||
|
||||
|
|
@ -111,18 +182,18 @@ router.use('/auth', authRoutes);
|
|||
* Player Management Routes
|
||||
* /api/player/*
|
||||
*/
|
||||
const playerRoutes = express.Router();
|
||||
const playerManagementRoutes = express.Router();
|
||||
|
||||
// All player routes require authentication
|
||||
playerRoutes.use(authenticatePlayer);
|
||||
playerManagementRoutes.use(authenticatePlayerToken);
|
||||
|
||||
playerRoutes.get('/dashboard', playerController.getDashboard);
|
||||
playerManagementRoutes.get('/dashboard', playerController.getDashboard);
|
||||
|
||||
playerRoutes.get('/resources', playerController.getResources);
|
||||
playerManagementRoutes.get('/resources', playerController.getResources);
|
||||
|
||||
playerRoutes.get('/stats', playerController.getStats);
|
||||
playerManagementRoutes.get('/stats', playerController.getStats);
|
||||
|
||||
playerRoutes.put('/settings',
|
||||
playerManagementRoutes.put('/settings',
|
||||
validateRequest(require('joi').object({
|
||||
// TODO: Define settings schema
|
||||
notifications: require('joi').object({
|
||||
|
|
@ -139,19 +210,19 @@ playerRoutes.put('/settings',
|
|||
playerController.updateSettings
|
||||
);
|
||||
|
||||
playerRoutes.get('/activity',
|
||||
playerManagementRoutes.get('/activity',
|
||||
validators.validatePagination,
|
||||
playerController.getActivity
|
||||
);
|
||||
|
||||
playerRoutes.get('/notifications',
|
||||
playerManagementRoutes.get('/notifications',
|
||||
validateRequest(require('joi').object({
|
||||
unreadOnly: require('joi').boolean().default(false)
|
||||
}), 'query'),
|
||||
playerController.getNotifications
|
||||
);
|
||||
|
||||
playerRoutes.put('/notifications/read',
|
||||
playerManagementRoutes.put('/notifications/read',
|
||||
validateRequest(require('joi').object({
|
||||
notificationIds: require('joi').array().items(
|
||||
require('joi').number().integer().positive()
|
||||
|
|
@ -160,8 +231,8 @@ playerRoutes.put('/notifications/read',
|
|||
playerController.markNotificationsRead
|
||||
);
|
||||
|
||||
// Mount player routes
|
||||
router.use('/player', playerRoutes);
|
||||
// Mount player management routes (separate from game feature routes)
|
||||
router.use('/player', playerManagementRoutes);
|
||||
|
||||
/**
|
||||
* Combat Routes
|
||||
|
|
@ -171,169 +242,25 @@ router.use('/combat', require('./api/combat'));
|
|||
|
||||
/**
|
||||
* Game Feature Routes
|
||||
* These will be expanded with actual game functionality
|
||||
* Connect to existing working player route modules
|
||||
*/
|
||||
|
||||
// Colonies Routes (placeholder)
|
||||
router.get('/colonies',
|
||||
authenticatePlayer,
|
||||
validators.validatePagination,
|
||||
(req, res) => {
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Colonies endpoint - feature not yet implemented',
|
||||
data: {
|
||||
colonies: [],
|
||||
pagination: {
|
||||
page: 1,
|
||||
limit: 20,
|
||||
total: 0,
|
||||
totalPages: 0
|
||||
}
|
||||
},
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
}
|
||||
);
|
||||
// Import existing player route modules for game features
|
||||
const playerGameRoutes = require('./player');
|
||||
|
||||
router.post('/colonies',
|
||||
authenticatePlayer,
|
||||
rateLimiters.gameAction,
|
||||
validators.validateColonyCreation,
|
||||
(req, res) => {
|
||||
res.status(501).json({
|
||||
success: false,
|
||||
message: 'Colony creation feature not yet implemented',
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
}
|
||||
);
|
||||
// Mount player game routes under /player-game prefix to avoid conflicts
|
||||
// These contain the actual game functionality (colonies, resources, fleets, etc.)
|
||||
router.use('/player-game', playerGameRoutes);
|
||||
|
||||
// Fleets Routes (placeholder)
|
||||
router.get('/fleets',
|
||||
authenticatePlayer,
|
||||
validators.validatePagination,
|
||||
(req, res) => {
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Fleets endpoint - feature not yet implemented',
|
||||
data: {
|
||||
fleets: [],
|
||||
pagination: {
|
||||
page: 1,
|
||||
limit: 20,
|
||||
total: 0,
|
||||
totalPages: 0
|
||||
}
|
||||
},
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
router.post('/fleets',
|
||||
authenticatePlayer,
|
||||
rateLimiters.gameAction,
|
||||
validators.validateFleetCreation,
|
||||
(req, res) => {
|
||||
res.status(501).json({
|
||||
success: false,
|
||||
message: 'Fleet creation feature not yet implemented',
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Research Routes (placeholder)
|
||||
router.get('/research',
|
||||
authenticatePlayer,
|
||||
(req, res) => {
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Research endpoint - feature not yet implemented',
|
||||
data: {
|
||||
currentResearch: null,
|
||||
availableResearch: [],
|
||||
completedResearch: []
|
||||
},
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
router.post('/research',
|
||||
authenticatePlayer,
|
||||
rateLimiters.gameAction,
|
||||
validators.validateResearchInitiation,
|
||||
(req, res) => {
|
||||
res.status(501).json({
|
||||
success: false,
|
||||
message: 'Research initiation feature not yet implemented',
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Galaxy Routes (placeholder)
|
||||
router.get('/galaxy',
|
||||
authenticatePlayer,
|
||||
validateRequest(require('joi').object({
|
||||
sector: require('joi').string().pattern(/^[A-Z]\d+$/).optional(),
|
||||
coordinates: require('joi').string().pattern(/^[A-Z]\d+-\d+-[A-Z]$/).optional()
|
||||
}), 'query'),
|
||||
(req, res) => {
|
||||
const { sector, coordinates } = req.query;
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Galaxy endpoint - feature not yet implemented',
|
||||
data: {
|
||||
sector: sector || null,
|
||||
coordinates: coordinates || null,
|
||||
systems: [],
|
||||
playerColonies: [],
|
||||
playerFleets: []
|
||||
},
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Messages Routes (placeholder)
|
||||
router.get('/messages',
|
||||
authenticatePlayer,
|
||||
validators.validatePagination,
|
||||
(req, res) => {
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Messages endpoint - feature not yet implemented',
|
||||
data: {
|
||||
messages: [],
|
||||
unreadCount: 0,
|
||||
pagination: {
|
||||
page: 1,
|
||||
limit: 20,
|
||||
total: 0,
|
||||
totalPages: 0
|
||||
}
|
||||
},
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
router.post('/messages',
|
||||
authenticatePlayer,
|
||||
rateLimiters.messaging,
|
||||
validators.validateMessageSend,
|
||||
(req, res) => {
|
||||
res.status(501).json({
|
||||
success: false,
|
||||
message: 'Message sending feature not yet implemented',
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
}
|
||||
);
|
||||
// Direct mount of specific game features for convenience (these are duplicates of what's in /player/*)
|
||||
// These provide direct access without the /player prefix for backwards compatibility
|
||||
router.use('/colonies', authenticatePlayerToken, require('./player/colonies'));
|
||||
router.use('/resources', authenticatePlayerToken, require('./player/resources'));
|
||||
router.use('/fleets', authenticatePlayerToken, require('./player/fleets'));
|
||||
router.use('/research', authenticatePlayerToken, require('./player/research'));
|
||||
router.use('/galaxy', optionalPlayerToken, require('./player/galaxy'));
|
||||
router.use('/notifications', authenticatePlayerToken, require('./player/notifications'));
|
||||
router.use('/events', authenticatePlayerToken, require('./player/events'));
|
||||
|
||||
/**
|
||||
* Error handling for API routes
|
||||
|
|
|
|||
|
|
@ -8,29 +8,29 @@ const router = express.Router();
|
|||
|
||||
// Import controllers
|
||||
const {
|
||||
initiateCombat,
|
||||
getActiveCombats,
|
||||
getCombatHistory,
|
||||
getCombatEncounter,
|
||||
getCombatStatistics,
|
||||
updateFleetPosition,
|
||||
getCombatTypes,
|
||||
forceResolveCombat
|
||||
initiateCombat,
|
||||
getActiveCombats,
|
||||
getCombatHistory,
|
||||
getCombatEncounter,
|
||||
getCombatStatistics,
|
||||
updateFleetPosition,
|
||||
getCombatTypes,
|
||||
forceResolveCombat,
|
||||
} = require('../../controllers/api/combat.controller');
|
||||
|
||||
// Import middleware
|
||||
const { authenticatePlayer } = require('../../middleware/auth.middleware');
|
||||
const {
|
||||
validateCombatInitiation,
|
||||
validateFleetPositionUpdate,
|
||||
validateCombatHistoryQuery,
|
||||
validateParams,
|
||||
checkFleetOwnership,
|
||||
checkBattleAccess,
|
||||
checkCombatCooldown,
|
||||
checkFleetAvailability,
|
||||
combatRateLimit,
|
||||
logCombatAction
|
||||
validateCombatInitiation,
|
||||
validateFleetPositionUpdate,
|
||||
validateCombatHistoryQuery,
|
||||
validateParams,
|
||||
checkFleetOwnership,
|
||||
checkBattleAccess,
|
||||
checkCombatCooldown,
|
||||
checkFleetAvailability,
|
||||
combatRateLimit,
|
||||
logCombatAction,
|
||||
} = require('../../middleware/combat.middleware');
|
||||
|
||||
// Apply authentication to all combat routes
|
||||
|
|
@ -42,12 +42,12 @@ router.use(authenticatePlayer);
|
|||
* @access Private
|
||||
*/
|
||||
router.post('/initiate',
|
||||
logCombatAction('initiate_combat'),
|
||||
combatRateLimit(5, 15), // Max 5 combat initiations per 15 minutes
|
||||
checkCombatCooldown,
|
||||
validateCombatInitiation,
|
||||
checkFleetAvailability,
|
||||
initiateCombat
|
||||
logCombatAction('initiate_combat'),
|
||||
combatRateLimit(5, 15), // Max 5 combat initiations per 15 minutes
|
||||
checkCombatCooldown,
|
||||
validateCombatInitiation,
|
||||
checkFleetAvailability,
|
||||
initiateCombat,
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
@ -56,8 +56,8 @@ router.post('/initiate',
|
|||
* @access Private
|
||||
*/
|
||||
router.get('/active',
|
||||
logCombatAction('get_active_combats'),
|
||||
getActiveCombats
|
||||
logCombatAction('get_active_combats'),
|
||||
getActiveCombats,
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
@ -66,9 +66,9 @@ router.get('/active',
|
|||
* @access Private
|
||||
*/
|
||||
router.get('/history',
|
||||
logCombatAction('get_combat_history'),
|
||||
validateCombatHistoryQuery,
|
||||
getCombatHistory
|
||||
logCombatAction('get_combat_history'),
|
||||
validateCombatHistoryQuery,
|
||||
getCombatHistory,
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
@ -77,9 +77,9 @@ router.get('/history',
|
|||
* @access Private
|
||||
*/
|
||||
router.get('/encounter/:encounterId',
|
||||
logCombatAction('get_combat_encounter'),
|
||||
validateParams('encounterId'),
|
||||
getCombatEncounter
|
||||
logCombatAction('get_combat_encounter'),
|
||||
validateParams('encounterId'),
|
||||
getCombatEncounter,
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
@ -88,8 +88,8 @@ router.get('/encounter/:encounterId',
|
|||
* @access Private
|
||||
*/
|
||||
router.get('/statistics',
|
||||
logCombatAction('get_combat_statistics'),
|
||||
getCombatStatistics
|
||||
logCombatAction('get_combat_statistics'),
|
||||
getCombatStatistics,
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
@ -98,11 +98,11 @@ router.get('/statistics',
|
|||
* @access Private
|
||||
*/
|
||||
router.put('/position/:fleetId',
|
||||
logCombatAction('update_fleet_position'),
|
||||
validateParams('fleetId'),
|
||||
checkFleetOwnership,
|
||||
validateFleetPositionUpdate,
|
||||
updateFleetPosition
|
||||
logCombatAction('update_fleet_position'),
|
||||
validateParams('fleetId'),
|
||||
checkFleetOwnership,
|
||||
validateFleetPositionUpdate,
|
||||
updateFleetPosition,
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
@ -111,8 +111,8 @@ router.put('/position/:fleetId',
|
|||
* @access Private
|
||||
*/
|
||||
router.get('/types',
|
||||
logCombatAction('get_combat_types'),
|
||||
getCombatTypes
|
||||
logCombatAction('get_combat_types'),
|
||||
getCombatTypes,
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
@ -121,10 +121,10 @@ router.get('/types',
|
|||
* @access Private (requires special permission)
|
||||
*/
|
||||
router.post('/resolve/:battleId',
|
||||
logCombatAction('force_resolve_combat'),
|
||||
validateParams('battleId'),
|
||||
checkBattleAccess,
|
||||
forceResolveCombat
|
||||
logCombatAction('force_resolve_combat'),
|
||||
validateParams('battleId'),
|
||||
checkBattleAccess,
|
||||
forceResolveCombat,
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router;
|
||||
|
|
|
|||
|
|
@ -12,545 +12,545 @@ const logger = require('../utils/logger');
|
|||
|
||||
// Middleware to ensure debug routes are only available in development
|
||||
router.use((req, res, next) => {
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
return res.status(404).json({
|
||||
error: 'Debug endpoints not available in production'
|
||||
});
|
||||
}
|
||||
next();
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
return res.status(404).json({
|
||||
error: 'Debug endpoints not available in production',
|
||||
});
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
/**
|
||||
* Debug API Information
|
||||
*/
|
||||
router.get('/', (req, res) => {
|
||||
res.json({
|
||||
name: 'Shattered Void - Debug API',
|
||||
environment: process.env.NODE_ENV,
|
||||
timestamp: new Date().toISOString(),
|
||||
correlationId: req.correlationId,
|
||||
endpoints: {
|
||||
database: '/debug/database',
|
||||
redis: '/debug/redis',
|
||||
websocket: '/debug/websocket',
|
||||
system: '/debug/system',
|
||||
logs: '/debug/logs',
|
||||
player: '/debug/player/:playerId',
|
||||
colonies: '/debug/colonies',
|
||||
resources: '/debug/resources',
|
||||
gameEvents: '/debug/game-events'
|
||||
}
|
||||
});
|
||||
res.json({
|
||||
name: 'Shattered Void - Debug API',
|
||||
environment: process.env.NODE_ENV,
|
||||
timestamp: new Date().toISOString(),
|
||||
correlationId: req.correlationId,
|
||||
endpoints: {
|
||||
database: '/debug/database',
|
||||
redis: '/debug/redis',
|
||||
websocket: '/debug/websocket',
|
||||
system: '/debug/system',
|
||||
logs: '/debug/logs',
|
||||
player: '/debug/player/:playerId',
|
||||
colonies: '/debug/colonies',
|
||||
resources: '/debug/resources',
|
||||
gameEvents: '/debug/game-events',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Database Debug Information
|
||||
*/
|
||||
router.get('/database', async (req, res) => {
|
||||
try {
|
||||
// Test database connection
|
||||
const dbTest = await db.raw('SELECT NOW() as current_time, version() as db_version');
|
||||
|
||||
// Get table information
|
||||
const tables = await db.raw(`
|
||||
try {
|
||||
// Test database connection
|
||||
const dbTest = await db.raw('SELECT NOW() as current_time, version() as db_version');
|
||||
|
||||
// Get table information
|
||||
const tables = await db.raw(`
|
||||
SELECT table_name, table_rows
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = ?
|
||||
AND table_type = 'BASE TABLE'
|
||||
`, [process.env.DB_NAME || 'shattered_void_dev']);
|
||||
|
||||
res.json({
|
||||
status: 'connected',
|
||||
connection: {
|
||||
host: process.env.DB_HOST,
|
||||
database: process.env.DB_NAME,
|
||||
currentTime: dbTest.rows[0].current_time,
|
||||
version: dbTest.rows[0].db_version
|
||||
},
|
||||
tables: tables.rows,
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
res.json({
|
||||
status: 'connected',
|
||||
connection: {
|
||||
host: process.env.DB_HOST,
|
||||
database: process.env.DB_NAME,
|
||||
currentTime: dbTest.rows[0].current_time,
|
||||
version: dbTest.rows[0].db_version,
|
||||
},
|
||||
tables: tables.rows,
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Database debug error:', error);
|
||||
res.status(500).json({
|
||||
status: 'error',
|
||||
error: error.message,
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Database debug error:', error);
|
||||
res.status(500).json({
|
||||
status: 'error',
|
||||
error: error.message,
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Redis Debug Information
|
||||
*/
|
||||
router.get('/redis', async (req, res) => {
|
||||
try {
|
||||
const redisClient = getRedisClient();
|
||||
|
||||
if (!redisClient) {
|
||||
return res.json({
|
||||
status: 'not_connected',
|
||||
message: 'Redis client not available',
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
}
|
||||
try {
|
||||
const redisClient = getRedisClient();
|
||||
|
||||
// Test Redis connection
|
||||
const pong = await redisClient.ping();
|
||||
const info = await redisClient.info();
|
||||
|
||||
res.json({
|
||||
status: 'connected',
|
||||
ping: pong,
|
||||
info: info.split('\r\n').slice(0, 20), // First 20 lines of info
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Redis debug error:', error);
|
||||
res.status(500).json({
|
||||
status: 'error',
|
||||
error: error.message,
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
if (!redisClient) {
|
||||
return res.json({
|
||||
status: 'not_connected',
|
||||
message: 'Redis client not available',
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
}
|
||||
|
||||
// Test Redis connection
|
||||
const pong = await redisClient.ping();
|
||||
const info = await redisClient.info();
|
||||
|
||||
res.json({
|
||||
status: 'connected',
|
||||
ping: pong,
|
||||
info: info.split('\r\n').slice(0, 20), // First 20 lines of info
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Redis debug error:', error);
|
||||
res.status(500).json({
|
||||
status: 'error',
|
||||
error: error.message,
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* WebSocket Debug Information
|
||||
*/
|
||||
router.get('/websocket', (req, res) => {
|
||||
try {
|
||||
const io = getWebSocketServer();
|
||||
const stats = getConnectionStats();
|
||||
try {
|
||||
const io = getWebSocketServer();
|
||||
const stats = getConnectionStats();
|
||||
|
||||
if (!io) {
|
||||
return res.json({
|
||||
status: 'not_initialized',
|
||||
message: 'WebSocket server not available',
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
status: 'running',
|
||||
stats,
|
||||
sockets: {
|
||||
count: io.sockets.sockets.size,
|
||||
rooms: Array.from(io.sockets.adapter.rooms.keys())
|
||||
},
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('WebSocket debug error:', error);
|
||||
res.status(500).json({
|
||||
status: 'error',
|
||||
error: error.message,
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
if (!io) {
|
||||
return res.json({
|
||||
status: 'not_initialized',
|
||||
message: 'WebSocket server not available',
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
status: 'running',
|
||||
stats,
|
||||
sockets: {
|
||||
count: io.sockets.sockets.size,
|
||||
rooms: Array.from(io.sockets.adapter.rooms.keys()),
|
||||
},
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('WebSocket debug error:', error);
|
||||
res.status(500).json({
|
||||
status: 'error',
|
||||
error: error.message,
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* System Debug Information
|
||||
*/
|
||||
router.get('/system', (req, res) => {
|
||||
const memUsage = process.memoryUsage();
|
||||
const cpuUsage = process.cpuUsage();
|
||||
const memUsage = process.memoryUsage();
|
||||
const cpuUsage = process.cpuUsage();
|
||||
|
||||
res.json({
|
||||
process: {
|
||||
pid: process.pid,
|
||||
uptime: process.uptime(),
|
||||
version: process.version,
|
||||
platform: process.platform,
|
||||
arch: process.arch
|
||||
},
|
||||
memory: {
|
||||
rss: Math.round(memUsage.rss / 1024 / 1024),
|
||||
heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024),
|
||||
heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024),
|
||||
external: Math.round(memUsage.external / 1024 / 1024)
|
||||
},
|
||||
cpu: {
|
||||
user: cpuUsage.user,
|
||||
system: cpuUsage.system
|
||||
},
|
||||
environment: {
|
||||
nodeEnv: process.env.NODE_ENV,
|
||||
port: process.env.PORT,
|
||||
logLevel: process.env.LOG_LEVEL
|
||||
},
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
res.json({
|
||||
process: {
|
||||
pid: process.pid,
|
||||
uptime: process.uptime(),
|
||||
version: process.version,
|
||||
platform: process.platform,
|
||||
arch: process.arch,
|
||||
},
|
||||
memory: {
|
||||
rss: Math.round(memUsage.rss / 1024 / 1024),
|
||||
heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024),
|
||||
heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024),
|
||||
external: Math.round(memUsage.external / 1024 / 1024),
|
||||
},
|
||||
cpu: {
|
||||
user: cpuUsage.user,
|
||||
system: cpuUsage.system,
|
||||
},
|
||||
environment: {
|
||||
nodeEnv: process.env.NODE_ENV,
|
||||
port: process.env.PORT,
|
||||
logLevel: process.env.LOG_LEVEL,
|
||||
},
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Recent Logs Debug Information
|
||||
*/
|
||||
router.get('/logs', (req, res) => {
|
||||
const { level = 'info', limit = 50 } = req.query;
|
||||
const { level = 'info', limit = 50 } = req.query;
|
||||
|
||||
// Note: This is a placeholder. In a real implementation,
|
||||
// you'd want to read from your log files or log storage system
|
||||
res.json({
|
||||
message: 'Log retrieval not implemented',
|
||||
note: 'This would show recent log entries filtered by level',
|
||||
requested: {
|
||||
level,
|
||||
limit: parseInt(limit)
|
||||
},
|
||||
suggestion: 'Check log files directly in logs/ directory',
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
// Note: This is a placeholder. In a real implementation,
|
||||
// you'd want to read from your log files or log storage system
|
||||
res.json({
|
||||
message: 'Log retrieval not implemented',
|
||||
note: 'This would show recent log entries filtered by level',
|
||||
requested: {
|
||||
level,
|
||||
limit: parseInt(limit),
|
||||
},
|
||||
suggestion: 'Check log files directly in logs/ directory',
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Player Debug Information
|
||||
*/
|
||||
router.get('/player/:playerId', async (req, res) => {
|
||||
try {
|
||||
const playerId = parseInt(req.params.playerId);
|
||||
try {
|
||||
const playerId = parseInt(req.params.playerId);
|
||||
|
||||
if (isNaN(playerId)) {
|
||||
return res.status(400).json({
|
||||
error: 'Invalid player ID',
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
}
|
||||
|
||||
// Get comprehensive player information
|
||||
const player = await db('players')
|
||||
.where('id', playerId)
|
||||
.first();
|
||||
|
||||
if (!player) {
|
||||
return res.status(404).json({
|
||||
error: 'Player not found',
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
}
|
||||
|
||||
const resources = await db('player_resources')
|
||||
.where('player_id', playerId)
|
||||
.first();
|
||||
|
||||
const stats = await db('player_stats')
|
||||
.where('player_id', playerId)
|
||||
.first();
|
||||
|
||||
const colonies = await db('colonies')
|
||||
.where('player_id', playerId)
|
||||
.select(['id', 'name', 'coordinates', 'created_at']);
|
||||
|
||||
const fleets = await db('fleets')
|
||||
.where('player_id', playerId)
|
||||
.select(['id', 'name', 'status', 'created_at']);
|
||||
|
||||
// Remove sensitive information
|
||||
delete player.password_hash;
|
||||
|
||||
res.json({
|
||||
player,
|
||||
resources,
|
||||
stats,
|
||||
colonies,
|
||||
fleets,
|
||||
summary: {
|
||||
totalColonies: colonies.length,
|
||||
totalFleets: fleets.length,
|
||||
accountAge: Math.floor((Date.now() - new Date(player.created_at).getTime()) / (1000 * 60 * 60 * 24))
|
||||
},
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Player debug error:', error);
|
||||
res.status(500).json({
|
||||
error: error.message,
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
if (isNaN(playerId)) {
|
||||
return res.status(400).json({
|
||||
error: 'Invalid player ID',
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
}
|
||||
|
||||
// Get comprehensive player information
|
||||
const player = await db('players')
|
||||
.where('id', playerId)
|
||||
.first();
|
||||
|
||||
if (!player) {
|
||||
return res.status(404).json({
|
||||
error: 'Player not found',
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
}
|
||||
|
||||
const resources = await db('player_resources')
|
||||
.where('player_id', playerId)
|
||||
.first();
|
||||
|
||||
const stats = await db('player_stats')
|
||||
.where('player_id', playerId)
|
||||
.first();
|
||||
|
||||
const colonies = await db('colonies')
|
||||
.where('player_id', playerId)
|
||||
.select(['id', 'name', 'coordinates', 'created_at']);
|
||||
|
||||
const fleets = await db('fleets')
|
||||
.where('player_id', playerId)
|
||||
.select(['id', 'name', 'status', 'created_at']);
|
||||
|
||||
// Remove sensitive information
|
||||
delete player.password_hash;
|
||||
|
||||
res.json({
|
||||
player,
|
||||
resources,
|
||||
stats,
|
||||
colonies,
|
||||
fleets,
|
||||
summary: {
|
||||
totalColonies: colonies.length,
|
||||
totalFleets: fleets.length,
|
||||
accountAge: Math.floor((Date.now() - new Date(player.created_at).getTime()) / (1000 * 60 * 60 * 24)),
|
||||
},
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Player debug error:', error);
|
||||
res.status(500).json({
|
||||
error: error.message,
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Test Endpoint for Various Scenarios
|
||||
*/
|
||||
router.get('/test/:scenario', (req, res) => {
|
||||
const { scenario } = req.params;
|
||||
const { scenario } = req.params;
|
||||
|
||||
switch (scenario) {
|
||||
case 'error':
|
||||
throw new Error('Test error for debugging');
|
||||
|
||||
case 'slow':
|
||||
setTimeout(() => {
|
||||
res.json({
|
||||
message: 'Slow response test completed',
|
||||
delay: '3 seconds',
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
}, 3000);
|
||||
break;
|
||||
|
||||
case 'memory':
|
||||
// Create a large object to test memory usage
|
||||
const largeArray = new Array(1000000).fill('test data');
|
||||
res.json({
|
||||
message: 'Memory test completed',
|
||||
arrayLength: largeArray.length,
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
res.json({
|
||||
message: 'Test scenario not recognized',
|
||||
availableScenarios: ['error', 'slow', 'memory'],
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
}
|
||||
switch (scenario) {
|
||||
case 'error':
|
||||
throw new Error('Test error for debugging');
|
||||
|
||||
case 'slow':
|
||||
setTimeout(() => {
|
||||
res.json({
|
||||
message: 'Slow response test completed',
|
||||
delay: '3 seconds',
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
}, 3000);
|
||||
break;
|
||||
|
||||
case 'memory':
|
||||
// Create a large object to test memory usage
|
||||
const largeArray = new Array(1000000).fill('test data');
|
||||
res.json({
|
||||
message: 'Memory test completed',
|
||||
arrayLength: largeArray.length,
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
res.json({
|
||||
message: 'Test scenario not recognized',
|
||||
availableScenarios: ['error', 'slow', 'memory'],
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Colony Debug Information
|
||||
*/
|
||||
router.get('/colonies', async (req, res) => {
|
||||
try {
|
||||
const { playerId, limit = 10 } = req.query;
|
||||
try {
|
||||
const { playerId, limit = 10 } = req.query;
|
||||
|
||||
let query = db('colonies')
|
||||
.select([
|
||||
'colonies.*',
|
||||
'planet_types.name as planet_type_name',
|
||||
'galaxy_sectors.name as sector_name',
|
||||
'players.username'
|
||||
])
|
||||
.leftJoin('planet_types', 'colonies.planet_type_id', 'planet_types.id')
|
||||
.leftJoin('galaxy_sectors', 'colonies.sector_id', 'galaxy_sectors.id')
|
||||
.leftJoin('players', 'colonies.player_id', 'players.id')
|
||||
.orderBy('colonies.founded_at', 'desc')
|
||||
.limit(parseInt(limit));
|
||||
let query = db('colonies')
|
||||
.select([
|
||||
'colonies.*',
|
||||
'planet_types.name as planet_type_name',
|
||||
'galaxy_sectors.name as sector_name',
|
||||
'players.username',
|
||||
])
|
||||
.leftJoin('planet_types', 'colonies.planet_type_id', 'planet_types.id')
|
||||
.leftJoin('galaxy_sectors', 'colonies.sector_id', 'galaxy_sectors.id')
|
||||
.leftJoin('players', 'colonies.player_id', 'players.id')
|
||||
.orderBy('colonies.founded_at', 'desc')
|
||||
.limit(parseInt(limit));
|
||||
|
||||
if (playerId) {
|
||||
query = query.where('colonies.player_id', parseInt(playerId));
|
||||
}
|
||||
|
||||
const colonies = await query;
|
||||
|
||||
// Get building counts for each colony
|
||||
const coloniesWithBuildings = await Promise.all(colonies.map(async (colony) => {
|
||||
const buildingCount = await db('colony_buildings')
|
||||
.where('colony_id', colony.id)
|
||||
.count('* as count')
|
||||
.first();
|
||||
|
||||
const resourceProduction = await db('colony_resource_production')
|
||||
.select([
|
||||
'resource_types.name as resource_name',
|
||||
'colony_resource_production.production_rate',
|
||||
'colony_resource_production.current_stored'
|
||||
])
|
||||
.join('resource_types', 'colony_resource_production.resource_type_id', 'resource_types.id')
|
||||
.where('colony_resource_production.colony_id', colony.id)
|
||||
.where('colony_resource_production.production_rate', '>', 0);
|
||||
|
||||
return {
|
||||
...colony,
|
||||
buildingCount: parseInt(buildingCount.count) || 0,
|
||||
resourceProduction
|
||||
};
|
||||
}));
|
||||
|
||||
res.json({
|
||||
colonies: coloniesWithBuildings,
|
||||
totalCount: coloniesWithBuildings.length,
|
||||
filters: { playerId, limit },
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Colony debug error:', error);
|
||||
res.status(500).json({
|
||||
error: error.message,
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
if (playerId) {
|
||||
query = query.where('colonies.player_id', parseInt(playerId));
|
||||
}
|
||||
|
||||
const colonies = await query;
|
||||
|
||||
// Get building counts for each colony
|
||||
const coloniesWithBuildings = await Promise.all(colonies.map(async (colony) => {
|
||||
const buildingCount = await db('colony_buildings')
|
||||
.where('colony_id', colony.id)
|
||||
.count('* as count')
|
||||
.first();
|
||||
|
||||
const resourceProduction = await db('colony_resource_production')
|
||||
.select([
|
||||
'resource_types.name as resource_name',
|
||||
'colony_resource_production.production_rate',
|
||||
'colony_resource_production.current_stored',
|
||||
])
|
||||
.join('resource_types', 'colony_resource_production.resource_type_id', 'resource_types.id')
|
||||
.where('colony_resource_production.colony_id', colony.id)
|
||||
.where('colony_resource_production.production_rate', '>', 0);
|
||||
|
||||
return {
|
||||
...colony,
|
||||
buildingCount: parseInt(buildingCount.count) || 0,
|
||||
resourceProduction,
|
||||
};
|
||||
}));
|
||||
|
||||
res.json({
|
||||
colonies: coloniesWithBuildings,
|
||||
totalCount: coloniesWithBuildings.length,
|
||||
filters: { playerId, limit },
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Colony debug error:', error);
|
||||
res.status(500).json({
|
||||
error: error.message,
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Resource Debug Information
|
||||
*/
|
||||
router.get('/resources', async (req, res) => {
|
||||
try {
|
||||
const { playerId } = req.query;
|
||||
try {
|
||||
const { playerId } = req.query;
|
||||
|
||||
// Get resource types
|
||||
const resourceTypes = await db('resource_types')
|
||||
.where('is_active', true)
|
||||
.orderBy('category')
|
||||
.orderBy('name');
|
||||
// Get resource types
|
||||
const resourceTypes = await db('resource_types')
|
||||
.where('is_active', true)
|
||||
.orderBy('category')
|
||||
.orderBy('name');
|
||||
|
||||
let resourceSummary = {};
|
||||
const resourceSummary = {};
|
||||
|
||||
if (playerId) {
|
||||
// Get specific player resources
|
||||
const playerResources = await db('player_resources')
|
||||
.select([
|
||||
'player_resources.*',
|
||||
'resource_types.name as resource_name',
|
||||
'resource_types.category'
|
||||
])
|
||||
.join('resource_types', 'player_resources.resource_type_id', 'resource_types.id')
|
||||
.where('player_resources.player_id', parseInt(playerId));
|
||||
if (playerId) {
|
||||
// Get specific player resources
|
||||
const playerResources = await db('player_resources')
|
||||
.select([
|
||||
'player_resources.*',
|
||||
'resource_types.name as resource_name',
|
||||
'resource_types.category',
|
||||
])
|
||||
.join('resource_types', 'player_resources.resource_type_id', 'resource_types.id')
|
||||
.where('player_resources.player_id', parseInt(playerId));
|
||||
|
||||
resourceSummary.playerResources = playerResources;
|
||||
resourceSummary.playerResources = playerResources;
|
||||
|
||||
// Get player's colony resource production
|
||||
const colonyProduction = await db('colony_resource_production')
|
||||
.select([
|
||||
'colonies.name as colony_name',
|
||||
'resource_types.name as resource_name',
|
||||
'colony_resource_production.production_rate',
|
||||
'colony_resource_production.current_stored'
|
||||
])
|
||||
.join('colonies', 'colony_resource_production.colony_id', 'colonies.id')
|
||||
.join('resource_types', 'colony_resource_production.resource_type_id', 'resource_types.id')
|
||||
.where('colonies.player_id', parseInt(playerId))
|
||||
.where('colony_resource_production.production_rate', '>', 0);
|
||||
// Get player's colony resource production
|
||||
const colonyProduction = await db('colony_resource_production')
|
||||
.select([
|
||||
'colonies.name as colony_name',
|
||||
'resource_types.name as resource_name',
|
||||
'colony_resource_production.production_rate',
|
||||
'colony_resource_production.current_stored',
|
||||
])
|
||||
.join('colonies', 'colony_resource_production.colony_id', 'colonies.id')
|
||||
.join('resource_types', 'colony_resource_production.resource_type_id', 'resource_types.id')
|
||||
.where('colonies.player_id', parseInt(playerId))
|
||||
.where('colony_resource_production.production_rate', '>', 0);
|
||||
|
||||
resourceSummary.colonyProduction = colonyProduction;
|
||||
} else {
|
||||
// Get global resource statistics
|
||||
const totalResources = await db('player_resources')
|
||||
.select([
|
||||
'resource_types.name as resource_name',
|
||||
db.raw('SUM(player_resources.amount) as total_amount'),
|
||||
db.raw('COUNT(player_resources.id) as player_count'),
|
||||
db.raw('AVG(player_resources.amount) as average_amount')
|
||||
])
|
||||
.join('resource_types', 'player_resources.resource_type_id', 'resource_types.id')
|
||||
.groupBy('resource_types.id', 'resource_types.name')
|
||||
.orderBy('resource_types.name');
|
||||
resourceSummary.colonyProduction = colonyProduction;
|
||||
} else {
|
||||
// Get global resource statistics
|
||||
const totalResources = await db('player_resources')
|
||||
.select([
|
||||
'resource_types.name as resource_name',
|
||||
db.raw('SUM(player_resources.amount) as total_amount'),
|
||||
db.raw('COUNT(player_resources.id) as player_count'),
|
||||
db.raw('AVG(player_resources.amount) as average_amount'),
|
||||
])
|
||||
.join('resource_types', 'player_resources.resource_type_id', 'resource_types.id')
|
||||
.groupBy('resource_types.id', 'resource_types.name')
|
||||
.orderBy('resource_types.name');
|
||||
|
||||
resourceSummary.globalStats = totalResources;
|
||||
}
|
||||
|
||||
res.json({
|
||||
resourceTypes,
|
||||
...resourceSummary,
|
||||
filters: { playerId },
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Resource debug error:', error);
|
||||
res.status(500).json({
|
||||
error: error.message,
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
resourceSummary.globalStats = totalResources;
|
||||
}
|
||||
|
||||
res.json({
|
||||
resourceTypes,
|
||||
...resourceSummary,
|
||||
filters: { playerId },
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Resource debug error:', error);
|
||||
res.status(500).json({
|
||||
error: error.message,
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Game Events Debug Information
|
||||
*/
|
||||
router.get('/game-events', (req, res) => {
|
||||
try {
|
||||
const serviceLocator = require('../services/ServiceLocator');
|
||||
const gameEventService = serviceLocator.get('gameEventService');
|
||||
try {
|
||||
const serviceLocator = require('../services/ServiceLocator');
|
||||
const gameEventService = serviceLocator.get('gameEventService');
|
||||
|
||||
if (!gameEventService) {
|
||||
return res.json({
|
||||
status: 'not_available',
|
||||
message: 'Game event service not initialized',
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
}
|
||||
|
||||
const connectedPlayers = gameEventService.getConnectedPlayerCount();
|
||||
|
||||
// Get room information
|
||||
const io = gameEventService.io;
|
||||
const rooms = Array.from(io.sockets.adapter.rooms.entries()).map(([roomName, socketSet]) => ({
|
||||
name: roomName,
|
||||
socketCount: socketSet.size,
|
||||
type: roomName.includes(':') ? roomName.split(':')[0] : 'unknown'
|
||||
}));
|
||||
|
||||
res.json({
|
||||
status: 'active',
|
||||
connectedPlayers,
|
||||
rooms: {
|
||||
total: rooms.length,
|
||||
breakdown: rooms
|
||||
},
|
||||
eventTypes: [
|
||||
'colony_created',
|
||||
'building_constructed',
|
||||
'resources_updated',
|
||||
'resource_production',
|
||||
'colony_status_update',
|
||||
'error',
|
||||
'notification',
|
||||
'player_status_change',
|
||||
'system_announcement'
|
||||
],
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Game events debug error:', error);
|
||||
res.status(500).json({
|
||||
error: error.message,
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
if (!gameEventService) {
|
||||
return res.json({
|
||||
status: 'not_available',
|
||||
message: 'Game event service not initialized',
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
}
|
||||
|
||||
const connectedPlayers = gameEventService.getConnectedPlayerCount();
|
||||
|
||||
// Get room information
|
||||
const io = gameEventService.io;
|
||||
const rooms = Array.from(io.sockets.adapter.rooms.entries()).map(([roomName, socketSet]) => ({
|
||||
name: roomName,
|
||||
socketCount: socketSet.size,
|
||||
type: roomName.includes(':') ? roomName.split(':')[0] : 'unknown',
|
||||
}));
|
||||
|
||||
res.json({
|
||||
status: 'active',
|
||||
connectedPlayers,
|
||||
rooms: {
|
||||
total: rooms.length,
|
||||
breakdown: rooms,
|
||||
},
|
||||
eventTypes: [
|
||||
'colony_created',
|
||||
'building_constructed',
|
||||
'resources_updated',
|
||||
'resource_production',
|
||||
'colony_status_update',
|
||||
'error',
|
||||
'notification',
|
||||
'player_status_change',
|
||||
'system_announcement',
|
||||
],
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Game events debug error:', error);
|
||||
res.status(500).json({
|
||||
error: error.message,
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Add resources to a player (for testing)
|
||||
*/
|
||||
router.post('/add-resources', async (req, res) => {
|
||||
try {
|
||||
const { playerId, resources } = req.body;
|
||||
try {
|
||||
const { playerId, resources } = req.body;
|
||||
|
||||
if (!playerId || !resources) {
|
||||
return res.status(400).json({
|
||||
error: 'playerId and resources are required',
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
}
|
||||
|
||||
const serviceLocator = require('../services/ServiceLocator');
|
||||
const ResourceService = require('../services/resource/ResourceService');
|
||||
const gameEventService = serviceLocator.get('gameEventService');
|
||||
const resourceService = new ResourceService(gameEventService);
|
||||
|
||||
const updatedResources = await resourceService.addPlayerResources(
|
||||
playerId,
|
||||
resources,
|
||||
req.correlationId
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Resources added successfully',
|
||||
playerId,
|
||||
addedResources: resources,
|
||||
updatedResources,
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Add resources debug error:', error);
|
||||
res.status(500).json({
|
||||
error: error.message,
|
||||
correlationId: req.correlationId
|
||||
});
|
||||
if (!playerId || !resources) {
|
||||
return res.status(400).json({
|
||||
error: 'playerId and resources are required',
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
}
|
||||
|
||||
const serviceLocator = require('../services/ServiceLocator');
|
||||
const ResourceService = require('../services/resource/ResourceService');
|
||||
const gameEventService = serviceLocator.get('gameEventService');
|
||||
const resourceService = new ResourceService(gameEventService);
|
||||
|
||||
const updatedResources = await resourceService.addPlayerResources(
|
||||
playerId,
|
||||
resources,
|
||||
req.correlationId,
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Resources added successfully',
|
||||
playerId,
|
||||
addedResources: resources,
|
||||
updatedResources,
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Add resources debug error:', error);
|
||||
res.status(500).json({
|
||||
error: error.message,
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router;
|
||||
|
|
|
|||
|
|
@ -16,111 +16,111 @@ const adminRoutes = require('./admin');
|
|||
* Root endpoint - API information
|
||||
*/
|
||||
router.get('/', (req, res) => {
|
||||
const apiInfo = {
|
||||
name: 'Shattered Void MMO API',
|
||||
version: process.env.npm_package_version || '0.1.0',
|
||||
environment: process.env.NODE_ENV || 'development',
|
||||
status: 'operational',
|
||||
timestamp: new Date().toISOString(),
|
||||
endpoints: {
|
||||
health: '/health',
|
||||
api: '/api',
|
||||
admin: '/api/admin'
|
||||
},
|
||||
documentation: {
|
||||
api: '/docs/api',
|
||||
admin: '/docs/admin'
|
||||
},
|
||||
correlationId: req.correlationId
|
||||
};
|
||||
const apiInfo = {
|
||||
name: 'Shattered Void MMO API',
|
||||
version: process.env.npm_package_version || '0.1.0',
|
||||
environment: process.env.NODE_ENV || 'development',
|
||||
status: 'operational',
|
||||
timestamp: new Date().toISOString(),
|
||||
endpoints: {
|
||||
health: '/health',
|
||||
api: '/api',
|
||||
admin: '/api/admin',
|
||||
},
|
||||
documentation: {
|
||||
api: '/docs/api',
|
||||
admin: '/docs/admin',
|
||||
},
|
||||
correlationId: req.correlationId,
|
||||
};
|
||||
|
||||
res.json(apiInfo);
|
||||
res.json(apiInfo);
|
||||
});
|
||||
|
||||
/**
|
||||
* API Documentation endpoint (placeholder)
|
||||
*/
|
||||
router.get('/docs', (req, res) => {
|
||||
res.json({
|
||||
message: 'API Documentation',
|
||||
note: 'Interactive API documentation will be available here',
|
||||
version: process.env.npm_package_version || '0.1.0',
|
||||
correlationId: req.correlationId,
|
||||
links: {
|
||||
playerAPI: '/docs/api',
|
||||
adminAPI: '/docs/admin'
|
||||
}
|
||||
});
|
||||
res.json({
|
||||
message: 'API Documentation',
|
||||
note: 'Interactive API documentation will be available here',
|
||||
version: process.env.npm_package_version || '0.1.0',
|
||||
correlationId: req.correlationId,
|
||||
links: {
|
||||
playerAPI: '/docs/api',
|
||||
adminAPI: '/docs/admin',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Player API Documentation (placeholder)
|
||||
*/
|
||||
router.get('/docs/api', (req, res) => {
|
||||
res.json({
|
||||
title: 'Shattered Void - Player API Documentation',
|
||||
version: process.env.npm_package_version || '0.1.0',
|
||||
description: 'API endpoints for player operations',
|
||||
baseUrl: '/api',
|
||||
correlationId: req.correlationId,
|
||||
endpoints: {
|
||||
authentication: {
|
||||
register: 'POST /api/auth/register',
|
||||
login: 'POST /api/auth/login',
|
||||
logout: 'POST /api/auth/logout',
|
||||
profile: 'GET /api/auth/me',
|
||||
updateProfile: 'PUT /api/auth/me',
|
||||
verify: 'GET /api/auth/verify'
|
||||
},
|
||||
player: {
|
||||
dashboard: 'GET /api/player/dashboard',
|
||||
resources: 'GET /api/player/resources',
|
||||
stats: 'GET /api/player/stats',
|
||||
notifications: 'GET /api/player/notifications'
|
||||
},
|
||||
game: {
|
||||
colonies: 'GET /api/colonies',
|
||||
fleets: 'GET /api/fleets',
|
||||
research: 'GET /api/research',
|
||||
galaxy: 'GET /api/galaxy'
|
||||
}
|
||||
},
|
||||
note: 'Full interactive documentation coming soon'
|
||||
});
|
||||
res.json({
|
||||
title: 'Shattered Void - Player API Documentation',
|
||||
version: process.env.npm_package_version || '0.1.0',
|
||||
description: 'API endpoints for player operations',
|
||||
baseUrl: '/api',
|
||||
correlationId: req.correlationId,
|
||||
endpoints: {
|
||||
authentication: {
|
||||
register: 'POST /api/auth/register',
|
||||
login: 'POST /api/auth/login',
|
||||
logout: 'POST /api/auth/logout',
|
||||
profile: 'GET /api/auth/me',
|
||||
updateProfile: 'PUT /api/auth/me',
|
||||
verify: 'GET /api/auth/verify',
|
||||
},
|
||||
player: {
|
||||
dashboard: 'GET /api/player/dashboard',
|
||||
resources: 'GET /api/player/resources',
|
||||
stats: 'GET /api/player/stats',
|
||||
notifications: 'GET /api/player/notifications',
|
||||
},
|
||||
game: {
|
||||
colonies: 'GET /api/colonies',
|
||||
fleets: 'GET /api/fleets',
|
||||
research: 'GET /api/research',
|
||||
galaxy: 'GET /api/galaxy',
|
||||
},
|
||||
},
|
||||
note: 'Full interactive documentation coming soon',
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Admin API Documentation (placeholder)
|
||||
*/
|
||||
router.get('/docs/admin', (req, res) => {
|
||||
res.json({
|
||||
title: 'Shattered Void - Admin API Documentation',
|
||||
version: process.env.npm_package_version || '0.1.0',
|
||||
description: 'API endpoints for administrative operations',
|
||||
baseUrl: '/api/admin',
|
||||
correlationId: req.correlationId,
|
||||
endpoints: {
|
||||
authentication: {
|
||||
login: 'POST /api/admin/auth/login',
|
||||
logout: 'POST /api/admin/auth/logout',
|
||||
profile: 'GET /api/admin/auth/me',
|
||||
verify: 'GET /api/admin/auth/verify',
|
||||
stats: 'GET /api/admin/auth/stats'
|
||||
},
|
||||
playerManagement: {
|
||||
listPlayers: 'GET /api/admin/players',
|
||||
getPlayer: 'GET /api/admin/players/:id',
|
||||
updatePlayer: 'PUT /api/admin/players/:id',
|
||||
deactivatePlayer: 'DELETE /api/admin/players/:id'
|
||||
},
|
||||
systemManagement: {
|
||||
systemStats: 'GET /api/admin/system/stats',
|
||||
events: 'GET /api/admin/events',
|
||||
analytics: 'GET /api/admin/analytics'
|
||||
}
|
||||
},
|
||||
note: 'Full interactive documentation coming soon'
|
||||
});
|
||||
res.json({
|
||||
title: 'Shattered Void - Admin API Documentation',
|
||||
version: process.env.npm_package_version || '0.1.0',
|
||||
description: 'API endpoints for administrative operations',
|
||||
baseUrl: '/api/admin',
|
||||
correlationId: req.correlationId,
|
||||
endpoints: {
|
||||
authentication: {
|
||||
login: 'POST /api/admin/auth/login',
|
||||
logout: 'POST /api/admin/auth/logout',
|
||||
profile: 'GET /api/admin/auth/me',
|
||||
verify: 'GET /api/admin/auth/verify',
|
||||
stats: 'GET /api/admin/auth/stats',
|
||||
},
|
||||
playerManagement: {
|
||||
listPlayers: 'GET /api/admin/players',
|
||||
getPlayer: 'GET /api/admin/players/:id',
|
||||
updatePlayer: 'PUT /api/admin/players/:id',
|
||||
deactivatePlayer: 'DELETE /api/admin/players/:id',
|
||||
},
|
||||
systemManagement: {
|
||||
systemStats: 'GET /api/admin/system/stats',
|
||||
events: 'GET /api/admin/events',
|
||||
analytics: 'GET /api/admin/analytics',
|
||||
},
|
||||
},
|
||||
note: 'Full interactive documentation coming soon',
|
||||
});
|
||||
});
|
||||
|
||||
// Mount route modules
|
||||
|
|
@ -128,17 +128,17 @@ router.use('/api', apiRoutes);
|
|||
|
||||
// Admin routes (if enabled)
|
||||
if (process.env.ENABLE_ADMIN_ROUTES !== 'false') {
|
||||
router.use('/api/admin', adminRoutes);
|
||||
logger.info('Admin routes enabled');
|
||||
router.use('/api/admin', adminRoutes);
|
||||
logger.info('Admin routes enabled');
|
||||
} else {
|
||||
logger.info('Admin routes disabled');
|
||||
logger.info('Admin routes disabled');
|
||||
}
|
||||
|
||||
// Debug routes (development only)
|
||||
if (process.env.NODE_ENV === 'development' && process.env.ENABLE_DEBUG_ENDPOINTS === 'true') {
|
||||
const debugRoutes = require('./debug');
|
||||
router.use('/debug', debugRoutes);
|
||||
logger.info('Debug routes enabled');
|
||||
const debugRoutes = require('./debug');
|
||||
router.use('/debug', debugRoutes);
|
||||
logger.info('Debug routes enabled');
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router;
|
||||
|
|
|
|||
|
|
@ -64,4 +64,4 @@ router.post('/reset-password', asyncHandler(async (req, res) => {
|
|||
});
|
||||
}));
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router;
|
||||
|
|
|
|||
|
|
@ -7,42 +7,42 @@ const express = require('express');
|
|||
const router = express.Router();
|
||||
|
||||
const {
|
||||
createColony,
|
||||
getPlayerColonies,
|
||||
getColonyDetails,
|
||||
constructBuilding,
|
||||
getBuildingTypes,
|
||||
getPlanetTypes,
|
||||
getGalaxySectors
|
||||
createColony,
|
||||
getPlayerColonies,
|
||||
getColonyDetails,
|
||||
constructBuilding,
|
||||
getBuildingTypes,
|
||||
getPlanetTypes,
|
||||
getGalaxySectors,
|
||||
} = require('../../controllers/player/colony.controller');
|
||||
|
||||
const { validateRequest } = require('../../middleware/validation.middleware');
|
||||
const {
|
||||
createColonySchema,
|
||||
constructBuildingSchema,
|
||||
colonyIdParamSchema
|
||||
createColonySchema,
|
||||
constructBuildingSchema,
|
||||
colonyIdParamSchema,
|
||||
} = require('../../validators/colony.validators');
|
||||
|
||||
// Colony CRUD operations
|
||||
router.post('/',
|
||||
validateRequest(createColonySchema),
|
||||
createColony
|
||||
router.post('/',
|
||||
validateRequest(createColonySchema),
|
||||
createColony,
|
||||
);
|
||||
|
||||
router.get('/',
|
||||
getPlayerColonies
|
||||
router.get('/',
|
||||
getPlayerColonies,
|
||||
);
|
||||
|
||||
router.get('/:colonyId',
|
||||
validateRequest(colonyIdParamSchema, 'params'),
|
||||
getColonyDetails
|
||||
router.get('/:colonyId',
|
||||
validateRequest(colonyIdParamSchema, 'params'),
|
||||
getColonyDetails,
|
||||
);
|
||||
|
||||
// Building operations
|
||||
router.post('/:colonyId/buildings',
|
||||
validateRequest(colonyIdParamSchema, 'params'),
|
||||
validateRequest(constructBuildingSchema),
|
||||
constructBuilding
|
||||
router.post('/:colonyId/buildings',
|
||||
validateRequest(colonyIdParamSchema, 'params'),
|
||||
validateRequest(constructBuildingSchema),
|
||||
constructBuilding,
|
||||
);
|
||||
|
||||
// Reference data endpoints
|
||||
|
|
@ -50,4 +50,4 @@ router.get('/ref/building-types', getBuildingTypes);
|
|||
router.get('/ref/planet-types', getPlanetTypes);
|
||||
router.get('/ref/galaxy-sectors', getGalaxySectors);
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* Player Events Routes
|
||||
* Handles player event history and notifications
|
||||
*/
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
// TODO: Implement events routes
|
||||
router.get('/', (req, res) => {
|
||||
res.json({
|
||||
message: 'Events routes not yet implemented',
|
||||
available_endpoints: {
|
||||
'/history': 'Get event history',
|
||||
'/recent': 'Get recent events',
|
||||
'/unread': 'Get unread events'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/history', (req, res) => {
|
||||
res.json({ message: 'Event history endpoint not implemented' });
|
||||
});
|
||||
|
||||
router.get('/recent', (req, res) => {
|
||||
res.json({ message: 'Recent events endpoint not implemented' });
|
||||
});
|
||||
|
||||
router.get('/unread', (req, res) => {
|
||||
res.json({ message: 'Unread events endpoint not implemented' });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* Player Fleet Routes
|
||||
* Handles fleet management and operations
|
||||
*/
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const fleetController = require('../../controllers/api/fleet.controller');
|
||||
|
||||
// Fleet management routes
|
||||
router.get('/', fleetController.getPlayerFleets);
|
||||
router.post('/', fleetController.createFleet);
|
||||
router.get('/:fleetId', fleetController.getFleetDetails);
|
||||
router.delete('/:fleetId', fleetController.disbandFleet);
|
||||
|
||||
// Fleet operations
|
||||
router.post('/:fleetId/move', fleetController.moveFleet);
|
||||
|
||||
// TODO: Combat operations (will be implemented when combat system is enhanced)
|
||||
router.post('/:fleetId/attack', (req, res) => {
|
||||
res.status(501).json({
|
||||
success: false,
|
||||
error: 'Not implemented',
|
||||
message: 'Fleet combat operations will be available in a future update'
|
||||
});
|
||||
});
|
||||
|
||||
// Ship design routes
|
||||
router.get('/ship-designs/classes', fleetController.getShipClassesInfo);
|
||||
router.get('/ship-designs/:designId', fleetController.getShipDesignDetails);
|
||||
router.get('/ship-designs', fleetController.getAvailableShipDesigns);
|
||||
|
||||
// Ship construction validation
|
||||
router.post('/validate-construction', fleetController.validateShipConstruction);
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* Player Galaxy Routes
|
||||
* Handles galaxy exploration and sector viewing
|
||||
*/
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
// TODO: Implement galaxy routes
|
||||
router.get('/', (req, res) => {
|
||||
res.json({
|
||||
message: 'Galaxy routes not yet implemented',
|
||||
available_endpoints: {
|
||||
'/sectors': 'List galaxy sectors',
|
||||
'/explore': 'Explore new areas',
|
||||
'/map': 'View galaxy map'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/sectors', (req, res) => {
|
||||
res.json({ message: 'Galaxy sectors endpoint not implemented' });
|
||||
});
|
||||
|
||||
router.get('/explore', (req, res) => {
|
||||
res.json({ message: 'Galaxy exploration endpoint not implemented' });
|
||||
});
|
||||
|
||||
router.get('/map', (req, res) => {
|
||||
res.json({ message: 'Galaxy map endpoint not implemented' });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -47,4 +47,4 @@ router.get('/status', authenticateToken('player'), asyncHandler(async (req, res)
|
|||
});
|
||||
}));
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* Player Notifications Routes
|
||||
* Handles player notifications and messages
|
||||
*/
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
// TODO: Implement notifications routes
|
||||
router.get('/', (req, res) => {
|
||||
res.json({
|
||||
message: 'Notifications routes not yet implemented',
|
||||
available_endpoints: {
|
||||
'/unread': 'Get unread notifications',
|
||||
'/all': 'Get all notifications',
|
||||
'/mark-read': 'Mark notifications as read'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/unread', (req, res) => {
|
||||
res.json({ message: 'Unread notifications endpoint not implemented' });
|
||||
});
|
||||
|
||||
router.get('/all', (req, res) => {
|
||||
res.json({ message: 'All notifications endpoint not implemented' });
|
||||
});
|
||||
|
||||
router.post('/mark-read', (req, res) => {
|
||||
res.json({ message: 'Mark notifications read endpoint not implemented' });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* Player Profile Routes
|
||||
* Handles player profile management
|
||||
*/
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
// TODO: Implement profile routes
|
||||
router.get('/', (req, res) => {
|
||||
res.json({
|
||||
message: 'Profile routes not yet implemented',
|
||||
available_endpoints: {
|
||||
'/': 'Get player profile',
|
||||
'/update': 'Update player profile',
|
||||
'/settings': 'Get/update player settings'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
router.put('/', (req, res) => {
|
||||
res.json({ message: 'Profile update endpoint not implemented' });
|
||||
});
|
||||
|
||||
router.get('/settings', (req, res) => {
|
||||
res.json({ message: 'Profile settings endpoint not implemented' });
|
||||
});
|
||||
|
||||
router.put('/settings', (req, res) => {
|
||||
res.json({ message: 'Profile settings update endpoint not implemented' });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* Player Research Routes
|
||||
* Handles research and technology management
|
||||
*/
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
// Import controllers and middleware
|
||||
const researchController = require('../../controllers/api/research.controller');
|
||||
const {
|
||||
validateStartResearch,
|
||||
validateTechnologyTreeFilter,
|
||||
validateResearchStats
|
||||
} = require('../../validators/research.validators');
|
||||
|
||||
/**
|
||||
* Get current research status for the authenticated player
|
||||
* GET /player/research/
|
||||
*/
|
||||
router.get('/', researchController.getResearchStatus);
|
||||
|
||||
/**
|
||||
* Get available technologies for research
|
||||
* GET /player/research/available
|
||||
*/
|
||||
router.get('/available', researchController.getAvailableTechnologies);
|
||||
|
||||
/**
|
||||
* Get completed technologies
|
||||
* GET /player/research/completed
|
||||
*/
|
||||
router.get('/completed', researchController.getCompletedTechnologies);
|
||||
|
||||
/**
|
||||
* Get full technology tree with player progress
|
||||
* GET /player/research/technology-tree
|
||||
* Query params: category, tier, status, include_unavailable, sort_by, sort_order
|
||||
*/
|
||||
router.get('/technology-tree',
|
||||
validateTechnologyTreeFilter,
|
||||
researchController.getTechnologyTree
|
||||
);
|
||||
|
||||
/**
|
||||
* Get research queue (current and queued research)
|
||||
* GET /player/research/queue
|
||||
*/
|
||||
router.get('/queue', researchController.getResearchQueue);
|
||||
|
||||
/**
|
||||
* Start research on a technology
|
||||
* POST /player/research/start
|
||||
* Body: { technology_id: number }
|
||||
*/
|
||||
router.post('/start',
|
||||
validateStartResearch,
|
||||
researchController.startResearch
|
||||
);
|
||||
|
||||
/**
|
||||
* Cancel current research
|
||||
* POST /player/research/cancel
|
||||
*/
|
||||
router.post('/cancel', researchController.cancelResearch);
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -7,48 +7,48 @@ const express = require('express');
|
|||
const router = express.Router();
|
||||
|
||||
const {
|
||||
getPlayerResources,
|
||||
getPlayerResourceSummary,
|
||||
getResourceProduction,
|
||||
addResources,
|
||||
transferResources,
|
||||
getResourceTypes
|
||||
getPlayerResources,
|
||||
getPlayerResourceSummary,
|
||||
getResourceProduction,
|
||||
addResources,
|
||||
transferResources,
|
||||
getResourceTypes,
|
||||
} = require('../../controllers/player/resource.controller');
|
||||
|
||||
const { validateRequest } = require('../../middleware/validation.middleware');
|
||||
const {
|
||||
transferResourcesSchema,
|
||||
addResourcesSchema,
|
||||
resourceQuerySchema
|
||||
transferResourcesSchema,
|
||||
addResourcesSchema,
|
||||
resourceQuerySchema,
|
||||
} = require('../../validators/resource.validators');
|
||||
|
||||
// Resource information endpoints
|
||||
router.get('/',
|
||||
validateRequest(resourceQuerySchema, 'query'),
|
||||
getPlayerResources
|
||||
router.get('/',
|
||||
validateRequest(resourceQuerySchema, 'query'),
|
||||
getPlayerResources,
|
||||
);
|
||||
|
||||
router.get('/summary',
|
||||
getPlayerResourceSummary
|
||||
router.get('/summary',
|
||||
getPlayerResourceSummary,
|
||||
);
|
||||
|
||||
router.get('/production',
|
||||
getResourceProduction
|
||||
router.get('/production',
|
||||
getResourceProduction,
|
||||
);
|
||||
|
||||
// Resource manipulation endpoints
|
||||
router.post('/transfer',
|
||||
validateRequest(transferResourcesSchema),
|
||||
transferResources
|
||||
router.post('/transfer',
|
||||
validateRequest(transferResourcesSchema),
|
||||
transferResources,
|
||||
);
|
||||
|
||||
// Development/testing endpoints
|
||||
router.post('/add',
|
||||
validateRequest(addResourcesSchema),
|
||||
addResources
|
||||
router.post('/add',
|
||||
validateRequest(addResourcesSchema),
|
||||
addResources,
|
||||
);
|
||||
|
||||
// Reference data endpoints
|
||||
router.get('/types', getResourceTypes);
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue