backend changes

This commit is contained in:
Chandini 2025-09-02 13:59:36 +05:30
parent 86e68a2e6b
commit f927bd3d3b
16 changed files with 3588 additions and 100 deletions

1937
dashboard-service/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -545,7 +545,7 @@ services:
- PORT=8000
- REDIS_HOST=redis
- REDIS_PORT=6379
- REDIS_PASSWORD=${REDIS_PASSWORD}
- REDIS_PASSWORD=redis123
- POSTGRES_HOST=postgres
- POSTGRES_PORT=5432
- POSTGRES_DB=dev_pipeline
@ -555,6 +555,17 @@ services:
- RABBITMQ_PORT=5672
- RABBITMQ_USER=pipeline_admin
- RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD}
# Internal service URLs for proxying (Docker network names)
- USER_AUTH_URL=http://user-auth:8011
- TEMPLATE_MANAGER_URL=http://template-manager:8009
- REQUIREMENT_PROCESSOR_URL=http://requirement-processor:8001
- TECH_STACK_SELECTOR_URL=http://tech-stack-selector:8002
- ARCHITECTURE_DESIGNER_URL=http://architecture-designer:8003
- CODE_GENERATOR_URL=http://code-generator:8004
- TEST_GENERATOR_URL=http://test-generator:8005
- DEPLOYMENT_MANAGER_URL=http://deployment-manager:8006
- DASHBOARD_URL=http://dashboard:8008
- SELF_IMPROVING_GENERATOR_URL=http://self-improving-generator:8007
networks:
- pipeline_network
depends_on:
@ -578,7 +589,7 @@ services:
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- REDIS_HOST=redis
- REDIS_PORT=6379
- REDIS_PASSWORD=${REDIS_PASSWORD}
- REDIS_PASSWORD=redis123
- MONGODB_HOST=mongodb
- MONGODB_PORT=27017
- NEO4J_URI=bolt://neo4j:7687
@ -586,7 +597,7 @@ services:
- NEO4J_PASSWORD=password
- CHROMA_HOST=chromadb
- CHROMA_PORT=8000
- REDIS_URL=redis://redis:6379
- REDIS_URL=redis://:redis123@redis:6379
networks:
- pipeline_network
depends_on:
@ -610,7 +621,7 @@ services:
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- REDIS_HOST=redis
- REDIS_PORT=6379
- REDIS_PASSWORD=${REDIS_PASSWORD}
- REDIS_PASSWORD=redis123
networks:
- pipeline_network
depends_on:
@ -666,7 +677,7 @@ services:
- MONGODB_PORT=27017
- REDIS_HOST=redis
- REDIS_PORT=6379
- REDIS_PASSWORD=${REDIS_PASSWORD}
- REDIS_PASSWORD=redis123
- CLAUDE_API_KEY=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
- OPENAI_API_KEY=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
# - NEO4J_URI=bolt://neo4j:7687
@ -711,7 +722,7 @@ services:
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- REDIS_HOST=redis
- REDIS_PORT=6379
- REDIS_PASSWORD=${REDIS_PASSWORD}
- REDIS_PASSWORD=redis123
networks:
- pipeline_network
depends_on:
@ -763,7 +774,7 @@ services:
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- REDIS_HOST=redis
- REDIS_PORT=6379
- REDIS_PASSWORD=${REDIS_PASSWORD}
- REDIS_PASSWORD=redis123
- JWT_ACCESS_SECRET=access-secret-key-2024-tech4biz-${POSTGRES_PASSWORD}
- JWT_REFRESH_SECRET=refresh-secret-key-2024-tech4biz-${POSTGRES_PASSWORD}
- JWT_ACCESS_EXPIRY=15m
@ -878,7 +889,15 @@ services:
- NODE_ENV=production
- PORT=8008
- DATABASE_URL=postgresql://pipeline_admin:${POSTGRES_PASSWORD}@postgres:5432/dev_pipeline
- REDIS_URL=redis://pipeline_redis:6379
- POSTGRES_HOST=postgres
- POSTGRES_PORT=5432
- POSTGRES_DB=dev_pipeline
- POSTGRES_USER=pipeline_admin
- POSTGRES_PASSWORD=secure_pipeline_2024
- REDIS_URL=redis://:redis123@redis:6379
- REDIS_HOST=redis
- REDIS_PORT=6379
- REDIS_PASSWORD=redis123
- API_GATEWAY_URL=http://pipeline_api_gateway:8000
- CODE_GENERATOR_URL=http://pipeline_code_generator:8004
- SELF_IMPROVING_URL=http://pipeline_self_improving_generator:8007

View File

