import 'dotenv/config'; import { z } from 'zod'; const optionalEnvironmentValue = (schema: T) => z.preprocess((value) => value === '' ? undefined : value, schema.optional()); const environmentSchema = z.object({ API_PORT: z.coerce.number().int().positive().default(3001), DATABASE_URL: z.string().url().default('postgres://compor:compor_local_password@localhost:5433/compor_hub'), FRONTEND_ORIGIN: z.string().url().default('http://localhost:3000'), APP_ENV: z.enum(['development', 'test', 'production']).default('development'), JWT_SECRET: z.string().min(32).default('development-only-secret-change-before-production'), SUPERADMIN_EMAIL: optionalEnvironmentValue(z.string().email()), SUPERADMIN_PASSWORD: optionalEnvironmentValue(z.string().min(12)), SUPERADMIN_NAME: z.string().min(1).max(120).default('Compor HUB Superadmin'), AUTH_RATE_LIMIT_MAX: z.coerce.number().int().min(1).max(1000).default(10), AUTH_RATE_LIMIT_WINDOW_SECONDS: z.coerce.number().int().min(60).max(86_400).default(900), JWT_SESSION_TTL: z.string().regex(/^\d+[smhd]$/).default('7d'), INVITATION_TTL_HOURS: z.coerce.number().int().min(1).max(24 * 90).default(24 * 7), PASSWORD_RESET_TTL_HOURS: z.coerce.number().int().min(1).max(24 * 30).default(24), AUDIT_LOG_PAGE_SIZE: z.coerce.number().int().min(10).max(500).default(50), BUNNY_STREAM_LIBRARY_ID: optionalEnvironmentValue(z.coerce.number().int().positive()), BUNNY_STREAM_API_KEY: optionalEnvironmentValue(z.string().min(20)), BUNNY_EMBED_TOKEN_KEY: optionalEnvironmentValue(z.string().min(20)), BUNNY_WEBHOOK_SECRET: optionalEnvironmentValue(z.string().min(20)), BUNNY_EMBED_TOKEN_TTL_SECONDS: z.coerce.number().int().min(60).max(86_400).default(600), BUNNY_MAX_UPLOAD_MB: z.coerce.number().int().min(1).max(5120).default(5120), BUNNY_STORAGE_ZONE: optionalEnvironmentValue(z.string().trim().min(1).max(120)), BUNNY_STORAGE_PASSWORD: optionalEnvironmentValue(z.string().min(1)), BUNNY_STORAGE_ENDPOINT: optionalEnvironmentValue(z.string().url()), BUNNY_STORAGE_CDN_HOST: optionalEnvironmentValue(z.string().url()), BUNNY_COVER_MAX_UPLOAD_MB: z.coerce.number().int().min(1).max(50).default(10), }); export const config = environmentSchema.parse(process.env); if (config.APP_ENV === 'production' && config.JWT_SECRET === 'development-only-secret-change-before-production') { throw new Error('JWT_SECRET must be set to a unique value in production.'); } const bunnyConfigurationValues = [ config.BUNNY_STREAM_LIBRARY_ID, config.BUNNY_STREAM_API_KEY, config.BUNNY_EMBED_TOKEN_KEY, ]; if (bunnyConfigurationValues.some(Boolean) && !bunnyConfigurationValues.every(Boolean)) { throw new Error('BUNNY_STREAM_LIBRARY_ID, BUNNY_STREAM_API_KEY, and BUNNY_EMBED_TOKEN_KEY must be configured together.'); } const bunnyStorageConfigurationValues = [ config.BUNNY_STORAGE_ZONE, config.BUNNY_STORAGE_PASSWORD, config.BUNNY_STORAGE_ENDPOINT, config.BUNNY_STORAGE_CDN_HOST, ]; if (bunnyStorageConfigurationValues.some(Boolean) && !bunnyStorageConfigurationValues.every(Boolean)) { throw new Error('BUNNY_STORAGE_ZONE, BUNNY_STORAGE_PASSWORD, BUNNY_STORAGE_ENDPOINT, and BUNNY_STORAGE_CDN_HOST must be configured together.'); }