feat: implement comprehensive combat system with plugin architecture
- Complete combat system with instant, turn-based, and tactical combat - Plugin-based architecture with CombatPluginManager for extensibility - Real-time combat events via WebSocket - Fleet vs fleet and fleet vs colony combat support - Comprehensive combat statistics and history tracking - Admin panel for combat management and configuration - Database migrations for combat tables and fleet system - Complete test suite for combat functionality - Combat middleware for validation and logging - Service locator pattern for dependency management Combat system features: • Multiple combat resolution types with plugin support • Real-time combat events and spectator support • Detailed combat logs and casualty calculations • Experience gain and veterancy system for ships • Fleet positioning and tactical formations • Combat configurations and modifiers • Queue system for battle processing • Comprehensive admin controls and monitoring 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
1a60cf55a3
commit
8d9ef427be
37 changed files with 13302 additions and 26 deletions
|
|
@ -40,7 +40,8 @@ router.get('/', (req, res) => {
|
|||
players: '/api/admin/players',
|
||||
system: '/api/admin/system',
|
||||
events: '/api/admin/events',
|
||||
analytics: '/api/admin/analytics'
|
||||
analytics: '/api/admin/analytics',
|
||||
combat: '/api/admin/combat'
|
||||
},
|
||||
note: 'Administrative access required for all endpoints'
|
||||
});
|
||||
|
|
@ -336,6 +337,12 @@ systemRoutes.get('/health',
|
|||
// Mount system routes
|
||||
router.use('/system', systemRoutes);
|
||||
|
||||
/**
|
||||
* Combat Management Routes
|
||||
* /api/admin/combat/*
|
||||
*/
|
||||
router.use('/combat', require('./admin/combat'));
|
||||
|
||||
/**
|
||||
* Events Management Routes (placeholder)
|
||||
* /api/admin/events/*
|
||||
|
|
|
|||
345
src/routes/admin/combat.js
Normal file
345
src/routes/admin/combat.js
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
/**
|
||||
* Admin Combat Routes
|
||||
* Administrative endpoints for combat system management
|
||||
*/
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
// Import controllers
|
||||
const {
|
||||
getCombatStatistics,
|
||||
getCombatQueue,
|
||||
forceResolveCombat,
|
||||
cancelBattle,
|
||||
getCombatConfigurations,
|
||||
saveCombatConfiguration,
|
||||
deleteCombatConfiguration
|
||||
} = require('../../controllers/admin/combat.controller');
|
||||
|
||||
// Import middleware
|
||||
const { authenticateAdmin } = require('../../middleware/admin.middleware');
|
||||
const {
|
||||
validateCombatQueueQuery,
|
||||
validateParams,
|
||||
logCombatAction
|
||||
} = require('../../middleware/combat.middleware');
|
||||
const { validateCombatConfiguration } = require('../../validators/combat.validators');
|
||||
|
||||
// Apply admin authentication to all routes
|
||||
router.use(authenticateAdmin);
|
||||
|
||||
/**
|
||||
* @route GET /api/admin/combat/statistics
|
||||
* @desc Get comprehensive combat system statistics
|
||||
* @access Admin
|
||||
*/
|
||||
router.get('/statistics',
|
||||
logCombatAction('admin_get_combat_statistics'),
|
||||
getCombatStatistics
|
||||
);
|
||||
|
||||
/**
|
||||
* @route GET /api/admin/combat/queue
|
||||
* @desc Get combat queue with filtering options
|
||||
* @access Admin
|
||||
*/
|
||||
router.get('/queue',
|
||||
logCombatAction('admin_get_combat_queue'),
|
||||
validateCombatQueueQuery,
|
||||
getCombatQueue
|
||||
);
|
||||
|
||||
/**
|
||||
* @route POST /api/admin/combat/resolve/:battleId
|
||||
* @desc Force resolve a specific battle
|
||||
* @access Admin
|
||||
*/
|
||||
router.post('/resolve/:battleId',
|
||||
logCombatAction('admin_force_resolve_combat'),
|
||||
validateParams('battleId'),
|
||||
forceResolveCombat
|
||||
);
|
||||
|
||||
/**
|
||||
* @route POST /api/admin/combat/cancel/:battleId
|
||||
* @desc Cancel a battle
|
||||
* @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
|
||||
);
|
||||
|
||||
/**
|
||||
* @route GET /api/admin/combat/configurations
|
||||
* @desc Get all combat configurations
|
||||
* @access Admin
|
||||
*/
|
||||
router.get('/configurations',
|
||||
logCombatAction('admin_get_combat_configurations'),
|
||||
getCombatConfigurations
|
||||
);
|
||||
|
||||
/**
|
||||
* @route POST /api/admin/combat/configurations
|
||||
* @desc Create new combat configuration
|
||||
* @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
|
||||
);
|
||||
|
||||
/**
|
||||
* @route PUT /api/admin/combat/configurations/:configId
|
||||
* @desc Update existing combat configuration
|
||||
* @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
|
||||
);
|
||||
|
||||
/**
|
||||
* @route DELETE /api/admin/combat/configurations/:configId
|
||||
* @desc Delete combat configuration
|
||||
* @access Admin
|
||||
*/
|
||||
router.delete('/configurations/:configId',
|
||||
logCombatAction('admin_delete_combat_configuration'),
|
||||
validateParams('configId'),
|
||||
deleteCombatConfiguration
|
||||
);
|
||||
|
||||
/**
|
||||
* @route GET /api/admin/combat/battles
|
||||
* @desc Get all battles with filtering and pagination
|
||||
* @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;
|
||||
|
||||
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));
|
||||
|
||||
if (status) {
|
||||
query = query.where('battles.status', status);
|
||||
}
|
||||
|
||||
if (battle_type) {
|
||||
query = query.where('battles.battle_type', battle_type);
|
||||
}
|
||||
|
||||
if (location) {
|
||||
query = query.where('battles.location', location);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
const [{ total }] = await countQuery;
|
||||
|
||||
// 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
|
||||
}));
|
||||
|
||||
logger.info('Admin battles retrieved', {
|
||||
correlationId: req.correlationId,
|
||||
adminUser: req.user.id,
|
||||
count: battles.length,
|
||||
total: parseInt(total)
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* @route GET /api/admin/combat/encounters/:encounterId
|
||||
* @desc Get detailed combat encounter for admin review
|
||||
* @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');
|
||||
|
||||
// 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'
|
||||
});
|
||||
}
|
||||
|
||||
// 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)
|
||||
}))
|
||||
};
|
||||
|
||||
logger.info('Admin combat encounter retrieved', {
|
||||
correlationId: req.correlationId,
|
||||
adminUser: req.user.id,
|
||||
encounterId
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: detailedEncounter
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -0,0 +1,586 @@
|
|||
/**
|
||||
* Admin System Management Routes
|
||||
* Provides administrative controls for game tick system, configuration, and monitoring
|
||||
*/
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const logger = require('../../utils/logger');
|
||||
const {
|
||||
gameTickService,
|
||||
getGameTickStatus,
|
||||
triggerManualTick
|
||||
} = require('../../services/game-tick.service');
|
||||
const db = require('../../database/connection');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
/**
|
||||
* Get game tick system status and metrics
|
||||
* GET /admin/system/tick/status
|
||||
*/
|
||||
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
|
||||
});
|
||||
|
||||
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(
|
||||
db.raw('AVG(EXTRACT(EPOCH FROM (completed_at - started_at)) * 1000) as avg_duration_ms'),
|
||||
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')
|
||||
)
|
||||
.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')
|
||||
)
|
||||
.where('started_at', '>=', db.raw("NOW() - INTERVAL '24 hours'"))
|
||||
.groupBy('user_group')
|
||||
.orderBy('user_group');
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
service: status,
|
||||
performance: performanceStats,
|
||||
userGroups: userGroupStats,
|
||||
recentLogs: recentLogs.map(log => ({
|
||||
id: log.id,
|
||||
tickNumber: log.tick_number,
|
||||
userGroup: log.user_group,
|
||||
status: log.status,
|
||||
processedPlayers: log.processed_players,
|
||||
duration: log.performance_metrics?.duration_ms,
|
||||
startedAt: log.started_at,
|
||||
completedAt: log.completed_at,
|
||||
errorMessage: log.error_message
|
||||
}))
|
||||
},
|
||||
timestamp: new Date().toISOString(),
|
||||
correlationId
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Failed to get game tick status', {
|
||||
correlationId,
|
||||
adminId: req.user?.id,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to retrieve game tick status',
|
||||
correlationId
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Trigger manual game tick
|
||||
* POST /admin/system/tick/trigger
|
||||
*/
|
||||
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
|
||||
});
|
||||
|
||||
const result = await triggerManualTick(correlationId);
|
||||
|
||||
// Log admin action
|
||||
await db('audit_log').insert({
|
||||
entity_type: 'game_tick',
|
||||
entity_id: 0,
|
||||
action: 'manual_tick_triggered',
|
||||
actor_type: 'admin',
|
||||
actor_id: req.user?.id,
|
||||
changes: {
|
||||
correlation_id: correlationId,
|
||||
triggered_by: req.user?.username
|
||||
},
|
||||
ip_address: req.ip,
|
||||
user_agent: req.get('User-Agent')
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Manual game tick triggered successfully',
|
||||
data: result,
|
||||
timestamp: new Date().toISOString(),
|
||||
correlationId
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Failed to trigger manual game tick', {
|
||||
correlationId,
|
||||
adminId: req.user?.id,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message || 'Failed to trigger manual game tick',
|
||||
correlationId
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Update game tick configuration
|
||||
* PUT /admin/system/tick/config
|
||||
*/
|
||||
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
|
||||
} = req.body;
|
||||
|
||||
logger.info('Admin updating game tick configuration', {
|
||||
correlationId,
|
||||
adminId: req.user?.id,
|
||||
adminUsername: req.user?.username,
|
||||
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');
|
||||
}
|
||||
|
||||
if (validationErrors.length > 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Configuration validation failed',
|
||||
details: validationErrors,
|
||||
correlationId
|
||||
});
|
||||
}
|
||||
|
||||
// Get current configuration
|
||||
const currentConfig = await db('game_tick_config')
|
||||
.where('is_active', true)
|
||||
.first();
|
||||
|
||||
if (!currentConfig) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'No active game tick configuration found',
|
||||
correlationId
|
||||
});
|
||||
}
|
||||
|
||||
// Update configuration
|
||||
const updatedConfig = await db('game_tick_config')
|
||||
.where('id', currentConfig.id)
|
||||
.update({
|
||||
tick_interval_ms: tick_interval_ms || currentConfig.tick_interval_ms,
|
||||
user_groups_count: user_groups_count || currentConfig.user_groups_count,
|
||||
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()
|
||||
})
|
||||
.returning('*');
|
||||
|
||||
// Log admin action
|
||||
await db('audit_log').insert({
|
||||
entity_type: 'game_tick_config',
|
||||
entity_id: currentConfig.id,
|
||||
action: 'configuration_updated',
|
||||
actor_type: 'admin',
|
||||
actor_id: req.user?.id,
|
||||
changes: {
|
||||
before: currentConfig,
|
||||
after: updatedConfig[0],
|
||||
updated_by: req.user?.username
|
||||
},
|
||||
ip_address: req.ip,
|
||||
user_agent: req.get('User-Agent')
|
||||
});
|
||||
|
||||
// Reload configuration in the service
|
||||
await gameTickService.loadConfig();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Game tick configuration updated successfully',
|
||||
data: {
|
||||
previousConfig: currentConfig,
|
||||
newConfig: updatedConfig[0]
|
||||
},
|
||||
timestamp: new Date().toISOString(),
|
||||
correlationId
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Failed to update game tick configuration', {
|
||||
correlationId,
|
||||
adminId: req.user?.id,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to update game tick configuration',
|
||||
correlationId
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Get game tick logs with filtering
|
||||
* GET /admin/system/tick/logs
|
||||
*/
|
||||
router.get('/tick/logs', async (req, res) => {
|
||||
const correlationId = req.correlationId || uuidv4();
|
||||
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 50,
|
||||
status,
|
||||
userGroup,
|
||||
tickNumber,
|
||||
startDate,
|
||||
endDate
|
||||
} = req.query;
|
||||
|
||||
const pageNum = parseInt(page);
|
||||
const limitNum = Math.min(parseInt(limit), 100); // Max 100 records per page
|
||||
const offset = (pageNum - 1) * limitNum;
|
||||
|
||||
let query = db('game_tick_log').select('*');
|
||||
|
||||
// Apply filters
|
||||
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));
|
||||
}
|
||||
|
||||
// Get total count for pagination
|
||||
const countQuery = query.clone().clearSelect().count('* as total');
|
||||
const [{ total }] = await countQuery;
|
||||
|
||||
// Get paginated results
|
||||
const logs = await query
|
||||
.orderBy('tick_number', 'desc')
|
||||
.orderBy('user_group', 'asc')
|
||||
.limit(limitNum)
|
||||
.offset(offset);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
logs: logs.map(log => ({
|
||||
id: log.id,
|
||||
tickNumber: log.tick_number,
|
||||
userGroup: log.user_group,
|
||||
status: log.status,
|
||||
processedPlayers: log.processed_players,
|
||||
retryCount: log.retry_count,
|
||||
errorMessage: log.error_message,
|
||||
performanceMetrics: log.performance_metrics,
|
||||
startedAt: log.started_at,
|
||||
completedAt: log.completed_at
|
||||
})),
|
||||
pagination: {
|
||||
page: pageNum,
|
||||
limit: limitNum,
|
||||
total: parseInt(total),
|
||||
pages: Math.ceil(total / limitNum)
|
||||
}
|
||||
},
|
||||
timestamp: new Date().toISOString(),
|
||||
correlationId
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Failed to get game tick logs', {
|
||||
correlationId,
|
||||
adminId: req.user?.id,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to retrieve game tick logs',
|
||||
correlationId
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Get system performance metrics
|
||||
* GET /admin/system/performance
|
||||
*/
|
||||
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";
|
||||
}
|
||||
|
||||
// Get tick performance metrics
|
||||
const tickMetrics = await db('game_tick_log')
|
||||
.select(
|
||||
db.raw('DATE_TRUNC(\'hour\', started_at) as hour'),
|
||||
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('AVG(processed_players) as avg_players_processed'),
|
||||
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)'))
|
||||
.orderBy('hour');
|
||||
|
||||
// Get database performance metrics
|
||||
const dbMetrics = await db.raw(`
|
||||
SELECT
|
||||
schemaname,
|
||||
tablename,
|
||||
n_tup_ins as inserts,
|
||||
n_tup_upd as updates,
|
||||
n_tup_del as deletes,
|
||||
seq_scan as sequential_scans,
|
||||
idx_scan as index_scans
|
||||
FROM pg_stat_user_tables
|
||||
WHERE schemaname = 'public'
|
||||
ORDER BY (n_tup_ins + n_tup_upd + n_tup_del) DESC
|
||||
LIMIT 10
|
||||
`);
|
||||
|
||||
// Get active player count
|
||||
const playerStats = await db('players')
|
||||
.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')
|
||||
)
|
||||
.first();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
timeRange,
|
||||
tickMetrics: tickMetrics.map(metric => ({
|
||||
hour: metric.hour,
|
||||
totalTicks: parseInt(metric.total_ticks),
|
||||
successfulTicks: parseInt(metric.successful_ticks),
|
||||
failedTicks: parseInt(metric.failed_ticks),
|
||||
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)
|
||||
})),
|
||||
databaseMetrics: dbMetrics.rows,
|
||||
playerStats
|
||||
},
|
||||
timestamp: new Date().toISOString(),
|
||||
correlationId
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Failed to get system performance metrics', {
|
||||
correlationId,
|
||||
adminId: req.user?.id,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to retrieve performance metrics',
|
||||
correlationId
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Stop game tick service
|
||||
* POST /admin/system/tick/stop
|
||||
*/
|
||||
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
|
||||
});
|
||||
|
||||
gameTickService.stop();
|
||||
|
||||
// Log admin action
|
||||
await db('audit_log').insert({
|
||||
entity_type: 'game_tick',
|
||||
entity_id: 0,
|
||||
action: 'service_stopped',
|
||||
actor_type: 'admin',
|
||||
actor_id: req.user?.id,
|
||||
changes: {
|
||||
correlation_id: correlationId,
|
||||
stopped_by: req.user?.username,
|
||||
timestamp: new Date().toISOString()
|
||||
},
|
||||
ip_address: req.ip,
|
||||
user_agent: req.get('User-Agent')
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Game tick service stopped successfully',
|
||||
timestamp: new Date().toISOString(),
|
||||
correlationId
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Failed to stop game tick service', {
|
||||
correlationId,
|
||||
adminId: req.user?.id,
|
||||
error: error.message
|
||||
});
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to stop game tick service',
|
||||
correlationId
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Start game tick service
|
||||
* POST /admin/system/tick/start
|
||||
*/
|
||||
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
|
||||
});
|
||||
|
||||
await gameTickService.initialize();
|
||||
|
||||
// Log admin action
|
||||
await db('audit_log').insert({
|
||||
entity_type: 'game_tick',
|
||||
entity_id: 0,
|
||||
action: 'service_started',
|
||||
actor_type: 'admin',
|
||||
actor_id: req.user?.id,
|
||||
changes: {
|
||||
correlation_id: correlationId,
|
||||
started_by: req.user?.username,
|
||||
timestamp: new Date().toISOString()
|
||||
},
|
||||
ip_address: req.ip,
|
||||
user_agent: req.get('User-Agent')
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Game tick service started successfully',
|
||||
data: gameTickService.getStatus(),
|
||||
timestamp: new Date().toISOString(),
|
||||
correlationId
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Failed to start game tick service', {
|
||||
correlationId,
|
||||
adminId: req.user?.id,
|
||||
error: error.message
|
||||
});
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message || 'Failed to start game tick service',
|
||||
correlationId
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -39,7 +39,8 @@ router.get('/', (req, res) => {
|
|||
colonies: '/api/colonies',
|
||||
fleets: '/api/fleets',
|
||||
research: '/api/research',
|
||||
galaxy: '/api/galaxy'
|
||||
galaxy: '/api/galaxy',
|
||||
combat: '/api/combat'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -162,6 +163,12 @@ playerRoutes.put('/notifications/read',
|
|||
// Mount player routes
|
||||
router.use('/player', playerRoutes);
|
||||
|
||||
/**
|
||||
* Combat Routes
|
||||
* /api/combat/*
|
||||
*/
|
||||
router.use('/combat', require('./api/combat'));
|
||||
|
||||
/**
|
||||
* Game Feature Routes
|
||||
* These will be expanded with actual game functionality
|
||||
|
|
|
|||
130
src/routes/api/combat.js
Normal file
130
src/routes/api/combat.js
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
/**
|
||||
* Combat API Routes
|
||||
* Defines all combat-related endpoints for players
|
||||
*/
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
// Import controllers
|
||||
const {
|
||||
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
|
||||
} = require('../../middleware/combat.middleware');
|
||||
|
||||
// Apply authentication to all combat routes
|
||||
router.use(authenticatePlayer);
|
||||
|
||||
/**
|
||||
* @route POST /api/combat/initiate
|
||||
* @desc Initiate combat between fleets or fleet vs colony
|
||||
* @access Private
|
||||
*/
|
||||
router.post('/initiate',
|
||||
logCombatAction('initiate_combat'),
|
||||
combatRateLimit(5, 15), // Max 5 combat initiations per 15 minutes
|
||||
checkCombatCooldown,
|
||||
validateCombatInitiation,
|
||||
checkFleetAvailability,
|
||||
initiateCombat
|
||||
);
|
||||
|
||||
/**
|
||||
* @route GET /api/combat/active
|
||||
* @desc Get active combats for the current player
|
||||
* @access Private
|
||||
*/
|
||||
router.get('/active',
|
||||
logCombatAction('get_active_combats'),
|
||||
getActiveCombats
|
||||
);
|
||||
|
||||
/**
|
||||
* @route GET /api/combat/history
|
||||
* @desc Get combat history for the current player
|
||||
* @access Private
|
||||
*/
|
||||
router.get('/history',
|
||||
logCombatAction('get_combat_history'),
|
||||
validateCombatHistoryQuery,
|
||||
getCombatHistory
|
||||
);
|
||||
|
||||
/**
|
||||
* @route GET /api/combat/encounter/:encounterId
|
||||
* @desc Get detailed combat encounter information
|
||||
* @access Private
|
||||
*/
|
||||
router.get('/encounter/:encounterId',
|
||||
logCombatAction('get_combat_encounter'),
|
||||
validateParams('encounterId'),
|
||||
getCombatEncounter
|
||||
);
|
||||
|
||||
/**
|
||||
* @route GET /api/combat/statistics
|
||||
* @desc Get combat statistics for the current player
|
||||
* @access Private
|
||||
*/
|
||||
router.get('/statistics',
|
||||
logCombatAction('get_combat_statistics'),
|
||||
getCombatStatistics
|
||||
);
|
||||
|
||||
/**
|
||||
* @route PUT /api/combat/position/:fleetId
|
||||
* @desc Update fleet positioning for tactical combat
|
||||
* @access Private
|
||||
*/
|
||||
router.put('/position/:fleetId',
|
||||
logCombatAction('update_fleet_position'),
|
||||
validateParams('fleetId'),
|
||||
checkFleetOwnership,
|
||||
validateFleetPositionUpdate,
|
||||
updateFleetPosition
|
||||
);
|
||||
|
||||
/**
|
||||
* @route GET /api/combat/types
|
||||
* @desc Get available combat types and configurations
|
||||
* @access Private
|
||||
*/
|
||||
router.get('/types',
|
||||
logCombatAction('get_combat_types'),
|
||||
getCombatTypes
|
||||
);
|
||||
|
||||
/**
|
||||
* @route POST /api/combat/resolve/:battleId
|
||||
* @desc Force resolve a combat (emergency use only)
|
||||
* @access Private (requires special permission)
|
||||
*/
|
||||
router.post('/resolve/:battleId',
|
||||
logCombatAction('force_resolve_combat'),
|
||||
validateParams('battleId'),
|
||||
checkBattleAccess,
|
||||
forceResolveCombat
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -35,7 +35,10 @@ router.get('/', (req, res) => {
|
|||
websocket: '/debug/websocket',
|
||||
system: '/debug/system',
|
||||
logs: '/debug/logs',
|
||||
player: '/debug/player/:playerId'
|
||||
player: '/debug/player/:playerId',
|
||||
colonies: '/debug/colonies',
|
||||
resources: '/debug/resources',
|
||||
gameEvents: '/debug/game-events'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -311,4 +314,243 @@ router.get('/test/:scenario', (req, res) => {
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Colony Debug Information
|
||||
*/
|
||||
router.get('/colonies', async (req, res) => {
|
||||
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));
|
||||
|
||||
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;
|
||||
|
||||
// Get resource types
|
||||
const resourceTypes = await db('resource_types')
|
||||
.where('is_active', true)
|
||||
.orderBy('category')
|
||||
.orderBy('name');
|
||||
|
||||
let 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));
|
||||
|
||||
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);
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Game Events Debug Information
|
||||
*/
|
||||
router.get('/game-events', (req, res) => {
|
||||
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
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Add resources to a player (for testing)
|
||||
*/
|
||||
router.post('/add-resources', async (req, res) => {
|
||||
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
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* Player Colony Routes
|
||||
* Handles all colony-related endpoints for players
|
||||
*/
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
const {
|
||||
createColony,
|
||||
getPlayerColonies,
|
||||
getColonyDetails,
|
||||
constructBuilding,
|
||||
getBuildingTypes,
|
||||
getPlanetTypes,
|
||||
getGalaxySectors
|
||||
} = require('../../controllers/player/colony.controller');
|
||||
|
||||
const { validateRequest } = require('../../middleware/validation.middleware');
|
||||
const {
|
||||
createColonySchema,
|
||||
constructBuildingSchema,
|
||||
colonyIdParamSchema
|
||||
} = require('../../validators/colony.validators');
|
||||
|
||||
// Colony CRUD operations
|
||||
router.post('/',
|
||||
validateRequest(createColonySchema),
|
||||
createColony
|
||||
);
|
||||
|
||||
router.get('/',
|
||||
getPlayerColonies
|
||||
);
|
||||
|
||||
router.get('/:colonyId',
|
||||
validateRequest(colonyIdParamSchema, 'params'),
|
||||
getColonyDetails
|
||||
);
|
||||
|
||||
// Building operations
|
||||
router.post('/:colonyId/buildings',
|
||||
validateRequest(colonyIdParamSchema, 'params'),
|
||||
validateRequest(constructBuildingSchema),
|
||||
constructBuilding
|
||||
);
|
||||
|
||||
// Reference data endpoints
|
||||
router.get('/ref/building-types', getBuildingTypes);
|
||||
router.get('/ref/planet-types', getPlanetTypes);
|
||||
router.get('/ref/galaxy-sectors', getGalaxySectors);
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
const express = require('express');
|
||||
const { authenticateToken, optionalAuth } = require('../../middleware/auth');
|
||||
const { asyncHandler } = require('../../middleware/error-handler');
|
||||
const { asyncHandler } = require('../../middleware/error.middleware');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
|
|
@ -12,6 +12,7 @@ const router = express.Router();
|
|||
const authRoutes = require('./auth');
|
||||
const profileRoutes = require('./profile');
|
||||
const coloniesRoutes = require('./colonies');
|
||||
const resourcesRoutes = require('./resources');
|
||||
const fleetsRoutes = require('./fleets');
|
||||
const researchRoutes = require('./research');
|
||||
const galaxyRoutes = require('./galaxy');
|
||||
|
|
@ -25,6 +26,7 @@ router.use('/galaxy', optionalAuth('player'), galaxyRoutes);
|
|||
// Protected routes (authentication required)
|
||||
router.use('/profile', authenticateToken('player'), profileRoutes);
|
||||
router.use('/colonies', authenticateToken('player'), coloniesRoutes);
|
||||
router.use('/resources', authenticateToken('player'), resourcesRoutes);
|
||||
router.use('/fleets', authenticateToken('player'), fleetsRoutes);
|
||||
router.use('/research', authenticateToken('player'), researchRoutes);
|
||||
router.use('/events', authenticateToken('player'), eventsRoutes);
|
||||
|
|
|
|||
54
src/routes/player/resources.js
Normal file
54
src/routes/player/resources.js
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* Player Resource Routes
|
||||
* Handles all resource-related endpoints for players
|
||||
*/
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
const {
|
||||
getPlayerResources,
|
||||
getPlayerResourceSummary,
|
||||
getResourceProduction,
|
||||
addResources,
|
||||
transferResources,
|
||||
getResourceTypes
|
||||
} = require('../../controllers/player/resource.controller');
|
||||
|
||||
const { validateRequest } = require('../../middleware/validation.middleware');
|
||||
const {
|
||||
transferResourcesSchema,
|
||||
addResourcesSchema,
|
||||
resourceQuerySchema
|
||||
} = require('../../validators/resource.validators');
|
||||
|
||||
// Resource information endpoints
|
||||
router.get('/',
|
||||
validateRequest(resourceQuerySchema, 'query'),
|
||||
getPlayerResources
|
||||
);
|
||||
|
||||
router.get('/summary',
|
||||
getPlayerResourceSummary
|
||||
);
|
||||
|
||||
router.get('/production',
|
||||
getResourceProduction
|
||||
);
|
||||
|
||||
// Resource manipulation endpoints
|
||||
router.post('/transfer',
|
||||
validateRequest(transferResourcesSchema),
|
||||
transferResources
|
||||
);
|
||||
|
||||
// Development/testing endpoints
|
||||
router.post('/add',
|
||||
validateRequest(addResourcesSchema),
|
||||
addResources
|
||||
);
|
||||
|
||||
// Reference data endpoints
|
||||
router.get('/types', getResourceTypes);
|
||||
|
||||
module.exports = router;
|
||||
Loading…
Add table
Add a link
Reference in a new issue