@ -15,6 +15,7 @@
"express-rate-limit": "^6.8.1",
"express-validator": "^7.0.1",
"helmet": "^7.0.0",
"http-proxy-middleware": "^3.0.5",
"jsonwebtoken": "^9.0.1",
"morgan": "^1.10.0",
"pg": "^8.11.1",
@ -1117,6 +1118,15 @@
"@types/node": "*"
}
},
"node_modules/@types/http-proxy": {
"version": "1.17.16",
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz",
"integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
@ -1493,7 +1503,6 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"license": "MIT",
"dependencies": {
"fill-range": "^7.1.1"
@ -2260,6 +2269,12 @@
"node": ">= 0.6"
}
},
"node_modules/eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
"license": "MIT"
},
"node_modules/execa": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
@ -2408,7 +2423,6 @@
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
@ -2774,6 +2788,60 @@
"node": ">= 0.8"
}
},
"node_modules/http-proxy": {
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
"license": "MIT",
"dependencies": {
"eventemitter3": "^4.0.0",
"follow-redirects": "^1.0.0",
"requires-port": "^1.0.0"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/http-proxy-middleware": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.5.tgz",
"integrity": "sha512-GLZZm1X38BPY4lkXA01jhwxvDoOkkXqjgVyUzVxiEK4iuRu03PZoYHhHRwxnfhQMDuaxi3vVri0YgSro/1oWqg==",
"license": "MIT",
"dependencies": {
"@types/http-proxy": "^1.17.15",
"debug": "^4.3.6",
"http-proxy": "^1.18.1",
"is-glob": "^4.0.3",
"is-plain-object": "^5.0.0",
"micromatch": "^4.0.8"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/http-proxy-middleware/node_modules/debug": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/http-proxy-middleware/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/human-signals": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
@ -2900,7 +2968,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@ -2930,7 +2997,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
@ -2943,12 +3009,20 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
@ -4001,7 +4075,6 @@
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"license": "MIT",
"dependencies": {
"braces": "^3.0.3",
@ -4547,7 +4620,6 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
@ -4803,6 +4875,12 @@
"node": ">=0.10.0"
}
},
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
"license": "MIT"
},
"node_modules/resolve": {
"version": "1.22.10",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
@ -5442,7 +5520,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-number": "^7.0.0"

View File

@ -9,22 +9,23 @@
"test": "jest"
},
"dependencies": {
"express": "^4.18.2",
"cors": "^2.8.5",
"helmet": "^7.0.0",
"express-rate-limit": "^6.8.1",
"jsonwebtoken": "^9.0.1",
"redis": "^4.6.7",
"pg": "^8.11.1",
"axios": "^1.4.0",
"winston": "^3.10.0",
"socket.io": "^4.7.2",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"express-rate-limit": "^6.8.1",
"express-validator": "^7.0.1",
"morgan": "^1.10.0"
"helmet": "^7.0.0",
"http-proxy-middleware": "^2.0.6",
"jsonwebtoken": "^9.0.1",
"morgan": "^1.10.0",
"pg": "^8.11.1",
"redis": "^4.6.7",
"socket.io": "^4.7.2",
"winston": "^3.10.0"
},
"devDependencies": {
"nodemon": "^3.0.1",
"jest": "^29.6.1"
"jest": "^29.6.1",
"nodemon": "^3.0.1"
}
}

View File

@ -0,0 +1,122 @@
const jwt = require('jsonwebtoken');
const axios = require('axios');
// JWT token verification middleware
const verifyToken = async (req, res, next) => {
try {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({
success: false,
message: 'Access token required',
error: 'No token provided'
});
}
// Verify JWT token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
// Add user context to headers for downstream services
req.headers['x-user-id'] = decoded.id || decoded.userId;
req.headers['x-user-email'] = decoded.email;
req.headers['x-user-role'] = decoded.role || 'user';
next();
} catch (error) {
console.error('Token verification failed:', error.message);
if (error.name === 'TokenExpiredError') {
return res.status(401).json({
success: false,
message: 'Token expired',
error: 'Please login again'
});
}
if (error.name === 'JsonWebTokenError') {
return res.status(401).json({
success: false,
message: 'Invalid token',
error: 'Token verification failed'
});
}
return res.status(401).json({
success: false,
message: 'Authentication failed',
error: error.message
});
}
};
// Forward user context to downstream services
const forwardUserContext = (req, res, next) => {
if (req.user) {
// Add gateway headers for service identification
req.headers['x-gateway-request-id'] = req.requestId;
req.headers['x-gateway-timestamp'] = new Date().toISOString();
req.headers['x-forwarded-by'] = 'api-gateway';
req.headers['x-forwarded-for'] = req.ip;
req.headers['x-forwarded-proto'] = req.protocol;
req.headers['x-forwarded-host'] = req.get('host');
}
next();
};
// Optional token verification (doesn't fail if no token)
const verifyTokenOptional = async (req, res, next) => {
try {
const token = req.headers.authorization?.split(' ')[1];
if (token) {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
// Add user context to headers
req.headers['x-user-id'] = decoded.id || decoded.userId;
req.headers['x-user-email'] = decoded.email;
req.headers['x-user-role'] = decoded.role || 'user';
}
next();
} catch (error) {
// Continue without authentication for optional routes
console.log('Optional token verification failed:', error.message);
next();
}
};
// Role-based authorization middleware
const requireRole = (roles) => {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({
success: false,
message: 'Authentication required'
});
}
const userRole = req.user.role || 'user';
const allowedRoles = Array.isArray(roles) ? roles : [roles];
if (!allowedRoles.includes(userRole)) {
return res.status(403).json({
success: false,
message: 'Insufficient permissions',
required_roles: allowedRoles,
user_role: userRole
});
}
next();
};
};
module.exports = {
verifyToken,
forwardUserContext,
verifyTokenOptional,
requireRole
};

View File

