Spaces:
Paused
Paused
File size: 10,143 Bytes
21cac8a 838da4d 21cac8a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 | const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
require('dotenv').config();
const { initializeDatabase } = require('./database/init');
const path = require('path');
const analysisRoutes = require('./routes/analysis');
const healthRoutes = require('./routes/health');
const docRoutes = require('./routes/docs');
// New robust architecture imports
const configManager = require('./config/ConfigManager');
const serviceContainer = require('./core/ServiceContainer');
const ScreenshotService = require('./services/screenshotService');
const AICritiqueService = require('./services/aiCritiqueService');
const VisualDesignAnalyzer = require('./services/visualDesignAnalyzer');
const AnalysisService = require('./services/analysisService');
const CodeGenerationService = require('./services/codeGenerationService');
const app = express();
app.set('trust proxy', 1);
let config = null;
let PORT = process.env.PORT || 3000;
// Configure middleware and routes after initialization
function setupMiddleware() {
// Security middleware
app.use(helmet());
app.use(cors());
// Rate limiting (disabled in development)
if (config.server.environment === 'production') {
const limiter = rateLimit({
windowMs: config.server.rateLimitWindowMs || 900000, // 15 minutes
max: config.server.rateLimitMaxRequests || 10,
message: 'Too many requests, please try again later.',
});
app.use('/api/', limiter);
}
// Body parsing
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
// Make services available to routes
app.use((req, res, next) => {
req.services = serviceContainer;
req.config = config;
next();
});
// Routes
app.use('/api/health', healthRoutes);
app.use('/api/analyze', analysisRoutes);
app.use('/api-docs', docRoutes);
// HF Space required health check at root
app.get('/health', (req, res) => {
res.status(200).json({ status: 'healthy', timestamp: new Date().toISOString() });
});
// Serve static frontend files
app.use(express.static(path.join(__dirname, '../frontend/dist')));
// Static files for screenshots
app.use('/screenshots', express.static(config.screenshots.storagePath || 'data/screenshots'));
// Error handling
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({
error: 'Something went wrong!',
message: config.server.environment === 'development' ? err.message : undefined
});
});
// 404 handler for API routes
app.use('/api/*', (req, res) => {
res.status(404).json({ error: 'Not found' });
});
// Fallback to index.html for frontend routing (SPA)
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, '../frontend/dist/index.html'));
});
}
// Initialize robust architecture and start server
async function startServer() {
try {
console.log('π Starting UX Analyst AI server with robust architecture...');
// 1. Initialize configuration
console.log('π Initializing configuration...');
await configManager.initialize();
configManager.validateRequiredEnvVars();
config = configManager.get();
PORT = config.server.port;
console.log('β
Configuration loaded successfully');
// 2. Initialize database
console.log('ποΈ Initializing database...');
await initializeDatabase();
console.log('β
Database initialized');
// 3. Register services with dependency injection
console.log('π§ Registering services...');
await registerServices();
// 4. Start all services
console.log('βΆοΈ Starting services...');
await serviceContainer.startServices();
console.log('β
All services started successfully');
// 5. Setup Express middleware and routes
console.log('π Setting up Express middleware and routes...');
setupMiddleware();
// 6. Start HTTP server
await startServerWithPortHandling();
// 7. Health check
console.log('π Performing initial health check...');
const healthStatus = await serviceContainer.getHealthStatus();
console.log(`π₯ System health: ${healthStatus.status}`);
// 8. Graceful shutdown handling
process.on('SIGTERM', gracefulShutdown);
process.on('SIGINT', gracefulShutdown);
console.log('π UX Analyst AI server startup complete!');
} catch (error) {
console.error('β Failed to start server:', error);
await gracefulShutdown();
process.exit(1);
}
}
// Register all services with the service container
async function registerServices() {
// Register ScreenshotService
serviceContainer.register('screenshotService', (deps, container) => {
return new ScreenshotService(config);
}, {
singleton: true,
startupPriority: 10,
dependencies: [],
circuitBreaker: {
failureThreshold: 3,
recoveryTimeout: 30000
},
healthCheck: async (service) => service.getHealthStatus()
});
// Register VisualDesignAnalyzer
serviceContainer.register('visualDesignAnalyzer', (deps, container) => {
return new VisualDesignAnalyzer();
}, {
singleton: true,
startupPriority: 20,
dependencies: [],
healthCheck: async (service) => ({ status: 'healthy' })
});
// Register AICritiqueService
serviceContainer.register('aiCritiqueService', (deps, container) => {
return new AICritiqueService(config);
}, {
singleton: true,
startupPriority: 30,
dependencies: [],
circuitBreaker: {
failureThreshold: 5,
recoveryTimeout: 60000,
expectedErrors: ['rate limit', '429', '503', 'timeout', 'overloaded']
},
healthCheck: async (service) => service.getHealthStatus()
});
// Register CodeGenerationService
serviceContainer.register('codeGenerationService', (deps, container) => {
return new CodeGenerationService({
geminiApiKey: config.ai.geminiApiKey,
logger: console
});
}, {
singleton: true,
startupPriority: 35,
dependencies: [],
circuitBreaker: {
failureThreshold: 3,
recoveryTimeout: 30000,
expectedErrors: ['rate limit', '429', '503', 'timeout']
},
healthCheck: async (service) => service.getHealthStatus()
});
// Register AnalysisService (depends on other services)
serviceContainer.register('analysisService', (deps, container) => {
const screenshotService = container.get('screenshotService');
const aiCritiqueService = container.get('aiCritiqueService');
const visualDesignAnalyzer = container.get('visualDesignAnalyzer');
const codeGenerationService = container.get('codeGenerationService');
return new AnalysisService({
screenshotService,
aiCritiqueService,
visualDesignAnalyzer,
codeGenerationService,
config
});
}, {
singleton: true,
startupPriority: 40,
dependencies: ['screenshotService', 'aiCritiqueService', 'visualDesignAnalyzer', 'codeGenerationService'],
healthCheck: async (service) => service.getHealthStatus ? service.getHealthStatus() : { status: 'healthy' }
});
console.log('β
All services registered with dependency injection');
}
async function startServerWithPortHandling() {
return new Promise((resolve, reject) => {
const server = app.listen(PORT)
.on('listening', () => {
console.log(`π UX Analyst AI server running on port ${PORT}`);
console.log(`π Environment: ${config.server.environment}`);
console.log(`π Service endpoints:`);
console.log(` β’ Health: http://${config.server.host}:${PORT}/api/health`);
console.log(` β’ Analysis: http://${config.server.host}:${PORT}/api/analyze`);
console.log(` β’ Screenshots: http://${config.server.host}:${PORT}/screenshots/`);
console.log(`π‘οΈ Circuit breakers active for fault tolerance`);
console.log(`β»οΈ Resource cleanup and monitoring enabled`);
resolve(server);
})
.on('error', (error) => {
if (error.code === 'EADDRINUSE') {
console.warn(`Port ${PORT} is in use. Attempting to detect and clean up stuck processes...`);
// In production, you could add more sophisticated port cleanup here
// For development, we'll provide helpful information
console.error(`
β Port ${PORT} is already in use!
This usually means:
1. Another instance of this server is already running
2. A previous server instance crashed and didn't release the port
3. Another application is using this port
π§ To resolve this:
1. Check running processes: netstat -ano | findstr :${PORT}
2. Kill the process: taskkill /PID <process_id>
3. Or restart your terminal/system
For production deployment, consider implementing automatic port cleanup or using process managers like PM2.
`);
reject(error);
} else {
reject(error);
}
});
// Store server reference for graceful shutdown
global.server = server;
});
}
async function gracefulShutdown() {
console.log('π Received shutdown signal, cleaning up...');
try {
// Stop watching configuration files
if (configManager) {
configManager.stopWatching();
console.log('π Configuration manager stopped');
}
// Shutdown all services through service container
await serviceContainer.shutdown();
console.log('π§ All services shut down');
// Close HTTP server
if (global.server) {
await new Promise((resolve, reject) => {
global.server.close((error) => {
if (error) {
reject(error);
} else {
console.log('π HTTP server closed');
resolve();
}
});
});
}
console.log('β
Graceful shutdown complete');
process.exit(0);
} catch (error) {
console.error('β Error during graceful shutdown:', error);
// Force shutdown after error
setTimeout(() => {
console.error('β οΈ Forcing shutdown after error');
process.exit(1);
}, 5000);
}
}
startServer(); |