/** * Centralized configuration management for the application. * This file provides a type-safe way to access environment variables. */ import { envSchema, getRawEnv } from './env'; let memoizedConfig: ReturnType | undefined; /** * Creates and validates the configuration object. * Throws if validation fails. */ function createConfig() { const env = envSchema.parse(getRawEnv()); return { env: env.NODE_ENV, isProduction: env.NODE_ENV === 'production', isDevelopment: env.NODE_ENV === 'development', isTest: env.NODE_ENV === 'test', baseUrl: env.NEXT_PUBLIC_BASE_URL, analytics: { umami: { websiteId: env.NEXT_PUBLIC_UMAMI_WEBSITE_ID, scriptUrl: env.NEXT_PUBLIC_UMAMI_SCRIPT_URL, // The proxied path used in the frontend proxyPath: '/stats/script.js', enabled: Boolean(env.NEXT_PUBLIC_UMAMI_WEBSITE_ID), }, }, errors: { glitchtip: { // Use SENTRY_DSN for both server and client (proxied) dsn: env.SENTRY_DSN, // The proxied origin used in the frontend proxyPath: '/errors', enabled: Boolean(env.SENTRY_DSN), }, }, cache: { redis: { url: env.REDIS_URL, keyPrefix: env.REDIS_KEY_PREFIX, enabled: Boolean(env.REDIS_URL), }, }, logging: { level: env.LOG_LEVEL, }, mail: { host: env.MAIL_HOST, port: env.MAIL_PORT, user: env.MAIL_USERNAME, pass: env.MAIL_PASSWORD, from: env.MAIL_FROM, recipients: env.MAIL_RECIPIENTS, }, } as const; } /** * Returns the validated configuration. * Memoizes the result after the first call. */ export function getConfig() { if (!memoizedConfig) { memoizedConfig = createConfig(); } return memoizedConfig; } /** * Exported config object for convenience. * Uses getters to ensure it's only initialized when accessed. */ export const config = { get env() { return getConfig().env; }, get isProduction() { return getConfig().isProduction; }, get isDevelopment() { return getConfig().isDevelopment; }, get isTest() { return getConfig().isTest; }, get baseUrl() { return getConfig().baseUrl; }, get analytics() { return getConfig().analytics; }, get errors() { return getConfig().errors; }, get cache() { return getConfig().cache; }, get logging() { return getConfig().logging; }, get mail() { return getConfig().mail; }, }; /** * Helper to get a masked version of the config for logging. */ export function getMaskedConfig() { const c = getConfig(); const mask = (val: string | undefined) => (val ? `***${val.slice(-4)}` : 'not set'); return { env: c.env, baseUrl: c.baseUrl, analytics: { umami: { websiteId: mask(c.analytics.umami.websiteId), scriptUrl: c.analytics.umami.scriptUrl, enabled: c.analytics.umami.enabled, }, }, errors: { glitchtip: { dsn: mask(c.errors.glitchtip.dsn), enabled: c.errors.glitchtip.enabled, }, }, cache: { redis: { url: mask(c.cache.redis.url), keyPrefix: c.cache.redis.keyPrefix, enabled: c.cache.redis.enabled, }, }, logging: { level: c.logging.level, }, mail: { host: c.mail.host, port: c.mail.port, user: mask(c.mail.user), from: c.mail.from, recipients: c.mail.recipients, }, }; }