/opt/mawid/apps/api/src/config
Edit: /opt/mawid/apps/api/src/config/env.ts (2072B)
import { z } from 'zod';
// WhatsApp / OpenAI credentials are only exercised from Phase 2/3 onward,
// so they may be empty in development and test — production requires them.
const PRODUCTION_REQUIRED = [
'OPENAI_API_KEY',
'WA_PHONE_NUMBER_ID',
'WA_BUSINESS_ACCOUNT_ID',
'WA_ACCESS_TOKEN',
'WA_WEBHOOK_VERIFY_TOKEN',
'WA_APP_SECRET',
] as const;
export const envSchema = z
.object({
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
API_PORT: z.coerce.number().int().positive().default(3001),
DATABASE_URL: z.string().min(1),
REDIS_URL: z.string().min(1),
JWT_SECRET: z.string().min(16, 'JWT_SECRET must be at least 16 characters'),
OPENAI_API_KEY: z.string().default(''),
WA_PHONE_NUMBER_ID: z.string().default(''),
WA_BUSINESS_ACCOUNT_ID: z.string().default(''),
WA_ACCESS_TOKEN: z.string().default(''),
WA_WEBHOOK_VERIFY_TOKEN: z.string().default(''),
WA_APP_SECRET: z.string().default(''),
SENTRY_DSN: z.string().default(''),
APP_BASE_URL: z.string().url(),
DASHBOARD_BASE_URL: z.string().url(),
TZ: z.string().default('Europe/Istanbul'),
FEATURE_ECHO_MODE: z
.enum(['true', 'false'])
.default('false')
.transform((v) => v === 'true'),
})
.superRefine((env, ctx) => {
if (env.NODE_ENV !== 'production') return;
for (const key of PRODUCTION_REQUIRED) {
if (!env[key]) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: [key],
message: `${key} is required in production`,
});
}
}
});
export type Env = z.infer
;
/** Throws (refusing boot) when the environment is invalid. */
export function validateEnv(config: Record): Env {
const parsed = envSchema.safeParse(config);
if (!parsed.success) {
const details = parsed.error.issues
.map((i) => ` - ${i.path.join('.') || '(root)'}: ${i.message}`)
.join('\n');
throw new Error(`Invalid environment configuration:\n${details}`);
}
return parsed.data;
}