@ -0,0 +1,126 @@
const winston = require('winston');
// Configure Winston logger
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: { service: 'api-gateway' },
transports: [
new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
new winston.transports.File({ filename: 'logs/combined.log' }),
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
})
]
});
// Request logging middleware
const logRequest = (req, res, next) => {
const startTime = Date.now();
// Log incoming request
logger.info('Incoming Request', {
requestId: req.requestId,
method: req.method,
url: req.originalUrl,
ip: req.ip,
userAgent: req.get('User-Agent'),
timestamp: new Date().toISOString(),
headers: {
authorization: req.headers.authorization ? '[REDACTED]' : undefined,
'content-type': req.headers['content-type'],
'x-forwarded-for': req.headers['x-forwarded-for']
}
});
// Override res.end to log response
const originalEnd = res.end;
res.end = function(chunk, encoding) {
const responseTime = Date.now() - startTime;
// Log response
logger.info('Response Sent', {
requestId: req.requestId,
method: req.method,
url: req.originalUrl,
statusCode: res.statusCode,
responseTime: `${responseTime}ms`,
timestamp: new Date().toISOString()
});
// Call original end method
originalEnd.call(this, chunk, encoding);
};
next();
};
// Error logging middleware
const logError = (error, req, res, next) => {
logger.error('Request Error', {
requestId: req.requestId,
method: req.method,
url: req.originalUrl,
error: error.message,
stack: error.stack,
timestamp: new Date().toISOString()
});
next(error);
};
// Service proxy logging
const logProxyRequest = (serviceName, targetUrl) => {
return (proxyReq, req, res) => {
logger.info('Proxy Request', {
requestId: req.requestId,
service: serviceName,
method: req.method,
originalUrl: req.originalUrl,
targetUrl: targetUrl,
timestamp: new Date().toISOString()
});
};
};
const logProxyResponse = (serviceName) => {
return (proxyRes, req, res) => {
logger.info('Proxy Response', {
requestId: req.requestId,
service: serviceName,
method: req.method,
originalUrl: req.originalUrl,
statusCode: proxyRes.statusCode,
timestamp: new Date().toISOString()
});
};
};
const logProxyError = (serviceName) => {
return (err, req, res) => {
logger.error('Proxy Error', {
requestId: req.requestId,
service: serviceName,
method: req.method,
originalUrl: req.originalUrl,
error: err.message,
timestamp: new Date().toISOString()
});
};
};
module.exports = {
logger,
logRequest,
logError,
logProxyRequest,
logProxyResponse,
logProxyError
};

View File

@ -0,0 +1,140 @@
const axios = require('axios');
// Service health monitoring
class ServiceHealthMonitor {
constructor() {
this.serviceStatus = {};
this.healthCheckInterval = null;
this.healthCheckFrequency = parseInt(process.env.HEALTH_CHECK_FREQUENCY) || 30000; // 30 seconds
}
// Initialize health monitoring for all services
async initializeHealthMonitoring() {
const serviceTargets = {
USER_AUTH_URL: process.env.USER_AUTH_URL || 'http://localhost:8011',
TEMPLATE_MANAGER_URL: process.env.TEMPLATE_MANAGER_URL || 'http://localhost:8009',
REQUIREMENT_PROCESSOR_URL: process.env.REQUIREMENT_PROCESSOR_URL || 'http://localhost:8001',
TECH_STACK_SELECTOR_URL: process.env.TECH_STACK_SELECTOR_URL || 'http://localhost:8002',
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',
};
// Initial health check
await this.checkAllServices(serviceTargets);
// Start periodic health checks
this.startPeriodicHealthChecks(serviceTargets);
console.log('✅ Service health monitoring initialized');
}
// Check health of all services
async checkAllServices(serviceTargets) {
const healthPromises = Object.entries(serviceTargets).map(async ([serviceName, serviceUrl]) => {
try {
const startTime = Date.now();
const response = await axios.get(`${serviceUrl}/health`, {
timeout: 5000,
headers: { 'User-Agent': 'API-Gateway-Health-Check' }
});
const responseTime = Date.now() - startTime;
this.serviceStatus[serviceName] = {
status: response.status === 200 ? 'healthy' : 'unhealthy',
url: serviceUrl,
responseTime: responseTime,
lastChecked: new Date().toISOString(),
error: null
};
} catch (error) {
this.serviceStatus[serviceName] = {
status: 'unhealthy',
url: serviceUrl,
responseTime: null,
lastChecked: new Date().toISOString(),
error: error.message
};
}
});
await Promise.allSettled(healthPromises);
}
// Start periodic health checks
startPeriodicHealthChecks(serviceTargets) {
if (this.healthCheckInterval) {
clearInterval(this.healthCheckInterval);
}
this.healthCheckInterval = setInterval(async () => {
await this.checkAllServices(serviceTargets);
}, this.healthCheckFrequency);
}
// Stop health monitoring
stopHealthMonitoring() {
if (this.healthCheckInterval) {
clearInterval(this.healthCheckInterval);
this.healthCheckInterval = null;
}
}
// Get current service status
getServiceStatus() {
const totalServices = Object.keys(this.serviceStatus).length;
const healthyServices = Object.values(this.serviceStatus).filter(s => s.status === 'healthy').length;
return {
summary: {
total_services: totalServices,
healthy_services: healthyServices,
unhealthy_services: totalServices - healthyServices,
overall_health: healthyServices === totalServices ? 'healthy' : 'degraded'
},
services: this.serviceStatus,
last_updated: new Date().toISOString()
};
}
}
// Create singleton instance
const healthMonitor = new ServiceHealthMonitor();
// Middleware to get service status
const getServiceStatus = (req, res) => {
const status = healthMonitor.getServiceStatus();
res.json({
success: true,
...status
});
};
// Middleware to check if a specific service is healthy
const checkServiceHealth = (serviceName) => {
return (req, res, next) => {
const serviceStatus = healthMonitor.serviceStatus[serviceName];
if (!serviceStatus || serviceStatus.status !== 'healthy') {
return res.status(503).json({
success: false,
message: `Service ${serviceName} is currently unavailable`,
service_status: serviceStatus || { status: 'unknown' },
retry_after: 30
});
}
next();
};
};
module.exports = {
healthMonitor,
initializeHealthMonitoring: () => healthMonitor.initializeHealthMonitoring(),
getServiceStatus,
checkServiceHealth
};

