Files
irrlicht 38da498754 Split voting into clean read/write endpoints
Replaces the GET-mutates-state interactions route with a proper split:
- GET  /entry/{pid}/votes  reads the tally (public, read-only)
- POST /entry/{pid}/vote   casts/toggles the vote (auth, mode in body)

Shared voteTally/writeTally helpers back both handlers. Invalid mode now
returns 400. Frontend updated to read via GET and vote via POST.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 11:03:33 +02:00

4.9 KiB
Raw Permalink Blame History

kver API Definition

Abgeleitet aus dem bestehenden Flask-Code. Basis für die Go-Reimplementierung.


Authentifizierung

Sessions werden als Cookie (session) übertragen. Alle geschützten Endpunkte erwarten diesen Cookie.


Auth

POST /auth/login

Einloggen.

Request (form-encoded):

Feld Typ Beschreibung
user string Nutzername
pass string Passwort
timeout int Session-Dauer in Sekunden

Response:

  • 200 + setzt session-Cookie → Redirect auf /
  • 200 + Fehlertext bei falschen Credentials

GET /auth/login

Login-Seite anzeigen (wird in der Go-API nicht benötigt, Frontend übernimmt das).


POST /auth/newuser

Neuen Account registrieren.

Request (form-encoded):

Feld Typ Beschreibung
user string Nutzername (332 Zeichen, [a-zA-Z0-9._\s-])
pass1 string Passwort (min. 10 Zeichen)
pass2 string Passwort-Bestätigung

Response:

  • 200 bei Erfolg
  • 200 + Fehlerliste bei Validierungsfehler

GET /auth/logout

Session beenden und Cookie löschen.

Response: Redirect auf /


GET /auth/headerbar

Gibt zurück ob der Nutzer eingeloggt ist (für die UI-Headerleiste).

Response: HTML-Partial (in Go-API: JSON)


GET /auth/sessioninfo

Infos zur aktuellen Session.

Auth: erforderlich

Response:

{
  "uid": 123456,
  "created_at": 1700000000,
  "expires": 1700086400,
  "description": ""
}

Entries (Beiträge)

GET /entry/feed/:page

Paginierter Feed aller Beiträge, neueste zuerst.

Parameter:

Name Typ Beschreibung
page int Seite (0100), 20 Einträge pro Seite

Response:

[
  {
    "pid": 123456789,
    "created_at": 1700000000,
    "uid": 42,
    "content": "...",
    "filepath": "static/media/...",
    "username": "max"
  }
]

Leeres Array [] wenn keine weiteren Einträge vorhanden.


POST /entry/create

Neuen Beitrag erstellen.

Auth: erforderlich

Request (multipart/form-data):

Feld Typ Beschreibung
content string Text (max. 1000 Zeichen)
file file (optional) Bild (JPG oder GIF, wird auf max. 1024px skaliert)

Mindestens content oder file muss vorhanden sein.

Response:

  • 201 bei Erfolg
  • 400 bei leerem Beitrag
  • 401 wenn nicht eingeloggt

GET /entry/:pid/votes

Abstimmungsstand eines Beitrags lesen (read-only).

Response:

{ "pid": 123, "left": 4, "right": 2, "selected": "left" }

selected ist none, wenn nicht eingeloggt oder keine Stimme abgegeben.


POST /entry/:pid/vote

Eigene Stimme abgeben oder umschalten.

Auth: erforderlich

Request (form-encoded):

Feld Typ Beschreibung
mode string left oder right

Voting-Logik:

  • keine bisherige Stimme → neue Stimme
  • gleiche Stimme erneut → Stimme zurückziehen (Toggle)
  • andere Stimme → auf neuen Modus wechseln

Response: wie GET /entry/:pid/votes (aktualisierter Stand)

  • 400 bei ungültigem Modus
  • 401 ohne Login

User

GET /u/:username

Öffentliche Profilseite eines Nutzers.

Response: Nutzerprofil + Beiträge (Details noch offen)


GET /user/info

Eigene Account-Informationen.

Auth: erforderlich

Response:

{
  "uid": 42,
  "username": "max",
  "created_at": 1700000000
}

POST /user/delete

Eigenen Account dauerhaft löschen. Beiträge, Votes und Sessions werden mitgelöscht, zugehörige Mediendateien best effort entfernt.

Auth: erforderlich

Request (form-encoded):

Feld Typ Beschreibung
pass1 string Passwort zur Bestätigung

Response:

  • 200 {"status": "deleted"} + löscht den session-Cookie
  • 403 bei falschem Passwort

Datenbankschema (SQLite)

CREATE TABLE user (
    id       INTEGER PRIMARY KEY AUTOINCREMENT,
    uid      INTEGER UNIQUE,
    username TEXT UNIQUE,
    password BLOB,
    created_at INTEGER,
    last_login INTEGER
);

CREATE TABLE session (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    uid         INTEGER,
    value       TEXT UNIQUE,
    created_at  INTEGER,
    expires     INTEGER,
    description TEXT
);

CREATE TABLE entry (
    id         INTEGER PRIMARY KEY AUTOINCREMENT,
    pid        INTEGER UNIQUE,
    uid        INTEGER,
    created_at INTEGER,
    content    TEXT,
    filepath   TEXT
);

CREATE TABLE vote (
    id   INTEGER PRIMARY KEY AUTOINCREMENT,
    uid  INTEGER,
    pid  INTEGER,
    mode TEXT  -- 'left' | 'right'
);

Offene Punkte

  • Kein Rate-Limiting außer dem zufälligen sleep beim Login
  • session-Token ist nur randbelow(999999999) + timestamp — sollte auf crypto/rand umgestellt werden
  • Fehler-Responses sind aktuell Plaintext/HTML — in der Go-API einheitlich JSON