# Rezept Finder Gemeinsame Food-Swipe-Webapp, mit der mehrere Personen in einer Session Rezepte bewerten und am Ende ein gemeinsames Essen finden. Die App lädt dynamisch Rezeptvorschläge von Chefkoch, synchronisiert Teilnehmer per Socket.IO und unterstützt zwei Match-Modi: ein schnelles erstes Match oder eine zweite Finalrunde aus gemeinsamen Favoriten. ## Features - Gemeinsame Sessions mit kurzem Session-Code - Einladungslink für Gäste - Host-/Gast-Flow mit eigener Namenseingabe - Swipe-Bedienung wie bei Dating-Apps: links `Nein`, rechts `Ja` - Buttons als Alternative zum Swipen - Dynamische Chefkoch-Rezepte aus „Was koche ich heute?“ - Automatisches Nachladen weiterer 8 Rezepte - Asynchrones Voting: alle Teilnehmer können in eigener Geschwindigkeit voten - Einstellbare Like-Schwelle ab mindestens 2 Likes - Zwei Match-Modi: - `Erstes Match gewinnt` - `Finalrunde aus Favoriten` - Session-Neustart durch den Host mit frischen Rezepten - Same-Domain-Setup über `/api`, vorbereitet für Nginx ## Tech Stack - Frontend: React + Vite - Realtime: Socket.IO - Backend: Flask + Flask-SocketIO - Datenbank: PostgreSQL - ORM: Flask-SQLAlchemy - Container: Docker Compose ## Projektstruktur ```text . ├── backend/ │ ├── app/ │ │ ├── models/ │ │ ├── routes/ │ │ ├── services/ │ │ └── socket/ │ ├── Dockerfile │ ├── requirements.txt │ └── run.py ├── frontend/ │ ├── public/ │ ├── src/ │ ├── Dockerfile │ ├── index.html │ ├── package.json │ └── vite.config.js ├── docker-compose.yml ├── .env.example └── README.md ``` ## Schnellstart ```bash cp .env.example .env docker compose up --build ``` Danach ist die App lokal erreichbar unter: ```text http://localhost:5173 ``` Die API läuft im Dev-Setup über denselben Origin: ```text http://localhost:5173/api ``` Vite proxyt `/api` intern an den Backend-Container. ## Environment Alle zentralen Konfigurationswerte liegen in `.env`. Die Datei ist absichtlich nicht versioniert. Als Vorlage dient [.env.example](.env.example). Wichtige Variablen: | Variable | Bedeutung | | --- | --- | | `APP_DOMAIN` | Öffentliche Domain der App, z. B. `https://example.com` | | `FRONTEND_ORIGIN` | Erlaubter Browser-Origin für CORS und Socket.IO | | `DATABASE_URL` | SQLAlchemy-Verbindung zum Backend-Postgres | | `POSTGRES_DB` | Datenbankname für den lokalen Postgres-Container | | `POSTGRES_USER` | Datenbanknutzer für den lokalen Postgres-Container | | `POSTGRES_PASSWORD` | Datenbankpasswort für den lokalen Postgres-Container | | `SECRET_KEY` | Flask Secret Key | | `SOCKETIO_PATH` | Backend-Socket.IO-Pfad, standardmäßig `/api/socket.io` | | `VITE_API_BASE` | Frontend-API-Basis, standardmäßig `/api` | | `VITE_SOCKET_PATH` | Frontend-Socket.IO-Pfad, standardmäßig `/api/socket.io` | | `VITE_DEV_API_TARGET` | Ziel für den Vite-Dev-Proxy im Docker-Netzwerk | Beispiel für lokale Entwicklung: ```env APP_DOMAIN=http://localhost:5173 FRONTEND_ORIGIN=http://localhost:5173 POSTGRES_DB=rezept_finder POSTGRES_USER=rezept_finder POSTGRES_PASSWORD=rezept_finder DATABASE_URL=postgresql+psycopg2://rezept_finder:rezept_finder@postgres:5432/rezept_finder SECRET_KEY=dev-secret SOCKETIO_PATH=/api/socket.io VITE_API_BASE=/api VITE_SOCKET_PATH=/api/socket.io VITE_DEV_API_TARGET=http://backend:5000 ``` ## Same-Domain-Routing Das Frontend spricht bewusst keine harte Backend-URL mehr an. Stattdessen laufen alle Backend-Zugriffe unter `/api`. Aktuelle Pfade: | Pfad | Ziel | | --- | --- | | `/` | Frontend | | `/api/sessions` | Flask-API | | `/api/sessions//join` | Flask-API | | `/api/socket.io` | Socket.IO / WebSocket | Das macht die App bereit für ein Nginx-Setup mit einer einzigen öffentlichen Domain. ## Beispiel: Nginx Eine mögliche Nginx-Skizze: ```nginx server { server_name example.com; location / { proxy_pass http://frontend:5173; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } location /api/socket.io { proxy_pass http://backend:5000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } location /api/ { proxy_pass http://backend:5000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } ``` Je nach Deployment laufen `frontend` und `backend` natürlich unter anderen Upstream-Namen oder Ports. ## Docker-Kommandos Alle Services starten: ```bash docker compose up --build ``` Detached starten: ```bash docker compose up -d --build ``` Logs ansehen: ```bash docker compose logs -f ``` Nur Backend-Logs: ```bash docker compose logs -f backend ``` Services stoppen: ```bash docker compose down ``` Datenbankvolume ebenfalls löschen: ```bash docker compose down -v ``` ## Entwicklung Ohne Docker ### Backend ```bash cd backend python -m venv .venv source .venv/bin/activate pip install -r requirements.txt python run.py ``` Das Backend erwartet eine gültige `DATABASE_URL`. ### Frontend ```bash cd frontend npm install npm run dev ``` Für lokale Entwicklung ohne Docker muss `VITE_DEV_API_TARGET` passend auf dein Backend zeigen, z. B.: ```env VITE_DEV_API_TARGET=http://localhost:5000 ``` ## App-Ablauf 1. Host erstellt eine Session. 2. Gäste treten per Code oder Einladungslink bei. 3. Host stellt optional Modus und Like-Schwelle ein. 4. Host startet die Session. 5. Alle Teilnehmer bewerten Rezepte per Swipe oder Button. 6. Je nach Modus gewinnt: - das erste Rezept mit genug Likes - oder ein Rezept aus einer zweiten Finalrunde ## Match-Modi ### Erstes Match Gewinnt Ein Rezept gewinnt sofort, sobald es die eingestellte Like-Schwelle erreicht. Beispiel: - 4 Teilnehmer - Like-Schwelle: 2 - Sobald 2 Personen dasselbe Rezept liken, ist es das Match. ### Finalrunde Aus Favoriten In diesem Modus gewinnt nicht sofort das erste passende Rezept. Ablauf: 1. Alle bewerten die aktuellen 8 Rezepte. 2. Rezepte mit genug Likes werden Favoriten. 3. Gibt es weniger als 3 Favoriten, lädt die App weitere 8 Rezepte nach. 4. Sobald mindestens 3 Favoriten da sind, startet Runde 2. 5. In Runde 2 werden nur die Favoriten erneut bewertet. 6. In Runde 2 gewinnt ein Rezept, sobald es die Like-Schwelle erreicht. Die Reihenfolge im zweiten Modus wird pro Teilnehmer lokal zufällig gemischt. ## Rezeptquelle Die App lädt Vorschläge von: ```text https://www.chefkoch.de/rezepte/was-koche-ich-heute/ ``` Falls Chefkoch nicht erreichbar ist oder das Parsen fehlschlägt, nutzt das Backend Fallback-Rezepte, damit die App weiter bedienbar bleibt. ## Favicon Und Assets Das Favicon liegt unter: ```text frontend/public/ ``` Dateien: - `favicon.ico` - `favicon-32.png` - `favicon-512.png` - `apple-touch-icon.png` ## Hinweise Zur Datenbank Beim App-Start wird das Schema automatisch vorbereitet. Zusätzlich gibt es eine kleine idempotente Aktualisierung für neue Settings-Spalten in `session_settings`. Für produktive Deployments sollte später eine echte Migration-Strategie verwendet werden, z. B. Flask-Migrate/Alembic. ## Troubleshooting ### Frontend Kann Backend Nicht Erreichen Prüfe: ```bash docker compose ps docker compose logs backend docker compose logs frontend ``` Im Dev-Setup sollte dieser Request funktionieren: ```bash curl -i http://localhost:5173/api/socket.io/?EIO=4\&transport=polling ``` Und eine Session-Erstellung: ```bash curl -i -X POST http://localhost:5173/api/sessions \ -H 'Content-Type: application/json' \ -d '{"name":"Test"}' ``` ### Socket.IO Verbindet Nicht Hinter Nginx Stelle sicher, dass `/api/socket.io` WebSocket-Upgrades erlaubt: ```nginx proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; ``` ### SELinux / Permission Denied Bei Volumes Die Docker-Volumes nutzen in `docker-compose.yml` `:Z`, damit Bind-Mounts auf SELinux-Systemen lesbar sind. ### Alte Daten Stören Lokale Datenbank komplett zurücksetzen: ```bash docker compose down -v docker compose up --build ```