Design-Tokens aus docs/10-design-system.md als CSS-Variablen: Grundpalette, die sechs Klassifizierungsfarben, Eckenradius durchgehend 2 px, keine Schatten ausser dem Fokusring. Der Schwärzungsbalken mit dem einmaligen Durchwischen respektiert prefers-reduced-motion. Vier Tests halten die Tokens gegen das Dokument. IBM Plex selbst gehostet als woff2, latin und latin-ext getrennt über unicode-range, font-display: swap, immutable-Cache. Alle Oberflächentexte in lib/i18n/de.ts. Ein Test durchsucht app/ und components/ nach Umlauten im JSX und schlägt fehl, wenn dort deutscher Text steht. Er hat beim ersten Lauf gleich einen Verstoss gefunden. /api/health prüft Datenbank, Discord-Gateway und ClamAV. Die Gesamtbewertung fällt nur auf "down", wenn die Datenbank weg ist: die anderen Dienste haben eigene Healthchecks im Stack, und ein ausgefallener Virenscanner soll nicht den Web-Container als tot markieren. Strikte CSP mit Nonce, kein unsafe-inline für Skripte. Die erste Route liegt bei 100 kB JavaScript nach Kompression, Budget sind 120 kB. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
57 lines
1.7 KiB
TypeScript
57 lines
1.7 KiB
TypeScript
import { z } from 'zod'
|
|
|
|
/**
|
|
* Umgebungsvariablen werden beim Start einmal geprueft. Fehlt etwas, soll der
|
|
* Container sofort und mit klarer Meldung sterben statt spaeter beim ersten
|
|
* Request. Die Werte stehen im .env-Reiter von Dockge, Vorlage ist .env.example.
|
|
*/
|
|
const schema = z.object({
|
|
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
|
|
|
|
DATABASE_URL: z.string().min(1),
|
|
DIRECT_DATABASE_URL: z.string().min(1).optional(),
|
|
|
|
APP_URL: z.url(),
|
|
AUTH_SECRET: z.string().min(32),
|
|
|
|
DISCORD_CLIENT_ID: z.string().min(1),
|
|
DISCORD_CLIENT_SECRET: z.string().min(1),
|
|
DISCORD_BOT_TOKEN: z.string().min(1),
|
|
DISCORD_GUILD_ID: z.string().min(1),
|
|
|
|
/// Notfallzugang. Diese Discord-IDs haben immer Stufe 5 und alle Rechte,
|
|
/// unabhaengig vom Rollen-Mapping. Siehe docs/03-auth-und-rechte.md.
|
|
BOOTSTRAP_ADMIN_IDS: z
|
|
.string()
|
|
.default('')
|
|
.transform((v) => v.split(',').map((s) => s.trim()).filter(Boolean)),
|
|
|
|
FILES_DIR: z.string().default('/data/files'),
|
|
CLAMAV_SOCKET: z.string().default('/var/run/clamav/clamd.sock'),
|
|
PDF_SERVICE_URL: z.url().optional(),
|
|
|
|
TZ: z.string().default('Europe/Berlin'),
|
|
})
|
|
|
|
export type Env = z.infer<typeof schema>
|
|
|
|
let cached: Env | null = null
|
|
|
|
export function getEnv(): Env {
|
|
if (cached) return cached
|
|
const parsed = schema.safeParse(process.env)
|
|
if (!parsed.success) {
|
|
const missing = parsed.error.issues
|
|
.map((i) => `${i.path.join('.')}: ${i.message}`)
|
|
.join('\n ')
|
|
throw new Error(`Konfiguration unvollständig:\n ${missing}`)
|
|
}
|
|
cached = parsed.data
|
|
return cached
|
|
}
|
|
|
|
/** Nur fuer Tests, damit eine geaenderte Umgebung neu gelesen wird. */
|
|
export function resetEnvCache(): void {
|
|
cached = null
|
|
}
|