import { z } from "zod"; const configSchema = z.object({ NODE_ENV: z.enum(["development", "production", "test"]).default("development"), PORT: z.coerce.number().default(3000), HOST: z.string().default("0.0.0.0"), DATABASE_URL: z.string(), REDIS_URL: z.string().default("redis://localhost:6379"), SESSION_SECRET: z.string().min(32), ADMIN_EMAIL: z.string().email().optional(), ADMIN_PASSWORD: z.string().min(8).optional(), TMDB_API_KEY: z.string().optional(), /** OMDb (IMDb metadata) — used as admin search/link fallback when TMDB has no match */ OMDB_API_KEY: z.string().optional(), PUBLIC_URL: z.string().url().default("http://localhost:3000"), /** Base URL shown on TV for /link (often admin UI). Falls back to PUBLIC_URL. */ CLIENT_LINK_BASE_URL: z.preprocess( (v) => (typeof v === "string" && v.trim() === "" ? undefined : v), z.string().url().optional() ), PLAYBACK_IDLE_TIMEOUT_SECONDS: z.coerce.number().default(1200), PLAYBACK_ABSOLUTE_LIFETIME_SECONDS: z.coerce.number().default(43200), NODE_HEARTBEAT_INTERVAL_SECONDS: z.coerce.number().default(30), NODE_OFFLINE_THRESHOLD_SECONDS: z.coerce.number().default(90), HIDE_UNAVAILABLE_TITLES: z.coerce.boolean().default(true), /** Google Cloud Identity Groups — Closed Testing access control */ GOOGLE_CLOUD_PROJECT_ID: z.string().optional(), GOOGLE_SERVICE_ACCOUNT_EMAIL: z.string().email().optional(), GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY: z.string().optional(), GOOGLE_WORKSPACE_CUSTOMER_ID: z.string().optional(), GOOGLE_WORKSPACE_ADMIN_EMAIL: z.string().email().optional(), PLAY_ACCESS_GROUP_ID: z.string().optional(), PLAY_ACCESS_GROUP_EMAIL: z.string().email().optional(), /** Public Closed Testing opt-in URL (shown in admin UI) */ GOOGLE_PLAY_OPT_IN_URL: z.string().url().optional(), /** Periodic reconciliation interval (ms); 0 = disabled. Default: 24h */ GOOGLE_PLAY_SYNC_INTERVAL_MS: z.coerce.number().default(86_400_000), /** * Optionele rijke XMLTV-feed (icons/desc/episode), bv. Odido. * Wordt gemerged met XUI xmltv.php op epg_channel_id — voorkomt dat XUI icons weglaat. */ IPTV_EPG_XML_URL: z.preprocess( (v) => (typeof v === "string" && v.trim() === "" ? undefined : v), z.string().url().optional() ), /** * Shared secret for the Windows token-tray agent. * Header: X-Tray-Key. Empty/unset = agent endpoints disabled. */ TOKEN_TRAY_SECRET: z.preprocess( (v) => (typeof v === "string" && v.trim() === "" ? undefined : v), z.string().min(16).optional() ), }); export type Config = z.infer; export function loadConfig(): Config { const result = configSchema.safeParse(process.env); if (!result.success) { console.error("Invalid configuration:", result.error.flatten().fieldErrors); process.exit(1); } return result.data; }