/** * EmailSorter Backend Server * Main entry point */ import 'dotenv/config' import express from 'express' import cors from 'cors' import { fileURLToPath } from 'url' import { dirname, join } from 'path' // Config & Middleware import { config, validateConfig } from './config/index.mjs' import { errorHandler, asyncHandler, NotFoundError, ValidationError } from './middleware/errorHandler.mjs' import { respond } from './utils/response.mjs' import { logger, log } from './middleware/logger.mjs' import { limiters } from './middleware/rateLimit.mjs' // Routes import oauthRoutes from './routes/oauth.mjs' import emailRoutes from './routes/email.mjs' import stripeRoutes from './routes/stripe.mjs' import apiRoutes from './routes/api.mjs' import analyticsRoutes from './routes/analytics.mjs' const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename) // Validate configuration validateConfig() // Create Express app const app = express() // Trust proxy (for rate limiting behind reverse proxy) app.set('trust proxy', 1) // Request ID middleware app.use((req, res, next) => { req.id = Math.random().toString(36).substring(2, 15) res.setHeader('X-Request-ID', req.id) next() }) // CORS app.use(cors(config.cors)) // Request logging app.use(logger({ skip: (req) => req.path === '/api/health' || req.path.startsWith('/assets'), })) // Rate limiting app.use('/api', limiters.api) // Static files app.use(express.static(join(__dirname, '..', 'public'))) // Body parsing (BEFORE routes, AFTER static) // Note: Stripe webhook needs raw body, handled in stripe routes app.use('/api', express.json({ limit: '1mb' })) app.use('/api', express.urlencoded({ extended: true })) // Health check (no rate limit) app.get('/api/health', (req, res) => { res.json({ success: true, data: { status: 'healthy', timestamp: new Date().toISOString(), version: process.env.npm_package_version || '1.0.0', environment: config.nodeEnv, uptime: Math.floor(process.uptime()), }, }) }) // API Routes app.use('/api/oauth', oauthRoutes) app.use('/api/email', emailRoutes) app.use('/api/subscription', stripeRoutes) app.use('/api/analytics', analyticsRoutes) app.use('/api', apiRoutes) // Preferences endpoints (inline for simplicity) import { userPreferences } from './services/database.mjs' app.get('/api/preferences', asyncHandler(async (req, res) => { const { userId } = req.query if (!userId) throw new ValidationError('userId ist erforderlich') const prefs = await userPreferences.getByUser(userId) respond.success(res, prefs?.preferences || { vipSenders: [], blockedSenders: [], customRules: [], priorityTopics: [], }) })) app.post('/api/preferences', asyncHandler(async (req, res) => { const { userId, ...preferences } = req.body if (!userId) throw new ValidationError('userId ist erforderlich') await userPreferences.upsert(userId, preferences) respond.success(res, null, 'Einstellungen gespeichert') })) // Legacy Stripe webhook endpoint app.use('/stripe', stripeRoutes) // 404 handler for API routes app.use('/api/*', (req, res, next) => { next(new NotFoundError('Endpoint')) }) // SPA fallback for non-API routes app.get('*', (req, res) => { res.sendFile(join(__dirname, '..', 'public', 'index.html')) }) // Global error handler (must be last) app.use(errorHandler) // Graceful shutdown let server function gracefulShutdown(signal) { log.info(`${signal} empfangen, Server wird heruntergefahren...`) server.close(() => { log.info('HTTP Server geschlossen') process.exit(0) }) // Force close after 10 seconds setTimeout(() => { log.error('Erzwungenes Herunterfahren') process.exit(1) }, 10000) } process.on('SIGTERM', () => gracefulShutdown('SIGTERM')) process.on('SIGINT', () => gracefulShutdown('SIGINT')) // Handle uncaught errors process.on('uncaughtException', (err) => { log.error('Uncaught Exception:', { error: err.message, stack: err.stack }) process.exit(1) }) process.on('unhandledRejection', (reason, promise) => { log.error('Unhandled Rejection:', { reason, promise }) }) // Start server server = app.listen(config.port, () => { console.log('') log.success(`Server gestartet auf Port ${config.port}`) log.info(`Frontend URL: ${config.frontendUrl}`) log.info(`Environment: ${config.nodeEnv}`) console.log('') console.log(` 🌐 API: http://localhost:${config.port}/api`) console.log(` 💚 Health: http://localhost:${config.port}/api/health`) console.log('') }) export default app