diff --git a/src/config/env.ts b/src/config/env.ts new file mode 100644 index 0000000..2d2e4b4 --- /dev/null +++ b/src/config/env.ts @@ -0,0 +1,49 @@ +import { z } from 'zod'; + +const envSchema = z.object({ + NODE_ENV: z.enum(['development', 'test', 'production']).default('development'), + PORT: z.coerce.number().min(1).max(65535).default(3000), + HOST: z.string().default('0.0.0.0'), + + DATABASE_URL: z.string().min(1), + REDIS_URL: z.string().min(1).default('redis://localhost:6379'), + + JWT_SECRET: z.string().min(32), + JWT_ACCESS_TTL: z.string().default('15m'), + JWT_REFRESH_TTL: z.string().default('7d'), + + LLM_BASE_URL: z.string().url().default('http://localhost:11434/v1'), + LLM_MODEL: z.string().default('qwen2.5:14b'), + LLM_API_KEY: z.string().optional(), + LLM_TIMEOUT_MS: z.coerce.number().default(15000), + LLM_MAX_RETRIES: z.coerce.number().min(0).default(1), + LLM_TEMPERATURE: z.coerce.number().min(0).max(2).default(0.7), + LLM_MAX_TOKENS: z.coerce.number().default(2048), + + RATE_LIMIT_LOGIN: z.coerce.number().default(5), + RATE_LIMIT_REGISTER: z.coerce.number().default(3), + RATE_LIMIT_FORGOT_PASSWORD: z.coerce.number().default(3), + RATE_LIMIT_VERIFY_EMAIL: z.coerce.number().default(5), + RATE_LIMIT_API_AUTHED: z.coerce.number().default(100), + RATE_LIMIT_API_GUEST: z.coerce.number().default(30), + + CORS_ORIGINS: z.string().default('http://localhost:5173'), + SENTRY_DSN: z.string().optional(), +}); + +export type Env = z.infer; + +function parseEnv(): Env { + const result = envSchema.safeParse(process.env); + if (!result.success) { + const msg = result.error.flatten().fieldErrors; + throw new Error(`Invalid environment: ${JSON.stringify(msg)}`); + } + return result.data; +} + +export const env = parseEnv(); + +export function getCorsOrigins(): string[] { + return env.CORS_ORIGINS.split(',').map((s) => s.trim()).filter(Boolean); +}