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.
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user