commit d347574c8bf04eefcf20e7f2099a5739ba1c3c51 Author: Alexander Date: Tue Aug 11 18:07:28 2026 +0000 Die API-Anleitung diff --git a/API.md b/API.md new file mode 100644 index 0000000..6d26d83 --- /dev/null +++ b/API.md @@ -0,0 +1,218 @@ +# 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.