Moderation entfernt, JSON-API unter /api

- Moderationsseite und Melde-Funktion (report) komplett ausgebaut; folgt
  als eigenständiges Projekt
- alle API-Endpunkte unter /api, dort ausschließlich JSON: auch 404, 405,
  Rate-Limit (429) und Panics (500)
- Fehler nur über HTTP-Status; stille DB-Fehler in stats, logout und
  Vote-Zählern liefern jetzt 500 statt Nullen
- /auth/headerbar entfernt (Frontend nutzt /api/user/info)
- Frontend auf /api und statusbasierte Auswertung umgestellt
- notes/api.md neu als Referenz der aktuellen API

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiXsPUqw7oeomZ8wZrQW5q
This commit is contained in:
irrlicht
2026-09-26 23:10:02 +02:00
co-authored by Claude Opus 5.5
parent 2714fdb1a5
commit 030a5bd943
21 changed files with 430 additions and 726 deletions
+19 -22
View File
@@ -10,23 +10,22 @@ import (
// Fehler-Aufrufstelle verklebt waren -- wie schwer der Fehler ist (Kind),
// was genau passiert ist (Code) und was der Nutzer lesen soll (Msg).
//
// Der Sinn der Trennung: derselbe Fehler muss künftig von zwei Adaptern
// unterschiedlich dargestellt werden. Der HTML-Pfad braucht Msg und Field, um
// ein Formular mit Fehlermarkierung neu zu rendern; ein API-Client braucht
// Code, um ohne Parsen deutscher Strings reagieren zu können.
// Der Sinn der Trennung: das Frontend zeigt Msg an und markiert Field, ein
// Client braucht Code, um ohne Parsen deutscher Strings reagieren zu können,
// und der HTTP-Status ergibt sich allein aus Kind.
type Kind uint8
const (
KindInternal Kind = iota // Bug oder Infrastruktur -- Ursache nie nach außen
KindInvalid // Eingabe passt nicht
KindUnauth // nicht angemeldet
KindForbidden // angemeldet, aber nicht erlaubt
KindNotFound // Objekt existiert nicht (oder soll nicht sichtbar sein)
KindConflict // Name vergeben, Doppel-Post
KindTooLarge // Upload über dem Limit
KindRateLimited // zu viele Anfragen
KindUnavailable // Feature nicht konfiguriert
KindInternal Kind = iota // Bug oder Infrastruktur -- Ursache nie nach außen
KindInvalid // Eingabe passt nicht
KindUnauth // nicht angemeldet
KindForbidden // angemeldet, aber nicht erlaubt
KindNotFound // Objekt existiert nicht (oder soll nicht sichtbar sein)
KindConflict // Name vergeben, Doppel-Post
KindTooLarge // Upload über dem Limit
KindRateLimited // zu viele Anfragen
KindMethodNotAllowed // Route existiert, aber nicht mit dieser Methode
)
// Error ist der Fehlertyp, den die Service-Schicht nach oben gibt. Kind ist
@@ -56,7 +55,9 @@ func NotFound(code, msg string) *Error { return &Error{Kind: KindNotFound, Co
func Conflict(code, msg string) *Error { return &Error{Kind: KindConflict, Code: code, Msg: msg} }
func TooLarge(code, msg string) *Error { return &Error{Kind: KindTooLarge, Code: code, Msg: msg} }
func RateLimited(code, msg string) *Error { return &Error{Kind: KindRateLimited, Code: code, Msg: msg} }
func Unavailable(code, msg string) *Error { return &Error{Kind: KindUnavailable, Code: code, Msg: msg} }
func MethodNotAllowed(code, msg string) *Error {
return &Error{Kind: KindMethodNotAllowed, Code: code, Msg: msg}
}
// Internal verschluckt die Ursache absichtlich: der Nutzer sieht nie einen
// DB- oder Dateisystemfehler (der Interna und Pfade verraten würde), das Log
@@ -102,9 +103,7 @@ func asError(err error) *Error {
return Internal(err)
}
// statusOf bildet Kind auf HTTP ab. Beide HTTP-Adapter (HTML und JSON) teilen
// sich diese Zuordnung -- unterschiedliche Statuscodes für denselben Fehler
// wären genau die Divergenz, die die Taxonomie verhindern soll.
// statusOf bildet Kind auf HTTP ab -- die einzige Stelle, an der das passiert.
func statusOf(k Kind) int {
switch k {
case KindInvalid:
@@ -121,17 +120,15 @@ func statusOf(k Kind) int {
return http.StatusRequestEntityTooLarge
case KindRateLimited:
return http.StatusTooManyRequests
case KindUnavailable:
return http.StatusServiceUnavailable
case KindMethodNotAllowed:
return http.StatusMethodNotAllowed
default:
return http.StatusInternalServerError
}
}
// logInternal protokolliert nur echte Serverfehler -- Validierungs- und
// Rechtefehler sind normaler Betrieb und würden das Log zumüllen. Beide
// Adapter rufen das auf, damit ein Fehler unabhängig vom Ausgabeformat
// genau einmal im Log landet.
// Rechtefehler sind normaler Betrieb und würden das Log zumüllen.
func logInternal(e *Error) {
if e.Kind != KindInternal {
return