View File

@ -0,0 +1,136 @@
const jwt = require('jsonwebtoken');
// WebSocket authentication middleware
const authenticateSocket = (socket, next) => {
try {
const token = socket.handshake.auth.token || socket.handshake.headers.authorization?.split(' ')[1];
if (!token) {
return next(new Error('Authentication token required'));
}
const decoded = jwt.verify(token, process.env.JWT_SECRET);
socket.user = decoded;
socket.userId = decoded.id || decoded.userId;
console.log(`✅ WebSocket authenticated: ${socket.user.email || socket.userId}`);
next();
} catch (error) {
console.error('WebSocket authentication failed:', error.message);
next(new Error('Authentication failed'));
}
};
// WebSocket connection handler with authentication
const handleWebSocketConnections = (io) => {
// Authentication middleware for all connections
io.use(authenticateSocket);
io.on('connection', (socket) => {
console.log(`🔌 Client connected: ${socket.id} (User: ${socket.user.email || socket.userId})`);
// Join user-specific room
const userRoom = `user_${socket.userId}`;
socket.join(userRoom);
// Send connection confirmation
socket.emit('connected', {
message: 'Connected to CodeNuk API Gateway',
timestamp: new Date().toISOString(),
socketId: socket.id,
user: {
id: socket.userId,
email: socket.user.email,
role: socket.user.role
}
});
// Handle service-specific events
socket.on('subscribe_to_service', (data) => {
const { service } = data;
const serviceRoom = `service_${service}`;
socket.join(serviceRoom);
console.log(`📡 User ${socket.userId} subscribed to ${service} updates`);
socket.emit('subscribed', { service, room: serviceRoom });
});
socket.on('unsubscribe_from_service', (data) => {
const { service } = data;
const serviceRoom = `service_${service}`;
socket.leave(serviceRoom);
console.log(`📡 User ${socket.userId} unsubscribed from ${service} updates`);
socket.emit('unsubscribed', { service, room: serviceRoom });
});
// Handle project-specific events
socket.on('join_project', (data) => {
const { projectId } = data;
const projectRoom = `project_${projectId}`;
socket.join(projectRoom);
console.log(`📁 User ${socket.userId} joined project ${projectId}`);
socket.emit('joined_project', { projectId, room: projectRoom });
});
socket.on('leave_project', (data) => {
const { projectId } = data;
const projectRoom = `project_${projectId}`;
socket.leave(projectRoom);
console.log(`📁 User ${socket.userId} left project ${projectId}`);
socket.emit('left_project', { projectId, room: projectRoom });
});
// Handle real-time notifications
socket.on('send_notification', (data) => {
const { targetUserId, message, type } = data;
const targetRoom = `user_${targetUserId}`;
io.to(targetRoom).emit('notification', {
from: socket.userId,
message,
type,
timestamp: new Date().toISOString()
});
console.log(`📢 Notification sent from ${socket.userId} to ${targetUserId}`);
});
// Handle disconnection
socket.on('disconnect', (reason) => {
console.log(`🔌 Client disconnected: ${socket.id} (User: ${socket.user.email || socket.userId}) - Reason: ${reason}`);
});
// Handle errors
socket.on('error', (error) => {
console.error(`❌ Socket error for ${socket.id}:`, error);
});
});
// Broadcast service status updates
const broadcastServiceUpdate = (serviceName, status) => {
io.to(`service_${serviceName}`).emit('service_status_update', {
service: serviceName,
status,
timestamp: new Date().toISOString()
});
};
// Broadcast project updates
const broadcastProjectUpdate = (projectId, update) => {
io.to(`project_${projectId}`).emit('project_update', {
projectId,
update,
timestamp: new Date().toISOString()
});
};
return {
broadcastServiceUpdate,
broadcastProjectUpdate
};
};
module.exports = handleWebSocketConnections;

View File

@ -0,0 +1,68 @@
const express = require('express');
const { healthMonitor } = require('../middleware/serviceHealth');
const router = express.Router();
// Get comprehensive service health status
router.get('/services', (req, res) => {
const status = healthMonitor.getServiceStatus();
res.json({
success: true,
...status
});
});
// Get health status for a specific service
router.get('/service/:serviceName', (req, res) => {
const { serviceName } = req.params;
const serviceStatus = healthMonitor.serviceStatus[serviceName];
if (!serviceStatus) {
return res.status(404).json({
success: false,
message: `Service ${serviceName} not found`,
available_services: Object.keys(healthMonitor.serviceStatus)
});
}
res.json({
success: true,
service: serviceName,
...serviceStatus
});
});
// Trigger manual health check for all services
router.post('/check', async (req, res) => {
try {
const serviceTargets = {
USER_AUTH_URL: process.env.USER_AUTH_URL || 'http://localhost:8011',
TEMPLATE_MANAGER_URL: process.env.TEMPLATE_MANAGER_URL || 'http://localhost:8009',
REQUIREMENT_PROCESSOR_URL: process.env.REQUIREMENT_PROCESSOR_URL || 'http://localhost:8001',
TECH_STACK_SELECTOR_URL: process.env.TECH_STACK_SELECTOR_URL || 'http://localhost:8002',
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',
};
await healthMonitor.checkAllServices(serviceTargets);
const status = healthMonitor.getServiceStatus();
res.json({
success: true,
message: 'Health check completed',
...status
});
} catch (error) {
res.status(500).json({
success: false,
message: 'Health check failed',
error: error.message
});
}
});
module.exports = { router };

