137 lines
4.2 KiB
TypeScript
137 lines
4.2 KiB
TypeScript
/**
|
|
* LandveX Master Gateway
|
|
*
|
|
* Unifies all backend services:
|
|
* - Intelligence Lab (port 3002)
|
|
* - Operations API (port 3005)
|
|
* - Communications (port 3010)
|
|
* - Apollo CRM (port 3001)
|
|
* - AAMOS Ledger (port 3250)
|
|
* - AAMOS Incidents (port 3303)
|
|
*/
|
|
|
|
import http from 'http';
|
|
import { GATEWAY_CONFIG, SERVICES } from './config';
|
|
import { requireAuth, requireRole, verifyApiKey } from './auth';
|
|
import { proxyRequest, checkServiceHealth } from './proxy';
|
|
|
|
// Service health status
|
|
const serviceHealth: Record<string, { healthy: boolean; latency: number; lastCheck: number; error?: string }> = {};
|
|
|
|
// Rate limiting (simple in-memory)
|
|
const rateLimits: Record<string, { count: number; resetAt: number }> = {};
|
|
|
|
function checkRateLimit(identifier: string, maxRequests: number): boolean {
|
|
const now = Date.now();
|
|
const windowStart = Math.floor(now / GATEWAY_CONFIG.rateLimitWindow) * GATEWAY_CONFIG.rateLimitWindow;
|
|
|
|
if (!rateLimits[identifier] || rateLimits[identifier].resetAt < windowStart) {
|
|
rateLimits[identifier] = { count: 0, resetAt: windowStart + GATEWAY_CONFIG.rateLimitWindow };
|
|
}
|
|
|
|
rateLimits[identifier].count++;
|
|
return rateLimits[identifier].count <= maxRequests;
|
|
}
|
|
|
|
// CORS headers
|
|
function setCorsHeaders(res: http.ServerResponse): void {
|
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
|
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-API-Key');
|
|
}
|
|
|
|
// Main server
|
|
const server = http.createServer(async (req, res) => {
|
|
setCorsHeaders(res);
|
|
|
|
if (req.method === 'OPTIONS') {
|
|
res.statusCode = 204;
|
|
res.end();
|
|
return;
|
|
}
|
|
|
|
const url = req.url || '/';
|
|
|
|
// Health check
|
|
if (url === '/health') {
|
|
res.setHeader('Content-Type', 'application/json');
|
|
res.end(JSON.stringify({
|
|
status: 'ok',
|
|
version: '0.1.0-gateway',
|
|
environment: GATEWAY_CONFIG.env,
|
|
services: Object.entries(serviceHealth).map(([name, health]) => ({
|
|
name,
|
|
healthy: health.healthy,
|
|
latency: health.latency,
|
|
lastCheck: health.lastCheck,
|
|
error: health.error,
|
|
})),
|
|
}));
|
|
return;
|
|
}
|
|
|
|
// API Key auth for external API access
|
|
const apiKey = req.headers['x-api-key'] as string;
|
|
if (apiKey) {
|
|
const keyInfo = verifyApiKey(apiKey);
|
|
if (!keyInfo.valid) {
|
|
res.statusCode = 401;
|
|
res.end(JSON.stringify({ error: 'Invalid API key' }));
|
|
return;
|
|
}
|
|
if (!checkRateLimit(apiKey, keyInfo.rateLimit || 100)) {
|
|
res.statusCode = 429;
|
|
res.end(JSON.stringify({ error: 'Rate limit exceeded' }));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Route to services
|
|
if (url.startsWith('/api/v1/government') || url.startsWith('/api/v1/customers')) {
|
|
proxyRequest(req, res, SERVICES.operations);
|
|
return;
|
|
} else if (url.startsWith('/api/v1/communications')) {
|
|
proxyRequest(req, res, SERVICES.communications);
|
|
return;
|
|
} else if (url.startsWith('/api/v1/missions') || url.startsWith('/api/v1/artifacts')) {
|
|
proxyRequest(req, res, SERVICES.intelligence);
|
|
return;
|
|
} else if (url.startsWith('/api/apollo')) {
|
|
proxyRequest(req, res, SERVICES.apollo);
|
|
} else if (url.startsWith('/api/ledger')) {
|
|
proxyRequest(req, res, SERVICES.ledger);
|
|
} else if (url.startsWith('/api/incidents')) {
|
|
proxyRequest(req, res, SERVICES.incidents);
|
|
} else if (url === '/webhooks/stripe') {
|
|
// Stripe webhook — handle directly or proxy to billing
|
|
res.statusCode = 200;
|
|
res.end(JSON.stringify({ received: true }));
|
|
} else {
|
|
res.statusCode = 404;
|
|
res.end(JSON.stringify({ error: 'Not Found', path: url }));
|
|
}
|
|
});
|
|
|
|
// Health check loop
|
|
async function updateHealth() {
|
|
for (const [name, service] of Object.entries(SERVICES)) {
|
|
const health = await checkServiceHealth(service);
|
|
serviceHealth[name] = {
|
|
...health,
|
|
lastCheck: Date.now(),
|
|
};
|
|
}
|
|
}
|
|
|
|
// Initial health check
|
|
updateHealth();
|
|
setInterval(updateHealth, 30000); // Every 30 seconds
|
|
|
|
server.listen(GATEWAY_CONFIG.port, () => {
|
|
console.log(`🚀 LandveX Gateway running on port ${GATEWAY_CONFIG.port}`);
|
|
console.log(`📡 Services:`);
|
|
Object.entries(SERVICES).forEach(([key, service]) => {
|
|
console.log(` - ${service.name}: http://${service.host}:${service.port}`);
|
|
});
|
|
});
|