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;