# Kinderkleider-Börse Zu klein gewordene Kinderkleidung fotografieren, in einer Galerie zeigen und an Familie, Freunde und Nachbarn weitergeben. Wer etwas brauchen kann, reserviert es mit einem Klick – ohne Konto, ohne Anmeldung. Fürs Erfassen genügt das Smartphone: alles fotografieren, alle Fotos auf einmal hochladen, die Angaben in Ruhe nachtragen. --- ## In vier Schritten **1.** Einen leeren Ordner anlegen, zum Beispiel `kleiderboerse`. **2.** Darin eine Datei namens `docker-compose.yml` anlegen und den Inhalt von [`docker-compose.betrieb.yml`](docker-compose.betrieb.yml) hineinkopieren. **3.** Zwei Werte eintragen: ```yaml ADMIN_PASSWORD: "dein-passwort" SECRET_KEY: "..." ``` Den `SECRET_KEY` erzeugen: ```bash docker run --rm python:3.11-slim python -c "import secrets; print(secrets.token_urlsafe(48))" ``` Bleibt `ADMIN_PASSWORD` leer, ist der Erfassungsbereich gesperrt – die Galerie funktioniert trotzdem. Absichtlich ist kein Passwort vorgegeben: ein mitgeliefertes stünde in dieser Anleitung und wäre damit bekannt. **4.** Starten und öffnen: ```bash docker compose up -d ``` → --- ## Erfassen Nach dem Anmelden gibt es zwei Wege: **Viele auf einmal** – der Weg für den Erstbestand. Zuerst alles mit der Kamera-App fotografieren, dann alle Fotos zusammen hochladen. Pro Foto entsteht ein Entwurf; unter *Entwürfe* trägst du danach Grösse und Kategorie nach. Grösse, Kategorie, Saison und Zustand werden dabei vom zuletzt gespeicherten Teil übernommen – beim Abarbeiten einer Kiste sind das vier Felder weniger pro Stück. **Einzeln** – Formular mit Foto, für spätere Nachträge. Der Titel ist freiwillig. Bleibt er leer, steht dort automatisch Kategorie und Grösse, also zum Beispiel „Jacken 98/104". Wird die Grösse später korrigiert, ändert sich der Titel mit. ## Reservieren Besucher brauchen kein Konto. Nach dem Reservieren erscheint ein Link, mit dem sich die eigene Reservierung wieder aufheben lässt – dieser Link sollte aufbewahrt werden. Ohne ihn kann nur der Betreiber die Reservierung lösen. Der Name der reservierenden Person erscheint **nicht** in der Galerie, dort steht nur „reserviert". Sichtbar ist er ausschliesslich nach der Anmeldung. --- ## Betrieb ### Aus dem Internet erreichbar machen Die App gehört hinter einen Reverse-Proxy, der HTTPS beendet. Wichtig dabei: - `X-Forwarded-Proto` und `X-Forwarded-For` weiterreichen. Ohne das erkennt die App nicht, dass der Browser verschlüsselt verbunden ist, und alle Rate-Limits zählen auf die Adresse des Proxys – ein einzelner Besucher sperrte damit alle anderen aus. - `COOKIE_HTTPS_ONLY` auf `"true"` lassen. Die Galerie ist frei zugänglich, wird aber per `noindex` und `robots.txt` von Suchmaschinen ferngehalten: sonst wären Fotos und Texte noch auffindbar, wenn die Börse längst abgeräumt ist. ### Sicherung Zwei Volumes enthalten alles, was nicht im Code steht: | Volume | Inhalt | |---|---| | `kleiderboerse-db` | Datenbank (`/data`) | | `kleiderboerse-bilder` | hochgeladene Fotos (`/uploads`) | ```bash docker run --rm -v kleiderboerse-db:/d -v "$PWD":/sicherung alpine \ tar czf /sicherung/db.tar.gz -C /d . docker run --rm -v kleiderboerse-bilder:/b -v "$PWD":/sicherung alpine \ tar czf /sicherung/bilder.tar.gz -C /b . ``` ### Einstellungen Alle über Umgebungsvariablen, keine im Image: | Variable | Standard | Bedeutung | |---|---|---| | `ADMIN_PASSWORD` | – | Passwort fürs Erfassen. Leer = gesperrt. | | `ADMIN_PASSWORD_HASH` | – | Alternative: fertiger bcrypt-Hash. Hat Vorrang. | | `SECRET_KEY` | – | Signiert das Sitzungs-Cookie. Ohne festen Wert nach jedem Neustart abgemeldet. | | `COOKIE_HTTPS_ONLY` | `true` | Sitzungs-Cookie nur über HTTPS. | | `RATE_LIMIT_RESERVE` | `5/hour` | Reservierungen pro IP. | | `RATE_LIMIT_LOGIN` | `10/hour` | Anmeldeversuche pro IP. | | `SESSION_IDLE_SECONDS` | `7200` | Abmeldung nach Ruhezeit. | | `MAX_UPLOAD_BYTES` | `10485760` | Grösse eines einzelnen Fotos. | | `DATABASE_PATH` | `/data/kleiderboerse.sqlite` | Ort der Datenbank. | | `UPLOAD_PATH` | `/uploads` | Ort der Fotos. | --- ## Entwickeln ```bash cp .env-example .env # dann ADMIN_PASSWORD darin anpassen docker compose up -d --build ``` → , API-Oberfläche unter `/docs` Tests: ```bash docker run --rm -v "$PWD":/app -w /app python:3.11-slim bash -c \ 'pip install -q -r requirements-dev.txt && python -m pytest -q' ``` Datenbank-Migration nach einer Modelländerung: ```bash alembic revision --autogenerate -m "Beschreibung" ``` ### Aufbau Ein Dienst, eine SQLite-Datei. FastAPI liefert sowohl die REST-API unter `/api/v1/…` als auch die Seiten aus; HTMX tauscht beim Filtern nur die Liste aus. Tailwind wird im Docker-Build über die Standalone-Binärdatei übersetzt – kein Node.js nötig. ``` app/ ├── main.py Einstiegspunkt, Middleware, Sicherheits-Header ├── config.py Einstellungen aus Umgebungsvariablen ├── models.py Datenmodelle ├── crud.py Datenbankzugriffe ├── security.py Anmeldung, Sitzung, CSRF, Rate-Limits ├── images.py Upload prüfen und nach WebP wandeln ├── routers/ API- und Seiten-Routen └── templates/ Jinja2-Vorlagen ``` Fremde Dateien (HTMX, Swagger, Schriften) liegen bewusst lokal im Repository statt von einem CDN geladen zu werden: die Content-Security-Policy erlaubt nur `script-src 'self'`, und ein CDN-Aufruf gäbe die IP-Adresse jedes Besuchers an einen Dritten weiter. ## Veröffentlichen Ein Versions-Tag löst den Bau des Images aus: ```bash git tag -a v1.0.0 -m "Version 1.0.0 – ..." git push origin v1.0.0 ``` Die CI baut, **testet das fertige Image** und stellt es erst danach in die Registry. `latest` wird nur bei echten Versions-Tags gesetzt. Ein Push auf `main` löst nichts aus.