View File

@ -0,0 +1,130 @@
const { createProxyMiddleware } = require('http-proxy-middleware');
const { logProxyRequest, logProxyResponse, logProxyError } = require('../middleware/requestLogger');
// Create service proxy with enhanced logging and error handling
const createServiceProxy = (targetUrl, serviceName, options = {}) => {
return createProxyMiddleware({
target: targetUrl,
changeOrigin: true,
ws: true,
timeout: parseInt(process.env.PROXY_TIMEOUT) || 30000,
proxyTimeout: parseInt(process.env.PROXY_TIMEOUT) || 30000,
pathRewrite: options.pathRewrite || {},
onProxyReq: logProxyRequest(serviceName, targetUrl),
onProxyRes: logProxyResponse(serviceName),
onError: (err, req, res) => {
logProxyError(serviceName)(err, req, res);
if (!res.headersSent) {
// Handle different types of proxy errors
if (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND') {
return res.status(503).json({
success: false,
message: 'Service temporarily unavailable',
service: serviceName,
error: 'The requested service is currently unavailable',
request_id: req.requestId,
retry_after: 30
});
}
if (err.code === 'ETIMEDOUT' || err.message.includes('timeout')) {
return res.status(504).json({
success: false,
message: 'Service timeout',
service: serviceName,
error: 'The service took too long to respond',
request_id: req.requestId,
retry_after: 60
});
}
// Generic proxy error
res.status(502).json({
success: false,
message: 'Bad Gateway',
service: serviceName,
error: 'Unable to connect to the service',
request_id: req.requestId
});
}
}
});
};
// Create auth-specific proxy (no authentication required for login/register)
const createAuthProxy = () => {
const targetUrl = process.env.USER_AUTH_URL || 'http://localhost:8011';
console.log(`🔧 [PROXY CREATION] Creating auth proxy targeting: ${targetUrl}`);
return createProxyMiddleware({
target: targetUrl,
changeOrigin: true,
ws: true,
timeout: 30000,
proxyTimeout: 30000,
// Don't parse body - let it pass through
parseReqBody: false,
// Strip /api/auth prefix before forwarding to service
pathRewrite: {
'^/api/auth': ''
},
onProxyReq: (proxyReq, req, res) => {
console.log(`🔥 [PROXY MIDDLEWARE TRIGGERED!] This means the proxy is working!`);
// Calculate the rewritten path
const rewrittenPath = req.url.replace(/^\/api\/auth/, '');
const finalUrl = `${targetUrl}${rewrittenPath}`;
console.log(`🔐 [AUTH PROXY DEBUG]`);
console.log(` Original URL: ${req.originalUrl}`);
console.log(` Request URL: ${req.url}`);
console.log(` Target URL: ${targetUrl}`);
console.log(` Rewritten Path: ${rewrittenPath}`);
console.log(` FINAL URL HITTING: ${finalUrl}`);
console.log(` Method: ${req.method}`);
console.log(` Headers: ${JSON.stringify(req.headers, null, 2)}`);
// Add gateway headers
proxyReq.setHeader('X-Gateway-Request-ID', req.requestId);
proxyReq.setHeader('X-Gateway-Timestamp', new Date().toISOString());
proxyReq.setHeader('X-Forwarded-By', 'api-gateway');
proxyReq.setHeader('X-Forwarded-For', req.ip);
proxyReq.setHeader('X-Forwarded-Proto', req.protocol);
proxyReq.setHeader('X-Forwarded-Host', req.get('host'));
// Ensure content-type and content-length are preserved
if (req.headers['content-type']) {
proxyReq.setHeader('Content-Type', req.headers['content-type']);
}
if (req.headers['content-length']) {
proxyReq.setHeader('Content-Length', req.headers['content-length']);
}
},
onProxyRes: (proxyRes, req, res) => {
console.log(`🔐 [AUTH RESPONSE] ${proxyRes.statusCode} for ${req.method} ${req.originalUrl}`);
// Add CORS headers
proxyRes.headers['Access-Control-Allow-Origin'] = req.headers.origin || '*';
proxyRes.headers['Access-Control-Allow-Credentials'] = 'true';
},
onError: (err, req, res) => {
console.error(`❌ [AUTH PROXY ERROR] ${req.method} ${req.originalUrl}:`, err.message);
if (!res.headersSent) {
res.status(502).json({
success: false,
message: 'Authentication service unavailable',
error: 'Unable to connect to authentication service',
request_id: req.requestId
});
}
}
});
};
module.exports = {
createServiceProxy,
createAuthProxy
};

View File

