355 lines
8.5 KiB
Markdown
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
|
|
```
|