export interface Config { port: number; publicOrigin: string; /** Extra origins accepted for WS/CSRF checks (e.g. Vite dev server). */ allowedOrigins: string[]; dbPath: string; discord: { clientId: string; clientSecret: string } | null; sessionSecret: string; secureCookies: boolean; /** Trust X-Forwarded-For (only when running behind our reverse proxy). */ trustProxy: boolean; superadminDiscordIds: string[]; } /** The site owner's Discord ID (docs/14-admin.md ยง2). */ export const DEFAULT_SUPERADMIN = '293719842274541579'; export function loadConfig(env: Record = process.env): Config { const publicOrigin = (env.PUBLIC_ORIGIN ?? 'http://localhost:5173').replace(/\/$/, ''); const extra = (env.ALLOWED_ORIGINS ?? '').split(',').map((s) => s.trim()).filter(Boolean); const clientId = env.DISCORD_CLIENT_ID; const clientSecret = env.DISCORD_CLIENT_SECRET; const sessionSecret = env.SESSION_SECRET ?? ''; if (publicOrigin.startsWith('https://') && sessionSecret.length < 32) { throw new Error('SESSION_SECRET must be at least 32 characters in production'); } return { port: Number(env.PORT ?? 3000), publicOrigin, allowedOrigins: [publicOrigin, ...extra], dbPath: env.DB_PATH ?? './data/app.db', discord: clientId && clientSecret ? { clientId, clientSecret } : null, sessionSecret: sessionSecret || 'dev-only-insecure-session-secret-change-me', secureCookies: publicOrigin.startsWith('https://'), trustProxy: env.TRUST_PROXY === '1', superadminDiscordIds: (env.SUPERADMIN_DISCORD_IDS ?? DEFAULT_SUPERADMIN).split(',').map((s) => s.trim()).filter(Boolean), }; }