feat: fehlende Bilder finden, Massen-Umbenennen, Bilder verkleinern, Einstellungen mitnehmen

FEHLENDE BILDER. Die Frage, die bisher niemand beantwortet hat: das Fotostudio
weiss, was es aufgenommen hat, der Dienst weiss, was liegt, und die Differenz
rechnet keiner aus -- weshalb "das Handy zeigt bei manchen Autos kein Bild"
erst im Spiel auffaellt.

POST /api/dash/fehlende nimmt ein Muster mit genau EINEM Platzhalter und eine
Liste (als Array oder als Textblock aus einem Textfeld) und gibt beide
Richtungen zurueck: was fehlt, und was daliegt ohne in der Liste zu stehen.
Beides bedeutet etwas anderes -- fehlende sind Arbeit fuers Fotostudio,
ueberzaehlige sind Platz, den man zurueckbekommt.

Gesucht wird KLEINGESCHRIEBEN, weil so abgelegt wird: wer "WEAPON_SMG"
hereinreicht, meint weapon_smg.png und bekaeme sonst ein "fehlt", das gelogen
waere. Gemessen an echten Namen: 6 geprueft, 4 da, 2 fehlen, 985 ueberzaehlig.

MASSEN-UMBENENNEN mit Vorschau. Bei 3578 Items ist eine Umbenennung von Hand
keine. DIE VORSCHAU IST DER EIGENTLICHE TEIL: was hier passiert, ist dieselbe
Warnung wie beim Ordner-Umbenennen -- jede alte Adresse gibt danach 404 --, nur
mal tausend.

Geprueft wird VOR dem ersten Schritt: Kollisionen untereinander, belegte Ziele,
unzulaessige Pfade. Und EIN Problem verhindert ALLES; eine halbe Umbenennung
ist der Zustand, aus dem niemand mehr herausfindet, weil die Grenze nirgends
steht. Gemessen: Vorschau ueber 134 Dateien fasst nichts an; eine erzwungene
Kollision gibt 409 und laesst alle drei Dateien stehen; der saubere Lauf
benennt zwei um, Vorschau wandert mit, ein Eintrag im Verlauf statt zwei.

BILDER BEIM ABLEGEN VERKLEINERN, je Token einstellbar. NICHT als Schutz -- das
waere gemessen falsch, ein 12000x12000-PNG kostet 374 ms und 47 MB, weil
libvips kachelweise arbeitet. Es geht um Plattenplatz und Ladezeit: ein
Bildschirmfoto ist 3840 breit und wird auf einem Handy auf 400 gezeigt.
withoutEnlargement ist dabei entscheidend, sonst blaeht die Einstellung kleine
Icons auf.

DABEI WAERE MIR EIN ECHTER FEHLER UNTERLAUFEN: der sha256 wird weiter oben aus
dem Original gerechnet (fuer den Ersatzpfad, wenn kein X-Path kam). Wird das
Bild verkleinert, ist das eine ANDERE Datei -- ein Datensatz mit dem Hash des
Originals waere schlicht falsch, und jede spaetere Gegenprobe verglichen gegen
etwas, das nirgends liegt. Gemessen nach der Korrektur: 3000x2000 -> 512x341,
82 KB -> 3 KB, und Datenbank und Platte stimmen in Groesse und Hash ueberein.

EINSTELLUNGEN MITNEHMEN. Export als JSON, Import zurueck. GEHEIMNISSE GEHEN
STANDARDMAESSIG NICHT MIT: unter den Schluesseln liegen das
Discord-Client-Geheimnis und das Nextcloud-Passwort, und eine Datei, die man
"mal eben" exportiert und in einen Chat zieht, ist genau der Weg, auf dem so
etwas abhandenkommt. Wer sie braucht, sagt ?geheim=1 -- und der Hinweis steht
dann IN der Datei, nicht nur auf dem Bildschirm, von dem er beim Weiterreichen
abfaellt. Der Export mit Geheimnissen steht im Verwaltungs-Verlauf.

Beim Import ueberschreiben LEERE WERTE NICHTS: eine Datei ohne Geheimnisse --
der Normalfall -- traegt sie als leere Zeichenkette, und die darf ein
vorhandenes Passwort nicht loeschen. Gemessen: nach einem Import mit leerem
client_secret stehen die 32 Zeichen unveraendert da.

Nicht mitgenommen wird sicherung.letzte -- das ist der Bericht des letzten
Laufs, kein Einstellungswert. In eine frische Anlage eingespielt behauptete er
dem Wachhund eine Sicherung, die es dort nie gab.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-12 07:53:31 +02:00
co-authored by Claude Opus 5
parent 29e516559a
commit 381feade4e
4 changed files with 509 additions and 12 deletions
+9
View File
@@ -205,6 +205,13 @@ CREATE TABLE IF NOT EXISTS settings (
// laengst, aber es galt je Upload. Ein Token mit "hoechstens 2 MB je Bild"
// konnte trotzdem die Platte fuellen, es brauchte nur genug Bilder.
const kSpalten = db.prepare('PRAGMA table_info(tokens)').all() as { name: string }[]
// Groesste erlaubte Kantenlaenge fuer Bilder ueber diesen Token. Null =
// unveraendert ablegen.
if (!kSpalten.some((s) => s.name === 'max_kante')) {
db.exec('ALTER TABLE tokens ADD COLUMN max_kante INTEGER')
console.log('[db] Spalte tokens.max_kante angelegt')
}
if (!kSpalten.some((s) => s.name === 'quota_bytes')) {
db.exec('ALTER TABLE tokens ADD COLUMN quota_bytes INTEGER')
console.log('[db] Spalte tokens.quota_bytes angelegt')
@@ -339,6 +346,8 @@ export type Token = {
max_bytes: number | null
/** Wieviel dieser Token INSGESAMT halten darf. Null = so viel er will. */
quota_bytes: number | null
/** Groesste Kantenlaenge fuer Bilder. Null = ablegen, wie es kommt. */
max_kante: number | null
/** Erlaubte Medienarten, mit Komma getrennt. Leer heisst: alle. */
arten: string
/** Ab wann der Token nicht mehr gilt. Null heisst: unbefristet. */