From 909d446d770e9c3071b1a5f2a46cb955af61cd62 Mon Sep 17 00:00:00 2001 From: Chandini Date: Wed, 10 Sep 2025 17:19:05 +0530 Subject: [PATCH] backend changes in business rules --- docker-compose.yml | 1 + services/api-gateway/src/server.js | 126 +++++++++++++++--- .../template-manager/src/routes/features.js | 68 ++++++++-- services/user-auth/src/app.js | 4 +- services/user-auth/src/middleware/auth.js | 18 ++- services/user-auth/src/routes/auth.js | 16 +++ 6 files changed, 195 insertions(+), 38 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 3a3e494..e47d2ec 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -266,6 +266,7 @@ services: - DEPLOYMENT_MANAGER_URL=http://deployment-manager:8006 - DASHBOARD_URL=http://dashboard:8008 - SELF_IMPROVING_GENERATOR_URL=http://self-improving-generator:8007 + - AI_MOCKUP_URL=http://ai-mockup-service:8021 volumes: - api_gateway_logs:/app/logs # Add persistent volume for logs user: "node" # Run as node user instead of root diff --git a/services/api-gateway/src/server.js b/services/api-gateway/src/server.js index bcf2f14..feee54b 100644 --- a/services/api-gateway/src/server.js +++ b/services/api-gateway/src/server.js @@ -61,6 +61,7 @@ const serviceTargets = { DEPLOYMENT_MANAGER_URL: process.env.DEPLOYMENT_MANAGER_URL || 'http://localhost:8006', DASHBOARD_URL: process.env.DASHBOARD_URL || 'http://localhost:8008', SELF_IMPROVING_GENERATOR_URL: process.env.SELF_IMPROVING_GENERATOR_URL || 'http://localhost:8007', + AI_MOCKUP_URL: process.env.AI_MOCKUP_URL || 'http://localhost:8021', }; // ======================================== @@ -89,6 +90,7 @@ app.use('/api/auth', express.json({ limit: '10mb' })); app.use('/api/templates', express.json({ limit: '10mb' })); app.use('/api/features', express.json({ limit: '10mb' })); app.use('/api/github', express.json({ limit: '10mb' })); +app.use('/api/mockup', express.json({ limit: '10mb' })); app.use('/health', express.json({ limit: '10mb' })); // Trust proxy for accurate IP addresses @@ -106,18 +108,24 @@ 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 -}); +// Rate limiting configuration (disabled by default via env) +const isRateLimitDisabled = (process.env.GATEWAY_DISABLE_RATE_LIMIT || process.env.DISABLE_RATE_LIMIT || 'true').toLowerCase() === 'true'; +const createServiceLimiter = (maxRequests = 1000) => { + if (isRateLimitDisabled) { + return (req, res, next) => next(); + } + return rateLimit({ + windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS) || 15 * 60 * 1000, + max: maxRequests, + message: { + success: false, + message: 'Too many requests, please try again later.', + retry_after: 900 + }, + standardHeaders: true, + legacyHeaders: false + }); +}; // Health check endpoint (before rate limiting and authentication) app.get('/health', (req, res) => { @@ -140,7 +148,8 @@ app.get('/health', (req, res) => { 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' + self_improving_generator: process.env.SELF_IMPROVING_GENERATOR_URL ? 'configured' : 'not configured', + ai_mockup: process.env.AI_MOCKUP_URL ? 'configured' : 'not configured' }, websocket: 'enabled' }); @@ -569,6 +578,91 @@ app.use('/api/github', } ); +// AI Mockup Service - Direct HTTP forwarding +console.log('🔧 Registering /api/mockup proxy route...'); +app.use('/api/mockup', + createServiceLimiter(200), + // Public proxy: AI mockup endpoints do not require auth for basic generation + (req, res, next) => { + console.log(`🎨 [AI MOCKUP PROXY] ${req.method} ${req.originalUrl}`); + return next(); + }, + (req, res, next) => { + const aiMockupServiceUrl = serviceTargets.AI_MOCKUP_URL; + // Strip the /api/mockup prefix so /api/mockup/health -> /health at target + const rewrittenPath = (req.originalUrl || '').replace(/^\/api\/mockup/, ''); + const targetUrl = `${aiMockupServiceUrl}${rewrittenPath}`; + console.log(`🔥 [AI MOCKUP PROXY] ${req.method} ${req.originalUrl} → ${targetUrl}`); + + res.setTimeout(30000, () => { + console.error('❌ [AI MOCKUP PROXY] Response timeout'); + if (!res.headersSent) { + res.status(504).json({ error: 'Gateway timeout', service: 'ai-mockup' }); + } + }); + + const options = { + method: req.method, + url: targetUrl, + headers: { + 'Content-Type': 'application/json', + 'User-Agent': 'API-Gateway/1.0', + 'Connection': 'keep-alive', + 'Authorization': req.headers.authorization, + 'X-User-ID': req.user?.id || req.user?.userId, + 'X-User-Role': req.user?.role, + }, + timeout: 25000, + validateStatus: () => true, + maxRedirects: 0, + responseType: 'text' + }; + + if (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') { + options.data = req.body || {}; + console.log(`📦 [AI MOCKUP PROXY] Request body:`, JSON.stringify(req.body)); + } + + axios(options) + .then(response => { + console.log(`✅ [AI MOCKUP PROXY] Response: ${response.status} for ${req.method} ${req.originalUrl}`); + if (res.headersSent) return; + const contentType = response.headers['content-type'] || ''; + // Forward key headers + if (contentType) res.setHeader('Content-Type', contentType); + res.setHeader('X-Gateway-Request-ID', req.requestId); + res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*'); + res.setHeader('Access-Control-Allow-Credentials', 'true'); + + // If response is SVG or XML or plain text, send as-is; else JSON + if (contentType.includes('image/svg') || contentType.includes('xml') || contentType.includes('text/plain') || typeof response.data === 'string') { + res.status(response.status).send(response.data); + } else { + res.status(response.status).json(response.data); + } + }) + .catch(error => { + console.error(`❌ [AI MOCKUP PROXY ERROR]:`, error.message); + if (!res.headersSent) { + if (error.response) { + const ct = error.response.headers?.['content-type'] || ''; + if (ct.includes('image/svg') || ct.includes('xml') || typeof error.response.data === 'string') { + res.status(error.response.status).send(error.response.data); + } else { + res.status(error.response.status).json(error.response.data); + } + } else { + res.status(502).json({ + error: 'AI Mockup service unavailable', + message: error.code || error.message, + service: 'ai-mockup' + }); + } + } + }); + } +); + // Gateway management endpoints app.get('/api/gateway/info', authMiddleware.verifyToken, (req, res) => { res.json({ @@ -625,7 +719,8 @@ app.get('/', (req, res) => { tests: '/api/tests', deploy: '/api/deploy', dashboard: '/api/dashboard', - self_improving: '/api/self-improving' + self_improving: '/api/self-improving', + mockup: '/api/mockup' }, websocket: { endpoint: '/socket.io/', @@ -650,7 +745,8 @@ app.use('*', (req, res) => { tests: '/api/tests', deploy: '/api/deploy', dashboard: '/api/dashboard', - self_improving: '/api/self-improving' + self_improving: '/api/self-improving', + mockup: '/api/mockup' }, documentation: '/api/gateway/info' }); diff --git a/services/template-manager/src/routes/features.js b/services/template-manager/src/routes/features.js index e1c3ecd..af0083c 100644 --- a/services/template-manager/src/routes/features.js +++ b/services/template-manager/src/routes/features.js @@ -229,6 +229,21 @@ router.post('/', async (req, res) => { business_rules: featureData.business_rules, }); + // Also persist into feature_business_rules for defaults/suggested features + try { + // Prefer structured business_rules when provided; fallback to flat logic_rules + const rules = (featureData.business_rules ?? featureData.logic_rules ?? []); + if (featureData.template_id && (featureData.id || feature?.feature_id)) { + await FeatureBusinessRules.upsert( + featureData.template_id, + featureData.id || feature.feature_id, + rules + ); + } + } catch (ruleErr) { + console.error('⚠️ Failed to persist feature business rules (default/suggested):', ruleErr.message); + } + res.status(201).json({ success: true, data: feature, message: `Feature '${feature.name}' created successfully in template_features table` }); } catch (error) { console.error('❌ Error creating feature:', error.message); @@ -477,7 +492,8 @@ router.post('/custom', async (req, res) => { // Persist aggregated rules try { - const rules = Array.isArray(data.logic_rules) ? data.logic_rules : data.business_rules ?? []; + // Prefer structured business_rules; fallback to flat logic_rules + const rules = (data.business_rules ?? data.logic_rules ?? []); await FeatureBusinessRules.upsert(data.template_id, `custom_${created.id}`, rules); } catch (ruleErr) { console.error('⚠️ Failed to persist custom feature business rules:', ruleErr.message); @@ -553,20 +569,46 @@ router.put('/custom/:id', async (req, res) => { router.delete('/custom/:id', async (req, res) => { try { const { id } = req.params; - const existing = await CustomFeature.getById(id); - if (!existing) return res.status(404).json({ success: false, error: 'Not found' }); - await CustomFeature.delete(id); - // Remove mirrored template_features with feature_id = `custom_` - try { - const featureId = `custom_${id}` - const mirroredExisting = await Feature.getByFeatureId(existing.template_id, featureId) - if (mirroredExisting) { - await Feature.delete(mirroredExisting.id) + // const rawId = String(id).replace(/^custom_/, '') + + // Try deleting from custom_features first + let existing = await CustomFeature.getById(id); + if (existing) { + await CustomFeature.delete(id); + // Remove mirrored template_features with feature_id = `custom_` + try { + const featureId = id + const mirroredExisting = await Feature.getByFeatureId(existing.template_id, featureId) + if (mirroredExisting) { + await Feature.delete(mirroredExisting.id) + } + // Cleanup business rules if present + try { + await database.query('DELETE FROM feature_business_rules WHERE template_id = $1 AND feature_id = $2', [existing.template_id, featureId]) + } catch (cleanupErr) { console.error('Failed to cleanup business rules:', cleanupErr.message) } + } catch (mirrorErr) { + console.error('Failed to mirror custom feature delete:', mirrorErr.message) } - } catch (mirrorErr) { - console.error('Failed to mirror custom feature delete:', mirrorErr.message) + return res.json({ success: true, message: `Custom feature '${existing.name}' deleted successfully` }); } - res.json({ success: true, message: `Custom feature '${existing.name}' deleted successfully` }); + + // Fallback: handle case where only mirrored template_features exists or client sent prefixed id + try { + const prefixed = id.startsWith('custom_') ? id : `custom_${rawId}` + const tf = await database.query('SELECT id, template_id, name FROM template_features WHERE feature_id = $1', [prefixed]) + if (tf.rows.length > 0) { + const row = tf.rows[0] + await Feature.delete(row.id) + try { + await database.query('DELETE FROM feature_business_rules WHERE template_id = $1 AND feature_id = $2', [row.template_id, prefixed]) + } catch (cleanupErr) { console.error('Failed to cleanup business rules:', cleanupErr.message) } + return res.json({ success: true, message: `Mirrored feature '${row.name}' deleted successfully` }) + } + } catch (fallbackErr) { + console.error('Fallback delete check failed:', fallbackErr.message) + } + + return res.status(404).json({ success: false, error: 'Not found', message: 'Custom feature not found' }); } catch (e) { res.status(500).json({ success: false, error: 'Failed to delete custom feature', message: e.message }); } diff --git a/services/user-auth/src/app.js b/services/user-auth/src/app.js index 66e2c8a..b00e105 100644 --- a/services/user-auth/src/app.js +++ b/services/user-auth/src/app.js @@ -13,7 +13,6 @@ const authService = require('./services/authService'); // Import routes and middleware const authRoutes = require('./routes/auth'); const { - apiRateLimit, securityHeaders, authErrorHandler } = require('./middleware/auth'); @@ -76,8 +75,7 @@ app.use(morgan('combined', { format: ':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"' })); -// Rate limiting -app.use('/api', apiRateLimit); +// Rate limiting disabled by request // ======================================== // ROUTES diff --git a/services/user-auth/src/middleware/auth.js b/services/user-auth/src/middleware/auth.js index ca17aeb..0ec5ed9 100644 --- a/services/user-auth/src/middleware/auth.js +++ b/services/user-auth/src/middleware/auth.js @@ -86,8 +86,12 @@ const requireAdmin = requireRole(['admin']); // User or Admin middleware const requireUserOrAdmin = requireRole(['user', 'admin']); -// Rate Limiting Middleware +// Rate Limiting Middleware (disabled by default via env) +const isAuthRateLimitDisabled = (process.env.AUTH_DISABLE_RATE_LIMIT || process.env.DISABLE_RATE_LIMIT || 'true').toLowerCase() === 'true'; const createRateLimit = (windowMs, max, message) => { + if (isAuthRateLimitDisabled) { + return (req, res, next) => next(); + } return rateLimit({ windowMs, max, @@ -125,11 +129,11 @@ const passwordChangeRateLimit = createRateLimit( 'Too many password change attempts. Please try again in 1 hour.' ); -const apiRateLimit = createRateLimit( - 15 * 60 * 1000, // 15 minutes - 10000, // 100 requests - 'Too many API requests. Please slow down.' -); +// const apiRateLimit = createRateLimit( +// 15 * 60 * 1000, // 15 minutes +// 1000000, // 100 requests +// 'Too many API requests. Please slow down.' +// ); // Session Validation Middleware const validateSession = async (req, res, next) => { @@ -303,7 +307,7 @@ module.exports = { loginRateLimit, registerRateLimit, passwordChangeRateLimit, - apiRateLimit, + // apiRateLimit, validateSession, logAuthRequests, validateOwnership, diff --git a/services/user-auth/src/routes/auth.js b/services/user-auth/src/routes/auth.js index bc50aac..f794f1b 100644 --- a/services/user-auth/src/routes/auth.js +++ b/services/user-auth/src/routes/auth.js @@ -751,4 +751,20 @@ router.put('/admin/custom-templates/:id/review', authenticateToken, requireAdmin } }); +// ======================================== +// TOKEN VERIFICATION (For internal service checks) +// ======================================== + +// GET /api/auth/verify - Verify access token and return user info +router.get('/verify', authenticateToken, async (req, res) => { + try { + const user = req.user; + const payload = user && user.toJSON ? user.toJSON() : user; + return res.json({ success: true, data: { user: payload }, message: 'Token verified' }); + } catch (error) { + console.error('❌ Token verification error:', error.message); + return res.status(401).json({ success: false, error: 'Token verification failed', message: error.message }); + } +}); + module.exports = router; \ No newline at end of file