import { z } from 'zod'; import crypto from 'node:crypto'; const envSchema = z.object({ NODE_ENV: z.enum(['development', 'production', 'test']).default('development'), BACKEND_PORT: z.coerce.number().int().positive().default(3000), LOG_LEVEL: z.enum(['fatal', 'error', 'warn', 'info', 'debug', 'trace']).default('info'), DATABASE_URL: z.string().url(), PUBLIC_APP_URL: z.string().url().default('http://localhost:8080'), UPLOADS_DIR: z.string().default('./uploads'), JWT_SECRET: z.string().min(32, 'JWT_SECRET must be at least 32 chars'), COOKIE_SECRET: z.string().min(32, 'COOKIE_SECRET must be at least 32 chars'), USER1_USERNAME: z.string().min(3).max(64), USER1_PASSWORD_HASH: z.string().min(20, 'USER1_PASSWORD_HASH must be a bcrypt hash'), USER1_SLUG: z.string().min(3).max(32), USER1_DISPLAY_NAME: z.string().min(1).max(64), USER2_USERNAME: z.string().min(3).max(64), USER2_PASSWORD_HASH: z.string().min(20, 'USER2_PASSWORD_HASH must be a bcrypt hash'), USER2_SLUG: z.string().min(3).max(32), USER2_DISPLAY_NAME: z.string().min(1).max(64), }); export type Env = z.infer; function parseEnv(): Env { const parsed = envSchema.safeParse(process.env); if (!parsed.success) { // eslint-disable-next-line no-console console.error('\nInvalid environment configuration:\n'); for (const issue of parsed.error.issues) { // eslint-disable-next-line no-console console.error(` - ${issue.path.join('.')}: ${issue.message}`); } process.exit(1); } return parsed.data; } export const env = parseEnv(); export interface EnvUserConfig { id: string; username: string; passwordHash: string; slug: string; displayName: string; } function stableUserId(username: string): string { // 24-char stable id derived from username so DB seed can upsert deterministically // without depending on any external secret. return 'u_' + crypto.createHash('sha256').update(`user:${username}`).digest('hex').slice(0, 22); } export function resolveUsers(): EnvUserConfig[] { const usernames = new Set(); const slugs = new Set(); const users: EnvUserConfig[] = [ { id: stableUserId(env.USER1_USERNAME), username: env.USER1_USERNAME, passwordHash: env.USER1_PASSWORD_HASH, slug: env.USER1_SLUG, displayName: env.USER1_DISPLAY_NAME, }, { id: stableUserId(env.USER2_USERNAME), username: env.USER2_USERNAME, passwordHash: env.USER2_PASSWORD_HASH, slug: env.USER2_SLUG, displayName: env.USER2_DISPLAY_NAME, }, ]; for (const u of users) { if (usernames.has(u.username)) { throw new Error(`Duplicate USER*_USERNAME: ${u.username}`); } if (slugs.has(u.slug)) { throw new Error(`Duplicate USER*_SLUG: ${u.slug}`); } usernames.add(u.username); slugs.add(u.slug); } return users; }