38da498754
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>
262 lines
4.9 KiB
Markdown
262 lines
4.9 KiB
Markdown
# 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 (3–32 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:**
|
||
```json
|
||
{
|
||
"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 (0–100), 20 Einträge pro Seite |
|
||
|
||
**Response:**
|
||
```json
|
||
[
|
||
{
|
||
"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:**
|
||
```json
|
||
{ "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:**
|
||
```json
|
||
{
|
||
"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)
|
||
|
||
```sql
|
||
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
|