@ -0,0 +1,123 @@
const express = require('express');
const { verifyToken } = require('../middleware/authentication');
const router = express.Router();
// Get WebSocket connection info
router.get('/info', verifyToken, (req, res) => {
const io = global.io;
if (!io) {
return res.status(500).json({
success: false,
message: 'WebSocket server not initialized'
});
}
res.json({
success: true,
websocket: {
connected_clients: io.engine.clientsCount,
transport_types: ['websocket', 'polling'],
endpoint: '/socket.io/',
authentication_required: true
},
rooms: {
user_rooms: `user_${req.user.id || req.user.userId}`,
available_services: [
'user-auth', 'template-manager', 'requirement-processor',
'tech-stack-selector', 'architecture-designer', 'code-generator',
'test-generator', 'deployment-manager', 'dashboard', 'self-improving-generator'
]
}
});
});
// Broadcast message to specific service subscribers
router.post('/broadcast/service/:serviceName', verifyToken, (req, res) => {
const { serviceName } = req.params;
const { message, type = 'info' } = req.body;
const io = global.io;
if (!io) {
return res.status(500).json({
success: false,
message: 'WebSocket server not initialized'
});
}
const serviceRoom = `service_${serviceName}`;
io.to(serviceRoom).emit('service_broadcast', {
service: serviceName,
message,
type,
from: req.user.email || req.user.id,
timestamp: new Date().toISOString()
});
res.json({
success: true,
message: `Broadcast sent to ${serviceName} subscribers`,
room: serviceRoom
});
});
// Broadcast message to specific project
router.post('/broadcast/project/:projectId', verifyToken, (req, res) => {
const { projectId } = req.params;
const { message, type = 'info' } = req.body;
const io = global.io;
if (!io) {
return res.status(500).json({
success: false,
message: 'WebSocket server not initialized'
});
}
const projectRoom = `project_${projectId}`;
io.to(projectRoom).emit('project_broadcast', {
projectId,
message,
type,
from: req.user.email || req.user.id,
timestamp: new Date().toISOString()
});
res.json({
success: true,
message: `Broadcast sent to project ${projectId}`,
room: projectRoom
});
});
// Send notification to specific user
router.post('/notify/:userId', verifyToken, (req, res) => {
const { userId } = req.params;
const { message, type = 'info', title } = req.body;
const io = global.io;
if (!io) {
return res.status(500).json({
success: false,
message: 'WebSocket server not initialized'
});
}
const userRoom = `user_${userId}`;
io.to(userRoom).emit('notification', {
title,
message,
type,
from: req.user.email || req.user.id,
timestamp: new Date().toISOString()
});
res.json({
success: true,
message: `Notification sent to user ${userId}`,
room: userRoom
});
});
module.exports = router;

View File

