Files
Winz 2337b7588d
Deploy repository / deploy (push) Successful in 17s
new run
2026-06-11 17:35:25 +02:00

355 lines
8.5 KiB
Markdown

# 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/<code>/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
```