2 Commits
Author SHA1 Message Date
StefanandClaude Opus 5 e0212cfa2c Tests von 55 auf 12 Sekunden, Abhängigkeiten nur noch einmal installieren
Docker-Image bauen / build (push) Successful in 1m7s
Der Testlauf war zu langsam. Gemessen statt geraten: 0,68 s Vorbereitung
pro Test, bei 81 Tests praktisch die ganze Laufzeit.

Ursache war bcrypt. Pro Test wird ein Hash gebildet und geprüft, und das
ist absichtlich langsam - genau das soll es im Betrieb sein. Der Aufwand
ist jetzt über BCRYPT_ROUNDS einstellbar (Standard bleibt 12) und in den
Tests auf 4 gesetzt: dort geht es um die Ablauflogik, nicht um die Stärke
des Hashes.

Der Dekompressionsbomben-Test erzeugte ein Bild mit 400 Megapixeln, allein
dafür 4,8 s. Jetzt wird stattdessen die Grenze heruntergesetzt und ein
kleines Bild verwendet - dieselbe Codestelle, ohne die Wartezeit.

Ausserdem installierten Test- und Anwendungs-Image dieselben
Abhängigkeiten zweimal. Dockerfile.test setzt nun auf dem gebauten
Anwendungs-Image auf, der Testschritt kommt entsprechend danach.

Dabei zwei Fallen, die beide auffielen, weil die Tests plötzlich wieder
langsam waren:
- Das Basis-Image bringt seinen eigenen Stand von app/ mit. Ohne erneutes
  Kopieren prüfen die Tests den Code des Basis-Images - ist es veraltet,
  läuft alles gegen alten Code und meldet Erfolg.
- Der ENTRYPOINT des Anwendungs-Images startet eine Datenbank-Migration.
  Für Tests weder nötig noch erwünscht, darum geleert.

Ganze Kette lokal durchgespielt: bauen, testen, Image prüfen - 22 s.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o
2026-08-30 14:45:24 +02:00
StefanandClaude Opus 5 1d122b33f9 Phase 2: Bild-Upload, WebP-Konvertierung und Stapel-Import
Uploads werden geprüft, nach WebP gewandelt (max. 1200 px Breite) und über
eine eigene Route ausgeliefert. Dazu der Stapel-Import: viele Fotos auf
einmal hochladen, pro Foto entsteht ein Entwurf.

Der Upload ist die grösste Angriffsfläche der Anwendung - die Galerie ist
frei zugänglich, und hier landet fremdes Binärmaterial auf der Platte.
Entsprechend:

- Der Dateiname wird selbst erzeugt (UUID + .webp), nie aus dem Upload
  übernommen. Beim Ausliefern wird er zusätzlich gegen ein Muster geprüft:
  er kommt dort aus der URL und ist damit Nutzereingabe, auch wenn wir ihn
  ursprünglich vergeben haben.
- Der Inhalt entscheidet, nicht die Endung - Pillow muss das Bild öffnen
  können. Eine als .jpg getarnte PHP-Datei fällt durch.
- Pixelmasse ausdrücklich begrenzt, VOR dem Dekodieren. Pillow allein
  wirft erst bei der doppelten Zahl einen Fehler; dazwischen gibt es nur
  eine Warnung, die niemand sieht.
- Gespeichert wird nur das Ergebnis. Das entfernt eingebettete
  Fremdinhalte und die EXIF-Daten - bei Handyfotos oft samt
  GPS-Koordinaten der Wohnung. Die Drehung wird vorher auf die Bildpunkte
  angewendet, sonst lägen Hochkant-Fotos quer.
- Bilder liegen nicht unter static/, sondern werden mit festem
  Content-Type und nosniff ausgeliefert.

HEIC wird unterstützt (pillow-heif): iPhones nehmen so auf, und ohne das
fiele beim Stapel-Import jedes iPhone-Foto durch. Ein unlesbares Bild
lässt bewusst den ganzen Stapel scheitern - bei zwanzig Fotos fiele sonst
niemandem auf, dass eines fehlt.

Beim Löschen werden die Dateien mitentfernt (ON DELETE CASCADE räumt nur
die Zeilen weg), und wird das Vorschaubild gelöscht, rückt das nächste
nach.

42 Tests, alle grün. Zusätzlich von Hand gegen die laufende API geprüft:
3000x2200-Foto kommt als WebP 1200x880 an, Pfadmanipulation und getarnte
Dateien werden abgewiesen, Entwürfe bleiben aus der Galerie draussen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o
2026-08-29 22:25:06 +02:00