@ -1,113 +1,531 @@
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 { createServer } = require('http');
const { Server } = require('socket.io');
const { createProxyMiddleware } = require('http-proxy-middleware');
const jwt = require('jsonwebtoken');
const axios = require('axios');
require('dotenv').config();
const app = express();
const server = createServer(app);
const io = new Server(server, {
cors: {
origin: "*",
methods: ["GET", "POST"]
}
});
// Import middleware
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();
const server = http.createServer(app);
const PORT = process.env.PORT || 8000;
// Middleware
app.use(helmet());
app.use(cors());
app.use(morgan('combined'));
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
// Initialize Socket.IO with CORS
const io = socketIo(server, {
cors: {
origin: process.env.FRONTEND_URL || "*",
credentials: true,
methods: ['GET', 'POST']
},
transports: ['websocket', 'polling']
});
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 1000, // limit each IP to 1000 requests per windowMs
message: 'Too many requests from this IP, please try again later.',
// 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://localhost:8009',
REQUIREMENT_PROCESSOR_URL: process.env.REQUIREMENT_PROCESSOR_URL || 'http://localhost:8001',
TECH_STACK_SELECTOR_URL: process.env.TECH_STACK_SELECTOR_URL || 'http://localhost:8002',
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',
};
// ========================================
// 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 configuration
app.use(cors({
origin: process.env.FRONTEND_URL || "*",
credentials: true,
optionsSuccessStatus: 200,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: [
'Origin', 'X-Requested-With', 'Content-Type', 'Accept', 'Authorization',
'X-Gateway-Request-ID', 'X-Gateway-Timestamp', 'X-Forwarded-By',
'X-Forwarded-For', 'X-Forwarded-Proto', 'X-Forwarded-Host',
'X-Session-Token', 'X-Platform', 'X-App-Version'
],
exposedHeaders: [
'X-Gateway-Request-ID', 'X-Gateway-Timestamp', 'X-Forwarded-By',
'X-Forwarded-For', 'X-Forwarded-Proto', 'X-Forwarded-Host'
]
}));
// 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('/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
const createServiceLimiter = (maxRequests = 1000) => 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,
legacyHeaders: false
});
app.use('/api/', limiter);
// Health check endpoint
// Health check endpoint (before rate limiting and authentication)
app.get('/health', (req, res) => {
res.json({
status: 'healthy',
res.json({
success: true,
service: 'api-gateway',
status: 'healthy',
timestamp: new Date().toISOString(),
version: '1.0.0',
version: process.env.npm_package_version || '1.0.0',
environment: process.env.NODE_ENV || 'development',
uptime: process.uptime(),
memory: process.memoryUsage()
services: {
user_auth: process.env.USER_AUTH_URL ? 'configured' : 'not configured',
template_manager: process.env.TEMPLATE_MANAGER_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'
},
websocket: 'enabled'
});
});
// Basic routes
app.get('/api/v1/status', (req, res) => {
res.json({
message: 'API Gateway is running',
environment: process.env.NODE_ENV || 'development',
services: {
'requirement-processor': 'pending',
'tech-stack-selector': 'pending',
'architecture-designer': 'pending',
'code-generator': 'pending',
'test-generator': 'pending',
'deployment-manager': 'pending'
},
timestamp: new Date().toISOString()
});
});
// Service health monitoring routes
app.use('/health', healthRouter.router);
// WebSocket connection handling
io.on('connection', (socket) => {
console.log('Client connected:', socket.id);
const websocketHandlers = websocketAuth(io);
// Auth Service - Fixed proxy with proper connection handling
console.log('🔧 Registering /api/auth proxy route...');
app.use('/api/auth', (req, res, next) => {
console.log(`🔥 [AUTH PROXY] ${req.method} ${req.originalUrl} → http://localhost:8011${req.originalUrl}`);
socket.on('disconnect', () => {
console.log('Client disconnected:', socket.id);
// 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' });
}
});
// Send initial connection confirmation
socket.emit('connected', {
message: 'Connected to API Gateway',
timestamp: new Date().toISOString()
const options = {
method: req.method,
url: `http://localhost:8011${req.originalUrl}`,
headers: {
'Content-Type': 'application/json',
'User-Agent': 'API-Gateway/1.0',
'Connection': 'keep-alive'
},
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(`📦 [AUTH PROXY] Request body:`, JSON.stringify(req.body));
}
axios(options)
.then(response => {
console.log(`✅ [AUTH PROXY] Response: ${response.status} for ${req.method} ${req.originalUrl}`);
if (!res.headersSent) {
res.status(response.status).json(response.data);
}
})
.catch(error => {
console.error(`❌ [AUTH 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: 'Auth service unavailable',
message: error.code || error.message,
service: 'user-auth'
});
}
}
});
});
// 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),
authMiddleware.verifyToken,
authMiddleware.forwardUserContext,
(req, res, next) => {
console.log(`🔥 [TEMPLATE PROXY] ${req.method} ${req.originalUrl} → http://localhost:8009${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: `http://localhost:8009${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'
});
}
}
});
}
);
// Requirement Processor Service
app.use('/api/requirements',
createServiceLimiter(300),
authMiddleware.verifyToken,
authMiddleware.forwardUserContext,
serviceRouter.createServiceProxy(serviceTargets.REQUIREMENT_PROCESSOR_URL, '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')
);
// 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']
}
});
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error('Error:', err.stack);
res.status(500).json({
error: 'Internal server error',
message: process.env.NODE_ENV === 'development' ? err.message : 'Something went wrong!',
timestamp: new Date().toISOString()
// 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',
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'
},
websocket: {
endpoint: '/socket.io/',
authentication_required: true
}
});
});
// 404 handler
app.use('*', (req, res) => {
res.status(404).json({
error: 'Route not found',
path: req.originalUrl,
timestamp: new Date().toISOString()
res.status(404).json({
success: false,
message: 'Endpoint not found',
available_services: {
auth: '/api/auth',
templates: '/api/templates',
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'
},
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:${PORT}/health`);
console.log(`📖 Gateway info: http://localhost:${PORT}/api/gateway/info`);
console.log(`🔗 WebSocket enabled on: ws://localhost:${PORT}`);
// 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');
console.log('📴 SIGTERM received, shutting down gracefully...');
server.close(() => {
console.log('Process terminated');
console.log('✅ API Gateway shut down successfully');
process.exit(0);
});
});
server.listen(PORT, '0.0.0.0', () => {
console.log(`🚀 API Gateway running on port ${PORT}`);
console.log(`📊 Health check: http://localhost:${PORT}/health`);
console.log(`📡 WebSocket server started`);
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;

View File

@ -283,6 +283,13 @@ class CustomTemplate {
admin_reviewed_by
};
// Maintain the legacy boolean flag alongside the status for easier filtering
if (status === 'approved') {
updates.approved = true;
} else if (status === 'rejected' || status === 'duplicate') {
updates.approved = false;
}
if (canonical_template_id) {
updates.canonical_template_id = canonical_template_id;
}

View File

@ -485,13 +485,51 @@ router.post('/templates/:id/review', async (req, res) => {
console.log(`🔍 Admin: Reviewing template ${id} with status: ${status}`);
// If approving, also create a main template entry and link it, atomically
if (status === 'approved') {
const existingCustom = await CustomTemplate.getById(id);
if (!existingCustom) {
return res.status(404).json({
success: false,
error: 'Template not found',
message: 'The specified template does not exist'
});
}
// Create in main templates
const Template = require('../models/template');
const payload = {
type: existingCustom.type,
title: existingCustom.title,
description: existingCustom.description,
icon: existingCustom.icon,
category: existingCustom.category,
gradient: existingCustom.gradient,
border: existingCustom.border,
text: existingCustom.text,
subtext: existingCustom.subtext,
};
const created = await Template.create(payload);
// Update custom template flags and link canonical_template_id
const updatedCustom = await CustomTemplate.reviewTemplate(id, {
status: 'approved',
admin_notes,
canonical_template_id: created.id,
admin_reviewed_by: req.user.username || req.user.email
});
return res.json({
success: true,
data: { custom_template: updatedCustom, template: created },
message: `Template '${created.title}' created and custom template approved`
});
}
const template = await CustomTemplate.reviewTemplate(id, {
status,
admin_notes,
canonical_template_id,
admin_reviewed_by: req.user.username || req.user.email
});
if (!template) {
return res.status(404).json({
success: false,

View File

@ -737,7 +737,28 @@ router.post('/', async (req, res) => {
}
const template = await Template.create(templateData);
// Link back to custom_templates when approving from a custom
if (templateData.approved_from_custom) {
try {
const customId = templateData.approved_from_custom;
const uuidV4Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
if (uuidV4Regex.test(customId)) {
await CustomTemplate.update(customId, {
approved: true,
status: 'approved',
canonical_template_id: template.id,
admin_reviewed_at: new Date(),
admin_reviewed_by: 'system_auto'
});
} else {
console.warn('[POST /api/templates] approved_from_custom is not a valid UUID v4');
}
} catch (linkErr) {
console.error('⚠️ Failed to set approved=true on custom_templates:', linkErr.message);
}
}
res.status(201).json({
success: true,
data: template,
@ -763,6 +784,59 @@ router.post('/', async (req, res) => {
}
});
// POST /api/templates/approve-custom - Create main template and approve a custom template in one atomic flow
router.post('/approve-custom', async (req, res) => {
try {
const { custom_template_id, template } = req.body || {};
const customId = custom_template_id || req.body?.customTemplateId || req.body?.id;
const uuidV4Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
if (!customId || !uuidV4Regex.test(customId)) {
return res.status(400).json({ success: false, error: 'Invalid custom_template_id', message: 'Provide a valid UUID v4 for custom_template_id' });
}
// Load custom template to mirror missing fields if needed
const existingCustom = await CustomTemplate.getById(customId);
if (!existingCustom) {
return res.status(404).json({ success: false, error: 'Custom template not found', message: `No custom template with id ${customId}` });
}
const payload = {
type: template?.type || existingCustom.type,
title: template?.title || existingCustom.title,
description: template?.description ?? existingCustom.description,
icon: template?.icon ?? existingCustom.icon,
category: template?.category || existingCustom.category,
gradient: template?.gradient ?? existingCustom.gradient,
border: template?.border ?? existingCustom.border,
text: template?.text ?? existingCustom.text,
subtext: template?.subtext ?? existingCustom.subtext,
approved_from_custom: customId
};
// Create in main templates
const created = await Template.create(payload);
// Mark custom template as approved and link canonical_template_id
await CustomTemplate.update(customId, {
approved: true,
status: 'approved',
canonical_template_id: created.id,
admin_reviewed_at: new Date(),
admin_reviewed_by: (req.user && (req.user.username || req.user.email)) || 'admin'
});
return res.status(201).json({
success: true,
data: { template: created, custom_template_id: customId },
message: `Template '${created.title}' created and custom template approved`
});
} catch (error) {
console.error('❌ Error approving custom template:', error.message);
return res.status(500).json({ success: false, error: 'Failed to approve custom template', message: error.message });
}
});
// PUT /api/templates/:id - Update template or custom template based on isCustom flag
router.put('/:id', async (req, res) => {
try {

View File

@ -182,6 +182,42 @@ const TEMPLATE_DATA = {
display_order: 2
}
]
},
{
type: 'ai_chatbot_platform',
title: 'AI Chatbot Platform',
description: 'Deploy multi-channel AI chatbots with analytics and workflows',
icon: '🤖',
gradient: 'from-teal-50 to-teal-100',
border: 'border-teal-200',
text: 'text-teal-900',
subtext: 'text-teal-700',
features: [
{
feature_id: 'nlp_engine',
name: 'NLP Engine',
description: 'Natural language processing for intent and entity extraction',
feature_type: 'essential',
complexity: 'high',
display_order: 1
},
{
feature_id: 'channel_integrations',
name: 'Channel Integrations',
description: 'Integrate with web, WhatsApp, and Slack',
feature_type: 'essential',
complexity: 'medium',
display_order: 2
},
{
feature_id: 'conversation_analytics',
name: 'Conversation Analytics',
description: 'Track KPIs: containment rate, CSAT, and response time',
feature_type: 'suggested',
complexity: 'medium',
display_order: 3
}
]
}
],
'Custom & Others': [
@ -195,6 +231,42 @@ const TEMPLATE_DATA = {
text: 'text-gray-900',
subtext: 'text-gray-700',
features: []
},
{
type: 'project_management_tool',
title: 'Project Management Tool',
description: 'Tasks, Kanban boards, sprints, and team collaboration',
icon: '🗂️',
gradient: 'from-amber-50 to-amber-100',
border: 'border-amber-200',
text: 'text-amber-900',
subtext: 'text-amber-700',
features: [
{
feature_id: 'kanban_boards',
name: 'Kanban Boards',
description: 'Organize tasks into customizable columns',
feature_type: 'essential',
complexity: 'medium',
display_order: 1
},
{
feature_id: 'sprint_planning',
name: 'Sprint Planning',
description: 'Plan sprints, track velocity, and manage backlogs',
feature_type: 'essential',
complexity: 'high',
display_order: 2
},
{
feature_id: 'collaboration_tools',
name: 'Collaboration Tools',
description: 'Comments, mentions, and notifications',
feature_type: 'suggested',
complexity: 'medium',
display_order: 3
}
]
}
]
};