Liste eines Ordners, mit Token -- fuer Aufrufer, die die Namen nicht kennen

GET /api/liste?ordner=clothing nennt alle Pfade darin (mit Unterordnern).
/api/fehlende braucht die Namen, und die kennt beim Fotostudio nur der
Spiel-Client -- die Kleidungsnamen entstehen dort aus Natives. Statt 8.000
Namen ueber ein Netzereignis zum Server zu schicken, holt der Server die
Liste, und das Studio prueft selbst.

Schreibweise genau (substr statt LIKE, das in SQLite fuer ASCII
schreibweisenblind ist). Ohne Ordnerfessel wie /exists. Gekappt bei 100.000
mit `gekappt: true` daneben.

Geprueft gegen eine DB-Kopie im eigenen Container: 401 ohne Token, 400 ohne
ordner und mit ../, clothing 2591 Pfade und deckungsgleich mit /api/fehlende,
'Clothing' (gross) leer, items enthaelt items/WEAPON_PISTOL.png.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
D4rkst3randClaude Opus 5.5 committed 2026-10-03 10:06:54 +02:00
1 parent 6677b559f2
commit c8080fb2eb
2 files changed
+66

No files matched your search

+18
View File
@@ -432,6 +432,24 @@ Dashboards ruft dieselbe.
---
## GET /api/liste?ordner=&lt;ordner&gt;
Was liegt in einem Ordner, mit Unterordnern. Für Aufrufer, die die Namen
**nicht** vorher kennen — das Fotostudio erfährt die Kleidungsnamen erst im
Spiel-Client und prüft sie dort gegen diese Liste.
```json
{ "ordner": "clothing", "gesamt": 2591, "gekappt": false,
"pfade": ["clothing/f_c_10_Female_Heist_0.png", "…"] }
```
- Mit Token, ohne Ordnerfessel (wie `/api/exists`).
- **Schreibweise genau**: `?ordner=Clothing` ist ein anderer Ordner.
- Höchstens 100 000 Pfade; darüber steht `gekappt: true`. Eine gekappte
Liste ist **keine** Auskunft über das, was dahinter liegt.
---
## DELETE /api/media/&lt;pfad&gt;
Braucht einen Token mit dem Recht **darf löschen**.
+48
View File
@@ -144,6 +144,7 @@ uploadRoutes.use('/media/*', requireToken)
// soll.
uploadRoutes.use('/zugang', requireToken)
uploadRoutes.use('/fehlende', requireToken)
uploadRoutes.use('/liste', requireToken)
uploadRoutes.use('/exists', requireToken)
uploadRoutes.use('/exists/*', requireToken)
@@ -489,6 +490,53 @@ uploadRoutes.get('/zugang', (c) => {
* Kein .webp/.png am Ende der Adresse -- die Proxy-Falle von /exists/<pfad>
* (siehe unten) kann hier nicht zuschnappen.
*/
/** Was liegt in einem Ordner -- die Liste, nicht die Frage nach Namen.
*
* WOZU, ergaenzt am 03.10.2026. /api/fehlende braucht die NAMEN, und die
* kennt beim Fotostudio nur der Spiel-Client: die Kleidungsnamen entstehen
* dort aus Natives, je Textur eines. Sie zum Server zu schicken hiesse rund
* 8.000 Namen ueber ein Netzereignis. Umgekehrt ist es eine Anfrage: der
* Dienst sagt, was liegt, und das Studio prueft jeden Namen selbst.
*
* SCHREIBWEISE GENAU, wie ueberall: `substr(...) = ?` und nicht LIKE --
* LIKE ist in SQLite fuer ASCII schreibweisenblind und naehme
* `Clothing/...` mit in `clothing/`.
*
* Mit Unterordnern (felgen/<radtyp>/<nummer> wird einer sein). Ohne
* Ordnerfessel, wie /exists: alles hier ist ueber /f/ ohnehin abrufbar.
*
* GEKAPPT bei LISTE_HOECHSTENS, und dann steht `gekappt: true` daneben --
* eine abgeschnittene Liste darf nicht als vollstaendige durchgehen, sonst
* meldet der Aufrufer alles dahinter als fehlend.
*/
const LISTE_HOECHSTENS = 100_000
uploadRoutes.get('/liste', (c) => {
const roh = (c.req.query('ordner') ?? '').trim().replace(/^\/+|\/+$/g, '')
if (!roh) return c.json({ error: 'Abfrage "ordner" fehlt, z. B. ?ordner=clothing' }, 400)
let ordner: string
try {
ordner = checkPath(roh)
} catch (err) {
return c.json({ error: err instanceof PathError ? err.message : 'Ordner unbrauchbar' }, 400)
}
const praefix = ordner + '/'
const gesamt = (
db
.prepare('SELECT count(*) AS n FROM media WHERE substr(path, 1, ?) = ?')
.get(praefix.length, praefix) as { n: number }
).n
const pfade = (
db
.prepare(
'SELECT path FROM media WHERE substr(path, 1, ?) = ? ORDER BY path LIMIT ?',
)
.all(praefix.length, praefix, LISTE_HOECHSTENS) as { path: string }[]
).map((z) => z.path)
return c.json({ ordner, gesamt, gekappt: pfade.length < gesamt, pfade })
})
uploadRoutes.post('/fehlende', async (c) => {
let body: { muster?: unknown; namen?: unknown } | null = null
try {