backend changes

This commit is contained in:
Chandini 2025-09-12 17:47:53 +05:30
parent 49a19447dc
commit c5e62ae68b
8 changed files with 190 additions and 73 deletions

View File

@ -231,8 +231,8 @@ services:
- NODE_ENV=development - NODE_ENV=development
- PORT=8000 - PORT=8000
- HOST=0.0.0.0 - HOST=0.0.0.0
- FRONTEND_URL=http://localhost:3001 # Make sure this matches your frontend URL - FRONTEND_URL=* # Allow all URLs
- CORS_ORIGINS=http://localhost:3001 # Add this line - CORS_ORIGINS=* # Allow all URLs
- CORS_METHODS=GET,POST,PUT,DELETE,PATCH,OPTIONS # Add this line - CORS_METHODS=GET,POST,PUT,DELETE,PATCH,OPTIONS # Add this line
- CORS_CREDENTIALS=true # Add this line - CORS_CREDENTIALS=true # Add this line
# Database connections # Database connections
@ -500,11 +500,11 @@ services:
- REDIS_HOST=redis - REDIS_HOST=redis
- REDIS_PORT=6379 - REDIS_PORT=6379
- REDIS_PASSWORD=${REDIS_PASSWORD} - REDIS_PASSWORD=${REDIS_PASSWORD}
- JWT_ACCESS_SECRET=access-secret-key-2024-tech4biz-${POSTGRES_PASSWORD} - JWT_ACCESS_SECRET=access-secret-key-2024-tech4biz-secure_pipeline_2024
- JWT_REFRESH_SECRET=refresh-secret-key-2024-tech4biz-${POSTGRES_PASSWORD} - JWT_REFRESH_SECRET=refresh-secret-key-2024-tech4biz-${POSTGRES_PASSWORD}
- JWT_ACCESS_EXPIRY=24h - JWT_ACCESS_EXPIRY=24h
- JWT_REFRESH_EXPIRY=7d - JWT_REFRESH_EXPIRY=7d
- FRONTEND_URL=http://localhost:3001 - FRONTEND_URL=*
# Email Configuration # Email Configuration
- SMTP_HOST=${SMTP_HOST:-smtp.gmail.com} - SMTP_HOST=${SMTP_HOST:-smtp.gmail.com}
- SMTP_PORT=${SMTP_PORT:-587} - SMTP_PORT=${SMTP_PORT:-587}
@ -514,7 +514,7 @@ services:
- SMTP_FROM=${SMTP_FROM:-frontendtechbiz@gmail.com} - SMTP_FROM=${SMTP_FROM:-frontendtechbiz@gmail.com}
- GMAIL_USER=${GMAIL_USER:-frontendtechbiz@gmail.com} - GMAIL_USER=${GMAIL_USER:-frontendtechbiz@gmail.com}
- GMAIL_APP_PASSWORD=${GMAIL_APP_PASSWORD:-oidhhjeasgzbqptq} - GMAIL_APP_PASSWORD=${GMAIL_APP_PASSWORD:-oidhhjeasgzbqptq}
- AUTH_PUBLIC_URL=http://localhost:3001 - AUTH_PUBLIC_URL=*
- TEMPLATE_MANAGER_URL=http://template-manager:8009 - TEMPLATE_MANAGER_URL=http://template-manager:8009
networks: networks:
- pipeline_network - pipeline_network
@ -616,7 +616,7 @@ services:
environment: environment:
- PORT=8012 - PORT=8012
- HOST=0.0.0.0 - HOST=0.0.0.0
- FRONTEND_URL=http://localhost:3000 - FRONTEND_URL=*
- POSTGRES_HOST=postgres - POSTGRES_HOST=postgres
- POSTGRES_PORT=5432 - POSTGRES_PORT=5432
- POSTGRES_DB=dev_pipeline - POSTGRES_DB=dev_pipeline
@ -626,7 +626,7 @@ services:
- REDIS_PORT=6379 - REDIS_PORT=6379
- REDIS_PASSWORD=${REDIS_PASSWORD} - REDIS_PASSWORD=${REDIS_PASSWORD}
- NODE_ENV=development - NODE_ENV=development
- GITHUB_REDIRECT_URI=http://localhost:8012/api/github/auth/github/callback - GITHUB_REDIRECT_URI=*
- ATTACHED_REPOS_DIR=/tmp/attached-repos - ATTACHED_REPOS_DIR=/tmp/attached-repos
- SESSION_SECRET=git-integration-secret-key-2024 - SESSION_SECRET=git-integration-secret-key-2024
volumes: volumes:

View File

