import * as v from "valibot"; import "dotenv/config"; /** * Settings schema using Valibot for validation */ export const settingsSchema = v.object({ appName: v.string(), nodeEnv: v.string(), logLevel: v.string(), isDevelopment: v.optional(v.boolean()), redisUrl: v.string(), databaseUrl: v.string(), internalApiKey: v.string(), processorApiUrl: v.string(), betterAuthUrl: v.string(), betterAuthSecret: v.string(), twoFaSecret: v.string(), twofaSessionExpiryMinutes: v.optional(v.number()), twofaRequiredHours: v.optional(v.number()), defaultAdminEmail: v.string(), googleClientId: v.string(), googleClientSecret: v.string(), resendApiKey: v.string(), fromEmail: v.string(), qstashUrl: v.string(), qstashToken: v.string(), qstashCurrentSigningKey: v.string(), qstashNextSigningKey: v.string(), axiomDatasetName: v.string(), axiomApiToken: v.string(), // R2/Object Storage settings r2BucketName: v.string(), r2Region: v.string(), r2Endpoint: v.string(), r2AccessKey: v.string(), r2SecretKey: v.string(), r2PublicUrl: v.optional(v.string()), // File upload settings maxFileSize: v.number(), allowedMimeTypes: v.array(v.string()), allowedExtensions: v.array(v.string()), }); export type Settings = v.InferOutput; /** * Helper to get environment variable with default value */ function getEnv(key: string, defaultValue: string = ""): string { return process.env[key] ?? defaultValue; } /** * Helper to get environment variable as number with default value */ function getEnvNumber(key: string, defaultValue: number): number { const value = process.env[key]; if (!value) return defaultValue; const parsed = Number(value); return Number.isNaN(parsed) ? defaultValue : parsed; } /** * Parse comma-separated string into array */ function parseCommaSeparated(value: string): string[] { return value .split(",") .map((item) => item.trim()) .filter((item) => item.length > 0); } /** * Load and validate settings from environment variables */ function loadSettings(): Settings { const nodeEnv = getEnv("NODE_ENV", "development"); const rawSettings = { appName: getEnv("APP_NAME", "App"), nodeEnv, logLevel: getEnv("LOG_LEVEL", "info"), isDevelopment: nodeEnv === "development", redisUrl: getEnv("REDIS_URL", "redis://localhost:6379"), databaseUrl: getEnv("DATABASE_URL"), internalApiKey: getEnv("INTERNAL_API_KEY"), processorApiUrl: getEnv("PROCESSOR_API_URL", "http://localhost:3000"), betterAuthUrl: getEnv("BETTER_AUTH_URL"), betterAuthSecret: getEnv("BETTER_AUTH_SECRET"), twoFaSecret: getEnv("TWOFA_SECRET"), twofaSessionExpiryMinutes: getEnvNumber( "TWOFA_SESSION_EXPIRY_MINUTES", 10, ), twofaRequiredHours: getEnvNumber("TWOFA_REQUIRED_HOURS", 24), defaultAdminEmail: getEnv("DEFAULT_ADMIN_EMAIL"), googleClientId: getEnv("GOOGLE_CLIENT_ID"), googleClientSecret: getEnv("GOOGLE_CLIENT_SECRET"), resendApiKey: getEnv("RESEND_API_KEY"), fromEmail: getEnv("FROM_EMAIL"), qstashUrl: getEnv("QSTASH_URL"), qstashToken: getEnv("QSTASH_TOKEN"), qstashCurrentSigningKey: getEnv("QSTASH_CURRENT_SIGNING_KEY"), qstashNextSigningKey: getEnv("QSTASH_NEXT_SIGNING_KEY"), axiomDatasetName: getEnv("AXIOM_DATASET_NAME"), axiomApiToken: getEnv("AXIOM_API_TOKEN"), // R2/Object Storage settings r2BucketName: getEnv("R2_BUCKET_NAME"), r2Region: getEnv("R2_REGION", "auto"), r2Endpoint: getEnv("R2_ENDPOINT"), r2AccessKey: getEnv("R2_ACCESS_KEY"), r2SecretKey: getEnv("R2_SECRET_KEY"), r2PublicUrl: getEnv("R2_PUBLIC_URL") || undefined, // File upload settings maxFileSize: getEnvNumber("MAX_FILE_SIZE", 10485760), // 10MB default allowedMimeTypes: parseCommaSeparated( getEnv( "ALLOWED_MIME_TYPES", "image/jpeg,image/png,image/webp,image/gif,application/pdf,text/plain", ), ), allowedExtensions: parseCommaSeparated( getEnv("ALLOWED_EXTENSIONS", "jpg,jpeg,png,webp,gif,pdf,txt"), ), }; try { return v.parse(settingsSchema, rawSettings); } catch (error) { console.error("❌ Settings validation failed:"); if (error instanceof v.ValiError) { for (const issue of error.issues) { console.error( ` - ${issue.path?.map((p: any) => p.key).join(".")}: ${issue.message}`, ); } } else { console.error(error); } throw new Error( "Failed to load settings. Check environment variables.", ); } } export const settings = loadSettings(); export const getSetting = (key: K): Settings[K] => { return settings[key]; }; console.log(`✅ Settings loaded | ${settings.appName} (${settings.nodeEnv})`);