Repository Public
1/** Server-side configuration. Never import this from a client component. */2function req(name: string): string {3 const v = process.env[name];4 if (!v) throw new Error(`Missing environment variable ${name}`);5 return v;6}7const opt = (name: string) => process.env[name] || undefined;89export type MailTransport = "resend" | "smtp" | "test" | "none";1011export const env = {12 get databaseUrl() { return req("DATABASE_URL"); },13 get appUrl() { return (opt("APP_URL") ?? "http://localhost:8660").replace(/\/$/, ""); },14 get appSecret() {15 const s = req("APP_SECRET");16 if (s.length < 32) throw new Error("APP_SECRET must be at least 32 characters");17 return s;18 },19 get isProd() { return process.env.NODE_ENV === "production"; },20 get mailTransport(): MailTransport {21 const t = opt("MAIL_TRANSPORT") ?? "none";22 if (t !== "resend" && t !== "smtp" && t !== "test" && t !== "none") throw new Error(`Unknown MAIL_TRANSPORT ${t}`);23 if (t === "test" && process.env.NODE_ENV === "production") throw new Error("MAIL_TRANSPORT=test is refused in production");24 return t;25 },26 get resendApiKey() { return opt("RESEND_API_KEY"); },27 get smtpUrl() { return opt("SMTP_URL"); },28 get mailFrom() { return opt("MAIL_FROM"); },29 get ownerEmail() { return opt("OWNER_EMAIL"); },30 get ownerName() { return opt("OWNER_NAME") ?? "SP Boucher"; },31 /** Test-only clock override (ISO instant). Refused in production. */32 get fakeNow() { return process.env.NODE_ENV === "production" ? undefined : opt("SPCAL_FAKE_NOW"); },33};3435/** The only clock the application reads, so tests can pin it. */36export function nowMs(): number {37 const f = env.fakeNow;38 return f ? Temporal.Instant.from(f).epochMilliseconds : Date.now();39}4041export function mailConfigured(): { ok: boolean; transport: MailTransport; reason?: string } {42 const transport = env.mailTransport;43 if (transport === "none") return { ok: false, transport, reason: "MAIL_TRANSPORT is not set" };44 if (!env.mailFrom && transport !== "test") return { ok: false, transport, reason: "MAIL_FROM is not set" };45 if (transport === "resend" && !env.resendApiKey) return { ok: false, transport, reason: "RESEND_API_KEY is not set" };46 if (transport === "smtp" && !env.smtpUrl) return { ok: false, transport, reason: "SMTP_URL is not set" };47 return { ok: true, transport };48}49