diff --git a/Plan-Verbesserungen.md b/Plan-Verbesserungen.md new file mode 100644 index 0000000..1ce0828 --- /dev/null +++ b/Plan-Verbesserungen.md @@ -0,0 +1,95 @@ +# 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