Files
compor-academy/server/src/config.ts
Cauê Faleiros e15a44e733
All checks were successful
CI / Validate frontend and API (push) Successful in 44s
CI / Build and publish Docker images (push) Successful in 22s
feat: add Bunny Storage course cover uploads
2026-09-04 13:39:30 -03:00

61 lines
3.2 KiB
TypeScript

import 'dotenv/config';
import { z } from 'zod';
const optionalEnvironmentValue = <T extends z.ZodTypeAny>(schema: T) =>
z.preprocess((value) => value === '' ? undefined : value, schema.optional());
const environmentSchema = z.object({
API_PORT: z.coerce.number().int().positive().default(3001),
DATABASE_URL: z.string().url().default('postgres://compor:compor_local_password@localhost:5433/compor_hub'),
FRONTEND_ORIGIN: z.string().url().default('http://localhost:3000'),
APP_ENV: z.enum(['development', 'test', 'production']).default('development'),
JWT_SECRET: z.string().min(32).default('development-only-secret-change-before-production'),
SUPERADMIN_EMAIL: optionalEnvironmentValue(z.string().email()),
SUPERADMIN_PASSWORD: optionalEnvironmentValue(z.string().min(12)),
SUPERADMIN_NAME: z.string().min(1).max(120).default('Compor HUB Superadmin'),
AUTH_RATE_LIMIT_MAX: z.coerce.number().int().min(1).max(1000).default(10),
AUTH_RATE_LIMIT_WINDOW_SECONDS: z.coerce.number().int().min(60).max(86_400).default(900),
JWT_SESSION_TTL: z.string().regex(/^\d+[smhd]$/).default('7d'),
INVITATION_TTL_HOURS: z.coerce.number().int().min(1).max(24 * 90).default(24 * 7),
PASSWORD_RESET_TTL_HOURS: z.coerce.number().int().min(1).max(24 * 30).default(24),
AUDIT_LOG_PAGE_SIZE: z.coerce.number().int().min(10).max(500).default(50),
BUNNY_STREAM_LIBRARY_ID: optionalEnvironmentValue(z.coerce.number().int().positive()),
BUNNY_STREAM_API_KEY: optionalEnvironmentValue(z.string().min(20)),
BUNNY_EMBED_TOKEN_KEY: optionalEnvironmentValue(z.string().min(20)),
BUNNY_WEBHOOK_SECRET: optionalEnvironmentValue(z.string().min(20)),
BUNNY_EMBED_TOKEN_TTL_SECONDS: z.coerce.number().int().min(60).max(86_400).default(600),
BUNNY_MAX_UPLOAD_MB: z.coerce.number().int().min(1).max(5120).default(5120),
BUNNY_STORAGE_ZONE: optionalEnvironmentValue(z.string().trim().min(1).max(120)),
BUNNY_STORAGE_PASSWORD: optionalEnvironmentValue(z.string().min(1)),
BUNNY_STORAGE_ENDPOINT: optionalEnvironmentValue(z.string().url()),
BUNNY_STORAGE_CDN_HOST: optionalEnvironmentValue(z.string().url()),
BUNNY_COVER_MAX_UPLOAD_MB: z.coerce.number().int().min(1).max(50).default(10),
});
export const config = environmentSchema.parse(process.env);
if (config.APP_ENV === 'production' && config.JWT_SECRET === 'development-only-secret-change-before-production') {
throw new Error('JWT_SECRET must be set to a unique value in production.');
}
const bunnyConfigurationValues = [
config.BUNNY_STREAM_LIBRARY_ID,
config.BUNNY_STREAM_API_KEY,
config.BUNNY_EMBED_TOKEN_KEY,
];
if (bunnyConfigurationValues.some(Boolean) && !bunnyConfigurationValues.every(Boolean)) {
throw new Error('BUNNY_STREAM_LIBRARY_ID, BUNNY_STREAM_API_KEY, and BUNNY_EMBED_TOKEN_KEY must be configured together.');
}
const bunnyStorageConfigurationValues = [
config.BUNNY_STORAGE_ZONE,
config.BUNNY_STORAGE_PASSWORD,
config.BUNNY_STORAGE_ENDPOINT,
config.BUNNY_STORAGE_CDN_HOST,
];
if (bunnyStorageConfigurationValues.some(Boolean) && !bunnyStorageConfigurationValues.every(Boolean)) {
throw new Error('BUNNY_STORAGE_ZONE, BUNNY_STORAGE_PASSWORD, BUNNY_STORAGE_ENDPOINT, and BUNNY_STORAGE_CDN_HOST must be configured together.');
}