- Full grant strategy framework for renewable energy & green hydrogen - AI-powered grant studio, partner outreach, financial modeling - Umami analytics with data-performance tracking - Live Degelas metrics connected to solar.degelas.be - Trilingual (EN/FR/AR) with i18n support - Dockerized with Nginx frontend + Express API proxy
85 lines
2.9 KiB
TypeScript
85 lines
2.9 KiB
TypeScript
import { z } from "zod";
|
|
|
|
/**
|
|
* Environment variable validation.
|
|
* Fails fast on startup if required vars are missing or invalid.
|
|
*/
|
|
|
|
const envSchema = z.object({
|
|
NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
|
|
PORT: z.coerce.number().default(3001),
|
|
// Optional: if omitted, AI routes return clearly labelled demo/mock output.
|
|
// First checks AINFT_API_KEY, falls back to OPENAI_API_KEY for compatibility
|
|
AI_API_KEY: z.string().optional().or(z.literal("")),
|
|
AI_BASE_URL: z.string().url().optional().or(z.literal("")),
|
|
AI_MODEL: z.string().optional(),
|
|
AI_MODEL_COMPLEX: z.string().optional(),
|
|
OPENAI_API_KEY: z.string().optional().or(z.literal("")),
|
|
AINFT_API_KEY: z.string().optional().or(z.literal("")),
|
|
DEGELAS_API_URL: z.string().url().optional().or(z.literal("")),
|
|
DEGELAS_API_KEY: z.string().optional().or(z.literal("")),
|
|
CLIENT_ORIGIN: z.string().default("*"),
|
|
LOG_LEVEL: z.enum(["fatal", "error", "warn", "info", "debug", "trace"]).default("info"),
|
|
});
|
|
|
|
type Env = z.infer<typeof envSchema>;
|
|
|
|
export function resolvedApiKey(env: Env): string | undefined {
|
|
return env.AI_API_KEY || env.AINFT_API_KEY || env.OPENAI_API_KEY || undefined;
|
|
}
|
|
|
|
export function resolvedBaseUrl(env: Env): string | undefined {
|
|
return env.AI_BASE_URL || undefined;
|
|
}
|
|
|
|
export function resolvedModel(env: Env): string {
|
|
return env.AI_MODEL || "deepseek-v4-flash";
|
|
}
|
|
|
|
export function resolvedModelComplex(env: Env): string {
|
|
return env.AI_MODEL_COMPLEX || "deepseek-v4-pro";
|
|
}
|
|
|
|
let validated: Env | null = null;
|
|
|
|
export function validateEnv(): Env {
|
|
if (validated) return validated;
|
|
|
|
const result = envSchema.safeParse(process.env);
|
|
if (!result.success) {
|
|
const errors = result.error.issues.map((e) => ` - ${e.path.join(".")}: ${e.message}`).join("\n");
|
|
console.error("❌ Environment validation failed:\n" + errors);
|
|
process.exit(1);
|
|
}
|
|
|
|
validated = result.data;
|
|
|
|
// Log startup info (without sensitive values)
|
|
console.log("✅ Environment validated");
|
|
console.log(` NODE_ENV: ${validated.NODE_ENV}`);
|
|
console.log(` PORT: ${validated.PORT}`);
|
|
console.log(` LOG_LEVEL: ${validated.LOG_LEVEL}`);
|
|
if (validated.DEGELAS_API_URL) {
|
|
console.log(` DEGELAS_API_URL: configured`);
|
|
} else {
|
|
console.log(` DEGELAS_API_URL: not configured (using mock data)`);
|
|
}
|
|
if (resolvedApiKey(validated)) {
|
|
console.log(` AI_API_KEY: configured`);
|
|
console.log(` AI_BASE_URL: ${validated.AI_BASE_URL || "https://api.openai.com/v1 (default)"}`);
|
|
console.log(` AI_MODEL: ${validated.AI_MODEL || "deepseek-v4-flash (default)"}`);
|
|
console.log(` AI_MODEL_COMPLEX: ${validated.AI_MODEL_COMPLEX || "deepseek-v4-pro (default)"}`);
|
|
} else {
|
|
console.log(` AI_API_KEY: not configured (AI features will return mock data)`);
|
|
}
|
|
|
|
return validated;
|
|
}
|
|
|
|
export function getEnv(): Env {
|
|
if (!validated) {
|
|
throw new Error("Environment not validated. Call validateEnv() on startup.");
|
|
}
|
|
return validated;
|
|
}
|