| 109 | } |
| 110 | |
| 111 | startWebServer() { |
| 112 | const app = express(); |
| 113 | const configuredPort = Number(this.config.api?.port || process.env.PORT || 3000); |
| 114 | const maxPortRetryAttempts = Number(process.env.PORT_RETRY_ATTEMPTS || 5); |
| 115 | const host = process.env.WEB_HOST || '0.0.0.0'; |
| 116 | const corsOrigin = this.config.api?.cors?.origin || '*'; |
| 117 | |
| 118 | app.use((req, res, next) => { |
| 119 | const allowedOrigins = Array.isArray(corsOrigin) ? corsOrigin : [corsOrigin]; |
| 120 | const origin = req.headers.origin; |
| 121 | |
| 122 | if (allowedOrigins.includes('*') || allowedOrigins.includes(origin)) { |
| 123 | res.header('Access-Control-Allow-Origin', origin || '*'); |
| 124 | } |
| 125 | res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); |
| 126 | res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization'); |
| 127 | |
| 128 | if (req.method === 'OPTIONS') { |
| 129 | return res.sendStatus(200); |
| 130 | } |
| 131 | next(); |
| 132 | }); |
| 133 | |
| 134 | const requestCounts = new Map(); |
| 135 | const windowMs = 60000; |
| 136 | const maxRequests = this.config.api?.rateLimit?.max || 100; |
| 137 | |
| 138 | app.use((req, res, next) => { |
| 139 | const ip = req.ip; |
| 140 | const now = Date.now(); |
| 141 | const windowStart = now - windowMs; |
| 142 | |
| 143 | if (!requestCounts.has(ip)) { |
| 144 | requestCounts.set(ip, []); |
| 145 | } |
| 146 | |
| 147 | const times = requestCounts.get(ip).filter(t => t > windowStart); |
| 148 | |
| 149 | if (times.length >= maxRequests) { |
| 150 | return res.status(429).json({ error: 'Too many requests' }); |
| 151 | } |
| 152 | |
| 153 | times.push(now); |
| 154 | requestCounts.set(ip, times); |
| 155 | next(); |
| 156 | }); |
| 157 | |
| 158 | app.get('/health', (req, res) => { |
| 159 | const dbStatus = this.db?.getStatus?.() || { isDegraded: 'unknown' }; |
| 160 | const status = { |
| 161 | status: 'healthy', |
| 162 | timestamp: new Date().toISOString(), |
| 163 | uptime: process.uptime(), |
| 164 | database: { |
| 165 | connected: dbStatus.connectionType !== 'none', |
| 166 | degraded: dbStatus.isDegraded, |
| 167 | type: dbStatus.connectionType |
| 168 | } |