const express = require('express'); const helmet = require('helmet'); const rateLimit = require('express-rate-limit'); const { createSessionMiddleware, requireAuth } = require('./middleware/session'); const { registerLimiter, loginLimiter, routes: authRoutes } = require('./routes/auth'); const { routes: characterRoutes } = require('./routes/character'); const { routes: huntRoutes } = require('./routes/hunt'); const { routes: zonesRoutes } = require('./routes/zones'); const { routes: combatLogRoutes } = require('./routes/combatLog'); const { routes: inventoryRoutes } = require('./routes/inventory'); const { routes: shopRoutes } = require('./routes/shop'); const { routes: expeditionRoutes } = require('./routes/expeditions'); const { routes: leaderboardRoutes } = require('./routes/leaderboard'); const { routes: heroProfileRoutes } = require('./routes/heroProfile'); const { AppError } = require('./utils/errors'); // General API rate limiter: 120 req/min per IP (SECURITY.md). const apiLimiter = rateLimit({ windowMs: 60 * 1000, // 1 minute max: process.env.NODE_ENV === 'test' ? 1000 : 120, message: { ok: false, error: { code: 'RATE_LIMITED', message: 'Too many requests. Try again later.' } }, }); function createApp() { const app = express(); // Security headers app.use(helmet()); // Trust proxy (for rate limiting behind reverse proxy) app.set('trust proxy', 1); // JSON body parser with small limit app.use(express.json({ limit: '10kb' })); // Session middleware app.use(createSessionMiddleware()); // Static files app.use(express.static('public')); // Health endpoint (no auth) app.get('/api/health', (_req, res) => { res.json({ ok: true }); }); // Auth routes (public) app.post('/api/auth/register', registerLimiter, authRoutes.postRegister); app.post('/api/auth/login', loginLimiter, authRoutes.postLogin); // Auth routes (require session) app.post('/api/auth/logout', requireAuth, authRoutes.postLogout); app.get('/api/auth/me', requireAuth, authRoutes.getMe); // General API rate limiter (applies to all non-auth routes below) app.use('/api', apiLimiter); // Character routes (require session) app.post('/api/character', requireAuth, characterRoutes.postCharacter); app.get('/api/character', requireAuth, characterRoutes.getCharacter); // Hunt routes (require session) app.post('/api/hunt', requireAuth, huntRoutes.postHunt); // Zone routes (require session) app.get('/api/zones', requireAuth, zonesRoutes.getZones); // Combat log routes (require session) app.get('/api/combat-log', requireAuth, combatLogRoutes.getCombatLog); // Inventory routes (require session) app.get('/api/inventory', requireAuth, inventoryRoutes.getInventory); app.post('/api/inventory/equip', requireAuth, inventoryRoutes.postEquip); app.post('/api/inventory/unequip', requireAuth, inventoryRoutes.postUnequip); app.post('/api/inventory/use', requireAuth, inventoryRoutes.postUse); // Shop routes (require session) app.get('/api/shop', requireAuth, shopRoutes.getShop); app.post('/api/shop/buy', requireAuth, shopRoutes.postBuy); app.post('/api/shop/sell', requireAuth, shopRoutes.postSell); // Expedition routes (require session) app.get('/api/expeditions/defs', requireAuth, expeditionRoutes.getDefs); app.post('/api/expeditions/start', requireAuth, expeditionRoutes.postStart); app.get('/api/expeditions/active', requireAuth, expeditionRoutes.getActive); app.post('/api/expeditions/claim', requireAuth, expeditionRoutes.postClaim); // Leaderboard route (require session) app.get('/api/leaderboard', requireAuth, leaderboardRoutes.getLeaderboard); // Hero profile route (public, no session required) app.get('/api/heroes/:name', heroProfileRoutes.getHeroProfile); // Central error handler app.use((err, _req, res, _next) => { // Log stack traces server-side only if (err.stack) { console.error(err.stack); } if (err instanceof AppError) { return res.status(err.status).json({ ok: false, error: { code: err.code, message: err.message, }, }); } // Check for Zod-like validation errors if (err.name === 'ZodError' || (err.errors && Array.isArray(err.errors))) { return res.status(400).json({ ok: false, error: { code: 'VALIDATION_ERROR', message: 'Request validation failed.', }, }); } // Generic error res.status(500).json({ ok: false, error: { code: 'INTERNAL_ERROR', message: 'Something went wrong.', }, }); }); return app; } module.exports = { createApp };