diff --git a/config/urls.js b/config/urls.js index 4095df2..522ef9f 100644 --- a/config/urls.js +++ b/config/urls.js @@ -6,11 +6,10 @@ // ======================================== // 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'; -// ======================================== -// LOCAL DEVELOPMENT URLS (Comment out live URLs above and uncomment these) + // ======================================== // const FRONTEND_URL = 'http://localhost:3001'; // const BACKEND_URL = 'http://localhost:8000'; diff --git a/docker-compose.yml b/docker-compose.yml index c94ae30..abdac7f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -233,7 +233,7 @@ services: - NODE_ENV=development - PORT=8000 - 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_METHODS=GET,POST,PUT,DELETE,PATCH,OPTIONS # Add this line - CORS_CREDENTIALS=true # Add this line @@ -507,7 +507,7 @@ services: - JWT_ACCESS_EXPIRY=24h - JWT_ADMIN_ACCESS_EXPIRY=7d - JWT_REFRESH_EXPIRY=7d - - FRONTEND_URL=https://dashboard.codenuk.com + - FRONTEND_URL=http://192.168.1.31:3001 # Email Configuration - SMTP_HOST=smtp.gmail.com - SMTP_PORT=587 @@ -613,7 +613,7 @@ services: environment: - PORT=8012 - HOST=0.0.0.0 - - FRONTEND_URL=https://dashboard.codenuk.com + - FRONTEND_URL=http://192.168.1.31:3001 - POSTGRES_HOST=postgres - POSTGRES_PORT=5432 - POSTGRES_DB=dev_pipeline diff --git a/services/api-gateway/.env .prod b/services/api-gateway/.env .prod index e5e5dcd..5a6f40e 100644 --- a/services/api-gateway/.env .prod +++ b/services/api-gateway/.env .prod @@ -28,9 +28,9 @@ RABBITMQ_USER=pipeline_admin RABBITMQ_PASSWORD=secure_rabbitmq_password # CORS -FRONTEND_URL=https://dashboard.codenuk.com +FRONTEND_URL=http://192.168.1.31:3001 # 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_CREDENTIALS=true \ No newline at end of file diff --git a/services/template-manager/src/migrations/migrate.js b/services/template-manager/src/migrations/migrate.js index 1ee82ac..14ba5f8 100644 --- a/services/template-manager/src/migrations/migrate.js +++ b/services/template-manager/src/migrations/migrate.js @@ -30,17 +30,18 @@ async function markMigrationApplied(version) { async function runMigrations() { console.log('๐ Starting template-manager database migrations...'); - + try { // Create migrations tracking table first await createMigrationsTable(); console.log('โ Migration tracking table ready'); // Get all migration files in order + // Reordered to ensure custom_templates table exists before admin_approval_workflow const migrationFiles = [ '001_initial_schema.sql', - '002_admin_approval_workflow.sql', - '003_custom_templates.sql', + '003_custom_templates.sql', // Moved earlier since others depend on it + '002_admin_approval_workflow.sql', // Now runs after custom_templates is created '004_add_is_custom_flag.sql', '004_add_user_id_to_custom_templates.sql', '005_fix_custom_features_foreign_key.sql', @@ -50,10 +51,10 @@ async function runMigrations() { let appliedCount = 0; let skippedCount = 0; - + for (const migrationFile of migrationFiles) { const migrationPath = path.join(__dirname, migrationFile); - + // Check if migration file exists if (!fs.existsSync(migrationPath)) { console.log(`โ ๏ธ Migration file not found: ${migrationFile}`); @@ -66,7 +67,7 @@ async function runMigrations() { skippedCount++; continue; } - + const migrationSQL = fs.readFileSync(migrationPath, 'utf8'); // Skip destructive migrations unless explicitly allowed @@ -79,17 +80,17 @@ async function runMigrations() { } console.log(`๐ Running migration: ${migrationFile}`); - + // Execute the migration await database.query(migrationSQL); await markMigrationApplied(migrationFile); - + console.log(`โ Migration ${migrationFile} completed!`); appliedCount++; } - + console.log(`๐ Migration summary: ${appliedCount} applied, ${skippedCount} skipped`); - + // Verify tables were created const result = await database.query(` 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') ORDER BY table_name `); - + console.log('๐ Verified tables:', result.rows.map(row => row.table_name)); - + } catch (error) { console.error('โ Migration failed:', error.message); console.error('๐ Error details:', error); diff --git a/services/user-auth/src/services/authService.js b/services/user-auth/src/services/authService.js index d233fd6..1ce8572 100644 --- a/services/user-auth/src/services/authService.js +++ b/services/user-auth/src/services/authService.js @@ -10,32 +10,32 @@ class AuthService { // Register new user async register(userData) { const { username, email, password, first_name, last_name } = userData; - + // Validate input if (!username || !email || !password) { throw new Error('Username, email, and password are required'); } - + if (!User.validateEmail(email)) { throw new Error('Invalid email format'); } - + const passwordValidation = User.validatePassword(password); if (!passwordValidation.valid) { throw new Error(passwordValidation.message); } - + // Check if user already exists const existingUser = await User.findByEmail(email); if (existingUser) { throw new Error('User with this email already exists'); } - + const existingUsername = await User.findByUsername(username); if (existingUsername) { throw new Error('Username already taken'); } - + // Create user const newUser = await User.create({ username, @@ -44,9 +44,9 @@ class AuthService { first_name, last_name }); - + console.log(`๐ค New user registered: ${newUser.email}`); - + // Send verification email (non-blocking but awaited to surface errors in dev) try { await this.sendVerificationEmail(newUser); @@ -57,7 +57,7 @@ class AuthService { user: newUser.email, stack: err.stack }); - + // In development, don't fail the registration if email fails if (process.env.NODE_ENV === 'development') { 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'); } } - + return newUser; } @@ -74,46 +74,46 @@ class AuthService { async login(credentials, sessionInfo = {}) { const { email, password } = credentials; const { ip_address, user_agent, device_info } = sessionInfo; - + if (!email || !password) { throw new Error('Email and password are required'); } - + // Find user const user = await User.findByEmail(email); if (!user) { throw new Error('Invalid email or password'); } - + // Require email to be verified before allowing login if (!user.email_verified) { throw new Error('Please verify your email before logging in'); } - + // Verify password const isPasswordValid = await user.verifyPassword(password); if (!isPasswordValid) { throw new Error('Invalid email or password'); } - + // Update last login await user.updateLastLogin(); - + // Generate tokens const tokens = jwtConfig.generateTokenPair(user); - + // Store refresh token await this.storeRefreshToken(user.id, tokens.refreshToken); - + // Create session const session = await this.createSession(user.id, { ip_address, user_agent, device_info }); - + console.log(`๐ User logged in: ${user.email}`); - + return { user: user.toJSON(), tokens, @@ -148,9 +148,31 @@ class AuthService { async sendVerificationEmail(user) { const token = await this.createEmailVerificationToken(user.id); - // Send users to the frontend verification page; the frontend will call the backend and handle redirects - const { getVerificationUrl } = require('../../../../config/urls'); - const verifyUrl = getVerificationUrl(token); + // Resolve verification URL. Prefer environment variable (works in Docker). If not present, + // fall back to the repository-level config/urls.js when available (development). + 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 dateString = today.toLocaleDateString('en-US'); @@ -219,7 +241,7 @@ class AuthService { if (!refreshToken) { throw new Error('Refresh token is required'); } - + // Verify refresh token let decoded; try { @@ -227,33 +249,33 @@ class AuthService { } catch (error) { throw new Error('Invalid refresh token'); } - + // Check if token exists and is not revoked (support deterministic + legacy bcrypt storage) const storedToken = await this.findStoredRefreshToken(decoded.userId, refreshToken); - + if (!storedToken || storedToken.is_revoked) { throw new Error('Refresh token is revoked or invalid'); } - + if (new Date() > storedToken.expires_at) { throw new Error('Refresh token has expired'); } - + // Get user const user = await User.findById(decoded.userId); if (!user) { throw new Error('User not found'); } - + // Generate new tokens const tokens = jwtConfig.generateTokenPair(user); - + // Revoke old refresh token and store new one await this.revokeRefreshTokenById(storedToken.id); await this.storeRefreshToken(user.id, tokens.refreshToken); - + console.log(`๐ Token refreshed for user: ${user.email}`); - + return { user: user.toJSON(), tokens @@ -273,11 +295,11 @@ class AuthService { console.warn('โ ๏ธ Logout could not find refresh token to revoke:', e.message); } } - + if (sessionToken) { await this.endSession(sessionToken); } - + console.log('๐ช User logged out'); return { message: 'Logged out successfully' }; } @@ -287,13 +309,13 @@ class AuthService { const tokenHash = this.hashDeterministic(refreshToken); const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days const id = uuidv4(); - + const query = ` INSERT INTO refresh_tokens (id, user_id, token_hash, expires_at) VALUES ($1, $2, $3, $4) RETURNING id `; - + const result = await database.query(query, [id, userId, tokenHash, expiresAt]); return result.rows[0]; } @@ -304,7 +326,7 @@ class AuthService { SELECT * FROM refresh_tokens WHERE token_hash = $1 `; - + const result = await database.query(query, [tokenHash]); return result.rows[0] || null; } @@ -316,7 +338,7 @@ class AuthService { SET is_revoked = true, revoked_at = NOW() WHERE token_hash = $1 `; - + await database.query(query, [tokenHash]); } @@ -335,14 +357,14 @@ class AuthService { const sessionToken = uuidv4(); const id = uuidv4(); const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // 30 days - + const query = ` INSERT INTO user_sessions ( id, user_id, session_token, ip_address, user_agent, device_info, expires_at ) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING * `; - + const values = [ id, userId, @@ -352,7 +374,7 @@ class AuthService { sessionInfo.device_info ? JSON.stringify(sessionInfo.device_info) : null, expiresAt ]; - + const result = await database.query(query, values); return result.rows[0]; } @@ -364,7 +386,7 @@ class AuthService { SET is_active = false WHERE session_token = $1 `; - + await database.query(query, [sessionToken]); } @@ -376,7 +398,7 @@ class AuthService { WHERE session_token = $1 AND is_active = true RETURNING * `; - + const result = await database.query(query, [sessionToken]); return result.rows[0]; } @@ -388,7 +410,7 @@ class AuthService { WHERE user_id = $1 AND is_active = true ORDER BY last_activity DESC `; - + const result = await database.query(query, [userId]); return result.rows; } @@ -398,11 +420,11 @@ class AuthService { try { const decoded = jwtConfig.verifyAccessToken(token); const user = await User.findById(decoded.userId); - + if (!user) { throw new Error('User not found'); } - + return user; } catch (error) { throw new Error('Invalid access token'); @@ -452,17 +474,17 @@ class AuthService { // Cleanup expired tokens and sessions async cleanup() { console.log('๐งน Starting auth cleanup...'); - + // Cleanup expired tokens const tokenResult = await database.query('SELECT cleanup_expired_tokens()'); const deletedTokens = tokenResult.rows[0].cleanup_expired_tokens; - + // Cleanup inactive sessions const sessionResult = await database.query('SELECT cleanup_inactive_sessions()'); const inactiveSessions = sessionResult.rows[0].cleanup_inactive_sessions; - + console.log(`๐งน Cleanup completed: ${deletedTokens} tokens, ${inactiveSessions} sessions`); - + return { deletedTokens, inactiveSessions }; } @@ -472,17 +494,17 @@ class AuthService { if (!user) { throw new Error('User not found'); } - + const passwordValidation = User.validatePassword(newPassword); if (!passwordValidation.valid) { throw new Error(passwordValidation.message); } - + await user.changePassword(currentPassword, newPassword); - + // Revoke all refresh tokens to force re-login await this.revokeAllUserTokens(userId); - + console.log(`๐ Password changed for user: ${user.email}`); return { message: 'Password changed successfully' }; } @@ -494,7 +516,7 @@ class AuthService { SET is_revoked = true, revoked_at = NOW() WHERE user_id = $1 AND is_revoked = false `; - + 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 created_at > NOW() - INTERVAL '7 days') as new_users_7d `; - + const result = await database.query(query); return result.rows[0]; } diff --git a/services/web-dashboard/src/components/project-builder/BusinessQuestionsScreen.js b/services/web-dashboard/src/components/project-builder/BusinessQuestionsScreen.js index b5e056a..078098d 100644 --- a/services/web-dashboard/src/components/project-builder/BusinessQuestionsScreen.js +++ b/services/web-dashboard/src/components/project-builder/BusinessQuestionsScreen.js @@ -7,7 +7,7 @@ export default function BusinessQuestionsScreen() { const [isLoadingQuestions, setIsLoadingQuestions] = useState(true); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(null); - + const { selectedFeatures, setCurrentStep, projectName, projectType } = useProjectStore(); // Load business questions when component mounts @@ -28,7 +28,15 @@ export default function BusinessQuestionsScreen() { console.log('๐ Generating comprehensive business questions for integrated system:', selectedFeatures); // 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'), { method: 'POST', headers: { @@ -51,14 +59,14 @@ export default function BusinessQuestionsScreen() { if (data.success && data.data.businessQuestions) { setBusinessQuestions(data.data.businessQuestions); - + // Initialize answers object const initialAnswers = {}; data.data.businessQuestions.forEach((question, index) => { initialAnswers[index] = ''; }); setBusinessAnswers(initialAnswers); - + console.log(`๐ฏ Generated ${data.data.businessQuestions.length} comprehensive questions for integrated system`); } else { setError('Failed to generate comprehensive business questions'); @@ -85,7 +93,7 @@ export default function BusinessQuestionsScreen() { // Validate that at least some questions are answered const answeredQuestions = Object.values(businessAnswers).filter(answer => answer.trim()).length; - + if (answeredQuestions === 0) { alert('Please answer at least one question before proceeding.'); return; @@ -100,13 +108,13 @@ export default function BusinessQuestionsScreen() { businessQuestions: businessQuestions, businessAnswers: businessAnswers, timestamp: new Date().toISOString(), - + // For backward compatibility with tech-stack-selector featureName: `${projectName} - Integrated System`, description: `Complete ${projectType} system with ${selectedFeatures.length} integrated features`, requirements: selectedFeatures.flatMap(f => f.requirements || []), - complexity: selectedFeatures.some(f => f.complexity === 'high') ? 'high' : - selectedFeatures.some(f => f.complexity === 'medium') ? 'medium' : 'low', + complexity: selectedFeatures.some(f => f.complexity === 'high') ? 'high' : + selectedFeatures.some(f => f.complexity === 'medium') ? 'medium' : 'low', logicRules: selectedFeatures.flatMap(f => f.logicRules || []) }; @@ -129,10 +137,10 @@ export default function BusinessQuestionsScreen() { console.log('โ Tech stack recommendations received:', techRecommendations); // Store results in project store - useProjectStore.setState({ + useProjectStore.setState({ finalProjectData: completeData, techStackRecommendations: techRecommendations, - businessQuestionsCompleted: true + businessQuestionsCompleted: true }); // Move to summary step to show recommendations @@ -206,7 +214,7 @@ export default function BusinessQuestionsScreen() { ๐ก
- Tip: Answer as many questions as possible for better technology recommendations. + Tip: 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.