Gitea legt das Wiki-Repo erst mit der ersten Seite an; Klon und Push antworten davor mit 500, obwohl has_wiki auf true steht. Angelegt ueber die API (POST /api/v1/repos/<besitzer>/<repo>/wiki/new) -- danach ist es ein gewoehnliches Git-Repo und laesst sich klonen wie jedes andere. https://git.d4rkst3r.de/D4rkst3r/d4rk_media/wiki DIE QUELLE BLEIBT docs/ IM REPO. Ein Wiki hat keinen Zusammenhang mit dem Code: niemand sieht, ob die Anleitung noch zu dem passt, was der Dienst tut. Unter docs/ wandert sie im selben Commit mit der Aenderung, die sie beschreibt. tools/wiki.ps1 traegt sie danach hinaus -- EINSEITIG, und die Wiki-Seite sagt das auch: wer dort tippt, verliert es beim naechsten Lauf. Unschoen, aber ehrlicher als zwei Quellen, die auseinanderlaufen. Zwei Seiten: Home (Zuschnitt, was der Dienst kann, was zu tun ist, wenn etwas nicht geht) und API (Endpunkte, Kopfzeilen, Fehlerantworten, Grenzen -- und die beiden Proxy-Fallen von heute). Nachgemessen: beide Seiten sind ohne Anmeldung lesbar (200), ein zweiter Lauf des Skripts erkennt "bereits aktuell", und eine Aenderung kommt an. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
223 lines
7.0 KiB
Markdown
223 lines
7.0 KiB
Markdown
# Die API von d4rk_media
|
|
|
|
Diese Seite ist für den Fall geschrieben, dass jemand ein Skript anbindet und
|
|
etwas nicht tut, was es soll. Sie nennt deshalb nicht nur die Endpunkte,
|
|
sondern auch **was schiefgehen kann und wie die Antwort dann aussieht**.
|
|
|
|
Der Dienst läuft unter `https://fivemanage.d4rkst3r.de`. Dateien liegen darunter
|
|
unter `/f/`.
|
|
|
|
---
|
|
|
|
## Der Grundsatz: der Pfad ist der Schlüssel
|
|
|
|
`vehicles/adder.webp` bleibt `vehicles/adder.webp`. Die öffentliche Adresse
|
|
entsteht daraus, indem der Ursprung davorgesetzt wird:
|
|
|
|
```
|
|
vehicles/adder.webp → https://fivemanage.d4rkst3r.de/f/vehicles/adder.webp
|
|
```
|
|
|
|
Deshalb kann die Vorlage in einem Handy-Skript schlicht `{model}.webp` lauten.
|
|
**Die Adressen stehen nirgends in der Datenbank** — sie werden bei jeder
|
|
Antwort neu gebaut. Ein Umzug auf einen anderen Namen ist eine geänderte
|
|
Variable, kein Datenbankeingriff.
|
|
|
|
### Erlaubte Pfade
|
|
|
|
Jeder Teil zwischen den Schrägstrichen muss mit einem **Buchstaben oder einer
|
|
Ziffer** beginnen; danach sind zusätzlich Punkt, Strich und Unterstrich
|
|
erlaubt. Höchstens 8 Ebenen, höchstens 200 Zeichen.
|
|
|
|
Damit sind `..`, versteckte Dateien und alles mit Backslash von vornherein
|
|
draußen. Was nicht passt, wird **abgelehnt und nicht zurechtgebogen** — sonst
|
|
läge eine Datei unter einem anderen Namen als dem, den der Aufrufer erwartet,
|
|
und niemand merkte es, bis das Bild fehlt.
|
|
|
|
> Ein führender Unterstrich fällt durch. Das ist kein erfundener Fall: das
|
|
> Fotostudio legt seine Leerbilder als `_plate` ab.
|
|
|
|
---
|
|
|
|
## Anmeldung: Token
|
|
|
|
Alle Skript-Wege brauchen einen Token im Kopf:
|
|
|
|
```
|
|
Authorization: Bearer d4rk_…
|
|
```
|
|
|
|
Tokens werden im Dashboard angelegt. Der Klartext wird **genau einmal** gezeigt
|
|
— danach steht nur noch sein Hash in der Datenbank, und auch der Dienst kann
|
|
ihn nicht mehr herausgeben.
|
|
|
|
Ein Token kann zwei Fesseln haben:
|
|
|
|
| Fessel | Wirkung |
|
|
|---|---|
|
|
| Präfix | darf nur unterhalb dieses Pfades schreiben, z. B. `vehicles/` |
|
|
| darf löschen | ohne dieses Recht antwortet `DELETE` mit 403 |
|
|
|
|
---
|
|
|
|
## POST /api/upload
|
|
|
|
Nimmt die Datei in einer von **drei Rumpfformen** entgegen:
|
|
|
|
1. `multipart/form-data` mit dem Feld `file`
|
|
2. Base64-Text, wenn `X-Encoding: base64` gesetzt ist
|
|
3. rohe Bytes, sonst
|
|
|
|
### Kopfzeilen
|
|
|
|
| Kopf | Bedeutung |
|
|
|---|---|
|
|
| `X-Path` | Wohin die Datei kommt. **Ohne ihn** wird aus dem SHA-256 ein Pfad gebaut — dann ist die Adresse nicht mehr vorhersagbar. |
|
|
| `X-Encoding` | `base64`, wenn der Rumpf Base64-Text ist. Ein Data-URL-Vorspann (`data:image/webp;base64,`) wird abgeschnitten. |
|
|
| `X-Overwrite` | `false` verweigert das Überschreiben und antwortet mit 409. Standard ist überschreiben. |
|
|
| `X-Mime` | Überschreibt die Art. Sonst entscheidet die Endung. |
|
|
|
|
### Antwort
|
|
|
|
```json
|
|
{
|
|
"url": "https://fivemanage.d4rkst3r.de/f/vehicles/adder.webp",
|
|
"path": "vehicles/adder.webp",
|
|
"size": 132760,
|
|
"sha256": "36cb4ddf…",
|
|
"mime": "image/webp",
|
|
"replaced": false
|
|
}
|
|
```
|
|
|
|
### Was schiefgehen kann
|
|
|
|
| Antwort | Grund |
|
|
|---|---|
|
|
| 401 | Token fehlt oder ist unbekannt |
|
|
| 403 | Der Token darf dort nicht schreiben (Präfix-Fessel) |
|
|
| 400 | Pfad unzulässig, Rumpf leer, kaputtes Base64 |
|
|
| 409 | `X-Overwrite: false` und der Pfad ist belegt |
|
|
| 413 | größer als `MAX_UPLOAD_MB` (Standard 64) |
|
|
|
|
Jede Fehlerantwort ist JSON mit einem Feld `error` und einem **Satz auf
|
|
Deutsch**. Ein nackter 500 ohne Text ist genau der Fehler, wegen dem dieser
|
|
Dienst gebaut wurde.
|
|
|
|
---
|
|
|
|
## Aus einer FiveM-Resource
|
|
|
|
```lua
|
|
local TOKEN = 'd4rk_…' -- im Dashboard anlegen
|
|
|
|
local function hochladen(pfad, bytes, cb)
|
|
PerformHttpRequest('https://fivemanage.d4rkst3r.de/api/upload',
|
|
function(status, body)
|
|
if status ~= 200 then return cb(nil, body) end
|
|
cb(json.decode(body).url)
|
|
end, 'POST', B64.encode(bytes), {
|
|
['Authorization'] = 'Bearer ' .. TOKEN,
|
|
['X-Path'] = pfad,
|
|
['X-Encoding'] = 'base64',
|
|
['Content-Type'] = 'text/plain; charset=utf-8',
|
|
})
|
|
end
|
|
```
|
|
|
|
**Warum Base64 und nicht die rohen Bytes.** `PerformHttpRequest` nimmt den
|
|
Rumpf als Lua-Zeichenkette, und ob Nullbytes darin unverändert ankommen, hat
|
|
hier niemand nachgemessen — ein PNG besteht zu gutem Teil daraus. Base64 kostet
|
|
ein Drittel mehr auf der Leitung und nimmt die Frage aus dem Weg. Nachgemessen
|
|
ist: Base64 kommt mit demselben SHA-256 an wie die rohen Bytes.
|
|
|
|
**Warum vom Server und nicht aus der NUI.** Die NUI läuft unter `https`, und
|
|
CEF hebt jede `http`-Adresse selbst dorthin an. Ein Upload aus dem Fenster
|
|
heraus wäre entweder Mixed Content oder ginge ins Leere.
|
|
|
|
---
|
|
|
|
## GET /api/exists/<pfad>
|
|
|
|
Für „nur fehlende" in einem Serienlauf: eine winzige Antwort gegen 130 KB
|
|
Upload.
|
|
|
|
```json
|
|
{ "exists": true, "path": "vehicles/adder.webp",
|
|
"url": "…", "size": 132760, "sha256": "…", "updated_at": 1786466462488 }
|
|
```
|
|
|
|
> **Drei Zustände, nicht zwei.** Wer einen Netzfehler als „nicht da" durchgehen
|
|
> lässt, lädt bei einem Husten alles ein zweites Mal hoch. Ein Aufrufer sollte
|
|
> zwischen `exists: false` und „ich weiß es nicht" unterscheiden.
|
|
|
|
---
|
|
|
|
## DELETE /api/media/<pfad>
|
|
|
|
Braucht einen Token mit dem Recht **darf löschen**.
|
|
|
|
```json
|
|
{ "deleted": "vehicles/adder.webp" }
|
|
```
|
|
|
|
Löschen ist endgültig: die Datei ist von der Platte weg, und es gibt keinen
|
|
Papierkorb. Die tägliche Sicherung hilft nur bis zum letzten Lauf.
|
|
|
|
---
|
|
|
|
## Grenzen
|
|
|
|
| | |
|
|
|---|---|
|
|
| Größte Datei | 64 MB (`MAX_UPLOAD_MB`) |
|
|
| Pfadlänge | 200 Zeichen, höchstens 8 Ebenen |
|
|
| Anmeldeversuche | 5 frei, danach Sperre ab 30 s (verdoppelt bis 15 min) |
|
|
| Sitzung | 30 Tage |
|
|
|
|
---
|
|
|
|
## Der Zwischenspeicher des Proxys
|
|
|
|
Vor dem Dienst steht Nginx Proxy Manager. Dessen `assets.conf` greift über eine
|
|
Regel auf **jede URL, die auf `.webp`, `.png`, `.js`, `.css` … endet** — und
|
|
legt die Antwort für 30 Minuten weg, dabei ignoriert sie unsere eigenen
|
|
Kopfzeilen.
|
|
|
|
Das hatte zwei sichtbare Folgen, beide nachgemessen und beide behoben:
|
|
|
|
- Ein **überschriebenes Bild** blieb bis zu 30 Minuten alt, obwohl Upload und
|
|
Datenbank „fertig" sagten.
|
|
- `/api/exists/…webp` wurde mit Antwort zwischengespeichert und danach **auch
|
|
ohne Token** ausgeliefert.
|
|
|
|
Behoben durch drei Blöcke in der *Custom Nginx Configuration* des Proxy-Hosts:
|
|
|
|
```nginx
|
|
location ^~ /api/ { proxy_cache off; include conf.d/include/proxy.conf; }
|
|
location ^~ /f/ { proxy_cache off; include conf.d/include/proxy.conf; }
|
|
location ^~ /t/ { proxy_cache off; include conf.d/include/proxy.conf; }
|
|
```
|
|
|
|
Das `^~` ist der ganze Trick: eine gewöhnliche Präfix-Regel **verliert** gegen
|
|
die Regex aus `assets.conf`. In einem Wegwerf-Nginx nachgemessen.
|
|
|
|
---
|
|
|
|
## Export
|
|
|
|
Im Dashboard unter **API** — oder direkt, mit gültiger Sitzung:
|
|
|
|
```
|
|
/api/dash/export?format=json alles, was der Dienst weiß
|
|
/api/dash/export?format=csv Semikolon und CRLF, für Excel
|
|
/api/dash/export?format=lua fertige Tabelle Name → Adresse
|
|
/api/dash/export?format=lua&folder=vehicles
|
|
```
|
|
|
|
Die Lua-Fassung liest man in einer Resource als `Urls.adder` aus.
|
|
|
|
---
|
|
|
|
*Diese Seite wird aus `docs/` im Repo erzeugt — Änderungen hier gehen beim nächsten Lauf verloren.*
|