This repository has been archived on 2026-05-04. You can view files and clone it, but cannot push or open issues or pull requests.
Galaxy-Strike-Online/API/server.js
2026-01-24 22:01:08 -04:00

226 lines
6.5 KiB
JavaScript

const express = require('express');
const http = require('http');
const cors = require('cors');
const helmet = require('helmet');
const compression = require('compression');
const rateLimit = require('rate-limiter-flexible');
require('dotenv').config();
const logger = require('./utils/logger');
const connectDB = require('./config/database');
const authRoutes = require('./routes/auth');
const serverRoutes = require('./routes/servers');
const { errorHandler, notFound } = require('./middleware/errorHandler');
const GameServer = require('./models/GameServer');
// Override console.error to properly log error objects
const originalConsoleError = console.error;
console.error = (...args) => {
args.forEach(arg => {
if (arg instanceof Error) {
logger.error('Console Error:', {
message: arg.message,
stack: arg.stack,
name: arg.name
});
} else if (typeof arg === 'object' && arg !== null) {
logger.error('Console Error Object:', arg);
} else {
logger.error('Console Error:', arg);
}
});
};
const app = express();
const server = http.createServer(app);
// Middleware
app.use(helmet());
app.use(compression());
app.use(cors({
origin: ["https://galaxystrike.online", "https://api.korvarix.com", "http://api.korvarix.com:3001", "https://dev.gameserver.galaxystrike.online"],
credentials: true
}));
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
// Static file serving
app.use(express.static('../Website/dist'));
// Rate limiting
const { RateLimiterMemory } = require('rate-limiter-flexible');
const limiter = new RateLimiterMemory({
keyGenerator: (req) => req.ip,
points: 100, // limit each IP to 100 requests per windowMs
duration: 900, // 15 minutes
blockDuration: 900, // Block for 15 minutes
});
app.use('/api/', async (req, res, next) => {
try {
const resLimiter = await limiter.consume(req.ip);
if (!resLimiter.remainingPoints) {
return res.status(429).json({ error: 'Too many requests, please try again later.' });
}
next();
} catch (error) {
next();
}
});
// Routes - API Server Only (Auth + Server Browser)
app.use('/api/auth', authRoutes);
app.use('/api/servers', serverRoutes);
// Manual cleanup endpoint (for testing)
app.post('/api/admin/cleanup-dead-servers', async (req, res) => {
try {
await cleanupDeadServers();
res.json({ success: true, message: 'Dead server cleanup completed' });
} catch (error) {
logger.error('Manual cleanup error:', error);
res.status(500).json({ success: false, error: 'Cleanup failed' });
}
});
// Health check
app.get('/health', (req, res) => {
res.status(200).json({
status: 'API Server OK',
service: 'galaxystrikeonline-api',
timestamp: new Date().toISOString(),
uptime: process.uptime()
});
});
// API version endpoint
app.get('/api/ssc/version', (req, res) => {
res.status(200).json({
version: '1.0.0',
service: 'galaxystrikeonline-api',
timestamp: new Date().toISOString()
});
});
// Fallback route for SPA - only serve index.html for non-API routes
app.get('*', (req, res) => {
// Don't try to serve index.html for API routes
if (req.path.startsWith('/api/')) {
return res.status(404).json({ error: 'API endpoint not found' });
}
// Try dist first (for built files), fallback to public (for development)
const distPath = require('path').resolve(__dirname, '../dist/index.html');
const publicPath = require('path').resolve(__dirname, '../public/index.html');
const fs = require('fs');
if (fs.existsSync(distPath)) {
res.sendFile(distPath);
} else if (fs.existsSync(publicPath)) {
res.sendFile(publicPath);
} else {
res.status(404).json({ error: 'Frontend not found' });
}
});
// Error handling
app.use(notFound);
app.use(errorHandler);
// Clean up dead servers
async function cleanupDeadServers() {
try {
logger.info('[API SERVER] Starting dead server cleanup...');
// Find servers that haven't been updated in the last 5 minutes
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
const deadServers = await GameServer.find({
lastActivity: { $lt: fiveMinutesAgo }
});
if (deadServers.length > 0) {
logger.info(`[API SERVER] Found ${deadServers.length} potentially dead servers, checking...`);
for (const server of deadServers) {
const isAlive = await checkServerHealth(server.gameServerUrl);
if (!isAlive) {
logger.info(`[API SERVER] Removing dead server: ${server.name} (${server.serverId})`);
await GameServer.deleteOne({ _id: server._id });
} else {
logger.info(`[API SERVER] Server ${server.name} is still alive, updating lastActivity`);
server.lastActivity = new Date();
await server.save();
}
}
} else {
logger.info('[API SERVER] No dead servers found');
}
} catch (error) {
logger.error('[API SERVER] Error during dead server cleanup:', error);
}
}
// Check if a server is healthy
async function checkServerHealth(serverUrl) {
try {
if (!serverUrl) {
return false;
}
// Add /health endpoint to the URL
const healthUrl = serverUrl.endsWith('/') ? `${serverUrl}health` : `${serverUrl}/health`;
const response = await fetch(healthUrl, {
method: 'GET',
timeout: 5000 // 5 second timeout
});
return response.ok;
} catch (error) {
logger.warn(`[API SERVER] Health check failed for ${serverUrl}:`, error.message);
return false;
}
}
// Initialize database only (no game systems for API server)
async function startServer() {
try {
// Connect to database
await connectDB();
logger.info('Database connected successfully');
// Start API server
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
logger.info(`API Server running on port ${PORT}`);
logger.info('API Server handles: Authentication, Server Browser, User Data');
// Start dead server cleanup (every 2 minutes)
setInterval(cleanupDeadServers, 120000);
});
} catch (error) {
logger.error('Failed to start API server:', error);
process.exit(1);
}
}
// Handle uncaught errors
process.on('uncaughtException', (error) => {
logger.error('Uncaught Exception:', error);
});
process.on('unhandledRejection', (reason, promise) => {
logger.error('Unhandled Rejection at:', promise, 'reason:', reason);
});
// Handle HTTP server errors
server.on('error', (error) => {
logger.error('HTTP Server error:', error);
});
startServer();
module.exports = { app, server };