Files
AfiS-Aktensystem/web/lib/env.ts
T
HarlekinandClaude Opus 5 6b0cd331bb Web-Anwendung: Design-Tokens, Texte, Gesundheitsendpunkt
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>
2026-09-01 00:01:41 +02:00

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
}