const express = require('express'); const path = require('path'); const bodyParser = require('body-parser'); const API = require('./src/js/index.js'); const { logger } = require('./src/js/logger.js'); const favicon = require('serve-favicon'); const app = express(); const port = process.env.PORT || 3512; require('./src/js/utils.js'); app.set('trust proxy', true); // Middleware app.use(bodyParser.json({ limit: '10mb' })); app.use(bodyParser.urlencoded({ extended: true, limit: '10mb' })); app.use(express.static(__dirname)); app.use(express.static(path.join(__dirname, 'public'))); app.use('/images', express.static(path.join(__dirname, 'src/images'))); app.use(favicon(path.join(__dirname, 'src', 'images', 'favicon.ico'))); app.use( bodyParser.json({ limit: '10mb', verify: (req, res, buf) => { req.rawBody = buf.toString(); }, }) ); // app.use(express.raw({ type: 'application/json', limit: '10mb' })); const fs = require('fs'); // Initialize key replacements let keyReplacements = {}; try { const replacementsPath = path.join( __dirname, 'src', 'data', 'replacements.json' ); if (fs.existsSync(replacementsPath)) { const replacementsContent = fs.readFileSync(replacementsPath, 'utf8'); keyReplacements = JSON.parse(replacementsContent); // logger.debug("Replacements loaded successfully"); } else { logger.warn('replacements.json not found, key replacement disabled'); } } catch (error) { logger.error('Error loading replacements file:', { error: error.message }); } const replaceJsonKeys = (obj) => { if (!obj || typeof obj !== 'object') return obj; if (Array.isArray(obj)) { return obj.map((item) => replaceJsonKeys(item)); } const newObj = {}; Object.keys(obj).forEach((key) => { // Replace key if it exists in replacements const newKey = keyReplacements[key] || key; // DEBUG: Log replacements when they happen // if (newKey !== key) { // logger.debug(`Replacing key "${key}" with "${newKey}"`); // } // Also check if the value should be replaced (if it's a string) let value = obj[key]; if (typeof value === 'string' && keyReplacements[value]) { value = keyReplacements[value]; // logger.debug(`Replacing value "${obj[key]}" with "${value}"`); } // Process value recursively if it's an object or array newObj[newKey] = replaceJsonKeys(value); }); return newObj; }; // Utility regex function const sanitizeJsonOutput = (data) => { if (!data) return data; // Convert to string to perform regex operations const jsonString = JSON.stringify(data); // Define regex pattern that matches HTML entities const regexPattern = /<span class=".*?">|<\/span>|">/g; // Replace unwanted patterns const sanitizedString = jsonString.replace(regexPattern, ''); // Parse back to object try { return JSON.parse(sanitizedString); } catch (e) { logger.error('Error parsing sanitized JSON:', e); return data; // Return original data if parsing fails } }; // Replace the processJsonOutput function with this more efficient version const processJsonOutput = ( data, options = { sanitize: true, replaceKeys: true } ) => { // Use a more efficient deep clone approach instead of JSON.parse(JSON.stringify()) function deepClone(obj) { if (obj === null || typeof obj !== 'object') { return obj; } if (Array.isArray(obj)) { return obj.map((item) => deepClone(item)); } const clone = {}; Object.keys(obj).forEach((key) => { clone[key] = deepClone(obj[key]); }); return clone; } // Create a deep copy of the data to avoid reference issues let processedData = deepClone(data); // Apply sanitization if needed if (options.sanitize) { processedData = sanitizeJsonOutput(processedData); } // Apply key replacement if needed if (options.replaceKeys) { processedData = replaceJsonKeys(processedData); } return processedData; }; // Store active sessions to avoid repeated logins const activeSessions = new Map(); // Utility function to create a timeout promise const timeoutPromise = (ms) => { return new Promise((_, reject) => { setTimeout(() => reject(new Error(`Request timed out after ${ms}ms`)), ms); }); }; // Helper function to ensure login const ensureLogin = async (ssoToken) => { if (!activeSessions.has(ssoToken)) { logger.info( `Attempting to login with SSO token: ${ssoToken.substring(0, 5)}...` ); // logger.info(`Attempting to login with SSO token: ${ssoToken}`); const loginResult = await Promise.race([ API.login(ssoToken), timeoutPromise(10000), // 10 second timeout ]); logger.debug(`Login successful: ${JSON.stringify(loginResult)}`); logger.debug(`Session created at: ${global.Utils.toIsoString(new Date())}`); activeSessions.set(ssoToken, new Date()); } else { logger.debug('Using existing session'); } }; // Helper function to handle API errors const handleApiError = (error, res) => { logger.error('API Error:', error); logger.error(`Error Stack: ${error.stack}`); logger.error(`Error Time: ${global.Utils.toIsoString(new Date())}`); // Try to extract more useful information from the error let errorMessage = error.message || 'Unknown API error'; let errorName = error.name || 'ApiError'; // Handle the specific JSON parsing error if (errorName === 'SyntaxError' && errorMessage.includes('JSON')) { logger.debug('JSON parsing error detected'); return res.status(200).json({ status: 'error', message: 'Failed to parse API response. This usually means the SSO token is invalid or expired.', error_type: 'InvalidResponseError', timestamp: global.Utils.toIsoString(new Date()), }); } // Send a more graceful response return res.status(200).json({ status: 'error', message: errorMessage, error_type: errorName, timestamp: global.Utils.toIsoString(new Date()), }); }; // API endpoint to fetch stats app.post('/api/stats', async (req, res) => { logger.debug('Received request for /api/stats'); logger.debug( `Request IP: ${ req.headers['x-forwarded-for'] || req.ip || req.connection.remoteAddress }` ); logger.debug( `Request JSON: ${JSON.stringify({ username: req.body.username, platform: req.body.platform, game: req.body.game, apiCall: req.body.apiCall, sanitize: req.body.sanitize, replaceKeys: req.body.replaceKeys, })}` ); try { const { username, ssoToken, platform, game, apiCall, sanitize, replaceKeys, } = req.body; /* logger.debug( `Request details - Username: ${username}, Platform: ${platform}, Game: ${game}, API Call: ${apiCall}` ); logger.debug("=== STATS REQUEST ==="); logger.debug(`Username: ${username || 'Not provided'}`); logger.debug(`Platform: ${platform}`); logger.debug(`Game: ${game}`); logger.debug(`API Call: ${apiCall}`); logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`); logger.debug("====================="); */ if (!ssoToken) { return res.status(400).json({ error: 'SSO Token is required' }); } // For mapList, username is not required if (apiCall !== 'mapList' && !username) { return res.status(400).json({ error: 'Username is required' }); } // Clear previous session if it exists if (activeSessions.has(ssoToken)) { logger.debug('Clearing previous session'); activeSessions.delete(ssoToken); } // Login with the provided SSO token try { await ensureLogin(ssoToken); } catch (loginError) { logger.error('Login error:', loginError); return res.status(200).json({ status: 'error', error_type: 'LoginError', message: 'SSO token login failed', details: loginError.message || 'Unknown login error', timestamp: global.Utils.toIsoString(new Date()), }); } // Create a wrapped function for each API call to handle timeout const fetchWithTimeout = async (apiFn) => { return Promise.race([ apiFn(), timeoutPromise(30000), // 30 second timeout ]); }; // Check if the platform is valid for the game const requiresUno = ['mw2', 'wz2', 'mw3', 'wzm'].includes(game); if (requiresUno && platform !== 'uno' && apiCall !== 'mapList') { logger.warn(`${game} requires Uno ID`); return res.status(200).json({ status: 'error', message: `${game} requires Uno ID (numerical ID)`, timestamp: global.Utils.toIsoString(new Date()), }); } try { logger.debug( `Attempting to fetch ${game} data for ${username} on ${platform}` ); let data; if (apiCall === 'fullData') { // Fetch lifetime stats based on game switch (game) { case 'mw': data = await fetchWithTimeout(() => API.ModernWarfare.fullData(username, platform) ); break; case 'wz': data = await fetchWithTimeout(() => API.Warzone.fullData(username, platform) ); break; case 'mw2': data = await fetchWithTimeout(() => API.ModernWarfare2.fullData(username) ); break; case 'wz2': data = await fetchWithTimeout(() => API.Warzone2.fullData(username) ); break; case 'mw3': data = await fetchWithTimeout(() => API.ModernWarfare3.fullData(username) ); break; case 'cw': data = await fetchWithTimeout(() => API.ColdWar.fullData(username, platform) ); break; case 'vg': data = await fetchWithTimeout(() => API.Vanguard.fullData(username, platform) ); break; case 'wzm': data = await fetchWithTimeout(() => API.WarzoneMobile.fullData(username) ); break; default: return res.status(200).json({ status: 'error', message: 'Invalid game selected', timestamp: global.Utils.toIsoString(new Date()), }); } } else if (apiCall === 'combatHistory') { // Fetch recent match history based on game switch (game) { case 'mw': data = await fetchWithTimeout(() => API.ModernWarfare.combatHistory(username, platform) ); break; case 'wz': data = await fetchWithTimeout(() => API.Warzone.combatHistory(username, platform) ); break; case 'mw2': data = await fetchWithTimeout(() => API.ModernWarfare2.combatHistory(username) ); break; case 'wz2': data = await fetchWithTimeout(() => API.Warzone2.combatHistory(username) ); break; case 'mw3': data = await fetchWithTimeout(() => API.ModernWarfare3.combatHistory(username) ); break; case 'cw': data = await fetchWithTimeout(() => API.ColdWar.combatHistory(username, platform) ); break; case 'vg': data = await fetchWithTimeout(() => API.Vanguard.combatHistory(username, platform) ); break; case 'wzm': data = await fetchWithTimeout(() => API.WarzoneMobile.combatHistory(username) ); break; default: return res.status(200).json({ status: 'error', message: 'Invalid game selected', timestamp: global.Utils.toIsoString(new Date()), }); } } else if (apiCall === 'mapList') { // Fetch map list (only valid for MW) if (game === 'mw') { data = await fetchWithTimeout(() => API.ModernWarfare.mapList(platform) ); } else { return res.status(200).json({ status: 'error', message: 'Map list is only available for Modern Warfare', timestamp: global.Utils.toIsoString(new Date()), }); } } logger.debug('Data fetched successfully'); logger.debug(`Response Size: ~${JSON.stringify(data).length / 1024} KB`); logger.debug(`Response Time: ${global.Utils.toIsoString(new Date())}`); // Safely handle the response data if (!data) { logger.warn('No data returned from API'); return res.json({ status: 'partial_success', message: 'No data returned from API, but no error thrown', data: null, timestamp: global.Utils.toIsoString(new Date()), }); } // logger.debug("Returning data to client"); const { sanitize, replaceKeys } = req.body; return res.json({ // status: "success", data: processJsonOutput(data, { sanitize, replaceKeys }), timestamp: global.Utils.toIsoString(new Date()), }); } catch (apiError) { return handleApiError(apiError, res); } } catch (serverError) { logger.error('Server Error:', serverError); // Return a structured error response even for unexpected errors return res.status(200).json({ status: 'server_error', message: 'The server encountered an unexpected error', error_details: serverError.message || 'Unknown server error', timestamp: global.Utils.toIsoString(new Date()), }); } }); // API endpoint to fetch recent matches app.post('/api/matches', async (req, res) => { logger.debug('Received request for /api/matches'); logger.debug( `Request IP: ${ req.headers['x-forwarded-for'] || req.ip || req.connection.remoteAddress }` ); logger.debug( `Request JSON: ${JSON.stringify({ username: req.body.username, platform: req.body.platform, game: req.body.game, sanitize: req.body.sanitize, replaceKeys: req.body.replaceKeys, })}` ); try { const { username, ssoToken, platform, game, sanitize, replaceKeys } = req.body; /* logger.debug( `Request details - Username: ${username}, Platform: ${platform}, Game: ${game}` ); logger.debug("=== MATCHES REQUEST ==="); logger.debug(`Username: ${username}`); logger.debug(`Platform: ${platform}`); logger.debug(`Game: ${game}`); logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`); logger.debug("========================"); */ if (!username || !ssoToken) { return res .status(400) .json({ error: 'Username and SSO Token are required' }); } try { await ensureLogin(ssoToken); } catch (loginError) { return res.status(200).json({ status: 'error', error_type: 'LoginError', message: 'SSO token login failed', details: loginError.message || 'Unknown login error', timestamp: global.Utils.toIsoString(new Date()), }); } // Create a wrapped function for each API call to handle timeout const fetchWithTimeout = async (apiFn) => { return Promise.race([ apiFn(), timeoutPromise(30000), // 30 second timeout ]); }; try { logger.debug( `Attempting to fetch combat history for ${username} on ${platform}` ); let data; // Check if the platform is valid for the game const requiresUno = ['mw2', 'wz2', 'mw3', 'wzm'].includes(game); if (requiresUno && platform !== 'uno') { return res.status(200).json({ status: 'error', message: `${game} requires Uno ID (numerical ID)`, timestamp: global.Utils.toIsoString(new Date()), }); } // Fetch combat history based on game switch (game) { case 'mw': data = await fetchWithTimeout(() => API.ModernWarfare.combatHistory(username, platform) ); break; case 'wz': data = await fetchWithTimeout(() => API.Warzone.combatHistory(username, platform) ); break; case 'mw2': data = await fetchWithTimeout(() => API.ModernWarfare2.combatHistory(username) ); break; case 'wz2': data = await fetchWithTimeout(() => API.Warzone2.combatHistory(username) ); break; case 'mw3': data = await fetchWithTimeout(() => API.ModernWarfare3.combatHistory(username) ); break; case 'cw': data = await fetchWithTimeout(() => API.ColdWar.combatHistory(username, platform) ); break; case 'vg': data = await fetchWithTimeout(() => API.Vanguard.combatHistory(username, platform) ); break; case 'wzm': data = await fetchWithTimeout(() => API.WarzoneMobile.combatHistory(username) ); break; default: return res.status(200).json({ status: 'error', message: 'Invalid game selected', timestamp: global.Utils.toIsoString(new Date()), }); } const { sanitize, replaceKeys } = req.body; return res.json({ // status: "success", data: processJsonOutput(data, { sanitize, replaceKeys }), timestamp: global.Utils.toIsoString(new Date()), }); } catch (apiError) { return handleApiError(apiError, res); } } catch (serverError) { return res.status(200).json({ status: 'server_error', message: 'The server encountered an unexpected error', error_details: serverError.message || 'Unknown server error', timestamp: global.Utils.toIsoString(new Date()), }); } }); // API endpoint to fetch match info app.post('/api/matchInfo', async (req, res) => { logger.debug('Received request for /api/matchInfo'); logger.debug( `Request IP: ${ req.headers['x-forwarded-for'] || req.ip || req.connection.remoteAddress }` ); logger.debug( `Request JSON: ${JSON.stringify({ matchId: req.body.matchId, platform: req.body.platform, game: req.body.game, sanitize: req.body.sanitize, replaceKeys: req.body.replaceKeys, })}` ); try { const { matchId, ssoToken, platform, game, sanitize, replaceKeys } = req.body; const mode = 'mp'; /* logger.debug( `Request details - Match ID: ${matchId}, Platform: ${platform}, Game: ${game}` ); logger.debug("=== MATCH INFO REQUEST ==="); logger.debug(`Match ID: ${matchId}`); logger.debug(`Platform: ${platform}`); logger.debug(`Game: ${game}`); logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`); logger.debug("=========================="); */ if (!matchId || !ssoToken) { return res .status(400) .json({ error: 'Match ID and SSO Token are required' }); } try { await ensureLogin(ssoToken); } catch (loginError) { return res.status(200).json({ status: 'error', error_type: 'LoginError', message: 'SSO token login failed', details: loginError.message || 'Unknown login error', timestamp: global.Utils.toIsoString(new Date()), }); } // Create a wrapped function for each API call to handle timeout const fetchWithTimeout = async (apiFn) => { return Promise.race([ apiFn(), timeoutPromise(30000), // 30 second timeout ]); }; try { logger.debug(`Attempting to fetch match info for match ID: ${matchId}`); let data; // Fetch match info based on game switch (game) { case 'mw': data = await fetchWithTimeout(() => API.ModernWarfare.matchInfo(matchId, platform) ); break; case 'wz': data = await fetchWithTimeout(() => API.Warzone.matchInfo(matchId, platform) ); break; case 'mw2': data = await fetchWithTimeout(() => API.ModernWarfare2.matchInfo(matchId) ); break; case 'wz2': data = await fetchWithTimeout(() => API.Warzone2.matchInfo(matchId)); break; case 'mw3': data = await fetchWithTimeout(() => API.ModernWarfare3.matchInfo(matchId) ); break; case 'cw': data = await fetchWithTimeout(() => API.ColdWar.matchInfo(matchId, platform) ); break; case 'vg': data = await fetchWithTimeout(() => API.Vanguard.matchInfo(matchId, platform) ); break; case 'wzm': data = await fetchWithTimeout(() => API.WarzoneMobile.matchInfo(matchId) ); break; default: return res.status(200).json({ status: 'error', message: 'Invalid game selected', timestamp: global.Utils.toIsoString(new Date()), }); } const { sanitize, replaceKeys } = req.body; return res.json({ // status: "success", data: processJsonOutput(data, { sanitize, replaceKeys }), timestamp: global.Utils.toIsoString(new Date()), }); } catch (apiError) { return handleApiError(apiError, res); } } catch (serverError) { return res.status(200).json({ status: 'server_error', message: 'The server encountered an unexpected error', error_details: serverError.message || 'Unknown server error', timestamp: global.Utils.toIsoString(new Date()), }); } }); // API endpoint for user-related API calls app.post('/api/user', async (req, res) => { logger.debug('Received request for /api/user'); logger.debug( `Request IP: ${ req.headers['x-forwarded-for'] || req.ip || req.connection.remoteAddress }` ); logger.debug( `Request JSON: ${JSON.stringify({ username: req.body.username, platform: req.body.platform, userCall: req.body.userCall, sanitize: req.body.sanitize, replaceKeys: req.body.replaceKeys, })}` ); try { const { username, ssoToken, platform, userCall, sanitize, replaceKeys } = req.body; /* logger.debug( `Request details - Username: ${username}, Platform: ${platform}, User Call: ${userCall}` ); logger.debug("=== USER DATA REQUEST ==="); logger.debug(`Username: ${username || 'Not provided'}`); logger.debug(`Platform: ${platform}`); logger.debug(`User Call: ${userCall}`); logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`); logger.debug("========================="); */ if (!ssoToken) { return res.status(400).json({ error: 'SSO Token is required' }); } // For eventFeed and identities, username is not required if ( !username && userCall !== 'eventFeed' && userCall !== 'friendFeed' && userCall !== 'identities' ) { return res .status(400) .json({ error: 'Username is required for this API call' }); } try { await ensureLogin(ssoToken); } catch (loginError) { return res.status(200).json({ status: 'error', error_type: 'LoginError', message: 'SSO token login failed', details: loginError.message || 'Unknown login error', timestamp: global.Utils.toIsoString(new Date()), }); } // Create a wrapped function for each API call to handle timeout const fetchWithTimeout = async (apiFn) => { return Promise.race([ apiFn(), timeoutPromise(30000), // 30 second timeout ]); }; try { logger.debug(`Attempting to fetch user data for ${userCall}`); let data; // Fetch user data based on userCall switch (userCall) { case 'codPoints': data = await fetchWithTimeout(() => API.Me.codPoints(username, platform) ); break; case 'connectedAccounts': data = await fetchWithTimeout(() => API.Me.connectedAccounts(username, platform) ); break; case 'eventFeed': data = await fetchWithTimeout(() => API.Me.eventFeed()); break; case 'friendFeed': data = await fetchWithTimeout(() => API.Me.friendFeed(username, platform) ); break; case 'identities': data = await fetchWithTimeout(() => API.Me.loggedInIdentities()); break; case 'friendsList': data = await fetchWithTimeout(() => API.Me.friendsList()); break; // case "settings": // data = await fetchWithTimeout(() => // API.Me.settings(username, platform) // ); // break; default: return res.status(200).json({ status: 'error', message: 'Invalid user API call selected', timestamp: global.Utils.toIsoString(new Date()), }); } const { sanitize, replaceKeys } = req.body; return res.json({ // status: "success", data: processJsonOutput(data, { sanitize, replaceKeys }), timestamp: global.Utils.toIsoString(new Date()), }); } catch (apiError) { return handleApiError(apiError, res); } } catch (serverError) { return res.status(200).json({ status: 'server_error', message: 'The server encountered an unexpected error', error_details: serverError.message || 'Unknown server error', timestamp: global.Utils.toIsoString(new Date()), }); } }); // API endpoint for fuzzy search app.post('/api/search', async (req, res) => { logger.debug('Received request for /api/search'); logger.debug( `Request IP: ${ req.headers['x-forwarded-for'] || req.ip || req.connection.remoteAddress }` ); logger.debug( `Request JSON: ${JSON.stringify({ username: req.body.username, platform: req.body.platform, sanitize: req.body.sanitize, replaceKeys: req.body.replaceKeys, })}` ); try { const { username, ssoToken, platform, sanitize, replaceKeys } = req.body; /* logger.debug( `Request details - Username to search: ${username}, Platform: ${platform}` ); logger.debug("=== SEARCH REQUEST ==="); logger.debug(`Search Term: ${username}`); logger.debug(`Platform: ${platform}`); logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`); logger.debug("======================"); */ if (!username || !ssoToken) { return res .status(400) .json({ error: 'Username and SSO Token are required' }); } try { await ensureLogin(ssoToken); } catch (loginError) { return res.status(200).json({ status: 'error', error_type: 'LoginError', message: 'SSO token login failed', details: loginError.message || 'Unknown login error', timestamp: global.Utils.toIsoString(new Date()), }); } // Create a wrapped function for each API call to handle timeout const fetchWithTimeout = async (apiFn) => { return Promise.race([ apiFn(), timeoutPromise(30000), // 30 second timeout ]); }; try { logger.debug( `Attempting fuzzy search for ${username} on platform ${platform}` ); const data = await fetchWithTimeout(() => API.Misc.search(username, platform) ); const { sanitize, replaceKeys } = req.body; return res.json({ // status: "success", data: processJsonOutput(data, { sanitize, replaceKeys }), timestamp: global.Utils.toIsoString(new Date()), // link: "Stats pulled using codtracker.rimmyscorner.com", }); } catch (apiError) { return handleApiError(apiError, res); } } catch (serverError) { return res.status(200).json({ status: 'server_error', message: 'The server encountered an unexpected error', error_details: serverError.message || 'Unknown server error', timestamp: global.Utils.toIsoString(new Date()), }); } }); // Improved logging endpoint app.post('/api/log', (req, res) => { const clientIP = req.headers['x-forwarded-for'] || req.ip || req.connection.remoteAddress; const userAgent = req.headers['user-agent']; const referer = req.headers['referer']; const origin = req.headers['origin']; let logData; try { // Handle data whether it comes as already parsed JSON or as a string if (typeof req.body === 'string') { logData = JSON.parse(req.body); } else if (req.body && typeof req.body === 'object') { logData = req.body; } else { // If no parsable data found, create a basic log entry logData = { eventType: 'unknown', timestamp: global.Utils.toIsoString(new Date()), }; } // Enrich log with server-side data const enrichedLog = { ...logData, meta: { clientIP, userAgent, referer, origin, requestHeaders: sanitizeHeaders(req.headers), serverTimestamp: global.Utils.toIsoString(new Date()), requestId: req.id || Math.random().toString(36).substring(2, 15), }, }; // Use the dedicated user activity logger logger.userActivity(enrichedLog.eventType || 'unknown', enrichedLog); } catch (error) { logger.error('Error processing log data', { error: error.message, rawBody: typeof req.body === 'object' ? '[Object]' : req.body, }); } // Always return 200 to avoid client-side errors res.status(200).send(); }); // Helper function to remove sensitive data from headers function sanitizeHeaders(headers) { const safeHeaders = { ...headers }; // Remove potential sensitive information const sensitiveHeaders = ['authorization', 'cookie', 'set-cookie']; sensitiveHeaders.forEach((header) => { if (safeHeaders[header]) { safeHeaders[header] = '[REDACTED]'; } }); return safeHeaders; } // Database storage function /* function storeLogInDatabase(logData) { // Example with MongoDB db.collection('user_logs').insertOne(logData) .catch(err => logger.error('Failed to store log in database:', err)); } */ // Basic health check endpoint app.get('/health', (req, res) => { res.json({ status: 'ok', timestamp: global.Utils.toIsoString(new Date()) }); }); // Serve the main HTML file app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'src', 'index.html')); }); // Start the server app.listen(port, () => { logger.info(`Server running on http://localhost:${port}`); });