# Plan: Verbesserungen Erstellt am 13.09.2026 nach einer Durchsicht des gesamten Repos, parallel zur Kantone-App. Ergänzt `Plan.md`, der den ursprünglichen Aufbau beschreibt. **Noch nichts davon ist umgesetzt.** ## Vorabprüfung (13.09.2026, erledigt) | | | |---|---| | Arbeitsverzeichnis | sauber | | Offene Branches | **keine** | | Tests | **81 grün in 12 Sekunden** | | Letzter Tag | v1.0.0 | **Der CI-Lauf zu v1.0.0 wurde nie bestätigt.** Der Tag wurde einmal auf dem Server gelöscht und danach auf `e0212cf` neu gesetzt. Vor dem nächsten Release prüfen, ob in der Registry tatsächlich ein Image liegt. ## Zum Zustand des Codes Die Durchsicht hat wenig ergeben, und das ist das Ergebnis: Escaping, CSRF, Token-Vergleich in konstanter Zeit, Bild-Upload mit Inhaltsprüfung statt Endung, bewusst vermiedenes N+1, CHECK-Constraints in der Datenbank statt nur in Python — das ist alles vorhanden und richtig gemacht. Die Punkte unten sind Ergänzungen, keine Korrekturen. --- ## P1 — Keine Sicherung der Datenbank Der wichtigste offene Punkt, und hier wiegt er schwerer als bei der Kantone-App: In `kleiderboerse.sqlite` stecken Einträge und in `uploads/` die dazugehörigen Fotos. **Beides ist nicht wiederherstellbar** — anders als die Kantonsdaten, die sich neu importieren liessen. `docker-compose.betrieb.yml` sieht keinen Sicherungsschritt vor. Ein `cp` der laufenden Datei genügt nicht; sie kann mitten in einer Schreiboperation erwischt werden. Richtig ist `sqlite3 ... ".backup ..."` oder `VACUUM INTO`, beide konsistent auch bei laufendem Zugriff. **Vorgehen** 1. Täglicher Lauf, sieben Stände, danach rollierend überschreiben. 2. **Die Bilder gehören dazu** — eine Datenbank ohne `uploads/` ist wertlos. 3. Einmal einen Stand tatsächlich zurückspielen und prüfen, dass die Galerie danach vollständig ist. Eine ungetestete Sicherung ist keine. Punkt 3 ist kein Formalismus: Am 13.09.2026 ist Jellyfin auf demselben Netzwerk an einer Datenbank-Migration hängengeblieben. Dass es dort Sicherungen gab, war der einzige Grund, warum die Lage beherrschbar blieb. ## P2 — Antworten werden nicht komprimiert `app/main.py` bindet keine `GZipMiddleware` ein. Jede HTML- und HTMX-Antwort geht unkomprimiert über die Leitung — bei einer Galerie mit vielen Kacheln merkbar, besonders über Mobilfunk. ```python from fastapi.middleware.gzip import GZipMiddleware app.add_middleware(GZipMiddleware, minimum_size=1000) ``` ## P2 — Statische Dateien ohne Cache-Dauer ```python app.mount("/static", StaticFiles(directory=...), name="static") ``` Ohne `max_age` werden `htmx.min.js`, `theme.js` und die beiden Swagger-Dateien bei jedem Aufruf neu verhandelt. Eine lange Cache-Dauer setzen und bei Änderungen einen Versionsanhänger an die URL hängen. Für die hochgeladenen Bilder gilt dasselbe — die Dateinamen sind zufällig erzeugt und ändern sich nie, die dürfen dauerhaft im Cache bleiben. ## P4 — Zwei Filterspalten ohne Index `crud.items_suchen()` filtert unter anderem nach `gender` und `season`. Beide Spalten haben keinen Index; `status`, `size` und `category_id` haben einen. Bei den zu erwartenden Stückzahlen ist das ohne praktische Bedeutung — der Vollständigkeit halber notiert, nicht als Handlungsbedarf. --- ## Reihenfolge 1. Sicherungen einrichten, inklusive Bilder, mit einer echten Rückspielprobe 2. GZip und Cache-Dauer — zusammen wenige Zeilen 3. Rest nach Bedarf