Prisma-Schema in vier Schichten: Konten und Zugang, gemeinsamer Katalog (über die TMDB-ID dedupliziert), persönliches Tracking je Nutzer sowie Universen mit mehreren Reihenfolgen. Dazu Benachrichtigungskanäle und Protokoll der Hintergrundläufe. Prisma 7 verlangt einen Treiber-Adapter statt der URL im Schema; die Konfiguration liegt deshalb in prisma.config.mjs — bewusst als .mjs, damit die CLI auch im schlanken Produktionsimage ohne TypeScript-Loader läuft.
92 lines
2.9 KiB
TypeScript
92 lines
2.9 KiB
TypeScript
import "server-only";
|
|
|
|
/**
|
|
* Zentraler, geprüfter Zugriff auf die Umgebungsvariablen.
|
|
*
|
|
* Absicht: Tippfehler und fehlende Pflichtwerte fallen beim Start auf, nicht
|
|
* erst irgendwo tief in einem Request. Optionale Bausteine (TMDB, SMTP, OIDC)
|
|
* melden sich stattdessen über `isConfigured`-Flags ab.
|
|
*/
|
|
|
|
function required(name: string, fallback?: string): string {
|
|
const value = process.env[name] ?? fallback;
|
|
if (!value) {
|
|
throw new Error(
|
|
`Umgebungsvariable ${name} fehlt. Bitte in der .env setzen — siehe .env.example.`,
|
|
);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function optional(name: string, fallback = ""): string {
|
|
return process.env[name]?.trim() ?? fallback;
|
|
}
|
|
|
|
function bool(name: string, fallback: boolean): boolean {
|
|
const raw = process.env[name]?.trim().toLowerCase();
|
|
if (raw === undefined || raw === "") return fallback;
|
|
return raw === "1" || raw === "true" || raw === "yes" || raw === "ja";
|
|
}
|
|
|
|
function int(name: string, fallback: number): number {
|
|
const raw = Number.parseInt(process.env[name] ?? "", 10);
|
|
return Number.isFinite(raw) ? raw : fallback;
|
|
}
|
|
|
|
const isBuildPhase = process.env.NEXT_PHASE === "phase-production-build";
|
|
|
|
export const env = {
|
|
appName: optional("APP_NAME", "Flimmer"),
|
|
appUrl: optional("APP_URL", "http://localhost:3000").replace(/\/+$/, ""),
|
|
databaseUrl: isBuildPhase
|
|
? optional("DATABASE_URL", "postgresql://build:build@localhost:5432/build")
|
|
: required("DATABASE_URL"),
|
|
authSecret: isBuildPhase ? optional("AUTH_SECRET", "build-time-placeholder") : required("AUTH_SECRET"),
|
|
dataDir: optional("DATA_DIR", "./data"),
|
|
timezone: optional("TZ", "Europe/Berlin"),
|
|
|
|
allowRegistrationDefault: bool("ALLOW_REGISTRATION", true),
|
|
|
|
tmdb: {
|
|
apiKey: optional("TMDB_API_KEY"),
|
|
accessToken: optional("TMDB_ACCESS_TOKEN"),
|
|
language: optional("TMDB_LANGUAGE", "de-DE"),
|
|
region: optional("TMDB_REGION", "DE"),
|
|
get isConfigured() {
|
|
return Boolean(this.apiKey || this.accessToken);
|
|
},
|
|
},
|
|
|
|
scheduler: {
|
|
enabled: bool("ENABLE_SCHEDULER", false),
|
|
hour: Math.min(23, Math.max(0, int("SYNC_HOUR", 4))),
|
|
cronSecret: optional("CRON_SECRET"),
|
|
},
|
|
|
|
smtp: {
|
|
host: optional("SMTP_HOST"),
|
|
port: int("SMTP_PORT", 587),
|
|
user: optional("SMTP_USER"),
|
|
password: optional("SMTP_PASSWORD"),
|
|
secure: bool("SMTP_SECURE", false),
|
|
from: optional("SMTP_FROM", "Flimmer <flimmer@localhost>"),
|
|
get isConfigured() {
|
|
return Boolean(this.host);
|
|
},
|
|
},
|
|
|
|
oidc: {
|
|
issuer: optional("OIDC_ISSUER"),
|
|
clientId: optional("OIDC_CLIENT_ID"),
|
|
clientSecret: optional("OIDC_CLIENT_SECRET"),
|
|
scopes: optional("OIDC_SCOPES", "openid profile email"),
|
|
buttonLabel: optional("OIDC_BUTTON_LABEL", "Mit SSO anmelden"),
|
|
autoRegister: bool("OIDC_AUTO_REGISTER", true),
|
|
get isConfigured() {
|
|
return Boolean(this.issuer && this.clientId && this.clientSecret);
|
|
},
|
|
},
|
|
} as const;
|
|
|
|
export const isProduction = process.env.NODE_ENV === "production";
|