import express from 'express'; import cors from 'cors'; import helmet from 'helmet'; import path from 'path'; import { fileURLToPath } from 'url'; import { keysRouter } from './routes/keys.js'; import { modelsRouter } from './routes/models.js'; import { proxyRouter, isRetryableError } from './routes/proxy.js'; import { fallbackRouter } from './routes/fallback.js'; import { analyticsRouter } from './routes/analytics.js'; import { healthRouter } from './routes/health.js'; import { settingsRouter } from './routes/settings.js'; import { errorHandler } from './middleware/errorHandler.js'; import { routeRequest, recordRateLimitHit, recordSuccess } from './services/router.js'; import { recordRequest, recordTokens, setCooldown } from './services/ratelimit.js'; import { getUnifiedApiKey } from './db/index.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); export function createApp() { const app = express(); app.use(helmet({ contentSecurityPolicy: false, hsts: false })); app.use(cors()); app.use(express.json({ limit: '1mb' })); // Admin Dashboard Authentication (Bearer Token) const adminPassword = process.env.ADMIN_PASSWORD; if (adminPassword) { // Login endpoint app.post('/api/login', (req, res) => { const { password } = req.body; if (password === adminPassword) { res.json({ token: adminPassword }); // Using password as token for simplicity } else { res.status(401).json({ error: 'Invalid password' }); } }); app.use((req, res, next) => { // Allow proxy endpoints, health check, and login if (req.path.startsWith('/v1/')) return next(); if (req.path === '/api' && req.method === 'POST') return next(); if (req.path === '/api/ping') return next(); if (req.path === '/api/login') return next(); // Only protect /api/* management routes if (req.path.startsWith('/api/')) { const authHeader = req.headers.authorization || ''; const token = authHeader.replace(/^Bearer\s+/i, ''); if (token === adminPassword) { return next(); } return res.status(401).json({ error: 'Authentication required' }); } // Allow static file serving (UI routing will handle redirects) return next(); }); } // API routes app.use('/api/keys', keysRouter); app.use('/api/models', modelsRouter); app.use('/api/fallback', fallbackRouter); app.use('/api/analytics', analyticsRouter); app.use('/api/health', healthRouter); app.use('/api/settings', settingsRouter); // Custom simple API for standard apps app.post('/api', async (req, res) => { const { prompt } = req.body; if (!prompt || typeof prompt !== 'string') { return res.status(400).json({ status: 'error', message: 'Missing prompt' }); } // Auth check const authHeader = req.headers.authorization; const isLocal = req.ip === '127.0.0.1' || req.ip === '::1' || req.ip === '::ffff:127.0.0.1'; if (authHeader && !isLocal) { const token = authHeader.replace(/^Bearer\s+/i, ''); const unifiedKey = getUnifiedApiKey(); if (token !== unifiedKey) { return res.status(401).json({ status: 'error', message: 'Invalid API key' }); } } const messages = [{ role: 'user' as const, content: prompt }]; const estimatedTokens = Math.ceil(prompt.length / 4) + 1000; const skipKeys = new Set(); let lastError: any = null; for (let attempt = 0; attempt < 20; attempt++) { try { const route = routeRequest(estimatedTokens, skipKeys.size > 0 ? skipKeys : undefined); recordRequest(route.platform, route.modelId, route.keyId); const result = await route.provider.chatCompletion( route.apiKey, messages, route.modelId, { temperature: 0.7 } ); const totalTokens = result.usage?.total_tokens ?? 0; recordTokens(route.platform, route.modelId, route.keyId, totalTokens); recordSuccess(route.modelDbId); return res.json({ status: 'success', text: result.choices[0]?.message?.content || '' }); } catch (err: any) { lastError = err; if (isRetryableError(err)) { // If we have a route, put it on cooldown try { const route = routeRequest(estimatedTokens, skipKeys.size > 0 ? skipKeys : undefined); const skipId = `${route.platform}:${route.modelId}:${route.keyId}`; skipKeys.add(skipId); setCooldown(route.platform, route.modelId, route.keyId, 120_000); recordRateLimitHit(route.modelDbId); } catch { // No more routes available } console.log(`[CustomAPI] ${err.message.slice(0, 60)}, falling back (attempt ${attempt + 1}/20)`); continue; } // Non-retryable error return res.status(500).json({ status: 'error', message: err.message }); } } res.status(429).json({ status: 'error', message: `All models failed after retries. Last: ${lastError?.message}` }); }); // OpenAI-compatible proxy app.use('/v1', proxyRouter); // Health check app.get('/api/ping', (_req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString() }); }); // Error handler (for API routes) app.use(errorHandler); // Serve client static files (after API error handler) const clientDist = path.resolve(__dirname, '../../client/dist'); app.use(express.static(clientDist)); // SPA fallback — serve index.html for non-API routes app.use((req, res, next) => { if (req.path === '/api' || req.path.startsWith('/api/') || req.path.startsWith('/v1/')) { next(); return; } res.sendFile(path.join(clientDist, 'index.html')); }); return app; }