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
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue