backend changes

This commit is contained in:
Chandini 2025-09-18 08:37:51 +05:30
parent 1c4f9b47ed
commit a9964e906d
6 changed files with 121 additions and 92 deletions

View File

@ -6,11 +6,10 @@
// ======================================== // ========================================
// LIVE PRODUCTION URLS (Currently Active) // LIVE PRODUCTION URLS (Currently Active)
// ======================================== // ========================================
const FRONTEND_URL = 'https://dashboard.codenuk.com'; const FRONTEND_URL = 'http://192.168.1.31:3001';
const BACKEND_URL = 'https://backend.codenuk.com'; const BACKEND_URL = 'https://backend.codenuk.com';
// ========================================
// LOCAL DEVELOPMENT URLS (Comment out live URLs above and uncomment these)
// ======================================== // ========================================
// const FRONTEND_URL = 'http://localhost:3001'; // const FRONTEND_URL = 'http://localhost:3001';
// const BACKEND_URL = 'http://localhost:8000'; // const BACKEND_URL = 'http://localhost:8000';

View File

@ -233,7 +233,7 @@ services:
- NODE_ENV=development - NODE_ENV=development
- PORT=8000 - PORT=8000
- HOST=0.0.0.0 - HOST=0.0.0.0
- FRONTEND_URL=https://dashboard.codenuk.com # Allow all URLs - FRONTEND_URL=http://192.168.1.31:3001 # Allow all URLs
- CORS_ORIGINS=* # Allow all URLs - 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
@ -507,7 +507,7 @@ services:
- JWT_ACCESS_EXPIRY=24h - JWT_ACCESS_EXPIRY=24h
- JWT_ADMIN_ACCESS_EXPIRY=7d - JWT_ADMIN_ACCESS_EXPIRY=7d
- JWT_REFRESH_EXPIRY=7d - JWT_REFRESH_EXPIRY=7d
- FRONTEND_URL=https://dashboard.codenuk.com - FRONTEND_URL=http://192.168.1.31:3001
# Email Configuration # Email Configuration
- SMTP_HOST=smtp.gmail.com - SMTP_HOST=smtp.gmail.com
- SMTP_PORT=587 - SMTP_PORT=587
@ -613,7 +613,7 @@ services:
environment: environment:
- PORT=8012 - PORT=8012
- HOST=0.0.0.0 - HOST=0.0.0.0
- FRONTEND_URL=https://dashboard.codenuk.com - FRONTEND_URL=http://192.168.1.31:3001
- POSTGRES_HOST=postgres - POSTGRES_HOST=postgres
- POSTGRES_PORT=5432 - POSTGRES_PORT=5432
- POSTGRES_DB=dev_pipeline - POSTGRES_DB=dev_pipeline

View File

@ -28,9 +28,9 @@ RABBITMQ_USER=pipeline_admin
RABBITMQ_PASSWORD=secure_rabbitmq_password RABBITMQ_PASSWORD=secure_rabbitmq_password
# CORS # CORS
FRONTEND_URL=https://dashboard.codenuk.com FRONTEND_URL=http://192.168.1.31:3001
# CORS Configuration # CORS Configuration
CORS_ORIGIN=https://dashboard.codenuk.com CORS_ORIGIN=http://192.168.1.31:3001
CORS_METHODS=GET,POST,PUT,DELETE,PATCH,OPTIONS CORS_METHODS=GET,POST,PUT,DELETE,PATCH,OPTIONS
CORS_CREDENTIALS=true CORS_CREDENTIALS=true

View File

