GZip: Die Startseite ging unkomprimiert raus - 9725 Byte, jetzt 1900. Zuletzt angemeldet heisst zuerst ausgefuehrt, die Komprimierung liegt damit aussen. Statische Dateien: StaticFiles kennt keine Cache-Dauer, per Unterklasse nachgeruestet. Bewusst nur eine Stunde - die Dateien tragen keinen Versionsanhaenger, eine lange Dauer wuerde nach einem Update die alte Fassung festhalten. (Die hochgeladenen Bilder hatten ihre Header laengst; der Plan behauptete faelschlich das Gegenteil, korrigiert.) Sicherung: sicherung.sh sichert Datenbank UND Bilder - eine Datenbank ohne die Fotos ist wertlos. Pfade werden aus der Anwendung gelesen statt geraten. Die Pruefung oeffnet die Kopie, nicht das Original. Erprobt: 4 Eintraege, 4 Bilder, vollstaendig; ein entferntes Bild wird erkannt. 81 Tests weiterhin gruen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o
115 lines
4.1 KiB
Markdown
115 lines
4.1 KiB
Markdown
# 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 ✅ ERLEDIGT 13.09.2026
|
||
|
||
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 ✅ ERLEDIGT 13.09.2026
|
||
|
||
`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 ✅ ERLEDIGT 13.09.2026
|
||
|
||
```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.
|
||
|
||
**Korrektur:** Für die hochgeladenen Bilder galt das *nicht* — die haben
|
||
ihre Cache-Header längst (`app/routers/images.py` setzt
|
||
`max-age=31536000, immutable`). Das war eine Fehlannahme beim Erstellen
|
||
dieses Plans.
|
||
|
||
## 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
|
||
|
||
---
|
||
|
||
## Umsetzung am 13.09.2026 (Branch `verbesserungen`)
|
||
|
||
| Punkt | Ergebnis |
|
||
|---|---|
|
||
| Sicherung | `sicherung.sh`, Datenbank **und** Bilder, geprüft |
|
||
| GZip | Startseite 9725 → 1900 Byte (−80 %) |
|
||
| Statische Dateien | `Cache-Control: public, max-age=3600` |
|
||
| Tests | 81 grün, unverändert |
|
||
|
||
Die Sicherung wurde gegen den laufenden Container erprobt: 4 Einträge,
|
||
4 Bilder, alle in der Datenbank verzeichneten Bilder auch wirklich im
|
||
Sicherungsordner. Gegenprobe mit entferntem Bild wird erkannt.
|
||
|
||
Offen bleibt P4 (Indizes auf `gender`/`season`) — ohne praktische Bedeutung.
|