Basic Architecture of Sport Web
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* The process refuses to boot with an invalid environment.
|
||||
*
|
||||
* Failing at startup — loudly, with every problem listed at once — is the only
|
||||
* acceptable behaviour. A missing JWT secret discovered at 2am by a customer
|
||||
* hitting login is not.
|
||||
*/
|
||||
const durationSchema = z.string().regex(/^\d+(ms|s|m|h|d)$/, 'Use a duration like 15m, 24h or 30d');
|
||||
|
||||
export const envSchema = z.object({
|
||||
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
|
||||
PORT: z.coerce.number().int().min(1).max(65535).default(4000),
|
||||
API_GLOBAL_PREFIX: z.string().default('api'),
|
||||
APP_VERSION: z.string().default('0.0.0'),
|
||||
|
||||
CORS_ORIGINS: z
|
||||
.string()
|
||||
.default('')
|
||||
.transform((value) =>
|
||||
value
|
||||
.split(',')
|
||||
.map((origin) => origin.trim())
|
||||
.filter(Boolean),
|
||||
),
|
||||
|
||||
DATABASE_URL: z.string().startsWith('postgresql://'),
|
||||
|
||||
REDIS_URL: z.string().startsWith('redis'),
|
||||
REDIS_KEY_PREFIX: z.string().default('sport:'),
|
||||
|
||||
JWT_ACCESS_SECRET: z.string().min(32, 'Use at least 32 characters'),
|
||||
JWT_REFRESH_SECRET: z.string().min(32, 'Use at least 32 characters'),
|
||||
JWT_ACCESS_TTL: durationSchema.default('15m'),
|
||||
JWT_REFRESH_TTL: durationSchema.default('30d'),
|
||||
JWT_ISSUER: z.string().default('sport-store'),
|
||||
|
||||
STORAGE_ENDPOINT: z.url(),
|
||||
STORAGE_REGION: z.string().default('auto'),
|
||||
STORAGE_BUCKET: z.string().min(1),
|
||||
STORAGE_ACCESS_KEY_ID: z.string().min(1),
|
||||
STORAGE_SECRET_ACCESS_KEY: z.string().min(1),
|
||||
STORAGE_FORCE_PATH_STYLE: z.stringbool().default(false),
|
||||
STORAGE_PUBLIC_URL: z.url(),
|
||||
|
||||
RATE_LIMIT_TTL_SECONDS: z.coerce.number().int().positive().default(60),
|
||||
RATE_LIMIT_MAX: z.coerce.number().int().positive().default(120),
|
||||
|
||||
LOG_LEVEL: z.enum(['fatal', 'error', 'warn', 'info', 'debug', 'trace']).default('info'),
|
||||
LOG_PRETTY: z.stringbool().default(false),
|
||||
});
|
||||
|
||||
export type Env = z.infer<typeof envSchema>;
|
||||
|
||||
export function validateEnv(raw: Record<string, unknown>): Env {
|
||||
const result = envSchema.safeParse(raw);
|
||||
|
||||
if (!result.success) {
|
||||
const details = result.error.issues
|
||||
.map((issue) => ` - ${issue.path.join('.') || '(root)'}: ${issue.message}`)
|
||||
.join('\n');
|
||||
throw new Error(`Invalid environment configuration:\n${details}`);
|
||||
}
|
||||
|
||||
if (result.data.JWT_ACCESS_SECRET === result.data.JWT_REFRESH_SECRET) {
|
||||
throw new Error('JWT_ACCESS_SECRET and JWT_REFRESH_SECRET must be different values.');
|
||||
}
|
||||
|
||||
return result.data;
|
||||
}
|
||||
Reference in New Issue
Block a user