be8dbb4902
GET /entry/{pid}/interactions/{mode} now reads the vote tally and, for
mode=left/right (auth required), toggles the user's vote: new vote, repeat
same mode removes it, different mode switches. Returns {left, right,
selected}. A UNIQUE(uid, pid) constraint on the vote table prevents
duplicate votes. The JS frontend renders Links/Rechts buttons with live
counts under each entry.
This completes the voting feature that was left commented-out and broken
in the Flask version.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
256 lines
5.1 KiB
Markdown
256 lines
5.1 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/interactions/:mode`
|
||
|
||
Abstimmungsstand lesen und optional die eigene Stimme umschalten.
|
||
|
||
**Parameter:**
|
||
| Name | Typ | Beschreibung |
|
||
|------|-----|--------------|
|
||
| `pid` | int | Post-ID |
|
||
| `mode` | string | `none` (nur lesen), `left` oder `right` (abstimmen) |
|
||
|
||
**Voting-Logik** (nur bei `left`/`right`, Auth erforderlich):
|
||
- keine bisherige Stimme → neue Stimme
|
||
- gleiche Stimme erneut → Stimme zurückziehen (Toggle)
|
||
- andere Stimme → auf neuen Modus wechseln
|
||
|
||
**Response:**
|
||
```json
|
||
{ "pid": 123, "left": 4, "right": 2, "selected": "left" }
|
||
```
|
||
`selected` ist `none`, wenn nicht eingeloggt oder keine Stimme abgegeben.
|
||
Bei `left`/`right` ohne Login → `401`.
|
||
|
||
> Hinweis: Mutation per GET ist aus dem ursprünglichen HTMX-Design übernommen.
|
||
> Sauberer wäre ein separater `POST` fürs Abstimmen — siehe offene Punkte.
|
||
|
||
---
|
||
|
||
## 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
|
||
|
||
- Voting läuft per GET (HTMX-Erbe) — perspektivisch auf `POST` umstellen (CSRF/Idempotenz)
|
||
- 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
|