import express from 'express'; import session from 'express-session'; import helmet from 'helmet'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; import { config } from './config.js'; import { initDatabase, closeDatabase } from './db/index.js'; import { createInitialAdmin } from './services/initService.js'; import { SQLiteSessionStore } from './services/sessionService.js'; import { apiRateLimiter } from './middleware/rateLimit.js'; import { errorHandler } from './middleware/errorHandler.js'; import { logger } from './utils/logger.js'; import authRoutes from './routes/auth.js'; import userRoutes from './routes/users.js'; import contentRoutes from './routes/content.js'; import generateRoutes from './routes/generate.js'; import healthRoutes from './routes/health.js'; const __dirname = dirname(fileURLToPath(import.meta.url)); const app = express(); // Trust proxy (for Cloudflare) if (config.trustProxy) { app.set('trust proxy', 1); } // Security headers app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "'unsafe-inline'"], styleSrc: ["'self'", "'unsafe-inline'"], imgSrc: ["'self'", 'data:', 'blob:'], mediaSrc: ["'self'", 'data:', 'blob:'], connectSrc: ["'self'"], }, }, crossOriginEmbedderPolicy: false, })); // Body parsing app.use(express.json({ limit: '15mb' })); app.use(express.urlencoded({ extended: true })); // Session middleware const sessionStore = new SQLiteSessionStore(); app.use(session({ secret: config.sessionSecret, name: 'sid', resave: false, saveUninitialized: false, store: sessionStore, cookie: { httpOnly: true, secure: config.isProduction, sameSite: 'strict', maxAge: config.sessionMaxAge, }, })); // Rate limiting for API routes app.use('/api/', apiRateLimiter); // API routes app.use('/api/auth', authRoutes); app.use('/api/users', userRoutes); app.use('/api/content', contentRoutes); app.use('/api/generate', generateRoutes); // Health check routes (no rate limiting) app.use('/health', healthRoutes); // Static files app.use(express.static(join(__dirname, '..', 'public'))); // SPA fallback - serve index.html for all non-API routes app.get('*', (req, res, next) => { if (req.path.startsWith('/api/') || req.path.startsWith('/health')) { return next(); } res.sendFile(join(__dirname, '..', 'public', 'index.html')); }); // Error handler app.use(errorHandler); // Startup async function start() { try { logger.info('Starting ComfyUI Frontend Service...'); // Initialize database initDatabase(); logger.info({ dbPath: config.dbPath }, 'Database initialized'); // Create initial admin user if needed await createInitialAdmin(); // Start server const server = app.listen(config.port, () => { logger.info({ port: config.port, env: config.nodeEnv }, 'Server started'); }); // Graceful shutdown const shutdown = async (signal: string) => { logger.info({ signal }, 'Shutdown signal received'); server.close(() => { logger.info('HTTP server closed'); sessionStore.close(); closeDatabase(); logger.info('Database closed'); process.exit(0); }); // Force exit after 10 seconds setTimeout(() => { logger.error('Forced shutdown after timeout'); process.exit(1); }, 10000); }; process.on('SIGTERM', () => shutdown('SIGTERM')); process.on('SIGINT', () => shutdown('SIGINT')); } catch (error) { logger.error({ error }, 'Failed to start server'); process.exit(1); } } start();