import 'server-only' /** * Speicherfehler in Sätze übersetzen, mit denen jemand etwas anfangen kann. * * Anlass ist eine Fehlersuche vom 31.08.2026. Beim Ablegen eines Schreibens * stand in der Oberfläche: * * Das Schreiben konnte nicht abgelegt werden. * * Dahinter lagen nacheinander DREI verschiedene Ursachen — ein fehlender * Ablagebereich, ein falsch konfigurierter Endpunkt und eine * Verschlüsselung, die der Speicher nicht anbietet. Jedes Mal derselbe * Satz. Die Suche dauerte entsprechend, und ohne Zugang zum Serverlog wäre * sie aussichtslos gewesen. * * ## Was hier NICHT passiert * * Es wird nichts durchgereicht, was der Speicher sagt. Fremde * Fehlertexte können Hostnamen, Eimernamen und Anfragekennungen * enthalten - das gehört ins Log, nicht auf den Bildschirm eines * Betreuers. Übersetzt werden nur die Fälle, die wir kennen; alles andere * bleibt bei der allgemeinen Meldung des Aufrufers. * * Die technische Ursache steht weiterhin vollständig im Log. */ /** Ein erkannter Speicherfehler samt Sprachfassung. */ export type Speicherfehler = { /** Kurzform für Protokoll und Gesundheitsauskunft. */ art: 'kein-ablagebereich' | 'verschluesselung' | 'zugang' | 'nicht-erreichbar' /** Was in der Oberfläche stehen soll. */ meldung: string } /** * Holt den Fehlercode heraus. * * Die Reihenfolge ist wichtig und war zuerst falsch: "name" traegt bei * einem gewoehnlichen Node-Fehler den Wert "Error" - eine nichtssagende * Zeichenkette, die aber nicht leer ist. Stuende sie vorn, kaeme der * eigentliche Code aus "code" (ECONNREFUSED und Verwandte) nie zum Zug. * * Deshalb: erst "Code" (AWS-SDK), dann "code" (Node-Systemaufruf), und * "name" nur als letzte Zuflucht. */ function code(fehler: unknown): string { if (typeof fehler !== 'object' || fehler === null) return '' const mitCode = fehler as { Code?: unknown; code?: unknown; name?: unknown } for (const wert of [mitCode.Code, mitCode.code, mitCode.name]) { if (typeof wert === 'string' && wert !== '' && wert !== 'Error') return wert } return '' } function nachricht(fehler: unknown): string { return fehler instanceof Error ? fehler.message : '' } /** * Erkennt die häufigen Speicherfehler. * * @returns null, wenn es keiner der bekannten Fälle ist - dann bleibt es * bei der allgemeinen Meldung des Aufrufers. */ export function speicherfehlerErkennen(fehler: unknown): Speicherfehler | null { const kennung = code(fehler) const text = nachricht(fehler) if (kennung === 'NoSuchBucket') { return { art: 'kein-ablagebereich', meldung: 'Im Objektspeicher fehlt der Ablagebereich für die Dokumente. Er wird bei der ' + 'Installation einmalig angelegt — siehe INSTALLATION.md, Schritt 6. Bis dahin lässt ' + 'sich nichts ablegen; deine Eingaben gehen aber nicht verloren.', } } // MinIO ohne KMS antwortet so, sobald S3_SSE gesetzt ist. if (kennung === 'NotImplemented' && /encryption/i.test(text)) { return { art: 'verschluesselung', meldung: 'Der Objektspeicher unterstützt die eingestellte serverseitige Verschlüsselung nicht. ' + 'Der mitgelieferte Speicher braucht sie auch nicht — die Anwendung verschlüsselt selbst. ' + 'Nimm S3_SSE aus der .env heraus.', } } if ( kennung === 'AccessDenied' || kennung === 'SignatureDoesNotMatch' || kennung === 'InvalidAccessKeyId' ) { return { art: 'zugang', meldung: 'Die Zugangsdaten zum Objektspeicher werden abgelehnt. Prüfe S3_ACCESS_KEY_ID und ' + 'S3_SECRET_ACCESS_KEY in der .env — und ob sie zu dem Speicher gehören, auf den ' + 'S3_ENDPOINT zeigt.', } } if ( ['ECONNREFUSED', 'ENOTFOUND', 'ETIMEDOUT', 'EAI_AGAIN', 'TimeoutError'].includes(kennung) || /ECONNREFUSED|ENOTFOUND|ETIMEDOUT/.test(text) ) { return { art: 'nicht-erreichbar', meldung: 'Der Objektspeicher antwortet nicht. Läuft er, und zeigt S3_ENDPOINT auf die richtige ' + 'Adresse? Deine Eingaben gehen nicht verloren.', } } return null } /** * Bequemer Aufruf für die Stellen, die eine Meldung brauchen. * * @param allgemein Was stehen soll, wenn der Fehler keiner der bekannten * ist - z.B. "Das Schreiben konnte nicht abgelegt werden." */ export function speicherfehlerMeldung(fehler: unknown, allgemein: string): string { const erkannt = speicherfehlerErkennen(fehler) return erkannt ? erkannt.meldung : allgemein }