@ -2,22 +2,8 @@ const cors = require('cors');
const corsMiddleware = cors({ const corsMiddleware = cors({
origin: function (origin, callback) { origin: function (origin, callback) {
// Allow requests from your frontend and other services // Allow all origins
const allowedOrigins = [
'http://localhost:3001', // Frontend (CodeNuk)
'http://localhost:3000', // Alternative frontend port
'http://localhost:8008', // Dashboard service
'http://localhost:8000', // API Gateway
process.env.CORS_ORIGIN,
process.env.FRONTEND_URL
].filter(Boolean);
// Allow requests with no origin (mobile apps, etc.) or from allowed origins
if (!origin || allowedOrigins.indexOf(origin) !== -1) {
callback(null, true); callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
}, },
methods: process.env.CORS_METHODS?.split(',') || ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], methods: process.env.CORS_METHODS?.split(',') || ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
credentials: process.env.CORS_CREDENTIALS === 'true' || true, credentials: process.env.CORS_CREDENTIALS === 'true' || true,

View File

@ -26,19 +26,23 @@ const websocketRouter = require('./routes/websocketRouter');
const app = express(); const app = express();
// Apply CORS middleware before other middleware // Apply CORS middleware before other middleware
app.use(corsMiddleware); 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 server = http.createServer(app);
const PORT = process.env.PORT || 8000; const PORT = process.env.PORT || 8000;
// Initialize Socket.IO with CORS // Initialize Socket.IO with CORS
const io = socketIo(server, { const io = socketIo(server, {
cors: { cors: {
origin: [ origin: "*",
'http://localhost:3001', // Frontend (CodeNuk)
'http://localhost:3000', // Alternative frontend port
'http://localhost:8008', // Dashboard service
'http://localhost:8000', // API Gateway
process.env.FRONTEND_URL
].filter(Boolean),
credentials: true, credentials: true,
methods: ['GET', 'POST'] methods: ['GET', 'POST']
}, },
@ -64,6 +68,12 @@ const serviceTargets = {
AI_MOCKUP_URL: process.env.AI_MOCKUP_URL || 'http://localhost:8021', 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 // MIDDLEWARE SETUP
// ======================================== // ========================================
@ -158,6 +168,32 @@ app.get('/health', (req, res) => {
// Service health monitoring routes // Service health monitoring routes
app.use('/health', healthRouter.router); 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 // WebSocket connection handling
const websocketHandlers = websocketAuth(io); const websocketHandlers = websocketAuth(io);
@ -165,7 +201,10 @@ const websocketHandlers = websocketAuth(io);
console.log('🔧 Registering /api/auth proxy route...'); console.log('🔧 Registering /api/auth proxy route...');
app.use('/api/auth', (req, res, next) => { app.use('/api/auth', (req, res, next) => {
const authServiceUrl = serviceTargets.USER_AUTH_URL; const authServiceUrl = serviceTargets.USER_AUTH_URL;
console.log(`🔥 [AUTH PROXY] ${req.method} ${req.originalUrl}${authServiceUrl}${req.originalUrl}`); 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 // Set response timeout to prevent hanging
res.setTimeout(15000, () => { res.setTimeout(15000, () => {
@ -177,15 +216,19 @@ app.use('/api/auth', (req, res, next) => {
const options = { const options = {
method: req.method, method: req.method,
url: `${authServiceUrl}${req.originalUrl}`, url: targetUrl,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'User-Agent': 'API-Gateway/1.0', 'User-Agent': 'API-Gateway/1.0',
'Connection': 'keep-alive', 'Connection': 'keep-alive',
// Forward Authorization header so protected auth-admin routes work // Forward Authorization header so protected auth-admin routes work
'Authorization': req.headers.authorization '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: 8000, timeout: 10000,
validateStatus: () => true, validateStatus: () => true,
maxRedirects: 0 maxRedirects: 0
}; };
@ -196,23 +239,46 @@ app.use('/api/auth', (req, res, next) => {
console.log(`📦 [AUTH PROXY] Request body:`, JSON.stringify(req.body)); console.log(`📦 [AUTH PROXY] Request body:`, JSON.stringify(req.body));
} }
console.log(`🚀 [AUTH PROXY] Making request to: ${targetUrl}`);
axios(options) axios(options)
.then(response => { .then(response => {
console.log(`✅ [AUTH PROXY] Response: ${response.status} for ${req.method} ${req.originalUrl}`); console.log(`✅ [AUTH PROXY] Response: ${response.status} for ${req.method} ${req.originalUrl}`);
console.log(`📊 [AUTH PROXY] Response headers:`, response.headers);
if (!res.headersSent) { 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); res.status(response.status).json(response.data);
} }
}) })
.catch(error => { .catch(error => {
console.error(`❌ [AUTH PROXY ERROR]:`, error.message); console.error(`❌ [AUTH PROXY ERROR]:`, error.message);
console.error(`❌ [AUTH PROXY ERROR CODE]:`, error.code);
console.error(`❌ [AUTH PROXY ERROR STACK]:`, error.stack);
if (!res.headersSent) { if (!res.headersSent) {
if (error.response) { 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); res.status(error.response.status).json(error.response.data);
} else { } else {
res.status(502).json({ res.status(502).json({
error: 'Auth service unavailable', error: 'Auth service unavailable',
message: error.code || error.message, message: error.code || error.message,
service: 'user-auth' service: 'user-auth',
target_url: targetUrl,
details: process.env.NODE_ENV === 'development' ? error.stack : undefined
}); });
} }
} }

View File

@ -22,11 +22,7 @@ const app = express();
const server = http.createServer(app); const server = http.createServer(app);
const io = new Server(server, { const io = new Server(server, {
cors: { cors: {
origin: [ origin: "*",
'http://localhost:3001', // Frontend (CodeNuk)
'http://localhost:3000', // Alternative frontend port
process.env.FRONTEND_URL
].filter(Boolean),
methods: ["GET", "POST"], methods: ["GET", "POST"],
credentials: true credentials: true
} }
@ -36,12 +32,7 @@ const PORT = process.env.PORT || 8009;
// Middleware // Middleware
app.use(helmet()); app.use(helmet());
app.use(cors({ app.use(cors({
origin: [ origin: "*",
'http://localhost:3001', // Frontend (CodeNuk)
'http://localhost:3000', // Alternative frontend port
'http://localhost:8000', // API Gateway
process.env.FRONTEND_URL
].filter(Boolean),
credentials: true, credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-User-ID', 'X-User-Role'] allowedHeaders: ['Content-Type', 'Authorization', 'X-User-ID', 'X-User-Role']

View File

@ -162,7 +162,7 @@ router.get('/type/:type', async (req, res) => {
} }
}); });
// GET /api/features/:id - Get specific default feature // GET /api/features/:id - Get specific default feature (with aggregated business rules if present)
router.get('/:id', async (req, res) => { router.get('/:id', async (req, res) => {
try { try {
const { id } = req.params; const { id } = req.params;
@ -178,11 +178,22 @@ router.get('/:id', async (req, res) => {
}); });
} }
res.json({ // Try to fetch aggregated business rules for this feature from feature_business_rules using both keys
success: true, try {
data: feature, const rulesQuery = `
message: `Feature '${feature.name}' retrieved successfully` SELECT business_rules
}); FROM feature_business_rules
WHERE template_id = $1 AND (feature_id = $2 OR feature_id = $3)
LIMIT 1
`
const rulesResult = await database.query(rulesQuery, [feature.template_id, String(id), feature.feature_id])
const additional = rulesResult.rows?.[0]?.business_rules || null
const payload = { ...feature, additional_business_rules: additional }
return res.json({ success: true, data: payload, message: `Feature '${feature.name}' retrieved successfully` })
} catch (rulesErr) {
console.warn('⚠️ Failed to fetch aggregated rules for feature:', rulesErr.message)
return res.json({ success: true, data: feature, message: `Feature '${feature.name}' retrieved successfully` })
}
} catch (error) { } catch (error) {
console.error('❌ Error fetching feature:', error.message); console.error('❌ Error fetching feature:', error.message);
res.status(500).json({ res.status(500).json({
@ -522,8 +533,63 @@ router.post('/custom', async (req, res) => {
router.get('/templates/:templateId/features', async (req, res) => { router.get('/templates/:templateId/features', async (req, res) => {
try { try {
const { templateId } = req.params; const { templateId } = req.params;
const defaults = await Feature.getByTemplateId(templateId); // Include aggregated rules for default/suggested features
const customs = await CustomFeature.getByTemplateId(templateId); const defaultsQuery = `
SELECT
tf.*,
fbr.business_rules AS additional_business_rules
FROM template_features tf
LEFT JOIN feature_business_rules fbr
ON tf.template_id = fbr.template_id
AND (
fbr.feature_id = (tf.id::text)
OR fbr.feature_id = tf.feature_id
)
WHERE tf.template_id = $1
ORDER BY
CASE tf.feature_type
WHEN 'essential' THEN 1
WHEN 'suggested' THEN 2
WHEN 'custom' THEN 3
END,
tf.display_order,
tf.usage_count DESC,
tf.name
`;
const defaultsResult = await database.query(defaultsQuery, [templateId]);
const defaults = defaultsResult.rows;
// Fetch custom features with joined business rules like in templates.js
const customFeaturesQuery = `
SELECT
cf.id,
cf.template_id,
cf.name,
cf.description,
cf.complexity,
cf.business_rules,
cf.technical_requirements,
'custom' as feature_type,
cf.created_at,
cf.updated_at,
cf.status,
cf.approved,
cf.usage_count,
0 as user_rating,
false as is_default,
true as created_by_user,
fbr.business_rules as additional_business_rules
FROM custom_features cf
LEFT JOIN feature_business_rules fbr
ON cf.template_id = fbr.template_id
AND (
fbr.feature_id = (cf.id::text)
OR fbr.feature_id = ('custom_' || cf.id::text)
)
WHERE cf.template_id = $1
ORDER BY cf.created_at DESC
`;
const customsResult = await database.query(customFeaturesQuery, [templateId]);
const customs = customsResult.rows;
// Map custom model to template-like shape // Map custom model to template-like shape
const customAsTemplate = customs.map(cf => ({ const customAsTemplate = customs.map(cf => ({
id: cf.id, id: cf.id,

View File

@ -516,7 +516,31 @@ router.get('/:id([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/f
console.log('📋 Fetching features from both template_features and custom_features tables'); console.log('📋 Fetching features from both template_features and custom_features tables');
// Get default/suggested features from template_features table // Get default/suggested features from template_features table
const defaultFeatures = await Feature.getByTemplateId(id); // Include aggregated business rules from feature_business_rules when available
const defaultFeaturesQuery = `
SELECT
tf.*,
fbr.business_rules AS additional_business_rules
FROM template_features tf
LEFT JOIN feature_business_rules fbr
ON tf.template_id = fbr.template_id
AND (
fbr.feature_id = (tf.id::text)
OR fbr.feature_id = tf.feature_id
)
WHERE tf.template_id = $1
ORDER BY
CASE tf.feature_type
WHEN 'essential' THEN 1
WHEN 'suggested' THEN 2
WHEN 'custom' THEN 3
END,
tf.display_order,
tf.usage_count DESC,
tf.name
`;
const defaultFeaturesResult = await database.query(defaultFeaturesQuery, [id]);
const defaultFeatures = defaultFeaturesResult.rows;
console.log(`📊 Found ${defaultFeatures.length} default/suggested features`); console.log(`📊 Found ${defaultFeatures.length} default/suggested features`);
// Get custom features from custom_features table with business rules (if table exists) // Get custom features from custom_features table with business rules (if table exists)

View File

@ -41,23 +41,7 @@ app.use(securityHeaders);
// CORS configuration // CORS configuration
const corsOptions = { const corsOptions = {
origin: function (origin, callback) { origin: "*",
// Allow requests from your web-dashboard and other services
const allowedOrigins = [
'http://localhost:3001', // Frontend (CodeNuk)
'http://localhost:3000', // Alternative frontend port
'http://localhost:8008', // Dashboard service
'http://localhost:8000', // API Gateway
process.env.FRONTEND_URL
].filter(Boolean);
// Allow requests with no origin (mobile apps, etc.)
if (!origin || allowedOrigins.indexOf(origin) !== -1) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true, // Allow cookies credentials: true, // Allow cookies
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Session-Token', 'X-Platform', 'X-App-Version'] allowedHeaders: ['Content-Type', 'Authorization', 'X-Session-Token', 'X-Platform', 'X-App-Version']

View File

@ -2,7 +2,7 @@ const jwt = require('jsonwebtoken');
class JWTConfig { class JWTConfig {
constructor() { constructor() {
this.accessTokenSecret = process.env.JWT_ACCESS_SECRET || 'access-secret-key-2024-tech4biz'; this.accessTokenSecret = process.env.JWT_ACCESS_SECRET || 'access-secret-key-2024-tech4biz-secure_pipeline_2024';
this.refreshTokenSecret = process.env.JWT_REFRESH_SECRET || 'refresh-secret-key-2024-tech4biz'; this.refreshTokenSecret = process.env.JWT_REFRESH_SECRET || 'refresh-secret-key-2024-tech4biz';
this.accessTokenExpiry = process.env.JWT_ACCESS_EXPIRY || '24h'; this.accessTokenExpiry = process.env.JWT_ACCESS_EXPIRY || '24h';
this.refreshTokenExpiry = process.env.JWT_REFRESH_EXPIRY || '7d'; this.refreshTokenExpiry = process.env.JWT_REFRESH_EXPIRY || '7d';