backend changes
This commit is contained in:
parent
49a19447dc
commit
c5e62ae68b
@ -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:
|
||||
|
||||
@ -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) {
|
||||
// Allow all origins
|
||||
callback(null, true);
|
||||
} else {
|
||||
callback(new Error('Not allowed by CORS'));
|
||||
}
|
||||
},
|
||||
methods: process.env.CORS_METHODS?.split(',') || ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
credentials: process.env.CORS_CREDENTIALS === 'true' || true,
|
||||
|
||||
@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -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']
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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']
|
||||
|
||||
@ -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';
|
||||
|
||||
Loading…
Reference in New Issue
Block a user