1862 lines
69 KiB
JavaScript
1862 lines
69 KiB
JavaScript
require('dotenv').config();
|
|
|
|
const express = require('express');
|
|
const http = require('http');
|
|
const socketIo = require('socket.io');
|
|
const cors = require('cors');
|
|
const helmet = require('helmet');
|
|
const morgan = require('morgan');
|
|
const rateLimit = require('express-rate-limit');
|
|
const { createProxyMiddleware } = require('http-proxy-middleware');
|
|
const jwt = require('jsonwebtoken');
|
|
const axios = require('axios');
|
|
|
|
// Import middleware
|
|
const corsMiddleware = require('./middleware/cors');
|
|
const authMiddleware = require('./middleware/authentication');
|
|
const serviceHealthMiddleware = require('./middleware/serviceHealth');
|
|
const requestLogger = require('./middleware/requestLogger');
|
|
const websocketAuth = require('./middleware/webSocket');
|
|
|
|
// Import route handlers
|
|
const serviceRouter = require('./routes/serviceRouter');
|
|
const healthRouter = require('./routes/healthRouter');
|
|
const websocketRouter = require('./routes/websocketRouter');
|
|
|
|
const app = express();
|
|
// Apply CORS middleware before other middleware
|
|
app.use(corsMiddleware);
|
|
// Ensure CORS preflight (OPTIONS) requests are handled globally before any proxies
|
|
app.options('*', corsMiddleware);
|
|
// Force explicit ACAO for credentialed requests (avoid downstream "*")
|
|
app.use((req, res, next) => {
|
|
const origin = req.headers.origin || '*';
|
|
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
res.setHeader('Vary', 'Origin');
|
|
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
next();
|
|
});
|
|
const server = http.createServer(app);
|
|
const PORT = process.env.PORT || 8000;
|
|
|
|
// Initialize Socket.IO with CORS
|
|
const io = socketIo(server, {
|
|
cors: {
|
|
origin: "*",
|
|
credentials: true,
|
|
methods: ['GET', 'POST']
|
|
},
|
|
transports: ['websocket', 'polling']
|
|
});
|
|
|
|
// Make io available globally for other modules
|
|
global.io = io;
|
|
|
|
// Service targets configuration
|
|
const serviceTargets = {
|
|
USER_AUTH_URL: process.env.USER_AUTH_URL || 'http://localhost:8011',
|
|
TEMPLATE_MANAGER_URL: process.env.TEMPLATE_MANAGER_URL || 'http://template-manager:8009',
|
|
GIT_INTEGRATION_URL: process.env.GIT_INTEGRATION_URL || 'http://localhost:8012',
|
|
REQUIREMENT_PROCESSOR_URL: process.env.REQUIREMENT_PROCESSOR_URL || 'http://requirement-processor:8001',
|
|
TECH_STACK_SELECTOR_URL: process.env.TECH_STACK_SELECTOR_URL || 'http://tech-stack-selector:8002',
|
|
UNIFIED_TECH_STACK_URL: process.env.UNIFIED_TECH_STACK_URL || 'http://unified-tech-stack-service:8013',
|
|
ARCHITECTURE_DESIGNER_URL: process.env.ARCHITECTURE_DESIGNER_URL || 'http://localhost:8003',
|
|
CODE_GENERATOR_URL: process.env.CODE_GENERATOR_URL || 'http://localhost:8004',
|
|
TEST_GENERATOR_URL: process.env.TEST_GENERATOR_URL || 'http://localhost:8005',
|
|
DEPLOYMENT_MANAGER_URL: process.env.DEPLOYMENT_MANAGER_URL || 'http://localhost:8006',
|
|
DASHBOARD_URL: process.env.DASHBOARD_URL || 'http://localhost:8008',
|
|
SELF_IMPROVING_GENERATOR_URL: process.env.SELF_IMPROVING_GENERATOR_URL || 'http://localhost:8007',
|
|
AI_MOCKUP_URL: process.env.AI_MOCKUP_URL || 'http://localhost:8021',
|
|
};
|
|
|
|
// Log service targets for debugging
|
|
console.log('🔧 Service Targets Configuration:');
|
|
Object.entries(serviceTargets).forEach(([name, url]) => {
|
|
console.log(` ${name}: ${url}`);
|
|
});
|
|
|
|
// ========================================
|
|
// MIDDLEWARE SETUP
|
|
// ========================================
|
|
|
|
// Security middleware
|
|
app.use(helmet({
|
|
contentSecurityPolicy: {
|
|
directives: {
|
|
defaultSrc: ["'self'"],
|
|
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
scriptSrc: ["'self'"],
|
|
imgSrc: ["'self'", "data:", "https:"],
|
|
connectSrc: ["'self'", "ws:", "wss:"]
|
|
}
|
|
}
|
|
}));
|
|
|
|
// CORS is already configured via corsMiddleware above
|
|
|
|
// Global body parser for all routes - MUST be first
|
|
app.use(express.json({ limit: '10mb' }));
|
|
app.use(express.urlencoded({ extended: true }));
|
|
|
|
// Request parsing middleware - only for non-proxy routes
|
|
app.use('/api/websocket', express.json({ limit: '10mb' }));
|
|
app.use('/api/gateway', express.json({ limit: '10mb' }));
|
|
app.use('/api/auth', express.json({ limit: '10mb' }));
|
|
app.use('/api/templates', express.json({ limit: '10mb' }));
|
|
app.use('/api/enhanced-ckg-tech-stack', express.json({ limit: '10mb' }));
|
|
app.use('/api/comprehensive-migration', express.json({ limit: '10mb' }));
|
|
app.use('/api/unified', express.json({ limit: '10mb' }));
|
|
app.use('/api/tech-stack', express.json({ limit: '10mb' }));
|
|
app.use('/api/features', express.json({ limit: '10mb' }));
|
|
app.use('/api/admin', express.json({ limit: '10mb' }));
|
|
app.use('/api/github', express.json({ limit: '10mb' }));
|
|
app.use('/api/mockup', express.json({ limit: '10mb' }));
|
|
app.use('/api/ai', express.json({ limit: '10mb' }));
|
|
app.use('/health', express.json({ limit: '10mb' }));
|
|
|
|
// Trust proxy for accurate IP addresses
|
|
app.set('trust proxy', 1);
|
|
|
|
// Request ID middleware for tracing
|
|
app.use((req, res, next) => {
|
|
req.requestId = `gw-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
res.setHeader('X-Request-ID', req.requestId);
|
|
next();
|
|
});
|
|
|
|
app.use(morgan(process.env.NODE_ENV === 'production' ? 'combined' : 'dev'));
|
|
|
|
// Custom request logger for service tracking
|
|
app.use(requestLogger.logRequest);
|
|
|
|
// Rate limiting configuration (disabled by default via env)
|
|
const isRateLimitDisabled = (process.env.GATEWAY_DISABLE_RATE_LIMIT || process.env.DISABLE_RATE_LIMIT || 'true').toLowerCase() === 'true';
|
|
const createServiceLimiter = (maxRequests = 1000) => {
|
|
if (isRateLimitDisabled) {
|
|
return (req, res, next) => next();
|
|
}
|
|
return rateLimit({
|
|
windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS) || 15 * 60 * 1000,
|
|
max: maxRequests,
|
|
message: {
|
|
success: false,
|
|
message: 'Too many requests, please try again later.',
|
|
retry_after: 900
|
|
},
|
|
standardHeaders: true,
|
|
legacyHeaders: false
|
|
});
|
|
};
|
|
|
|
// Health check endpoint (before rate limiting and authentication)
|
|
app.get('/health', (req, res) => {
|
|
res.json({
|
|
success: true,
|
|
service: 'api-gateway',
|
|
status: 'healthy',
|
|
timestamp: new Date().toISOString(),
|
|
version: process.env.npm_package_version || '1.0.0',
|
|
environment: process.env.NODE_ENV || 'development',
|
|
uptime: process.uptime(),
|
|
services: {
|
|
user_auth: process.env.USER_AUTH_URL ? 'configured' : 'not configured',
|
|
template_manager: process.env.TEMPLATE_MANAGER_URL ? 'configured' : 'not configured',
|
|
git_integration: process.env.GIT_INTEGRATION_URL ? 'configured' : 'not configured',
|
|
requirement_processor: process.env.REQUIREMENT_PROCESSOR_URL ? 'configured' : 'not configured',
|
|
tech_stack_selector: process.env.TECH_STACK_SELECTOR_URL ? 'configured' : 'not configured',
|
|
architecture_designer: process.env.ARCHITECTURE_DESIGNER_URL ? 'configured' : 'not configured',
|
|
code_generator: process.env.CODE_GENERATOR_URL ? 'configured' : 'not configured',
|
|
test_generator: process.env.TEST_GENERATOR_URL ? 'configured' : 'not configured',
|
|
deployment_manager: process.env.DEPLOYMENT_MANAGER_URL ? 'configured' : 'not configured',
|
|
dashboard: process.env.DASHBOARD_URL ? 'configured' : 'not configured',
|
|
self_improving_generator: process.env.SELF_IMPROVING_GENERATOR_URL ? 'configured' : 'not configured',
|
|
ai_mockup: process.env.AI_MOCKUP_URL ? 'configured' : 'not configured'
|
|
},
|
|
websocket: 'enabled'
|
|
});
|
|
});
|
|
|
|
// Service health monitoring routes
|
|
app.use('/health', healthRouter.router);
|
|
|
|
// Auth service health check endpoint
|
|
app.get('/api/auth/health', async (req, res) => {
|
|
const authServiceUrl = serviceTargets.USER_AUTH_URL;
|
|
const targetUrl = `${authServiceUrl}/health`;
|
|
|
|
try {
|
|
console.log(`🔍 [AUTH HEALTH] Checking: ${targetUrl}`);
|
|
const response = await axios.get(targetUrl, { timeout: 5000 });
|
|
res.json({
|
|
success: true,
|
|
auth_service: 'healthy',
|
|
target_url: targetUrl,
|
|
response: response.data
|
|
});
|
|
} catch (error) {
|
|
console.error(`❌ [AUTH HEALTH] Error:`, error.message);
|
|
res.status(502).json({
|
|
success: false,
|
|
auth_service: 'unhealthy',
|
|
target_url: targetUrl,
|
|
error: error.message,
|
|
code: error.code
|
|
});
|
|
}
|
|
});
|
|
|
|
// WebSocket connection handling
|
|
const websocketHandlers = websocketAuth(io);
|
|
|
|
// Auth Service - Fixed proxy with proper connection handling
|
|
console.log('🔧 Registering /api/auth proxy route...');
|
|
|
|
// Use dedicated keep-alive agents to avoid stale sockets and ECONNRESET after container idle/restarts
|
|
const http = require('http');
|
|
const https = require('https');
|
|
const axiosAuthUpstream = axios.create({
|
|
timeout: 15000,
|
|
// Keep connections healthy and reused properly
|
|
httpAgent: new http.Agent({ keepAlive: true, maxSockets: 100 }),
|
|
httpsAgent: new https.Agent({ keepAlive: true, maxSockets: 100 }),
|
|
decompress: true,
|
|
// Don't throw on non-2xx so we can forward exact status/data
|
|
validateStatus: () => true,
|
|
maxRedirects: 0
|
|
});
|
|
|
|
app.use('/api/auth', (req, res, next) => {
|
|
const authServiceUrl = serviceTargets.USER_AUTH_URL;
|
|
const targetUrl = `${authServiceUrl}${req.originalUrl}`;
|
|
console.log(`🔥 [AUTH PROXY] ${req.method} ${req.originalUrl} → ${targetUrl}`);
|
|
console.log(`🔍 [AUTH PROXY] Service URL: ${authServiceUrl}`);
|
|
console.log(`🔍 [AUTH PROXY] Full target: ${targetUrl}`);
|
|
|
|
// Set response timeout to prevent hanging
|
|
res.setTimeout(15000, () => {
|
|
console.error('❌ [AUTH PROXY] Response timeout');
|
|
if (!res.headersSent) {
|
|
res.status(504).json({ error: 'Gateway timeout', service: 'user-auth' });
|
|
}
|
|
});
|
|
|
|
const options = {
|
|
method: req.method,
|
|
url: targetUrl,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'User-Agent': 'API-Gateway/1.0',
|
|
// Let the agent manage connection header; forcing keep-alive can cause stale sockets in some environments
|
|
// Forward Authorization header so protected auth-admin routes work
|
|
'Authorization': req.headers.authorization,
|
|
// Forward all relevant headers
|
|
'X-Forwarded-For': req.ip,
|
|
'X-Forwarded-Proto': req.protocol,
|
|
'X-Forwarded-Host': req.get('host')
|
|
},
|
|
timeout: 15000
|
|
};
|
|
|
|
// Always include request body for POST/PUT/PATCH requests
|
|
if (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') {
|
|
options.data = req.body || {};
|
|
console.log(`📦 [AUTH PROXY] Request body:`, JSON.stringify(req.body));
|
|
}
|
|
|
|
console.log(`🚀 [AUTH PROXY] Making request to: ${targetUrl}`);
|
|
|
|
const performRequest = () => axiosAuthUpstream(options);
|
|
|
|
performRequest()
|
|
.then(response => {
|
|
console.log(`✅ [AUTH PROXY] Response: ${response.status} for ${req.method} ${req.originalUrl}`);
|
|
console.log(`📊 [AUTH PROXY] Response headers:`, response.headers);
|
|
if (!res.headersSent) {
|
|
// Forward response headers except CORS; gateway controls CORS
|
|
Object.keys(response.headers).forEach(key => {
|
|
const k = key.toLowerCase();
|
|
if (k === 'content-encoding' || k === 'transfer-encoding') return;
|
|
if (k.startsWith('access-control-')) return; // strip downstream CORS
|
|
res.setHeader(key, response.headers[key]);
|
|
});
|
|
// Set gateway CORS headers explicitly (support credentials)
|
|
const origin = req.headers.origin || '*';
|
|
res.removeHeader('Access-Control-Allow-Origin');
|
|
res.removeHeader('access-control-allow-origin');
|
|
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
res.setHeader('Vary', 'Origin');
|
|
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
res.setHeader('Access-Control-Expose-Headers', 'Content-Length, X-Total-Count, X-Gateway-Request-ID, X-Gateway-Timestamp, X-Forwarded-By, X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host');
|
|
res.status(response.status).json(response.data);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error(`❌ [AUTH PROXY ERROR]:`, error.message);
|
|
console.error(`❌ [AUTH PROXY ERROR CODE]:`, error.code);
|
|
console.error(`❌ [AUTH PROXY ERROR STACK]:`, error.stack);
|
|
// Retry once on transient network/socket errors that can occur after service restarts
|
|
const transientCodes = ['ECONNRESET', 'EPIPE', 'ETIMEDOUT', 'ECONNREFUSED'];
|
|
if (!req._authRetry && transientCodes.includes(error.code)) {
|
|
req._authRetry = true;
|
|
console.warn(`⚠️ [AUTH PROXY] Transient error ${error.code}. Retrying once: ${targetUrl}`);
|
|
return performRequest()
|
|
.then(r => {
|
|
if (!res.headersSent) {
|
|
const origin = req.headers.origin || '*';
|
|
Object.keys(r.headers).forEach(key => {
|
|
const k = key.toLowerCase();
|
|
if (k === 'content-encoding' || k === 'transfer-encoding') return;
|
|
if (k.startsWith('access-control-')) return;
|
|
res.setHeader(key, r.headers[key]);
|
|
});
|
|
res.removeHeader('Access-Control-Allow-Origin');
|
|
res.removeHeader('access-control-allow-origin');
|
|
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
res.setHeader('Vary', 'Origin');
|
|
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
res.setHeader('Access-Control-Expose-Headers', 'Content-Length, X-Total-Count, X-Gateway-Request-ID, X-Gateway-Timestamp, X-Forwarded-By, X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host');
|
|
return res.status(r.status).json(r.data);
|
|
}
|
|
})
|
|
.catch(() => {
|
|
// Fall through to final handler below
|
|
if (!res.headersSent) {
|
|
res.status(502).json({
|
|
error: 'Auth service unavailable',
|
|
message: error.code || error.message,
|
|
service: 'user-auth',
|
|
target_url: targetUrl
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
if (!res.headersSent) {
|
|
if (error.response) {
|
|
console.log(`📊 [AUTH PROXY] Error response status: ${error.response.status}`);
|
|
console.log(`📊 [AUTH PROXY] Error response data:`, error.response.data);
|
|
res.status(error.response.status).json(error.response.data);
|
|
} else {
|
|
res.status(502).json({
|
|
error: 'Auth service unavailable',
|
|
message: error.code || error.message,
|
|
service: 'user-auth',
|
|
target_url: targetUrl,
|
|
details: process.env.NODE_ENV === 'development' ? error.stack : undefined
|
|
});
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// WebSocket API routes for managing connections
|
|
app.use('/api/websocket', websocketRouter);
|
|
|
|
// Apply rate limiting to other API routes
|
|
app.use('/api', createServiceLimiter(1000));
|
|
|
|
// Template Manager Service - Direct HTTP forwarding
|
|
console.log('🔧 Registering /api/templates proxy route...');
|
|
app.use('/api/templates',
|
|
createServiceLimiter(200),
|
|
// Conditionally require auth: allow public GETs, require token for write ops
|
|
(req, res, next) => {
|
|
// Allow unauthenticated read operations
|
|
if (req.method === 'GET') {
|
|
return next();
|
|
}
|
|
// Allow unauthenticated POST to create a template at the root endpoint
|
|
// Mounted path is /api/templates, so req.path === '/' for the root
|
|
if (req.method === 'POST' && (req.path === '/' || req.originalUrl === '/api/templates')) {
|
|
return next();
|
|
}
|
|
// For other write operations, require authentication and forward user context
|
|
return authMiddleware.verifyToken(req, res, () => authMiddleware.forwardUserContext(req, res, next));
|
|
},
|
|
(req, res, next) => {
|
|
const templateServiceUrl = serviceTargets.TEMPLATE_MANAGER_URL;
|
|
console.log(`🔥 [TEMPLATE PROXY] ${req.method} ${req.originalUrl} → ${templateServiceUrl}${req.originalUrl}`);
|
|
|
|
// Set response timeout to prevent hanging
|
|
res.setTimeout(15000, () => {
|
|
console.error('❌ [TEMPLATE PROXY] Response timeout');
|
|
if (!res.headersSent) {
|
|
res.status(504).json({ error: 'Gateway timeout', service: 'template-manager' });
|
|
}
|
|
});
|
|
|
|
const options = {
|
|
method: req.method,
|
|
url: `${templateServiceUrl}${req.originalUrl}`,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'User-Agent': 'API-Gateway/1.0',
|
|
'Connection': 'keep-alive',
|
|
// Forward user context from auth middleware
|
|
'X-User-ID': req.user?.id || req.user?.userId,
|
|
'X-User-Role': req.user?.role,
|
|
'Authorization': req.headers.authorization
|
|
},
|
|
timeout: 8000,
|
|
validateStatus: () => true,
|
|
maxRedirects: 0
|
|
};
|
|
|
|
// Always include request body for POST/PUT/PATCH requests
|
|
if (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') {
|
|
options.data = req.body || {};
|
|
console.log(`📦 [TEMPLATE PROXY] Request body:`, JSON.stringify(req.body));
|
|
}
|
|
|
|
axios(options)
|
|
.then(response => {
|
|
console.log(`✅ [TEMPLATE PROXY] Response: ${response.status} for ${req.method} ${req.originalUrl}`);
|
|
if (!res.headersSent) {
|
|
res.status(response.status).json(response.data);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error(`❌ [TEMPLATE PROXY ERROR]:`, error.message);
|
|
if (!res.headersSent) {
|
|
if (error.response) {
|
|
res.status(error.response.status).json(error.response.data);
|
|
} else {
|
|
res.status(502).json({
|
|
error: 'Template service unavailable',
|
|
message: error.code || error.message,
|
|
service: 'template-manager'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
);
|
|
|
|
// Enhanced CKG Tech Stack Service - Direct HTTP forwarding
|
|
console.log('🔧 Registering /api/enhanced-ckg-tech-stack proxy route...');
|
|
app.use('/api/enhanced-ckg-tech-stack',
|
|
createServiceLimiter(200),
|
|
// Allow public access for all operations
|
|
(req, res, next) => {
|
|
console.log(`🟢 [ENHANCED-CKG PROXY] Public access → ${req.method} ${req.originalUrl}`);
|
|
return next();
|
|
},
|
|
(req, res, next) => {
|
|
const templateServiceUrl = serviceTargets.TEMPLATE_MANAGER_URL;
|
|
console.log(`🔥 [ENHANCED-CKG PROXY] ${req.method} ${req.originalUrl} → ${templateServiceUrl}${req.originalUrl}`);
|
|
|
|
// Set response timeout to prevent hanging
|
|
res.setTimeout(15000, () => {
|
|
console.error('❌ [ENHANCED-CKG PROXY] Response timeout');
|
|
if (!res.headersSent) {
|
|
res.status(504).json({ error: 'Gateway timeout', service: 'template-manager' });
|
|
}
|
|
});
|
|
|
|
const options = {
|
|
method: req.method,
|
|
url: `${templateServiceUrl}${req.originalUrl}`,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'User-Agent': 'API-Gateway/1.0',
|
|
'Connection': 'keep-alive',
|
|
'Authorization': req.headers.authorization
|
|
},
|
|
timeout: 8000,
|
|
validateStatus: () => true,
|
|
maxRedirects: 0
|
|
};
|
|
|
|
// Always include request body for POST/PUT/PATCH requests
|
|
if (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') {
|
|
options.data = req.body;
|
|
}
|
|
|
|
axios(options)
|
|
.then(response => {
|
|
console.log(`✅ [ENHANCED-CKG PROXY] ${response.status} for ${req.method} ${req.originalUrl}`);
|
|
|
|
// Set CORS headers
|
|
res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*');
|
|
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
|
|
// Forward the response
|
|
res.status(response.status).json(response.data);
|
|
})
|
|
.catch(error => {
|
|
console.error(`❌ [ENHANCED-CKG PROXY] Error for ${req.method} ${req.originalUrl}:`, error.message);
|
|
|
|
if (!res.headersSent) {
|
|
res.status(502).json({
|
|
success: false,
|
|
message: 'Template service unavailable',
|
|
error: 'Unable to connect to template service',
|
|
request_id: req.requestId
|
|
});
|
|
}
|
|
});
|
|
}
|
|
);
|
|
|
|
// Comprehensive Migration Service - Direct HTTP forwarding
|
|
console.log('🔧 Registering /api/comprehensive-migration proxy route...');
|
|
app.use('/api/comprehensive-migration',
|
|
createServiceLimiter(200),
|
|
// Allow public access for all operations
|
|
(req, res, next) => {
|
|
console.log(`🟢 [COMPREHENSIVE-MIGRATION PROXY] Public access → ${req.method} ${req.originalUrl}`);
|
|
return next();
|
|
},
|
|
(req, res, next) => {
|
|
const templateServiceUrl = serviceTargets.TEMPLATE_MANAGER_URL;
|
|
console.log(`🔥 [COMPREHENSIVE-MIGRATION PROXY] ${req.method} ${req.originalUrl} → ${templateServiceUrl}${req.originalUrl}`);
|
|
|
|
// Set response timeout to prevent hanging
|
|
res.setTimeout(15000, () => {
|
|
console.error('❌ [COMPREHENSIVE-MIGRATION PROXY] Response timeout');
|
|
if (!res.headersSent) {
|
|
res.status(504).json({ error: 'Gateway timeout', service: 'template-manager' });
|
|
}
|
|
});
|
|
|
|
const options = {
|
|
method: req.method,
|
|
url: `${templateServiceUrl}${req.originalUrl}`,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'User-Agent': 'API-Gateway/1.0',
|
|
'Connection': 'keep-alive',
|
|
'Authorization': req.headers.authorization
|
|
},
|
|
timeout: 8000,
|
|
validateStatus: () => true,
|
|
maxRedirects: 0
|
|
};
|
|
|
|
// Always include request body for POST/PUT/PATCH requests
|
|
if (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') {
|
|
options.data = req.body;
|
|
}
|
|
|
|
axios(options)
|
|
.then(response => {
|
|
console.log(`✅ [COMPREHENSIVE-MIGRATION PROXY] ${response.status} for ${req.method} ${req.originalUrl}`);
|
|
|
|
// Set CORS headers
|
|
res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*');
|
|
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
|
|
// Forward the response
|
|
res.status(response.status).json(response.data);
|
|
})
|
|
.catch(error => {
|
|
console.error(`❌ [COMPREHENSIVE-MIGRATION PROXY] Error for ${req.method} ${req.originalUrl}:`, error.message);
|
|
|
|
if (!res.headersSent) {
|
|
res.status(502).json({
|
|
success: false,
|
|
message: 'Template service unavailable',
|
|
error: 'Unable to connect to template service',
|
|
request_id: req.requestId
|
|
});
|
|
}
|
|
});
|
|
}
|
|
);
|
|
|
|
// Unified Tech Stack Service - Direct HTTP forwarding
|
|
console.log('🔧 Registering /api/unified proxy route...');
|
|
app.use('/api/unified',
|
|
createServiceLimiter(200),
|
|
// Allow public access for all operations
|
|
(req, res, next) => {
|
|
console.log(`🟢 [UNIFIED-TECH-STACK PROXY] Public access → ${req.method} ${req.originalUrl}`);
|
|
return next();
|
|
},
|
|
(req, res, next) => {
|
|
const unifiedServiceUrl = serviceTargets.UNIFIED_TECH_STACK_URL;
|
|
console.log(`🔥 [UNIFIED-TECH-STACK PROXY] ${req.method} ${req.originalUrl} → ${unifiedServiceUrl}${req.originalUrl}`);
|
|
|
|
// Set response timeout to prevent hanging
|
|
res.setTimeout(35000, () => {
|
|
console.error('❌ [UNIFIED-TECH-STACK PROXY] Response timeout');
|
|
if (!res.headersSent) {
|
|
res.status(504).json({ error: 'Gateway timeout', service: 'unified-tech-stack' });
|
|
}
|
|
});
|
|
|
|
const options = {
|
|
method: req.method,
|
|
url: `${unifiedServiceUrl}${req.originalUrl}`,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'User-Agent': 'API-Gateway/1.0',
|
|
'Connection': 'keep-alive',
|
|
'Authorization': req.headers.authorization,
|
|
'X-User-ID': req.user?.id || req.user?.userId,
|
|
'X-User-Role': req.user?.role,
|
|
},
|
|
timeout: 30000,
|
|
validateStatus: () => true,
|
|
maxRedirects: 0
|
|
};
|
|
|
|
// Always include request body for POST/PUT/PATCH requests
|
|
if (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') {
|
|
options.data = req.body || {};
|
|
console.log(`📦 [UNIFIED-TECH-STACK PROXY] Request body:`, JSON.stringify(req.body));
|
|
}
|
|
|
|
axios(options)
|
|
.then(response => {
|
|
console.log(`✅ [UNIFIED-TECH-STACK PROXY] Response: ${response.status} for ${req.method} ${req.originalUrl}`);
|
|
if (!res.headersSent) {
|
|
res.status(response.status).json(response.data);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error(`❌ [UNIFIED-TECH-STACK PROXY ERROR]:`, error.message);
|
|
if (!res.headersSent) {
|
|
if (error.response) {
|
|
res.status(error.response.status).json(error.response.data);
|
|
} else {
|
|
res.status(502).json({
|
|
error: 'Unified tech stack service unavailable',
|
|
message: error.code || error.message,
|
|
service: 'unified-tech-stack'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
);
|
|
|
|
// Old git proxy configuration removed - using enhanced version below
|
|
|
|
// Admin endpoints (Template Manager) - expose /api/admin via gateway
|
|
console.log('🔧 Registering /api/admin proxy route...');
|
|
app.use('/api/admin',
|
|
createServiceLimiter(300),
|
|
// Public proxy from gateway perspective; downstream service enforces JWT admin check
|
|
(req, res, next) => {
|
|
console.log(`🟠 [ADMIN PROXY] ${req.method} ${req.originalUrl}`);
|
|
return next();
|
|
},
|
|
(req, res, next) => {
|
|
const adminServiceUrl = serviceTargets.TEMPLATE_MANAGER_URL;
|
|
const targetUrl = `${adminServiceUrl}${req.originalUrl}`;
|
|
console.log(`🔥 [ADMIN PROXY] ${req.method} ${req.originalUrl} → ${targetUrl}`);
|
|
|
|
res.setTimeout(15000, () => {
|
|
console.error('❌ [ADMIN PROXY] Response timeout');
|
|
if (!res.headersSent) {
|
|
res.status(504).json({ error: 'Gateway timeout', service: 'template-manager(admin)' });
|
|
}
|
|
});
|
|
|
|
const options = {
|
|
method: req.method,
|
|
url: targetUrl,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'User-Agent': 'API-Gateway/1.0',
|
|
'Connection': 'keep-alive',
|
|
// Forward Authorization header for admin JWT check
|
|
'Authorization': req.headers.authorization
|
|
},
|
|
timeout: 8000,
|
|
validateStatus: () => true,
|
|
maxRedirects: 0
|
|
};
|
|
|
|
if (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') {
|
|
options.data = req.body || {};
|
|
console.log(`📦 [ADMIN PROXY] Request body:`, JSON.stringify(req.body));
|
|
}
|
|
|
|
axios(options)
|
|
.then(response => {
|
|
console.log(`✅ [ADMIN PROXY] Response: ${response.status} for ${req.method} ${req.originalUrl}`);
|
|
if (!res.headersSent) {
|
|
res.status(response.status).json(response.data);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error(`❌ [ADMIN PROXY ERROR]:`, error.message);
|
|
if (!res.headersSent) {
|
|
if (error.response) {
|
|
res.status(error.response.status).json(error.response.data);
|
|
} else {
|
|
res.status(502).json({
|
|
error: 'Admin endpoints unavailable',
|
|
message: error.code || error.message,
|
|
service: 'template-manager(admin)'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
);
|
|
|
|
// AI Feature Analysis - Specific route for analyze-feature endpoint
|
|
console.log('🔧 Registering /api/ai/analyze-feature proxy route...');
|
|
app.use('/api/ai/analyze-feature',
|
|
createServiceLimiter(300),
|
|
// Allow unauthenticated access for AI analysis (public feature in builder)
|
|
(req, res, next) => {
|
|
console.log(`🤖 [AI ANALYSIS PROXY] ${req.method} ${req.originalUrl}`);
|
|
console.log(`📦 [AI ANALYSIS PROXY] Request body type:`, typeof req.body);
|
|
console.log(`📦 [AI ANALYSIS PROXY] Request body:`, JSON.stringify(req.body, null, 2));
|
|
console.log(`📦 [AI ANALYSIS PROXY] Content-Type:`, req.headers['content-type']);
|
|
return next();
|
|
},
|
|
(req, res, next) => {
|
|
const templateManagerUrl = serviceTargets.TEMPLATE_MANAGER_URL;
|
|
// Map /api/requirements/analyze-feature to /api/analyze-feature in template-manager
|
|
const targetUrl = `${templateManagerUrl}/api/analyze-feature`;
|
|
console.log(`🔥 [AI ANALYSIS PROXY] ${req.method} ${req.originalUrl} → ${targetUrl}`);
|
|
|
|
res.setTimeout(30000, () => {
|
|
console.error('❌ [AI ANALYSIS PROXY] Response timeout');
|
|
if (!res.headersSent) {
|
|
res.status(504).json({ error: 'Gateway timeout', service: 'template-manager' });
|
|
}
|
|
});
|
|
|
|
const options = {
|
|
method: req.method,
|
|
url: targetUrl,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'User-Agent': 'API-Gateway/1.0',
|
|
'Connection': 'keep-alive',
|
|
'Authorization': req.headers.authorization,
|
|
'X-User-ID': req.user?.id || req.user?.userId,
|
|
'X-User-Role': req.user?.role,
|
|
},
|
|
timeout: 25000,
|
|
validateStatus: () => true,
|
|
maxRedirects: 0
|
|
};
|
|
|
|
if (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') {
|
|
options.data = req.body || {};
|
|
console.log(`📦 [AI ANALYSIS PROXY] Request body:`, JSON.stringify(req.body));
|
|
}
|
|
|
|
axios(options)
|
|
.then(response => {
|
|
console.log(`✅ [AI ANALYSIS PROXY] Response: ${response.status} for ${req.method} ${req.originalUrl}`);
|
|
if (!res.headersSent) {
|
|
res.status(response.status).json(response.data);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error(`❌ [AI ANALYSIS PROXY ERROR]:`, error.message);
|
|
if (!res.headersSent) {
|
|
if (error.response) {
|
|
res.status(error.response.status).json(error.response.data);
|
|
} else {
|
|
res.status(502).json({
|
|
error: 'AI analysis service unavailable',
|
|
message: error.code || error.message,
|
|
service: 'template-manager'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
);
|
|
|
|
// Template Manager AI - expose AI recommendations through the gateway
|
|
console.log('🔧 Registering /api/ai/tech-stack proxy route...');
|
|
app.use('/api/ai/tech-stack',
|
|
createServiceLimiter(300),
|
|
// Public (reads); Unison handles auth if needed
|
|
(req, res, next) => next(),
|
|
(req, res, next) => {
|
|
const aiUrl = serviceTargets.TEMPLATE_MANAGER_AI_URL;
|
|
// Map gateway paths to AI service:
|
|
// POST /api/ai/tech-stack/recommendations -> POST /ai/recommendations
|
|
// POST /api/ai/tech-stack/recommendations/formatted -> POST /ai/recommendations/formatted
|
|
// GET /api/ai/tech-stack/extract-keywords/:id -> GET /extract-keywords/:id
|
|
// POST /api/ai/tech-stack/extract-keywords/:id -> POST /extract-keywords/:id
|
|
// POST /api/ai/tech-stack/auto-workflow/:id -> POST /auto-workflow/:id
|
|
let rewrittenPath = req.originalUrl
|
|
.replace(/^\/api\/ai\/tech-stack\/recommendations\/formatted/, '/ai/recommendations/formatted')
|
|
.replace(/^\/api\/ai\/tech-stack\/recommendations/, '/ai/recommendations')
|
|
.replace(/^\/api\/ai\/tech-stack\/extract-keywords\//, '/extract-keywords/')
|
|
.replace(/^\/api\/ai\/tech-stack\/auto-workflow\//, '/auto-workflow/')
|
|
.replace(/^\/api\/ai\/tech-stack\/?$/, '/');
|
|
|
|
const targetUrl = `${aiUrl}${rewrittenPath.replace(/^\/api\/ai\/tech-stack/, '')}`;
|
|
console.log(`🔥 [TEMPLATE-AI PROXY] ${req.method} ${req.originalUrl} → ${targetUrl}`);
|
|
|
|
res.setTimeout(30000, () => {
|
|
console.error('❌ [TEMPLATE-AI PROXY] Response timeout');
|
|
if (!res.headersSent) {
|
|
res.status(504).json({ error: 'Gateway timeout', service: 'template-manager-ai' });
|
|
}
|
|
});
|
|
|
|
const options = {
|
|
method: req.method,
|
|
url: targetUrl,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'User-Agent': 'API-Gateway/1.0',
|
|
'Connection': 'keep-alive',
|
|
'Authorization': req.headers.authorization,
|
|
'X-User-ID': req.user?.id || req.user?.userId,
|
|
'X-User-Role': req.user?.role,
|
|
},
|
|
timeout: 25000,
|
|
validateStatus: () => true,
|
|
maxRedirects: 0
|
|
};
|
|
|
|
if (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') {
|
|
options.data = req.body || {};
|
|
console.log(`📦 [TEMPLATE-AI PROXY] Request body:`, JSON.stringify(req.body));
|
|
}
|
|
|
|
axios(options)
|
|
.then(response => {
|
|
console.log(`✅ [TEMPLATE-AI PROXY] Response: ${response.status} for ${req.method} ${req.originalUrl}`);
|
|
if (!res.headersSent) {
|
|
res.status(response.status).json(response.data);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error(`❌ [TEMPLATE-AI PROXY ERROR]:`, error.message);
|
|
if (!res.headersSent) {
|
|
if (error.response) {
|
|
res.status(error.response.status).json(error.response.data);
|
|
} else {
|
|
res.status(502).json({
|
|
error: 'Template Manager AI unavailable',
|
|
message: error.code || error.message,
|
|
service: 'template-manager-ai'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
);
|
|
|
|
// Requirement Processor Service - General routes (MUST come after specific routes)
|
|
app.use('/api/requirements',
|
|
createServiceLimiter(300),
|
|
authMiddleware.verifyToken,
|
|
authMiddleware.forwardUserContext,
|
|
serviceRouter.createServiceProxy(serviceTargets.REQUIREMENT_PROCESSOR_URL, 'requirement-processor')
|
|
);
|
|
|
|
// Questions (Requirement Processor) - expose /api/questions via gateway
|
|
// Rewrites /api/questions/* -> /api/v1/* at the Requirement Processor
|
|
console.log('🔧 Registering /api/questions proxy route...');
|
|
app.use('/api/questions',
|
|
createServiceLimiter(300),
|
|
// Allow unauthenticated access for generating questions (public step in builder)
|
|
(req, res, next) => next(),
|
|
(req, res, next) => {
|
|
const requirementServiceUrl = serviceTargets.REQUIREMENT_PROCESSOR_URL;
|
|
// Rewrite path: /api/questions -> /api/v1
|
|
const rewrittenPath = req.originalUrl.replace(/^\/api\/questions/, '/api/v1');
|
|
const targetUrl = `${requirementServiceUrl}${rewrittenPath}`;
|
|
console.log(`🔥 [QUESTIONS PROXY] ${req.method} ${req.originalUrl} → ${targetUrl}`);
|
|
|
|
// Set response timeout to prevent hanging
|
|
res.setTimeout(30000, () => {
|
|
console.error('❌ [QUESTIONS PROXY] Response timeout');
|
|
if (!res.headersSent) {
|
|
res.status(504).json({ error: 'Gateway timeout', service: 'requirement-processor' });
|
|
}
|
|
});
|
|
|
|
const options = {
|
|
method: req.method,
|
|
url: targetUrl,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'User-Agent': 'API-Gateway/1.0',
|
|
'Connection': 'keep-alive',
|
|
'Authorization': req.headers.authorization,
|
|
'X-User-ID': req.user?.id || req.user?.userId,
|
|
'X-User-Role': req.user?.role,
|
|
},
|
|
timeout: 25000,
|
|
validateStatus: () => true,
|
|
maxRedirects: 0
|
|
};
|
|
|
|
// Always include request body for POST/PUT/PATCH requests
|
|
if (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') {
|
|
options.data = req.body || {};
|
|
console.log(`📦 [QUESTIONS PROXY] Request body:`, JSON.stringify(req.body));
|
|
}
|
|
|
|
axios(options)
|
|
.then(response => {
|
|
console.log(`✅ [QUESTIONS PROXY] Response: ${response.status} for ${req.method} ${req.originalUrl}`);
|
|
if (!res.headersSent) {
|
|
res.status(response.status).json(response.data);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error(`❌ [QUESTIONS PROXY ERROR]:`, error.message);
|
|
if (!res.headersSent) {
|
|
if (error.response) {
|
|
res.status(error.response.status).json(error.response.data);
|
|
} else {
|
|
res.status(502).json({
|
|
error: 'Questions service unavailable',
|
|
message: error.code || error.message,
|
|
service: 'requirement-processor'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
);
|
|
|
|
// Tech Stack Selector Service
|
|
app.use('/api/tech-stack',
|
|
createServiceLimiter(200),
|
|
authMiddleware.verifyToken,
|
|
authMiddleware.forwardUserContext,
|
|
serviceRouter.createServiceProxy(serviceTargets.TECH_STACK_SELECTOR_URL, 'tech-stack-selector')
|
|
);
|
|
|
|
// Architecture Designer Service
|
|
app.use('/api/architecture',
|
|
createServiceLimiter(150),
|
|
authMiddleware.verifyToken,
|
|
authMiddleware.forwardUserContext,
|
|
serviceRouter.createServiceProxy(serviceTargets.ARCHITECTURE_DESIGNER_URL, 'architecture-designer')
|
|
);
|
|
|
|
// Code Generator Service
|
|
app.use('/api/codegen',
|
|
createServiceLimiter(100),
|
|
authMiddleware.verifyToken,
|
|
authMiddleware.forwardUserContext,
|
|
serviceRouter.createServiceProxy(serviceTargets.CODE_GENERATOR_URL, 'code-generator')
|
|
);
|
|
|
|
// Test Generator Service
|
|
app.use('/api/tests',
|
|
createServiceLimiter(150),
|
|
authMiddleware.verifyToken,
|
|
authMiddleware.forwardUserContext,
|
|
serviceRouter.createServiceProxy(serviceTargets.TEST_GENERATOR_URL, 'test-generator')
|
|
);
|
|
|
|
// Deployment Manager Service
|
|
app.use('/api/deploy',
|
|
createServiceLimiter(100),
|
|
authMiddleware.verifyToken,
|
|
authMiddleware.forwardUserContext,
|
|
serviceRouter.createServiceProxy(serviceTargets.DEPLOYMENT_MANAGER_URL, 'deployment-manager')
|
|
);
|
|
|
|
// Dashboard Service
|
|
app.use('/api/dashboard',
|
|
createServiceLimiter(300),
|
|
authMiddleware.verifyToken,
|
|
authMiddleware.forwardUserContext,
|
|
serviceRouter.createServiceProxy(serviceTargets.DASHBOARD_URL, 'dashboard')
|
|
);
|
|
|
|
// Self-Improving Generator Service
|
|
app.use('/api/self-improving',
|
|
createServiceLimiter(50),
|
|
authMiddleware.verifyToken,
|
|
authMiddleware.forwardUserContext,
|
|
serviceRouter.createServiceProxy(serviceTargets.SELF_IMPROVING_GENERATOR_URL, 'self-improving-generator')
|
|
);
|
|
|
|
// Unison (Unified Recommendations) Service
|
|
console.log('🔧 Registering /api/unison proxy route...');
|
|
app.use('/api/unison',
|
|
createServiceLimiter(200),
|
|
// Allow unauthenticated access for unified recommendations
|
|
(req, res, next) => next(),
|
|
(req, res, next) => {
|
|
const unisonUrl = serviceTargets.UNISON_URL;
|
|
// Forward to same path on Unison (e.g., /api/unison/recommendations/unified)
|
|
const rewrittenPath = (req.originalUrl || '').replace(/^\/api\/unison/, '/api');
|
|
const targetUrl = `${unisonUrl}${rewrittenPath}`;
|
|
console.log(`🔥 [UNISON PROXY] ${req.method} ${req.originalUrl} → ${targetUrl}`);
|
|
|
|
res.setTimeout(30000, () => {
|
|
console.error('❌ [UNISON PROXY] Response timeout');
|
|
if (!res.headersSent) {
|
|
res.status(504).json({ error: 'Gateway timeout', service: 'unison' });
|
|
}
|
|
});
|
|
|
|
const options = {
|
|
method: req.method,
|
|
url: targetUrl,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'User-Agent': 'API-Gateway/1.0',
|
|
'Connection': 'keep-alive',
|
|
'Authorization': req.headers.authorization,
|
|
'X-User-ID': req.user?.id || req.user?.userId,
|
|
'X-User-Role': req.user?.role,
|
|
},
|
|
timeout: 25000,
|
|
validateStatus: () => true,
|
|
maxRedirects: 0
|
|
};
|
|
|
|
if (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') {
|
|
options.data = req.body || {};
|
|
console.log(`📦 [UNISON PROXY] Request body:`, JSON.stringify(req.body));
|
|
}
|
|
|
|
axios(options)
|
|
.then(response => {
|
|
console.log(`✅ [UNISON PROXY] Response: ${response.status} for ${req.method} ${req.originalUrl}`);
|
|
if (!res.headersSent) {
|
|
res.status(response.status).json(response.data);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error(`❌ [UNISON PROXY ERROR]:`, error.message);
|
|
if (!res.headersSent) {
|
|
if (error.response) {
|
|
res.status(error.response.status).json(error.response.data);
|
|
} else {
|
|
res.status(502).json({
|
|
error: 'Unison service unavailable',
|
|
message: error.code || error.message,
|
|
service: 'unison'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
);
|
|
|
|
// Unified recommendations shortcut
|
|
console.log('🔧 Registering /api/recommendations proxy route (shortcut to Unison)...');
|
|
app.use('/api/recommendations',
|
|
createServiceLimiter(200),
|
|
(req, res, next) => next(),
|
|
(req, res, next) => {
|
|
const unisonUrl = serviceTargets.UNISON_URL;
|
|
// Keep path under /api/recommendations/* when forwarding to Unison
|
|
const targetUrl = `${unisonUrl}${req.originalUrl}`;
|
|
console.log(`🔥 [UNIFIED PROXY] ${req.method} ${req.originalUrl} → ${targetUrl}`);
|
|
|
|
res.setTimeout(30000, () => {
|
|
console.error('❌ [UNIFIED PROXY] Response timeout');
|
|
if (!res.headersSent) {
|
|
res.status(504).json({ error: 'Gateway timeout', service: 'unison' });
|
|
}
|
|
});
|
|
|
|
const options = {
|
|
method: req.method,
|
|
url: targetUrl,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'User-Agent': 'API-Gateway/1.0',
|
|
'Connection': 'keep-alive',
|
|
'Authorization': req.headers.authorization,
|
|
'X-User-ID': req.user?.id || req.user?.userId,
|
|
'X-User-Role': req.user?.role,
|
|
},
|
|
timeout: 25000,
|
|
validateStatus: () => true,
|
|
maxRedirects: 0
|
|
};
|
|
|
|
if (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') {
|
|
options.data = req.body || {};
|
|
console.log(`📦 [UNIFIED PROXY] Request body:`, JSON.stringify(req.body));
|
|
}
|
|
|
|
axios(options)
|
|
.then(response => {
|
|
console.log(`✅ [UNIFIED PROXY] Response: ${response.status} for ${req.method} ${req.originalUrl}`);
|
|
if (!res.headersSent) {
|
|
res.status(response.status).json(response.data);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error(`❌ [UNIFIED PROXY ERROR]:`, error.message);
|
|
if (!res.headersSent) {
|
|
if (error.response) {
|
|
res.status(error.response.status).json(error.response.data);
|
|
} else {
|
|
res.status(502).json({
|
|
error: 'Unison service unavailable',
|
|
message: error.code || error.message,
|
|
service: 'unison'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
);
|
|
|
|
// Convenience alias: POST /api/recommendations -> POST /api/recommendations/unified
|
|
console.log('🔧 Registering /api/recommendations (root) alias to unified...');
|
|
app.post('/api/recommendations',
|
|
createServiceLimiter(200),
|
|
(req, res, next) => {
|
|
const unisonUrl = serviceTargets.UNISON_URL;
|
|
const targetUrl = `${unisonUrl}/api/recommendations/unified`;
|
|
console.log(`🔥 [UNIFIED ROOT ALIAS] ${req.method} ${req.originalUrl} → ${targetUrl}`);
|
|
|
|
const options = {
|
|
method: 'POST',
|
|
url: targetUrl,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'User-Agent': 'API-Gateway/1.0',
|
|
'Connection': 'keep-alive',
|
|
'Authorization': req.headers.authorization,
|
|
'X-User-ID': req.user?.id || req.user?.userId,
|
|
'X-User-Role': req.user?.role,
|
|
},
|
|
timeout: 25000,
|
|
validateStatus: () => true,
|
|
maxRedirects: 0,
|
|
data: req.body || {}
|
|
};
|
|
|
|
axios(options)
|
|
.then(response => {
|
|
if (!res.headersSent) {
|
|
res.status(response.status).json(response.data);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
if (!res.headersSent) {
|
|
if (error.response) {
|
|
res.status(error.response.status).json(error.response.data);
|
|
} else {
|
|
res.status(502).json({
|
|
error: 'Unison service unavailable',
|
|
message: error.code || error.message,
|
|
service: 'unison'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
);
|
|
|
|
// Backward-compatible alias: /ai/recommendations -> Unison /api/recommendations
|
|
console.log('🔧 Registering /ai/recommendations alias to Unison...');
|
|
app.use('/ai/recommendations',
|
|
createServiceLimiter(200),
|
|
(req, res, next) => next(),
|
|
(req, res, next) => {
|
|
const unisonUrl = serviceTargets.UNISON_URL;
|
|
const targetUrl = `${unisonUrl}/api/recommendations${req.originalUrl.replace(/^\/ai\/recommendations/, '')}`;
|
|
console.log(`🔥 [AI→UNIFIED PROXY] ${req.method} ${req.originalUrl} → ${targetUrl}`);
|
|
|
|
res.setTimeout(30000, () => {
|
|
console.error('❌ [AI→UNIFIED PROXY] Response timeout');
|
|
if (!res.headersSent) {
|
|
res.status(504).json({ error: 'Gateway timeout', service: 'unison' });
|
|
}
|
|
});
|
|
|
|
const options = {
|
|
method: req.method,
|
|
url: targetUrl,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'User-Agent': 'API-Gateway/1.0',
|
|
'Connection': 'keep-alive',
|
|
'Authorization': req.headers.authorization,
|
|
'X-User-ID': req.user?.id || req.user?.userId,
|
|
'X-User-Role': req.user?.role,
|
|
},
|
|
timeout: 25000,
|
|
validateStatus: () => true,
|
|
maxRedirects: 0
|
|
};
|
|
|
|
if (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') {
|
|
options.data = req.body || {};
|
|
console.log(`📦 [AI→UNIFIED PROXY] Request body:`, JSON.stringify(req.body));
|
|
}
|
|
|
|
axios(options)
|
|
.then(response => {
|
|
console.log(`✅ [AI→UNIFIED PROXY] Response: ${response.status} for ${req.method} ${req.originalUrl}`);
|
|
if (!res.headersSent) {
|
|
res.status(response.status).json(response.data);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error(`❌ [AI→UNIFIED PROXY ERROR]:`, error.message);
|
|
if (!res.headersSent) {
|
|
if (error.response) {
|
|
res.status(error.response.status).json(error.response.data);
|
|
} else {
|
|
res.status(502).json({
|
|
error: 'Unison service unavailable',
|
|
message: error.code || error.message,
|
|
service: 'unison'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
);
|
|
|
|
// Features (Template Manager) - expose /api/features via gateway
|
|
console.log('🔧 Registering /api/features proxy route...');
|
|
app.use('/api/features',
|
|
createServiceLimiter(300),
|
|
// Public proxy: features endpoints do not require auth
|
|
(req, res, next) => {
|
|
console.log(`🟢 [FEATURES PROXY] Public access → ${req.method} ${req.originalUrl}`);
|
|
return next();
|
|
},
|
|
(req, res, next) => {
|
|
const templateServiceUrl = serviceTargets.TEMPLATE_MANAGER_URL;
|
|
console.log(`🔥 [FEATURES PROXY] ${req.method} ${req.originalUrl} → ${templateServiceUrl}${req.originalUrl}`);
|
|
|
|
res.setTimeout(15000, () => {
|
|
console.error('❌ [FEATURES PROXY] Response timeout');
|
|
if (!res.headersSent) {
|
|
res.status(504).json({ error: 'Gateway timeout', service: 'template-manager' });
|
|
}
|
|
});
|
|
|
|
const options = {
|
|
method: req.method,
|
|
url: `${templateServiceUrl}${req.originalUrl}`,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'User-Agent': 'API-Gateway/1.0',
|
|
'Connection': 'keep-alive',
|
|
'Authorization': req.headers.authorization,
|
|
'X-User-ID': req.user?.id || req.user?.userId,
|
|
'X-User-Role': req.user?.role,
|
|
},
|
|
timeout: 8000,
|
|
validateStatus: () => true,
|
|
maxRedirects: 0
|
|
};
|
|
|
|
if (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') {
|
|
options.data = req.body || {};
|
|
console.log(`📦 [FEATURES PROXY] Request body:`, JSON.stringify(req.body));
|
|
}
|
|
|
|
axios(options)
|
|
.then(response => {
|
|
console.log(`✅ [FEATURES PROXY] Response: ${response.status} for ${req.method} ${req.originalUrl}`);
|
|
if (!res.headersSent) {
|
|
res.status(response.status).json(response.data);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error(`❌ [FEATURES PROXY ERROR]:`, error.message);
|
|
if (!res.headersSent) {
|
|
if (error.response) {
|
|
res.status(error.response.status).json(error.response.data);
|
|
} else {
|
|
res.status(502).json({
|
|
error: 'Template feature service unavailable',
|
|
message: error.code || error.message,
|
|
service: 'template-manager'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
);
|
|
|
|
// Git Integration Service - Direct HTTP forwarding with proper OAuth redirect handling
|
|
console.log('🔧 Registering /api/github proxy route...');
|
|
app.use('/api/github',
|
|
createServiceLimiter(200),
|
|
// Debug: Log all requests to /api/github
|
|
(req, res, next) => {
|
|
console.log(`🚀 [GIT PROXY ENTRY] ${req.method} ${req.originalUrl}`);
|
|
console.log(`🚀 [GIT PROXY ENTRY] Headers:`, JSON.stringify(req.headers, null, 2));
|
|
next();
|
|
},
|
|
// Conditionally require auth: allow public GETs, require token for write ops
|
|
(req, res, next) => {
|
|
const url = req.originalUrl || '';
|
|
console.log(`🔍 [GIT PROXY AUTH] ${req.method} ${url}`);
|
|
|
|
// Allow unauthenticated access for read-only requests and specific public endpoints
|
|
if (req.method === 'GET') {
|
|
console.log(`✅ [GIT PROXY AUTH] GET request - using optional auth`);
|
|
return authMiddleware.verifyTokenOptional(req, res, () => authMiddleware.forwardUserContext(req, res, next));
|
|
}
|
|
|
|
// Allowlist certain POST endpoints that must be public to initiate flows
|
|
const isPublicGithubEndpoint = (
|
|
url.startsWith('/api/github/test-access') ||
|
|
url.startsWith('/api/github/auth/github') ||
|
|
url.startsWith('/api/github/auth/github/callback') ||
|
|
url.startsWith('/api/github/auth/github/status') ||
|
|
url.startsWith('/api/github/attach-repository') ||
|
|
url.startsWith('/api/github/webhook')
|
|
);
|
|
|
|
console.log(`🔍 [GIT PROXY AUTH] isPublicGithubEndpoint: ${isPublicGithubEndpoint}`);
|
|
console.log(`🔍 [GIT PROXY AUTH] URL checks:`, {
|
|
'test-access': url.startsWith('/api/github/test-access'),
|
|
'auth/github': url.startsWith('/api/github/auth/github'),
|
|
'auth/callback': url.startsWith('/api/github/auth/github/callback'),
|
|
'auth/status': url.startsWith('/api/github/auth/github/status'),
|
|
'attach-repository': url.startsWith('/api/github/attach-repository'),
|
|
'webhook': url.startsWith('/api/github/webhook')
|
|
});
|
|
|
|
if (isPublicGithubEndpoint) {
|
|
console.log(`✅ [GIT PROXY AUTH] Public endpoint - using optional auth`);
|
|
return authMiddleware.verifyTokenOptional(req, res, () => authMiddleware.forwardUserContext(req, res, next));
|
|
}
|
|
|
|
console.log(`🔒 [GIT PROXY AUTH] Protected endpoint - using required auth`);
|
|
return authMiddleware.verifyToken(req, res, () => authMiddleware.forwardUserContext(req, res, next));
|
|
},
|
|
(req, res, next) => {
|
|
const gitServiceUrl = serviceTargets.GIT_INTEGRATION_URL;
|
|
console.log(`🔥 [GIT PROXY] ${req.method} ${req.originalUrl} → ${gitServiceUrl}${req.originalUrl}`);
|
|
|
|
// Debug: Log incoming headers for webhook requests
|
|
console.log('🔍 [GIT PROXY DEBUG] All incoming headers:', req.headers);
|
|
if (req.originalUrl.includes('/webhook')) {
|
|
console.log('🔍 [GIT PROXY DEBUG] Webhook headers:', {
|
|
'x-hub-signature-256': req.headers['x-hub-signature-256'],
|
|
'x-hub-signature': req.headers['x-hub-signature'],
|
|
'x-github-event': req.headers['x-github-event'],
|
|
'x-github-delivery': req.headers['x-github-delivery']
|
|
});
|
|
}
|
|
|
|
// Set response timeout to prevent hanging (increased for repository operations)
|
|
res.setTimeout(150000, () => {
|
|
console.error('❌ [GIT PROXY] Response timeout');
|
|
if (!res.headersSent) {
|
|
res.status(504).json({ error: 'Gateway timeout', service: 'git-integration' });
|
|
}
|
|
});
|
|
|
|
const options = {
|
|
method: req.method,
|
|
url: `${gitServiceUrl}${req.originalUrl}`,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'User-Agent': 'API-Gateway/1.0',
|
|
'Connection': 'keep-alive',
|
|
// Forward user context from auth middleware
|
|
'X-User-ID': req.user?.id || req.user?.userId,
|
|
'X-User-Role': req.user?.role,
|
|
'Authorization': req.headers.authorization,
|
|
// Forward session and cookie data for OAuth flows
|
|
'Cookie': req.headers.cookie,
|
|
'X-Session-ID': req.sessionID,
|
|
// Forward all query parameters for OAuth callbacks
|
|
'X-Original-Query': req.originalUrl.includes('?') ? req.originalUrl.split('?')[1] : '',
|
|
// Forward GitHub webhook signature headers
|
|
'X-Hub-Signature-256': req.headers['x-hub-signature-256'],
|
|
'X-Hub-Signature': req.headers['x-hub-signature'],
|
|
'X-GitHub-Event': req.headers['x-github-event'],
|
|
'X-GitHub-Delivery': req.headers['x-github-delivery']
|
|
},
|
|
timeout: 120000, // Increased timeout for repository operations (2 minutes)
|
|
validateStatus: () => true,
|
|
maxRedirects: 5, // Allow following redirects for OAuth flows
|
|
responseType: 'text' // Handle both JSON and HTML responses as text
|
|
};
|
|
|
|
// Always include request body for POST/PUT/PATCH requests
|
|
if (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') {
|
|
options.data = req.body || {};
|
|
console.log(`📦 [GIT PROXY] Request body:`, JSON.stringify(req.body));
|
|
}
|
|
|
|
axios(options)
|
|
.then(response => {
|
|
console.log(`✅ [GIT PROXY] Response: ${response.status} for ${req.method} ${req.originalUrl}`);
|
|
|
|
// Handle OAuth redirects properly
|
|
if (response.status >= 300 && response.status < 400 && response.headers?.location) {
|
|
const location = response.headers.location;
|
|
console.log(`↪️ [GIT PROXY] Forwarding redirect to ${location}`);
|
|
|
|
// Update redirect URL to use gateway port if it points to git-integration service
|
|
let updatedLocation = location;
|
|
if (location.includes('localhost:8012')) {
|
|
updatedLocation = location.replace('backend.codenuk.com', 'backend.codenuk.com');
|
|
console.log(`🔄 [GIT PROXY] Updated redirect URL: ${updatedLocation}`);
|
|
}
|
|
|
|
if (!res.headersSent) {
|
|
// Set proper headers for redirect
|
|
res.setHeader('Location', updatedLocation);
|
|
res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*');
|
|
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
return res.redirect(response.status, updatedLocation);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!res.headersSent) {
|
|
// Forward response headers except CORS; gateway controls CORS
|
|
Object.keys(response.headers).forEach(key => {
|
|
const k = key.toLowerCase();
|
|
if (k === 'content-encoding' || k === 'transfer-encoding') return;
|
|
if (k.startsWith('access-control-')) return; // strip downstream CORS
|
|
res.setHeader(key, response.headers[key]);
|
|
});
|
|
|
|
// Set gateway CORS headers explicitly
|
|
const origin = req.headers.origin || '*';
|
|
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
res.setHeader('Vary', 'Origin');
|
|
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
res.setHeader('Access-Control-Expose-Headers', 'Content-Length, X-Total-Count, X-Gateway-Request-ID, X-Gateway-Timestamp, X-Forwarded-By, X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host');
|
|
|
|
res.status(response.status).json(response.data);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error(`❌ [GIT PROXY ERROR]:`, error.message);
|
|
if (!res.headersSent) {
|
|
if (error.response) {
|
|
res.status(error.response.status).json(error.response.data);
|
|
} else {
|
|
res.status(502).json({
|
|
error: 'Git integration service unavailable',
|
|
message: error.code || error.message,
|
|
service: 'git-integration'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
);
|
|
|
|
// VCS Integration Service - Direct HTTP forwarding for Bitbucket, GitLab, Gitea
|
|
console.log('🔧 Registering /api/vcs proxy route...');
|
|
app.use('/api/vcs',
|
|
createServiceLimiter(200),
|
|
// Allow unauthenticated access for OAuth flows and public endpoints
|
|
(req, res, next) => {
|
|
// Allow unauthenticated access for OAuth flows and public endpoints
|
|
const url = req.originalUrl || '';
|
|
const isPublicVcsEndpoint = (
|
|
url.includes('/auth/') ||
|
|
url.includes('/webhook') ||
|
|
url.includes('/attach-repository') ||
|
|
req.method === 'GET'
|
|
);
|
|
if (isPublicVcsEndpoint) {
|
|
return authMiddleware.verifyTokenOptional(req, res, () => authMiddleware.forwardUserContext(req, res, next));
|
|
}
|
|
return authMiddleware.verifyToken(req, res, () => authMiddleware.forwardUserContext(req, res, next));
|
|
},
|
|
(req, res, next) => {
|
|
const gitServiceUrl = serviceTargets.GIT_INTEGRATION_URL;
|
|
console.log(`🔥 [VCS PROXY] ${req.method} ${req.originalUrl} → ${gitServiceUrl}${req.originalUrl}`);
|
|
|
|
// Debug: Log incoming headers for webhook requests
|
|
if (req.originalUrl.includes('/webhook')) {
|
|
console.log('🔍 [VCS PROXY DEBUG] Incoming headers:', {
|
|
'x-hub-signature-256': req.headers['x-hub-signature-256'],
|
|
'x-hub-signature': req.headers['x-hub-signature'],
|
|
'x-github-event': req.headers['x-github-event'],
|
|
'x-github-delivery': req.headers['x-github-delivery']
|
|
});
|
|
}
|
|
|
|
// Set response timeout to prevent hanging
|
|
res.setTimeout(60000, () => {
|
|
console.error('❌ [VCS PROXY] Response timeout');
|
|
if (!res.headersSent) {
|
|
res.status(504).json({ error: 'Gateway timeout', service: 'git-integration' });
|
|
}
|
|
});
|
|
|
|
const options = {
|
|
method: req.method,
|
|
url: `${gitServiceUrl}${req.originalUrl}`,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'User-Agent': 'API-Gateway/1.0',
|
|
'Connection': 'keep-alive',
|
|
// Forward user context from auth middleware
|
|
'X-User-ID': req.user?.id || req.user?.userId,
|
|
'X-User-Role': req.user?.role,
|
|
'Authorization': req.headers.authorization,
|
|
// Forward session and cookie data for OAuth flows
|
|
'Cookie': req.headers.cookie,
|
|
'X-Session-ID': req.sessionID,
|
|
// Forward all query parameters for OAuth callbacks
|
|
'X-Original-Query': req.originalUrl.includes('?') ? req.originalUrl.split('?')[1] : '',
|
|
// Forward GitHub webhook signature headers
|
|
'X-Hub-Signature-256': req.headers['x-hub-signature-256'],
|
|
'X-Hub-Signature': req.headers['x-hub-signature'],
|
|
'X-GitHub-Event': req.headers['x-github-event'],
|
|
'X-GitHub-Delivery': req.headers['x-github-delivery']
|
|
},
|
|
timeout: 45000,
|
|
validateStatus: () => true,
|
|
maxRedirects: 5 // Allow following redirects for OAuth flows
|
|
};
|
|
|
|
// Always include request body for POST/PUT/PATCH requests
|
|
if (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') {
|
|
options.data = req.body || {};
|
|
console.log(`📦 [VCS PROXY] Request body:`, JSON.stringify(req.body));
|
|
}
|
|
|
|
axios(options)
|
|
.then(response => {
|
|
console.log(`✅ [VCS PROXY] Response: ${response.status} for ${req.method} ${req.originalUrl}`);
|
|
|
|
// Handle OAuth redirects properly
|
|
if (response.status >= 300 && response.status < 400 && response.headers?.location) {
|
|
const location = response.headers.location;
|
|
console.log(`↪️ [VCS PROXY] Forwarding redirect to ${location}`);
|
|
|
|
// Update redirect URL to use gateway port if it points to git-integration service
|
|
let updatedLocation = location;
|
|
if (location.includes('localhost:8012')) {
|
|
updatedLocation = location.replace('backend.codenuk.com', 'backend.codenuk.com');
|
|
console.log(`🔄 [VCS PROXY] Updated redirect URL: ${updatedLocation}`);
|
|
}
|
|
|
|
if (!res.headersSent) {
|
|
// Set proper headers for redirect
|
|
res.setHeader('Location', updatedLocation);
|
|
res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*');
|
|
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
return res.redirect(response.status, updatedLocation);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!res.headersSent) {
|
|
// Forward response headers except CORS; gateway controls CORS
|
|
Object.keys(response.headers).forEach(key => {
|
|
const k = key.toLowerCase();
|
|
if (k === 'content-encoding' || k === 'transfer-encoding') return;
|
|
if (k.startsWith('access-control-')) return; // strip downstream CORS
|
|
res.setHeader(key, response.headers[key]);
|
|
});
|
|
|
|
// Set gateway CORS headers explicitly
|
|
const origin = req.headers.origin || '*';
|
|
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
res.setHeader('Vary', 'Origin');
|
|
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
res.setHeader('Access-Control-Expose-Headers', 'Content-Length, X-Total-Count, X-Gateway-Request-ID, X-Gateway-Timestamp, X-Forwarded-By, X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host');
|
|
|
|
res.status(response.status).json(response.data);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error(`❌ [VCS PROXY ERROR]:`, error.message);
|
|
if (!res.headersSent) {
|
|
if (error.response) {
|
|
res.status(error.response.status).json(error.response.data);
|
|
} else {
|
|
res.status(502).json({
|
|
error: 'VCS integration service unavailable',
|
|
message: error.code || error.message,
|
|
service: 'git-integration'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
);
|
|
|
|
// AI Mockup Service - Direct HTTP forwarding
|
|
console.log('🔧 Registering /api/mockup proxy route...');
|
|
app.use('/api/mockup',
|
|
createServiceLimiter(200),
|
|
// Public proxy: AI mockup endpoints do not require auth for basic generation
|
|
(req, res, next) => {
|
|
console.log(`🎨 [AI MOCKUP PROXY] ${req.method} ${req.originalUrl}`);
|
|
return next();
|
|
},
|
|
(req, res, next) => {
|
|
const aiMockupServiceUrl = serviceTargets.AI_MOCKUP_URL;
|
|
// Strip the /api/mockup prefix so /api/mockup/health -> /health at target
|
|
const rewrittenPath = (req.originalUrl || '').replace(/^\/api\/mockup/, '');
|
|
const targetUrl = `${aiMockupServiceUrl}${rewrittenPath}`;
|
|
console.log(`🔥 [AI MOCKUP PROXY] ${req.method} ${req.originalUrl} → ${targetUrl}`);
|
|
|
|
res.setTimeout(30000, () => {
|
|
console.error('❌ [AI MOCKUP PROXY] Response timeout');
|
|
if (!res.headersSent) {
|
|
res.status(504).json({ error: 'Gateway timeout', service: 'ai-mockup' });
|
|
}
|
|
});
|
|
|
|
const options = {
|
|
method: req.method,
|
|
url: targetUrl,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'User-Agent': 'API-Gateway/1.0',
|
|
'Connection': 'keep-alive',
|
|
'Authorization': req.headers.authorization,
|
|
'X-User-ID': req.user?.id || req.user?.userId,
|
|
'X-User-Role': req.user?.role,
|
|
},
|
|
timeout: 25000,
|
|
validateStatus: () => true,
|
|
maxRedirects: 0,
|
|
responseType: 'text'
|
|
};
|
|
|
|
if (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') {
|
|
options.data = req.body || {};
|
|
console.log(`📦 [AI MOCKUP PROXY] Request body:`, JSON.stringify(req.body));
|
|
}
|
|
|
|
axios(options)
|
|
.then(response => {
|
|
console.log(`✅ [AI MOCKUP PROXY] Response: ${response.status} for ${req.method} ${req.originalUrl}`);
|
|
if (res.headersSent) return;
|
|
const contentType = response.headers['content-type'] || '';
|
|
// Forward key headers
|
|
if (contentType) res.setHeader('Content-Type', contentType);
|
|
res.setHeader('X-Gateway-Request-ID', req.requestId);
|
|
res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*');
|
|
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
|
|
// If response is SVG or XML or plain text, send as-is; else JSON
|
|
if (contentType.includes('image/svg') || contentType.includes('xml') || contentType.includes('text/plain') || typeof response.data === 'string') {
|
|
res.status(response.status).send(response.data);
|
|
} else {
|
|
res.status(response.status).json(response.data);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error(`❌ [AI MOCKUP PROXY ERROR]:`, error.message);
|
|
if (!res.headersSent) {
|
|
if (error.response) {
|
|
const ct = error.response.headers?.['content-type'] || '';
|
|
if (ct.includes('image/svg') || ct.includes('xml') || typeof error.response.data === 'string') {
|
|
res.status(error.response.status).send(error.response.data);
|
|
} else {
|
|
res.status(error.response.status).json(error.response.data);
|
|
}
|
|
} else {
|
|
res.status(502).json({
|
|
error: 'AI Mockup service unavailable',
|
|
message: error.code || error.message,
|
|
service: 'ai-mockup'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
);
|
|
|
|
// Gateway management endpoints
|
|
app.get('/api/gateway/info', authMiddleware.verifyToken, (req, res) => {
|
|
res.json({
|
|
success: true,
|
|
gateway: {
|
|
name: 'CodeNuk API Gateway',
|
|
version: process.env.npm_package_version || '1.0.0',
|
|
environment: process.env.NODE_ENV || 'development',
|
|
uptime: process.uptime()
|
|
},
|
|
services: {
|
|
total_services: Object.keys(serviceTargets).length,
|
|
operational_services: Object.keys(serviceTargets).length,
|
|
service_urls: serviceTargets
|
|
},
|
|
features: {
|
|
websocket_enabled: true,
|
|
authentication: true,
|
|
rate_limiting: true,
|
|
health_monitoring: true,
|
|
request_logging: true,
|
|
cors_enabled: true
|
|
},
|
|
websocket: {
|
|
connected_clients: io.engine.clientsCount,
|
|
transport_types: ['websocket', 'polling']
|
|
}
|
|
});
|
|
});
|
|
|
|
// Service status endpoint
|
|
app.get('/api/gateway/services', authMiddleware.verifyToken, serviceHealthMiddleware.getServiceStatus);
|
|
|
|
// Root endpoint
|
|
app.get('/', (req, res) => {
|
|
res.json({
|
|
success: true,
|
|
message: 'CodeNuk API Gateway',
|
|
version: process.env.npm_package_version || '1.0.0',
|
|
description: 'Central gateway for all CodeNuk microservices',
|
|
documentation: {
|
|
health: '/health',
|
|
gateway_info: '/api/gateway/info',
|
|
service_status: '/api/gateway/services'
|
|
},
|
|
services: {
|
|
auth: '/api/auth',
|
|
templates: '/api/templates',
|
|
github: '/api/github',
|
|
requirements: '/api/requirements',
|
|
tech_stack: '/api/tech-stack',
|
|
architecture: '/api/architecture',
|
|
codegen: '/api/codegen',
|
|
tests: '/api/tests',
|
|
deploy: '/api/deploy',
|
|
dashboard: '/api/dashboard',
|
|
self_improving: '/api/self-improving',
|
|
mockup: '/api/mockup',
|
|
unison: '/api/unison',
|
|
unified: '/api/recommendations'
|
|
},
|
|
websocket: {
|
|
endpoint: '/socket.io/',
|
|
authentication_required: true
|
|
}
|
|
});
|
|
});
|
|
|
|
// 404 handler
|
|
app.use('*', (req, res) => {
|
|
res.status(404).json({
|
|
success: false,
|
|
message: 'Endpoint not found',
|
|
available_services: {
|
|
auth: '/api/auth',
|
|
templates: '/api/templates',
|
|
github: '/api/github',
|
|
requirements: '/api/requirements',
|
|
tech_stack: '/api/tech-stack',
|
|
architecture: '/api/architecture',
|
|
codegen: '/api/codegen',
|
|
tests: '/api/tests',
|
|
deploy: '/api/deploy',
|
|
dashboard: '/api/dashboard',
|
|
self_improving: '/api/self-improving',
|
|
mockup: '/api/mockup'
|
|
},
|
|
documentation: '/api/gateway/info'
|
|
});
|
|
});
|
|
|
|
// Global error handler
|
|
app.use((error, req, res, next) => {
|
|
console.error(`[${req.requestId}] Gateway Error:`, error);
|
|
|
|
// Handle proxy errors
|
|
if (error.code === 'ECONNREFUSED' || error.code === 'ENOTFOUND') {
|
|
return res.status(503).json({
|
|
success: false,
|
|
message: 'Service temporarily unavailable',
|
|
error: 'The requested service is currently unavailable',
|
|
request_id: req.requestId
|
|
});
|
|
}
|
|
|
|
// Handle timeout errors
|
|
if (error.code === 'ETIMEDOUT' || error.message.includes('timeout')) {
|
|
return res.status(504).json({
|
|
success: false,
|
|
message: 'Service timeout',
|
|
error: 'The service took too long to respond',
|
|
request_id: req.requestId
|
|
});
|
|
}
|
|
|
|
// Handle authentication errors
|
|
if (error.name === 'JsonWebTokenError' || error.name === 'TokenExpiredError') {
|
|
return res.status(401).json({
|
|
success: false,
|
|
message: 'Authentication failed',
|
|
error: error.message
|
|
});
|
|
}
|
|
|
|
// Generic error handler
|
|
res.status(error.status || 500).json({
|
|
success: false,
|
|
message: error.message || 'Internal gateway error',
|
|
error: process.env.NODE_ENV === 'development' ? {
|
|
stack: error.stack,
|
|
name: error.name
|
|
} : undefined,
|
|
request_id: req.requestId
|
|
});
|
|
});
|
|
|
|
// Start server and initialize health monitoring
|
|
const startServer = async () => {
|
|
try {
|
|
console.log('🚀 Starting CodeNuk API Gateway...');
|
|
|
|
// Initialize service health monitoring
|
|
await serviceHealthMiddleware.initializeHealthMonitoring();
|
|
|
|
server.listen(PORT, '0.0.0.0', () => {
|
|
console.log(`✅ API Gateway running on port ${PORT}`);
|
|
console.log(`🌍 Environment: ${process.env.NODE_ENV || 'development'}`);
|
|
console.log(`📋 Health check: http://localhost:8000/health`);
|
|
console.log(`📖 Gateway info: http://localhost:8000/api/gateway/info`);
|
|
console.log(`🔗 WebSocket enabled on: wss://backend.codenuk.com`);
|
|
|
|
// Log service configuration
|
|
console.log('⚙️ Configured Services:');
|
|
Object.entries(serviceTargets).forEach(([name, url]) => {
|
|
console.log(` - ${name}: ${url}`);
|
|
});
|
|
|
|
console.log('🔧 Features:');
|
|
console.log(` - Rate Limiting: Enabled`);
|
|
console.log(` - Authentication: JWT with Auth Service`);
|
|
console.log(` - WebSocket: Real-time notifications`);
|
|
console.log(` - Health Monitoring: All services`);
|
|
console.log(` - Request Logging: Enabled`);
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('❌ Failed to start API Gateway:', error);
|
|
process.exit(1);
|
|
}
|
|
};
|
|
|
|
// Graceful shutdown
|
|
process.on('SIGTERM', () => {
|
|
console.log('📴 SIGTERM received, shutting down gracefully...');
|
|
server.close(() => {
|
|
console.log('✅ API Gateway shut down successfully');
|
|
process.exit(0);
|
|
});
|
|
});
|
|
|
|
process.on('SIGINT', () => {
|
|
console.log('📴 SIGINT received, shutting down gracefully...');
|
|
server.close(() => {
|
|
console.log('✅ API Gateway shut down successfully');
|
|
process.exit(0);
|
|
});
|
|
});
|
|
|
|
// Start the server
|
|
startServer();
|
|
|
|
module.exports = app; |