feat: initial commit at project root
This commit is contained in:
152
cmd/server/main.js
Normal file
152
cmd/server/main.js
Normal file
@@ -0,0 +1,152 @@
|
||||
import http from 'http';
|
||||
import process from 'process';
|
||||
import { config } from '../../internal/config/config.js';
|
||||
import { logger } from '../../internal/logger/logger.js';
|
||||
import { RateLimiter } from '../../internal/middleware/ratelimit.js';
|
||||
import { Store } from '../../internal/session/store.js';
|
||||
import { Hub } from '../../internal/ws/hub.js';
|
||||
import { setupWebSocket } from '../../internal/ws/handler.js';
|
||||
import { startTimer } from '../../internal/timer/timer.js';
|
||||
import { initializeDatabase, logSessionCreated } from '../../internal/db/db.js';
|
||||
|
||||
// Bootstrapping log
|
||||
logger.info('server_initiating', {
|
||||
addr: config.serverAddr,
|
||||
environment: config.environment
|
||||
});
|
||||
|
||||
const store = new Store();
|
||||
const hub = new Hub(store);
|
||||
const limiter = new RateLimiter(config.rateLimitPerMin, 60000); // 1 minute window
|
||||
|
||||
// Configure standard HTTP server to manage REST actions
|
||||
const server = http.createServer((req, res) => {
|
||||
const url = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
|
||||
|
||||
// Endpoint: GET /health
|
||||
if (url.pathname === '/health' && req.method === 'GET') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({
|
||||
status: 'ok',
|
||||
active_sessions: store.getActiveCount(),
|
||||
connected_clients: hub.getConnectedClientsCount()
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// Endpoint: POST /sessions
|
||||
// Pre-creates call session mapping. Authenticated using X-API-Key header.
|
||||
if (url.pathname === '/sessions' && req.method === 'POST') {
|
||||
const apiKeyHeader = req.headers['x-api-key'];
|
||||
if (!apiKeyHeader || apiKeyHeader !== config.apiKey) {
|
||||
logger.warn('unauthorized_session_creation_attempt', {
|
||||
remote_ip: req.socket.remoteAddress
|
||||
});
|
||||
res.writeHead(401, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: 'unauthorized' }));
|
||||
return;
|
||||
}
|
||||
|
||||
let body = '';
|
||||
req.on('data', chunk => {
|
||||
body += chunk;
|
||||
});
|
||||
|
||||
req.on('end', async () => {
|
||||
let params;
|
||||
try {
|
||||
params = JSON.parse(body);
|
||||
} catch (err) {
|
||||
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: 'invalid_json' }));
|
||||
return;
|
||||
}
|
||||
|
||||
const { ride_id, driver_id, passenger_id, driver_ip, passenger_ip } = params;
|
||||
if (!ride_id || !driver_id || !passenger_id) {
|
||||
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: 'missing_parameters' }));
|
||||
return;
|
||||
}
|
||||
|
||||
let sess;
|
||||
try {
|
||||
// Enforce hard 60s maximum call duration
|
||||
sess = store.createSession(ride_id, driver_id, passenger_id, 60000, driver_ip, passenger_ip);
|
||||
} catch (err) {
|
||||
logger.warn('session_creation_failed', { ride_id, error: err.message });
|
||||
res.writeHead(409, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: 'session_exists' }));
|
||||
return;
|
||||
}
|
||||
|
||||
// Start 60s timeout countdown
|
||||
sess.timer = startTimer(60000, () => {
|
||||
hub.forceEndSession(sess.sessionID, 'max_duration_reached');
|
||||
});
|
||||
|
||||
// Log session creation to database
|
||||
await logSessionCreated(sess.sessionID, sess.rideID, sess.driverID, sess.passengerID, sess.driverIP, sess.passengerIP);
|
||||
|
||||
logger.info('session_created', {
|
||||
session_id: sess.sessionID,
|
||||
ride_id: sess.rideID,
|
||||
driver_id: sess.driverID,
|
||||
passenger_id: sess.passengerID,
|
||||
driver_ip: sess.driverIP,
|
||||
passenger_ip: sess.passengerIP
|
||||
});
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({
|
||||
session_id: sess.sessionID,
|
||||
ride_id: sess.rideID,
|
||||
expires_in: 60
|
||||
}));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
||||
res.end('Not Found');
|
||||
});
|
||||
|
||||
// Bind WebSocket upgrades interceptor
|
||||
setupWebSocket(server, hub, limiter);
|
||||
|
||||
// Resolve address binding parts
|
||||
const [host, portStr] = config.serverAddr.split(':');
|
||||
const port = parseInt(portStr, 10);
|
||||
|
||||
// Initialize database first then start server
|
||||
initializeDatabase().then(() => {
|
||||
server.listen(port, host, () => {
|
||||
logger.info('server_running', { addr: config.serverAddr });
|
||||
});
|
||||
});
|
||||
|
||||
// Graceful exit handling
|
||||
function shutdown(signal) {
|
||||
logger.info('server_stopping', { signal });
|
||||
|
||||
// Close HTTP server to stop accepting new traffic
|
||||
server.close((err) => {
|
||||
if (err) {
|
||||
logger.error('server_close_error', { error: err.message });
|
||||
}
|
||||
logger.info('server_stopped_cleanly');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
// Warn active sockets and terminate connections
|
||||
hub.shutdownGracefully();
|
||||
|
||||
// Force close after 10s maximum timeout
|
||||
setTimeout(() => {
|
||||
logger.warn('server_shutdown_timeout_force_exit');
|
||||
process.exit(1);
|
||||
}, 10000).unref();
|
||||
}
|
||||
|
||||
process.on('SIGINT', () => shutdown('SIGINT'));
|
||||
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
||||
Reference in New Issue
Block a user