export interface CollectorConfig { readonly host: string; readonly port: number; readonly dataDir: string; /** boothId → bearer token. */ readonly boothTokens: ReadonlyMap; /** The single reviewer login; null = review screen and export refuse (503). */ readonly reviewer: { readonly user: string; readonly pass: string } | null; /** The trainer's job API on the compose network (http://trainer:8091); null = the * Training section is hidden and /api/training/* answers 503. */ readonly trainerUrl: string | null; } /** "booth-7:abc,booth-9:def" (commas, whitespace or newlines between pairs). */ export function parseBoothTokens(raw: string): Map { const out = new Map(); for (const pair of raw.split(/[,\s]+/)) { if (!pair) continue; const i = pair.indexOf(":"); if (i <= 0) throw new Error(`COLLECTOR_BOOTH_TOKENS: bad pair "${pair}" (want boothId:token)`); const booth = pair.slice(0, i).trim(); const token = pair.slice(i + 1).trim(); if (!booth || token.length < 16) throw new Error(`COLLECTOR_BOOTH_TOKENS: token for "${booth}" too short (>=16 chars)`); out.set(booth, token); } return out; } export function configFromEnv(env: NodeJS.ProcessEnv = process.env): CollectorConfig { const user = (env.COLLECTOR_REVIEWER_USER ?? "").trim(); const pass = env.COLLECTOR_REVIEWER_PASS ?? ""; return { host: env.COLLECTOR_HOST ?? "0.0.0.0", port: Number(env.COLLECTOR_PORT ?? 8090), dataDir: env.COLLECTOR_DATA_DIR ?? "/data", boothTokens: parseBoothTokens(env.COLLECTOR_BOOTH_TOKENS ?? ""), reviewer: user && pass.length >= 8 ? { user, pass } : null, trainerUrl: (env.COLLECTOR_TRAINER_URL ?? "").trim().replace(/\/+$/, "") || null, }; }