@ -30,17 +30,18 @@ async function markMigrationApplied(version) {
async function runMigrations() { async function runMigrations() {
console.log('🚀 Starting template-manager database migrations...'); console.log('🚀 Starting template-manager database migrations...');
try { try {
// Create migrations tracking table first // Create migrations tracking table first
await createMigrationsTable(); await createMigrationsTable();
console.log('✅ Migration tracking table ready'); console.log('✅ Migration tracking table ready');
// Get all migration files in order // Get all migration files in order
// Reordered to ensure custom_templates table exists before admin_approval_workflow
const migrationFiles = [ const migrationFiles = [
'001_initial_schema.sql', '001_initial_schema.sql',
'002_admin_approval_workflow.sql', '003_custom_templates.sql', // Moved earlier since others depend on it
'003_custom_templates.sql', '002_admin_approval_workflow.sql', // Now runs after custom_templates is created
'004_add_is_custom_flag.sql', '004_add_is_custom_flag.sql',
'004_add_user_id_to_custom_templates.sql', '004_add_user_id_to_custom_templates.sql',
'005_fix_custom_features_foreign_key.sql', '005_fix_custom_features_foreign_key.sql',
@ -50,10 +51,10 @@ async function runMigrations() {
let appliedCount = 0; let appliedCount = 0;
let skippedCount = 0; let skippedCount = 0;
for (const migrationFile of migrationFiles) { for (const migrationFile of migrationFiles) {
const migrationPath = path.join(__dirname, migrationFile); const migrationPath = path.join(__dirname, migrationFile);
// Check if migration file exists // Check if migration file exists
if (!fs.existsSync(migrationPath)) { if (!fs.existsSync(migrationPath)) {
console.log(`⚠️ Migration file not found: ${migrationFile}`); console.log(`⚠️ Migration file not found: ${migrationFile}`);
@ -66,7 +67,7 @@ async function runMigrations() {
skippedCount++; skippedCount++;
continue; continue;
} }
const migrationSQL = fs.readFileSync(migrationPath, 'utf8'); const migrationSQL = fs.readFileSync(migrationPath, 'utf8');
// Skip destructive migrations unless explicitly allowed // Skip destructive migrations unless explicitly allowed
@ -79,17 +80,17 @@ async function runMigrations() {
} }
console.log(`📄 Running migration: ${migrationFile}`); console.log(`📄 Running migration: ${migrationFile}`);
// Execute the migration // Execute the migration
await database.query(migrationSQL); await database.query(migrationSQL);
await markMigrationApplied(migrationFile); await markMigrationApplied(migrationFile);
console.log(`✅ Migration ${migrationFile} completed!`); console.log(`✅ Migration ${migrationFile} completed!`);
appliedCount++; appliedCount++;
} }
console.log(`📊 Migration summary: ${appliedCount} applied, ${skippedCount} skipped`); console.log(`📊 Migration summary: ${appliedCount} applied, ${skippedCount} skipped`);
// Verify tables were created // Verify tables were created
const result = await database.query(` const result = await database.query(`
SELECT table_name SELECT table_name
@ -98,9 +99,9 @@ async function runMigrations() {
AND table_name IN ('templates', 'template_features', 'feature_business_rules', 'feature_usage', 'custom_features', 'custom_templates', 'feature_synonyms', 'admin_notifications') AND table_name IN ('templates', 'template_features', 'feature_business_rules', 'feature_usage', 'custom_features', 'custom_templates', 'feature_synonyms', 'admin_notifications')
ORDER BY table_name ORDER BY table_name
`); `);
console.log('🔍 Verified tables:', result.rows.map(row => row.table_name)); console.log('🔍 Verified tables:', result.rows.map(row => row.table_name));
} catch (error) { } catch (error) {
console.error('❌ Migration failed:', error.message); console.error('❌ Migration failed:', error.message);
console.error('📍 Error details:', error); console.error('📍 Error details:', error);

View File

@ -10,32 +10,32 @@ class AuthService {
// Register new user // Register new user
async register(userData) { async register(userData) {
const { username, email, password, first_name, last_name } = userData; const { username, email, password, first_name, last_name } = userData;
// Validate input // Validate input
if (!username || !email || !password) { if (!username || !email || !password) {
throw new Error('Username, email, and password are required'); throw new Error('Username, email, and password are required');
} }
if (!User.validateEmail(email)) { if (!User.validateEmail(email)) {
throw new Error('Invalid email format'); throw new Error('Invalid email format');
} }
const passwordValidation = User.validatePassword(password); const passwordValidation = User.validatePassword(password);
if (!passwordValidation.valid) { if (!passwordValidation.valid) {
throw new Error(passwordValidation.message); throw new Error(passwordValidation.message);
} }
// Check if user already exists // Check if user already exists
const existingUser = await User.findByEmail(email); const existingUser = await User.findByEmail(email);
if (existingUser) { if (existingUser) {
throw new Error('User with this email already exists'); throw new Error('User with this email already exists');
} }
const existingUsername = await User.findByUsername(username); const existingUsername = await User.findByUsername(username);
if (existingUsername) { if (existingUsername) {
throw new Error('Username already taken'); throw new Error('Username already taken');
} }
// Create user // Create user
const newUser = await User.create({ const newUser = await User.create({
username, username,
@ -44,9 +44,9 @@ class AuthService {
first_name, first_name,
last_name last_name
}); });
console.log(`👤 New user registered: ${newUser.email}`); console.log(`👤 New user registered: ${newUser.email}`);
// Send verification email (non-blocking but awaited to surface errors in dev) // Send verification email (non-blocking but awaited to surface errors in dev)
try { try {
await this.sendVerificationEmail(newUser); await this.sendVerificationEmail(newUser);
@ -57,7 +57,7 @@ class AuthService {
user: newUser.email, user: newUser.email,
stack: err.stack stack: err.stack
}); });
// In development, don't fail the registration if email fails // In development, don't fail the registration if email fails
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
console.warn('⚠️ Registration completed but verification email failed. User can still login.'); console.warn('⚠️ Registration completed but verification email failed. User can still login.');
@ -66,7 +66,7 @@ class AuthService {
console.error('🚨 Critical: Verification email failed in production environment'); console.error('🚨 Critical: Verification email failed in production environment');
} }
} }
return newUser; return newUser;
} }
@ -74,46 +74,46 @@ class AuthService {
async login(credentials, sessionInfo = {}) { async login(credentials, sessionInfo = {}) {
const { email, password } = credentials; const { email, password } = credentials;
const { ip_address, user_agent, device_info } = sessionInfo; const { ip_address, user_agent, device_info } = sessionInfo;
if (!email || !password) { if (!email || !password) {
throw new Error('Email and password are required'); throw new Error('Email and password are required');
} }
// Find user // Find user
const user = await User.findByEmail(email); const user = await User.findByEmail(email);
if (!user) { if (!user) {
throw new Error('Invalid email or password'); throw new Error('Invalid email or password');
} }
// Require email to be verified before allowing login // Require email to be verified before allowing login
if (!user.email_verified) { if (!user.email_verified) {
throw new Error('Please verify your email before logging in'); throw new Error('Please verify your email before logging in');
} }
// Verify password // Verify password
const isPasswordValid = await user.verifyPassword(password); const isPasswordValid = await user.verifyPassword(password);
if (!isPasswordValid) { if (!isPasswordValid) {
throw new Error('Invalid email or password'); throw new Error('Invalid email or password');
} }
// Update last login // Update last login
await user.updateLastLogin(); await user.updateLastLogin();
// Generate tokens // Generate tokens
const tokens = jwtConfig.generateTokenPair(user); const tokens = jwtConfig.generateTokenPair(user);
// Store refresh token // Store refresh token
await this.storeRefreshToken(user.id, tokens.refreshToken); await this.storeRefreshToken(user.id, tokens.refreshToken);
// Create session // Create session
const session = await this.createSession(user.id, { const session = await this.createSession(user.id, {
ip_address, ip_address,
user_agent, user_agent,
device_info device_info
}); });
console.log(`🔑 User logged in: ${user.email}`); console.log(`🔑 User logged in: ${user.email}`);
return { return {
user: user.toJSON(), user: user.toJSON(),
tokens, tokens,
@ -148,9 +148,31 @@ class AuthService {
async sendVerificationEmail(user) { async sendVerificationEmail(user) {
const token = await this.createEmailVerificationToken(user.id); const token = await this.createEmailVerificationToken(user.id);
// Send users to the frontend verification page; the frontend will call the backend and handle redirects // Resolve verification URL. Prefer environment variable (works in Docker). If not present,
const { getVerificationUrl } = require('../../../../config/urls'); // fall back to the repository-level config/urls.js when available (development).
const verifyUrl = getVerificationUrl(token); let verifyUrl;
const frontendUrlFromEnv = process.env.FRONTEND_URL;
if (frontendUrlFromEnv) {
const FRONTEND_URL = frontendUrlFromEnv.replace(/\/$/, '');
verifyUrl = `${FRONTEND_URL}/verify-email?token=${encodeURIComponent(token)}`;
} else {
try {
// Attempt to load repo-level config (works when running locally from repo root)
// This is guarded so it won't crash inside Docker if the relative path isn't valid.
// eslint-disable-next-line global-require
const urls = require('../../../../config/urls');
if (urls && typeof urls.getVerificationUrl === 'function') {
verifyUrl = urls.getVerificationUrl(token);
} else if (urls && urls.FRONTEND_URL) {
const FRONTEND_URL = urls.FRONTEND_URL.replace(/\/$/, '');
verifyUrl = `${FRONTEND_URL}/verify-email?token=${encodeURIComponent(token)}`;
}
} catch (err) {
// As a last resort, build a relative backend-hosted verification endpoint
const backendHost = process.env.BACKEND_URL || `http://localhost:${process.env.PORT || 8011}`;
verifyUrl = `${backendHost.replace(/\/$/, '')}/api/auth/verify-email?token=${encodeURIComponent(token)}`;
}
}
const today = new Date(); const today = new Date();
const dateString = today.toLocaleDateString('en-US'); const dateString = today.toLocaleDateString('en-US');
@ -219,7 +241,7 @@ class AuthService {
if (!refreshToken) { if (!refreshToken) {
throw new Error('Refresh token is required'); throw new Error('Refresh token is required');
} }
// Verify refresh token // Verify refresh token
let decoded; let decoded;
try { try {
@ -227,33 +249,33 @@ class AuthService {
} catch (error) { } catch (error) {
throw new Error('Invalid refresh token'); throw new Error('Invalid refresh token');
} }
// Check if token exists and is not revoked (support deterministic + legacy bcrypt storage) // Check if token exists and is not revoked (support deterministic + legacy bcrypt storage)
const storedToken = await this.findStoredRefreshToken(decoded.userId, refreshToken); const storedToken = await this.findStoredRefreshToken(decoded.userId, refreshToken);
if (!storedToken || storedToken.is_revoked) { if (!storedToken || storedToken.is_revoked) {
throw new Error('Refresh token is revoked or invalid'); throw new Error('Refresh token is revoked or invalid');
} }
if (new Date() > storedToken.expires_at) { if (new Date() > storedToken.expires_at) {
throw new Error('Refresh token has expired'); throw new Error('Refresh token has expired');
} }
// Get user // Get user
const user = await User.findById(decoded.userId); const user = await User.findById(decoded.userId);
if (!user) { if (!user) {
throw new Error('User not found'); throw new Error('User not found');
} }
// Generate new tokens // Generate new tokens
const tokens = jwtConfig.generateTokenPair(user); const tokens = jwtConfig.generateTokenPair(user);
// Revoke old refresh token and store new one // Revoke old refresh token and store new one
await this.revokeRefreshTokenById(storedToken.id); await this.revokeRefreshTokenById(storedToken.id);
await this.storeRefreshToken(user.id, tokens.refreshToken); await this.storeRefreshToken(user.id, tokens.refreshToken);
console.log(`🔄 Token refreshed for user: ${user.email}`); console.log(`🔄 Token refreshed for user: ${user.email}`);
return { return {
user: user.toJSON(), user: user.toJSON(),
tokens tokens
@ -273,11 +295,11 @@ class AuthService {
console.warn('⚠️ Logout could not find refresh token to revoke:', e.message); console.warn('⚠️ Logout could not find refresh token to revoke:', e.message);
} }
} }
if (sessionToken) { if (sessionToken) {
await this.endSession(sessionToken); await this.endSession(sessionToken);
} }
console.log('🚪 User logged out'); console.log('🚪 User logged out');
return { message: 'Logged out successfully' }; return { message: 'Logged out successfully' };
} }
@ -287,13 +309,13 @@ class AuthService {
const tokenHash = this.hashDeterministic(refreshToken); const tokenHash = this.hashDeterministic(refreshToken);
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days
const id = uuidv4(); const id = uuidv4();
const query = ` const query = `
INSERT INTO refresh_tokens (id, user_id, token_hash, expires_at) INSERT INTO refresh_tokens (id, user_id, token_hash, expires_at)
VALUES ($1, $2, $3, $4) VALUES ($1, $2, $3, $4)
RETURNING id RETURNING id
`; `;
const result = await database.query(query, [id, userId, tokenHash, expiresAt]); const result = await database.query(query, [id, userId, tokenHash, expiresAt]);
return result.rows[0]; return result.rows[0];
} }
@ -304,7 +326,7 @@ class AuthService {
SELECT * FROM refresh_tokens SELECT * FROM refresh_tokens
WHERE token_hash = $1 WHERE token_hash = $1
`; `;
const result = await database.query(query, [tokenHash]); const result = await database.query(query, [tokenHash]);
return result.rows[0] || null; return result.rows[0] || null;
} }
@ -316,7 +338,7 @@ class AuthService {
SET is_revoked = true, revoked_at = NOW() SET is_revoked = true, revoked_at = NOW()
WHERE token_hash = $1 WHERE token_hash = $1
`; `;
await database.query(query, [tokenHash]); await database.query(query, [tokenHash]);
} }
@ -335,14 +357,14 @@ class AuthService {
const sessionToken = uuidv4(); const sessionToken = uuidv4();
const id = uuidv4(); const id = uuidv4();
const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // 30 days const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // 30 days
const query = ` const query = `
INSERT INTO user_sessions ( INSERT INTO user_sessions (
id, user_id, session_token, ip_address, user_agent, device_info, expires_at id, user_id, session_token, ip_address, user_agent, device_info, expires_at
) VALUES ($1, $2, $3, $4, $5, $6, $7) ) VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING * RETURNING *
`; `;
const values = [ const values = [
id, id,
userId, userId,
@ -352,7 +374,7 @@ class AuthService {
sessionInfo.device_info ? JSON.stringify(sessionInfo.device_info) : null, sessionInfo.device_info ? JSON.stringify(sessionInfo.device_info) : null,
expiresAt expiresAt
]; ];
const result = await database.query(query, values); const result = await database.query(query, values);
return result.rows[0]; return result.rows[0];
} }
@ -364,7 +386,7 @@ class AuthService {
SET is_active = false SET is_active = false
WHERE session_token = $1 WHERE session_token = $1
`; `;
await database.query(query, [sessionToken]); await database.query(query, [sessionToken]);
} }
@ -376,7 +398,7 @@ class AuthService {
WHERE session_token = $1 AND is_active = true WHERE session_token = $1 AND is_active = true
RETURNING * RETURNING *
`; `;
const result = await database.query(query, [sessionToken]); const result = await database.query(query, [sessionToken]);
return result.rows[0]; return result.rows[0];
} }
@ -388,7 +410,7 @@ class AuthService {
WHERE user_id = $1 AND is_active = true WHERE user_id = $1 AND is_active = true
ORDER BY last_activity DESC ORDER BY last_activity DESC
`; `;
const result = await database.query(query, [userId]); const result = await database.query(query, [userId]);
return result.rows; return result.rows;
} }
@ -398,11 +420,11 @@ class AuthService {
try { try {
const decoded = jwtConfig.verifyAccessToken(token); const decoded = jwtConfig.verifyAccessToken(token);
const user = await User.findById(decoded.userId); const user = await User.findById(decoded.userId);
if (!user) { if (!user) {
throw new Error('User not found'); throw new Error('User not found');
} }
return user; return user;
} catch (error) { } catch (error) {
throw new Error('Invalid access token'); throw new Error('Invalid access token');
@ -452,17 +474,17 @@ class AuthService {
// Cleanup expired tokens and sessions // Cleanup expired tokens and sessions
async cleanup() { async cleanup() {
console.log('🧹 Starting auth cleanup...'); console.log('🧹 Starting auth cleanup...');
// Cleanup expired tokens // Cleanup expired tokens
const tokenResult = await database.query('SELECT cleanup_expired_tokens()'); const tokenResult = await database.query('SELECT cleanup_expired_tokens()');
const deletedTokens = tokenResult.rows[0].cleanup_expired_tokens; const deletedTokens = tokenResult.rows[0].cleanup_expired_tokens;
// Cleanup inactive sessions // Cleanup inactive sessions
const sessionResult = await database.query('SELECT cleanup_inactive_sessions()'); const sessionResult = await database.query('SELECT cleanup_inactive_sessions()');
const inactiveSessions = sessionResult.rows[0].cleanup_inactive_sessions; const inactiveSessions = sessionResult.rows[0].cleanup_inactive_sessions;
console.log(`🧹 Cleanup completed: ${deletedTokens} tokens, ${inactiveSessions} sessions`); console.log(`🧹 Cleanup completed: ${deletedTokens} tokens, ${inactiveSessions} sessions`);
return { deletedTokens, inactiveSessions }; return { deletedTokens, inactiveSessions };
} }
@ -472,17 +494,17 @@ class AuthService {
if (!user) { if (!user) {
throw new Error('User not found'); throw new Error('User not found');
} }
const passwordValidation = User.validatePassword(newPassword); const passwordValidation = User.validatePassword(newPassword);
if (!passwordValidation.valid) { if (!passwordValidation.valid) {
throw new Error(passwordValidation.message); throw new Error(passwordValidation.message);
} }
await user.changePassword(currentPassword, newPassword); await user.changePassword(currentPassword, newPassword);
// Revoke all refresh tokens to force re-login // Revoke all refresh tokens to force re-login
await this.revokeAllUserTokens(userId); await this.revokeAllUserTokens(userId);
console.log(`🔒 Password changed for user: ${user.email}`); console.log(`🔒 Password changed for user: ${user.email}`);
return { message: 'Password changed successfully' }; return { message: 'Password changed successfully' };
} }
@ -494,7 +516,7 @@ class AuthService {
SET is_revoked = true, revoked_at = NOW() SET is_revoked = true, revoked_at = NOW()
WHERE user_id = $1 AND is_revoked = false WHERE user_id = $1 AND is_revoked = false
`; `;
await database.query(query, [userId]); await database.query(query, [userId]);
} }
@ -508,7 +530,7 @@ class AuthService {
(SELECT COUNT(*) FROM users WHERE last_login > NOW() - INTERVAL '24 hours') as users_24h, (SELECT COUNT(*) FROM users WHERE last_login > NOW() - INTERVAL '24 hours') as users_24h,
(SELECT COUNT(*) FROM users WHERE created_at > NOW() - INTERVAL '7 days') as new_users_7d (SELECT COUNT(*) FROM users WHERE created_at > NOW() - INTERVAL '7 days') as new_users_7d
`; `;
const result = await database.query(query); const result = await database.query(query);
return result.rows[0]; return result.rows[0];
} }

View File

@ -7,7 +7,7 @@ export default function BusinessQuestionsScreen() {
const [isLoadingQuestions, setIsLoadingQuestions] = useState(true); const [isLoadingQuestions, setIsLoadingQuestions] = useState(true);
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState(null); const [error, setError] = useState(null);
const { selectedFeatures, setCurrentStep, projectName, projectType } = useProjectStore(); const { selectedFeatures, setCurrentStep, projectName, projectType } = useProjectStore();
// Load business questions when component mounts // Load business questions when component mounts
@ -28,7 +28,15 @@ export default function BusinessQuestionsScreen() {
console.log('🚀 Generating comprehensive business questions for integrated system:', selectedFeatures); console.log('🚀 Generating comprehensive business questions for integrated system:', selectedFeatures);
// Call the new comprehensive endpoint // Call the new comprehensive endpoint
const { getApiUrl } = require('../../../../../../config/urls'); // Prefer an environment-provided backend URL for frontend builds (REACT_APP_BACKEND_URL or NEXT_PUBLIC_BACKEND_URL).
const backendBase = (process.env.REACT_APP_BACKEND_URL || process.env.NEXT_PUBLIC_BACKEND_URL || '').replace(/\/$/, '') || '';
const getApiUrl = (endpoint) => {
const clean = endpoint.startsWith('/') ? endpoint.slice(1) : endpoint;
if (backendBase) return `${backendBase}/${clean}`;
// Fallback to relative path (assumes frontend is served with proxy to backend)
return `/${clean}`;
};
const response = await fetch(getApiUrl('api/v1/generate-comprehensive-business-questions'), { const response = await fetch(getApiUrl('api/v1/generate-comprehensive-business-questions'), {
method: 'POST', method: 'POST',
headers: { headers: {
@ -51,14 +59,14 @@ export default function BusinessQuestionsScreen() {
if (data.success && data.data.businessQuestions) { if (data.success && data.data.businessQuestions) {
setBusinessQuestions(data.data.businessQuestions); setBusinessQuestions(data.data.businessQuestions);
// Initialize answers object // Initialize answers object
const initialAnswers = {}; const initialAnswers = {};
data.data.businessQuestions.forEach((question, index) => { data.data.businessQuestions.forEach((question, index) => {
initialAnswers[index] = ''; initialAnswers[index] = '';
}); });
setBusinessAnswers(initialAnswers); setBusinessAnswers(initialAnswers);
console.log(`🎯 Generated ${data.data.businessQuestions.length} comprehensive questions for integrated system`); console.log(`🎯 Generated ${data.data.businessQuestions.length} comprehensive questions for integrated system`);
} else { } else {
setError('Failed to generate comprehensive business questions'); setError('Failed to generate comprehensive business questions');
@ -85,7 +93,7 @@ export default function BusinessQuestionsScreen() {
// Validate that at least some questions are answered // Validate that at least some questions are answered
const answeredQuestions = Object.values(businessAnswers).filter(answer => answer.trim()).length; const answeredQuestions = Object.values(businessAnswers).filter(answer => answer.trim()).length;
if (answeredQuestions === 0) { if (answeredQuestions === 0) {
alert('Please answer at least one question before proceeding.'); alert('Please answer at least one question before proceeding.');
return; return;
@ -100,13 +108,13 @@ export default function BusinessQuestionsScreen() {
businessQuestions: businessQuestions, businessQuestions: businessQuestions,
businessAnswers: businessAnswers, businessAnswers: businessAnswers,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
// For backward compatibility with tech-stack-selector // For backward compatibility with tech-stack-selector
featureName: `${projectName} - Integrated System`, featureName: `${projectName} - Integrated System`,
description: `Complete ${projectType} system with ${selectedFeatures.length} integrated features`, description: `Complete ${projectType} system with ${selectedFeatures.length} integrated features`,
requirements: selectedFeatures.flatMap(f => f.requirements || []), requirements: selectedFeatures.flatMap(f => f.requirements || []),
complexity: selectedFeatures.some(f => f.complexity === 'high') ? 'high' : complexity: selectedFeatures.some(f => f.complexity === 'high') ? 'high' :
selectedFeatures.some(f => f.complexity === 'medium') ? 'medium' : 'low', selectedFeatures.some(f => f.complexity === 'medium') ? 'medium' : 'low',
logicRules: selectedFeatures.flatMap(f => f.logicRules || []) logicRules: selectedFeatures.flatMap(f => f.logicRules || [])
}; };
@ -129,10 +137,10 @@ export default function BusinessQuestionsScreen() {
console.log('✅ Tech stack recommendations received:', techRecommendations); console.log('✅ Tech stack recommendations received:', techRecommendations);
// Store results in project store // Store results in project store
useProjectStore.setState({ useProjectStore.setState({
finalProjectData: completeData, finalProjectData: completeData,
techStackRecommendations: techRecommendations, techStackRecommendations: techRecommendations,
businessQuestionsCompleted: true businessQuestionsCompleted: true
}); });
// Move to summary step to show recommendations // Move to summary step to show recommendations
@ -206,7 +214,7 @@ export default function BusinessQuestionsScreen() {
<span className="text-blue-600 text-lg">💡</span> <span className="text-blue-600 text-lg">💡</span>
<div> <div>
<p className="text-blue-800 text-sm"> <p className="text-blue-800 text-sm">
<strong>Tip:</strong> Answer as many questions as possible for better technology recommendations. <strong>Tip:</strong> Answer as many questions as possible for better technology recommendations.
You can skip questions you're unsure about. These questions consider your entire system, not individual features. You can skip questions you're unsure about. These questions consider your entire system, not individual features.
</p> </p>
</div> </div>
@ -224,7 +232,7 @@ export default function BusinessQuestionsScreen() {
<span>{question}</span> <span>{question}</span>
</span> </span>
</label> </label>
<textarea <textarea
value={businessAnswers[index] || ''} value={businessAnswers[index] || ''}
onChange={(e) => handleAnswerChange(index, e.target.value)} onChange={(e) => handleAnswerChange(index, e.target.value)}
@ -260,15 +268,14 @@ export default function BusinessQuestionsScreen() {
> >
Back to Features Back to Features
</button> </button>
<button <button
onClick={handleSubmit} onClick={handleSubmit}
disabled={isSubmitting || Object.values(businessAnswers).filter(answer => answer.trim()).length === 0} disabled={isSubmitting || Object.values(businessAnswers).filter(answer => answer.trim()).length === 0}
className={`px-6 py-2 rounded-md font-medium transition-colors ${ className={`px-6 py-2 rounded-md font-medium transition-colors ${isSubmitting || Object.values(businessAnswers).filter(answer => answer.trim()).length === 0
isSubmitting || Object.values(businessAnswers).filter(answer => answer.trim()).length === 0
? 'bg-gray-300 text-gray-500 cursor-not-allowed' ? 'bg-gray-300 text-gray-500 cursor-not-allowed'
: 'bg-green-600 text-white hover:bg-green-700' : 'bg-green-600 text-white hover:bg-green-700'
}`} }`}
> >
{isSubmitting ? ( {isSubmitting ? (
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">