Not Found war weder deutsch noch wahr
Beim Korrigieren einer Zuordnung geht der bearbeitete Eintrag im vorhandenen auf — er wird gelöscht. Die Antwort nannte aber nur die Daten des überlebenden, nicht seine Kennung, und so zeigte die Kachel weiter auf etwas, das es nicht mehr gab. Wer sie dann bearbeiten wollte, bekam „Not Found". Zwei Fehler in einer Meldung. Der Server sagt jetzt, welcher Eintrag überlebt hat und ob zusammengelegt wurde; die Kachel nimmt sich weg statt auf ein Loch zu zeigen, und sagt wohin. Und das Englische: in `detail` steht der Satz, den der Server selbst geschrieben hat — deutsch und mit einem Hinweis, was zu tun ist. `title` ist dagegen der Statusname aus dem Rahmenwerk, und genau der wurde angezeigt, wo `detail` fehlte. Er wird nicht mehr gelesen; wo der Server nichts zu sagen hat, schreibt der Client selbst einen deutschen Satz. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
c46063637f
commit
e70c33f7e2
+53
-6
@@ -191,6 +191,34 @@ export const meineSprache = (): string | null => sitzung.wer?.language ?? null;
|
||||
|
||||
/* ---------- Anfragen ---------- */
|
||||
|
||||
/** Ein deutscher Satz zu einem Status, den der Server nicht erklärt hat.
|
||||
*
|
||||
* **Weil „Not Found" keine Auskunft ist.** Wo der Server selbst etwas zu
|
||||
* sagen hat, steht es in `detail` und hat Vorrang. Antwortet er nur mit einer
|
||||
* Zahl, gehört hierher ein Satz — und zwar einer, der sagt, was gerade schief
|
||||
* ging, nicht wie der Status heisst.
|
||||
*/
|
||||
function satzZu(status: number): string {
|
||||
switch (status) {
|
||||
case 400: return 'Die Anfrage war so nicht zu gebrauchen.';
|
||||
case 401: return 'Die Anmeldung gilt nicht mehr. Melde dich neu an.';
|
||||
case 403: return 'Dafür fehlen die Rechte.';
|
||||
case 404: return 'Das gibt es nicht mehr. Vermutlich wurde es beim letzten '
|
||||
+ 'Scan mit einem anderen Eintrag zusammengelegt oder entfernt.';
|
||||
case 409: return 'Das geht gerade nicht — es läuft schon etwas anderes.';
|
||||
case 410: return 'Die Datei dazu liegt nicht mehr da.';
|
||||
case 413: return 'Das ist zu gross.';
|
||||
case 429: return 'Zu viele Anfragen auf einmal. Warte einen Augenblick.';
|
||||
case 504: return 'Der Server hat zu lange gebraucht.';
|
||||
default:
|
||||
return status >= 500
|
||||
? 'Auf dem Server ist etwas schiefgegangen. Die Einzelheiten stehen '
|
||||
+ 'im Protokoll von heute.'
|
||||
: 'Der Server hat die Anfrage abgelehnt. Die Einzelheiten stehen im '
|
||||
+ 'Protokoll von heute.';
|
||||
}
|
||||
}
|
||||
|
||||
export class ApiFehler extends Error {
|
||||
// **Der Name wird gesetzt, nicht geerbt.** Nach dem Bündeln heisst die
|
||||
// Klasse anders, und `instanceof` über Modulgrenzen ist nicht verlässlich;
|
||||
@@ -244,13 +272,22 @@ async function anfrage<T>(pfad: string, opts: RequestInit = {}): Promise<T> {
|
||||
}
|
||||
|
||||
if (!antwort.ok) {
|
||||
let text = `${antwort.status}`;
|
||||
// **Nur `detail`, niemals `title`.**
|
||||
//
|
||||
// In `detail` steht der Satz, den der Server selbst geschrieben hat —
|
||||
// deutsch und mit einem Hinweis, was zu tun ist. `title` ist dagegen der
|
||||
// englische Statusname aus dem Rahmenwerk: bei einer schlichten 404 steht
|
||||
// dort „Not Found", und genau das stand dann im Bilderdialog. Dasselbe
|
||||
// gilt für `errors`: die Prüftexte des Rahmenwerks sind englisch.
|
||||
let text = '';
|
||||
try {
|
||||
const fehler = await antwort.json();
|
||||
text = fehler.detail ?? fehler.title
|
||||
?? Object.values(fehler.errors ?? {}).flat().join(' ') ?? text;
|
||||
} catch { /* Keine JSON-Antwort — dann bleibt der Status. */ }
|
||||
throw new ApiFehler(antwort.status, text);
|
||||
if (typeof fehler?.detail === 'string' && fehler.detail.trim().length > 0) {
|
||||
text = fehler.detail.trim();
|
||||
}
|
||||
} catch { /* Keine JSON-Antwort — dann schreiben wir den Satz selbst. */ }
|
||||
|
||||
throw new ApiFehler(antwort.status, text || satzZu(antwort.status));
|
||||
}
|
||||
|
||||
// **Nicht jede erfolgreiche Antwort hat einen Körper.**
|
||||
@@ -831,8 +868,18 @@ export type Herkunft = { ordner: string | null; dateien: number; beispiele: stri
|
||||
export const herkunft = (art: 'movies' | 'series', id: string) =>
|
||||
anfrage<Herkunft>(`/${art}/${id}/herkunft`);
|
||||
|
||||
/** Wie die Zuordnung nach der Korrektur aussieht. */
|
||||
export type Zuordnungsstand = {
|
||||
tmdbId: number; imdbId: string | null; locked: boolean;
|
||||
title: string; year: number | null;
|
||||
/** Welcher Eintrag das jetzt ist — nach einem Zusammenlegen ein anderer. */
|
||||
id: string;
|
||||
/** Ob dabei zwei Einträge zu einem wurden. */
|
||||
merged: boolean;
|
||||
};
|
||||
|
||||
export const zuordnen = (art: 'movies' | 'series', id: string, tmdbId: number) =>
|
||||
anfrage<{ tmdbId: number; imdbId: string | null; locked: boolean; title: string; year: number | null }>(
|
||||
anfrage<Zuordnungsstand>(
|
||||
`/${art}/${id}/match`, { method: 'PUT', body: JSON.stringify({ tmdbId }) });
|
||||
|
||||
/* ---------- Hilfsmittel ---------- */
|
||||
|
||||
+16
-3
@@ -2,7 +2,7 @@ import {
|
||||
bildAdresse, gesehen, favorit, weglegen, istAdmin, film, serie,
|
||||
type Film, type Serie, type Angefangen, type Stand,
|
||||
} from './api';
|
||||
import { esc, anteil } from './util';
|
||||
import { esc, anteil, hinweisZeigen } from './util';
|
||||
import { icons } from './icons';
|
||||
import { kontextmenue, menueAnZiel, type Menuepunkt } from './menue';
|
||||
import { bilddialog } from './bilder';
|
||||
@@ -346,12 +346,25 @@ function kachelpunkte(
|
||||
// korrigiert werden soll — bei einem falschen Treffer grenzt es die
|
||||
// Suche auf genau das falsche Jahr ein. Ohne Angabe nimmt der Server
|
||||
// das aus dem Dateinamen.
|
||||
tun: () => zuordnungsdialog(art, k.id, k.titel, null,
|
||||
tun: () => zuordnungsdialog(art, k.id, k.titel, null, stand => {
|
||||
// **Zusammengelegt heisst: diesen Eintrag gibt es nicht mehr.**
|
||||
//
|
||||
// Vorher blieb die Kachel stehen und zeigte auf einen gelöschten
|
||||
// Eintrag; wer sie dann bearbeiten wollte, bekam „Not Found" — eine
|
||||
// englische Meldung für etwas, das gar kein Fehler war. Der
|
||||
// überlebende Eintrag steht ohnehin schon in der Liste.
|
||||
if (stand.merged) {
|
||||
knopf.remove();
|
||||
hinweisZeigen(`„${stand.title}" wurde mit dem vorhandenen Eintrag zusammengelegt.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// **Nur die Kachel, nicht die Seite.** `location.reload()` warf
|
||||
// Bildlauf, Filter und offene Reiter weg — für einen Titel, der sich
|
||||
// geändert hat. Bei zwanzig Korrekturen hintereinander ist das
|
||||
// zwanzigmal von vorn anfangen.
|
||||
() => void kachelAuffrischen(k, knopf)),
|
||||
void kachelAuffrischen(k, knopf);
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { vorschlaege, zuordnen, herkunft } from './api';
|
||||
import { vorschlaege, zuordnen, herkunft, type Zuordnungsstand } from './api';
|
||||
import { esc, knopf, feld, fehlerzeile, leer, meldung } from './util';
|
||||
import { icons } from './icons';
|
||||
|
||||
@@ -16,7 +16,12 @@ export function zuordnungsdialog(
|
||||
id: string,
|
||||
titel: string,
|
||||
jahr: number | null,
|
||||
fertig?: () => void,
|
||||
/** Wird nach einer übernommenen Zuordnung gerufen — mit dem neuen Stand.
|
||||
*
|
||||
* **Der Stand sagt auch, ob zusammengelegt wurde.** Dann gibt es den
|
||||
* korrigierten Eintrag nicht mehr, und wer weiter auf ihn zeigt, bekommt
|
||||
* beim nächsten Handgriff eine 404. */
|
||||
fertig?: (stand: Zuordnungsstand) => void,
|
||||
): void {
|
||||
const d = document.createElement('dialog');
|
||||
d.className = 'dlg dlg-breit';
|
||||
@@ -154,12 +159,14 @@ export function zuordnungsdialog(
|
||||
zeile.appendChild(lauf);
|
||||
|
||||
try {
|
||||
await zuordnen(art, id, v.tmdbId);
|
||||
const stand = await zuordnen(art, id, v.tmdbId);
|
||||
// Erst melden, dann schliessen: sonst blitzt nur der Dialog weg,
|
||||
// und es bleibt offen, ob es geklappt hat.
|
||||
lauf.innerHTML = '<span>Übernommen.</span>';
|
||||
lauf.innerHTML = stand.merged
|
||||
? '<span>Übernommen und mit dem vorhandenen Eintrag zusammengelegt.</span>'
|
||||
: '<span>Übernommen.</span>';
|
||||
d.close();
|
||||
fertig?.();
|
||||
fertig?.(stand);
|
||||
} catch (fehler) {
|
||||
lauf.remove();
|
||||
zeile.classList.remove('laeuft');
|
||||
|
||||
Reference in New Issue
Block a user