diff --git a/docker-compose.yml b/docker-compose.yml index e47d2ec..937ce13 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -231,8 +231,8 @@ services: - NODE_ENV=development - PORT=8000 - HOST=0.0.0.0 - - FRONTEND_URL=http://localhost:3001 # Make sure this matches your frontend URL - - CORS_ORIGINS=http://localhost:3001 # Add this line + - FRONTEND_URL=* # Allow all URLs + - CORS_ORIGINS=* # Allow all URLs - CORS_METHODS=GET,POST,PUT,DELETE,PATCH,OPTIONS # Add this line - CORS_CREDENTIALS=true # Add this line # Database connections @@ -500,11 +500,11 @@ services: - REDIS_HOST=redis - REDIS_PORT=6379 - 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_ACCESS_EXPIRY=24h - JWT_REFRESH_EXPIRY=7d - - FRONTEND_URL=http://localhost:3001 + - FRONTEND_URL=* # Email Configuration - SMTP_HOST=${SMTP_HOST:-smtp.gmail.com} - SMTP_PORT=${SMTP_PORT:-587} @@ -514,7 +514,7 @@ services: - SMTP_FROM=${SMTP_FROM:-frontendtechbiz@gmail.com} - GMAIL_USER=${GMAIL_USER:-frontendtechbiz@gmail.com} - GMAIL_APP_PASSWORD=${GMAIL_APP_PASSWORD:-oidhhjeasgzbqptq} - - AUTH_PUBLIC_URL=http://localhost:3001 + - AUTH_PUBLIC_URL=* - TEMPLATE_MANAGER_URL=http://template-manager:8009 networks: - pipeline_network @@ -616,7 +616,7 @@ services: environment: - PORT=8012 - HOST=0.0.0.0 - - FRONTEND_URL=http://localhost:3000 + - FRONTEND_URL=* - POSTGRES_HOST=postgres - POSTGRES_PORT=5432 - POSTGRES_DB=dev_pipeline @@ -626,7 +626,7 @@ services: - REDIS_PORT=6379 - REDIS_PASSWORD=${REDIS_PASSWORD} - NODE_ENV=development - - GITHUB_REDIRECT_URI=http://localhost:8012/api/github/auth/github/callback + - GITHUB_REDIRECT_URI=* - ATTACHED_REPOS_DIR=/tmp/attached-repos - SESSION_SECRET=git-integration-secret-key-2024 volumes: diff --git a/services/api-gateway/src/middleware/cors.js b/services/api-gateway/src/middleware/cors.js index 1ffae96..ec7dc54 100644 --- a/services/api-gateway/src/middleware/cors.js +++ b/services/api-gateway/src/middleware/cors.js @@ -2,22 +2,8 @@ const cors = require('cors'); const corsMiddleware = cors({ origin: function (origin, callback) { - // Allow requests from your frontend 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.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); - } else { - callback(new Error('Not allowed by CORS')); - } + // Allow all origins + callback(null, true); }, methods: process.env.CORS_METHODS?.split(',') || ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], credentials: process.env.CORS_CREDENTIALS === 'true' || true, diff --git a/services/api-gateway/src/server.js b/services/api-gateway/src/server.js index 6097663..0eed319 100644 --- a/services/api-gateway/src/server.js +++ b/services/api-gateway/src/server.js @@ -26,19 +26,23 @@ 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: [ - '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), + origin: "*", credentials: true, methods: ['GET', 'POST'] }, @@ -64,6 +68,12 @@ const serviceTargets = { 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 // ======================================== @@ -158,6 +168,32 @@ app.get('/health', (req, res) => { // 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); @@ -165,7 +201,10 @@ const websocketHandlers = websocketAuth(io); console.log('🔧 Registering /api/auth proxy route...'); app.use('/api/auth', (req, res, next) => { 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 res.setTimeout(15000, () => { @@ -177,15 +216,19 @@ app.use('/api/auth', (req, res, next) => { const options = { method: req.method, - url: `${authServiceUrl}${req.originalUrl}`, + url: targetUrl, headers: { 'Content-Type': 'application/json', 'User-Agent': 'API-Gateway/1.0', 'Connection': 'keep-alive', // 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, 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] Making request to: ${targetUrl}`); axios(options) .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); 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' + service: 'user-auth', + target_url: targetUrl, + details: process.env.NODE_ENV === 'development' ? error.stack : undefined }); } } diff --git a/services/template-manager/src/app.js b/services/template-manager/src/app.js index 823904d..92ef50c 100644 --- a/services/template-manager/src/app.js +++ b/services/template-manager/src/app.js @@ -22,11 +22,7 @@ const app = express(); const server = http.createServer(app); const io = new Server(server, { cors: { - origin: [ - 'http://localhost:3001', // Frontend (CodeNuk) - 'http://localhost:3000', // Alternative frontend port - process.env.FRONTEND_URL - ].filter(Boolean), + origin: "*", methods: ["GET", "POST"], credentials: true } @@ -36,12 +32,7 @@ const PORT = process.env.PORT || 8009; // Middleware app.use(helmet()); app.use(cors({ - origin: [ - 'http://localhost:3001', // Frontend (CodeNuk) - 'http://localhost:3000', // Alternative frontend port - 'http://localhost:8000', // API Gateway - process.env.FRONTEND_URL - ].filter(Boolean), + origin: "*", credentials: true, methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization', 'X-User-ID', 'X-User-Role'] diff --git a/services/template-manager/src/routes/features.js b/services/template-manager/src/routes/features.js index cd50675..7b7e393 100644 --- a/services/template-manager/src/routes/features.js +++ b/services/template-manager/src/routes/features.js @@ -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) => { try { const { id } = req.params; @@ -178,11 +178,22 @@ router.get('/:id', async (req, res) => { }); } - res.json({ - success: true, - data: feature, - message: `Feature '${feature.name}' retrieved successfully` - }); + // Try to fetch aggregated business rules for this feature from feature_business_rules using both keys + try { + const rulesQuery = ` + 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) { console.error('❌ Error fetching feature:', error.message); res.status(500).json({ @@ -522,8 +533,63 @@ router.post('/custom', async (req, res) => { router.get('/templates/:templateId/features', async (req, res) => { try { const { templateId } = req.params; - const defaults = await Feature.getByTemplateId(templateId); - const customs = await CustomFeature.getByTemplateId(templateId); + // Include aggregated rules for default/suggested features + 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 const customAsTemplate = customs.map(cf => ({ id: cf.id, diff --git a/services/template-manager/src/routes/templates.js b/services/template-manager/src/routes/templates.js index 6078b83..721e193 100644 --- a/services/template-manager/src/routes/templates.js +++ b/services/template-manager/src/routes/templates.js @@ -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'); // 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`); // Get custom features from custom_features table with business rules (if table exists) diff --git a/services/user-auth/src/app.js b/services/user-auth/src/app.js index b00e105..4ce7da6 100644 --- a/services/user-auth/src/app.js +++ b/services/user-auth/src/app.js @@ -41,23 +41,7 @@ app.use(securityHeaders); // CORS configuration const corsOptions = { - origin: function (origin, callback) { - // 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')); - } - }, + origin: "*", credentials: true, // Allow cookies methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization', 'X-Session-Token', 'X-Platform', 'X-App-Version'] diff --git a/services/user-auth/src/config/jwt.js b/services/user-auth/src/config/jwt.js index 0d58f9a..dfd1384 100644 --- a/services/user-auth/src/config/jwt.js +++ b/services/user-auth/src/config/jwt.js @@ -2,7 +2,7 @@ const jwt = require('jsonwebtoken'); class JWTConfig { 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.accessTokenExpiry = process.env.JWT_ACCESS_EXPIRY || '24h'; this.refreshTokenExpiry = process.env.JWT_REFRESH_EXPIRY || '7d';