/** * 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()); const target = env.NEXT_PUBLIC_TARGET || env.TARGET; return { env: env.NODE_ENV, target, isProduction: target === 'production' || !target, isStaging: target === 'staging', isTesting: target === 'testing', isDevelopment: target === 'development', feedbackEnabled: env.NEXT_PUBLIC_FEEDBACK_ENABLED, baseUrl: env.NEXT_PUBLIC_BASE_URL, analytics: { umami: { websiteId: env.UMAMI_WEBSITE_ID, apiEndpoint: env.UMAMI_API_ENDPOINT, enabled: Boolean(env.UMAMI_WEBSITE_ID), }, }, errors: { glitchtip: { dsn: env.SENTRY_DSN, proxyPath: '/errors', // On the client, we always enable it (it uses the tunnel / proxy defined in sentry.client.config.ts) // On the server, we only enable it if the DSN is provided. enabled: typeof window !== 'undefined' || Boolean(env.SENTRY_DSN), }, }, cache: { enabled: false, }, 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, }, directus: { url: env.DIRECTUS_URL, adminEmail: env.DIRECTUS_ADMIN_EMAIL, password: env.DIRECTUS_ADMIN_PASSWORD, token: env.DIRECTUS_API_TOKEN, internalUrl: env.INTERNAL_DIRECTUS_URL, proxyPath: '/cms', }, infraCMS: { url: env.INFRA_DIRECTUS_URL || env.DIRECTUS_URL, token: env.INFRA_DIRECTUS_TOKEN || env.DIRECTUS_API_TOKEN, }, notifications: { gotify: { url: env.GOTIFY_URL, token: env.GOTIFY_TOKEN, enabled: Boolean(env.GOTIFY_URL && env.GOTIFY_TOKEN), }, }, } 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 target() { return getConfig().target; }, get isProduction() { return getConfig().isProduction; }, get isStaging() { return getConfig().isStaging; }, get isTesting() { return getConfig().isTesting; }, get isDevelopment() { return getConfig().isDevelopment; }, 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; }, get directus() { return getConfig().directus; }, get notifications() { return getConfig().notifications; }, get feedbackEnabled() { return getConfig().feedbackEnabled; }, get infraCMS() { return getConfig().infraCMS; }, }; /** * 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), apiEndpoint: c.analytics.umami.apiEndpoint, enabled: c.analytics.umami.enabled, }, }, errors: { glitchtip: { dsn: mask(c.errors.glitchtip.dsn), enabled: c.errors.glitchtip.enabled, }, }, cache: { enabled: c.cache.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, }, directus: { url: c.directus.url, adminEmail: mask(c.directus.adminEmail), password: mask(c.directus.password), token: mask(c.directus.token), }, notifications: { gotify: { url: c.notifications.gotify.url, token: mask(c.notifications.gotify.token), enabled: c.notifications.gotify.enabled, }, }, }; }