v1.1
This commit is contained in:
@@ -0,0 +1,14 @@
|
|||||||
|
APP_DOMAIN=https://example.com
|
||||||
|
FRONTEND_ORIGIN=https://example.com
|
||||||
|
|
||||||
|
POSTGRES_DB=rezept_finder
|
||||||
|
POSTGRES_USER=rezept_finder
|
||||||
|
POSTGRES_PASSWORD=change-me
|
||||||
|
DATABASE_URL=postgresql+psycopg2://rezept_finder:change-me@postgres:5432/rezept_finder
|
||||||
|
|
||||||
|
SECRET_KEY=change-me
|
||||||
|
SOCKETIO_PATH=/api/socket.io
|
||||||
|
|
||||||
|
VITE_API_BASE=/api
|
||||||
|
VITE_SOCKET_PATH=/api/socket.io
|
||||||
|
VITE_DEV_API_TARGET=http://backend:5000
|
||||||
@@ -1,28 +1,354 @@
|
|||||||
# Rezept Finder
|
# Rezept Finder
|
||||||
|
|
||||||
MVP: Gemeinsame Food-Swipe-Webapp.
|
Gemeinsame Food-Swipe-Webapp, mit der mehrere Personen in einer Session Rezepte bewerten und am Ende ein gemeinsames Essen finden.
|
||||||
|
|
||||||
## Entwicklung starten
|
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
|
### Backend
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd backend
|
cd backend
|
||||||
python -m venv .venv
|
python -m venv .venv
|
||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
cp .env.example .env
|
|
||||||
flask --app app:create_app db upgrade
|
|
||||||
python run.py
|
python run.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Das Backend erwartet eine gültige `DATABASE_URL`.
|
||||||
|
|
||||||
### Frontend
|
### Frontend
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd frontend
|
cd frontend
|
||||||
npm install
|
npm install
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
## Postgres lokal per Docker
|
Für lokale Entwicklung ohne Docker muss `VITE_DEV_API_TARGET` passend auf dein Backend zeigen, z. B.:
|
||||||
```bash
|
|
||||||
docker compose up -d postgres
|
```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
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ def create_app():
|
|||||||
|
|
||||||
@app.after_request
|
@app.after_request
|
||||||
def add_cors_headers(response):
|
def add_cors_headers(response):
|
||||||
origin = os.getenv("FRONTEND_ORIGIN", "*")
|
origin = os.getenv("FRONTEND_ORIGIN") or os.getenv("APP_DOMAIN") or "*"
|
||||||
response.headers["Access-Control-Allow-Origin"] = origin
|
response.headers["Access-Control-Allow-Origin"] = origin
|
||||||
response.headers["Access-Control-Allow-Headers"] = "Content-Type"
|
response.headers["Access-Control-Allow-Headers"] = "Content-Type"
|
||||||
response.headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS"
|
response.headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS"
|
||||||
@@ -31,7 +31,11 @@ def create_app():
|
|||||||
|
|
||||||
db.init_app(app)
|
db.init_app(app)
|
||||||
migrate.init_app(app, db)
|
migrate.init_app(app, db)
|
||||||
socketio.init_app(app, cors_allowed_origins=os.getenv("FRONTEND_ORIGIN", "*"))
|
socketio.init_app(
|
||||||
|
app,
|
||||||
|
cors_allowed_origins=os.getenv("FRONTEND_ORIGIN") or os.getenv("APP_DOMAIN") or "*",
|
||||||
|
path=os.getenv("SOCKETIO_PATH", "/api/socket.io"),
|
||||||
|
)
|
||||||
|
|
||||||
from .routes.sessions import bp as sessions_bp
|
from .routes.sessions import bp as sessions_bp
|
||||||
app.register_blueprint(sessions_bp, url_prefix="/api/sessions")
|
app.register_blueprint(sessions_bp, url_prefix="/api/sessions")
|
||||||
|
|||||||
+11
-7
@@ -3,9 +3,9 @@ services:
|
|||||||
image: postgres:16
|
image: postgres:16
|
||||||
container_name: rezept_finder_postgres
|
container_name: rezept_finder_postgres
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_DB: rezept_finder
|
POSTGRES_DB: ${POSTGRES_DB:-rezept_finder}
|
||||||
POSTGRES_USER: rezept_finder
|
POSTGRES_USER: ${POSTGRES_USER:-rezept_finder}
|
||||||
POSTGRES_PASSWORD: rezept_finder
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-rezept_finder}
|
||||||
ports:
|
ports:
|
||||||
- "5432:5432"
|
- "5432:5432"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -21,9 +21,11 @@ services:
|
|||||||
context: ./backend
|
context: ./backend
|
||||||
container_name: rezept_finder_backend
|
container_name: rezept_finder_backend
|
||||||
environment:
|
environment:
|
||||||
DATABASE_URL: postgresql+psycopg2://rezept_finder:rezept_finder@postgres:5432/rezept_finder
|
APP_DOMAIN: ${APP_DOMAIN:-http://localhost:5173}
|
||||||
FRONTEND_ORIGIN: http://localhost:5173
|
DATABASE_URL: ${DATABASE_URL:-postgresql+psycopg2://rezept_finder:rezept_finder@postgres:5432/rezept_finder}
|
||||||
SECRET_KEY: dev-secret
|
FRONTEND_ORIGIN: ${FRONTEND_ORIGIN:-http://localhost:5173}
|
||||||
|
SECRET_KEY: ${SECRET_KEY:-dev-secret}
|
||||||
|
SOCKETIO_PATH: ${SOCKETIO_PATH:-/api/socket.io}
|
||||||
ports:
|
ports:
|
||||||
- "5000:5000"
|
- "5000:5000"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -40,7 +42,9 @@ services:
|
|||||||
context: ./frontend
|
context: ./frontend
|
||||||
container_name: rezept_finder_frontend
|
container_name: rezept_finder_frontend
|
||||||
environment:
|
environment:
|
||||||
VITE_API_URL: http://localhost:5000
|
VITE_API_BASE: ${VITE_API_BASE:-/api}
|
||||||
|
VITE_SOCKET_PATH: ${VITE_SOCKET_PATH:-/api/socket.io}
|
||||||
|
VITE_DEV_API_TARGET: ${VITE_DEV_API_TARGET:-http://backend:5000}
|
||||||
ports:
|
ports:
|
||||||
- "5173:5173"
|
- "5173:5173"
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="icon" href="/favicon.ico" sizes="any" />
|
||||||
|
<link rel="icon" type="image/png" href="/favicon-32.png" />
|
||||||
|
<link rel="icon" type="image/png" sizes="512x512" href="/favicon-512.png" />
|
||||||
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||||
<title>Rezept Finder</title>
|
<title>Rezept Finder</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 206 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
@@ -3,7 +3,9 @@ import { createRoot } from 'react-dom/client';
|
|||||||
import { io } from 'socket.io-client';
|
import { io } from 'socket.io-client';
|
||||||
import './style.css';
|
import './style.css';
|
||||||
|
|
||||||
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5000';
|
const API_BASE = import.meta.env.VITE_API_BASE || '/api';
|
||||||
|
const SOCKET_ORIGIN = import.meta.env.VITE_SOCKET_ORIGIN || window.location.origin;
|
||||||
|
const SOCKET_PATH = import.meta.env.VITE_SOCKET_PATH || `${API_BASE}/socket.io`;
|
||||||
|
|
||||||
function shuffle(items) {
|
function shuffle(items) {
|
||||||
const shuffled = [...items];
|
const shuffled = [...items];
|
||||||
@@ -33,7 +35,7 @@ function App() {
|
|||||||
const votingRoundRef = useRef(null);
|
const votingRoundRef = useRef(null);
|
||||||
const SWIPE_THRESHOLD = 110;
|
const SWIPE_THRESHOLD = 110;
|
||||||
|
|
||||||
const socket = useMemo(() => io(API_URL, { autoConnect: false }), []);
|
const socket = useMemo(() => io(SOCKET_ORIGIN, { autoConnect: false, path: SOCKET_PATH }), []);
|
||||||
const votedRecipeIdSet = useMemo(() => new Set(votedRecipeIds), [votedRecipeIds]);
|
const votedRecipeIdSet = useMemo(() => new Set(votedRecipeIds), [votedRecipeIds]);
|
||||||
const orderedRecipes = useMemo(() => {
|
const orderedRecipes = useMemo(() => {
|
||||||
if (session?.matchMode !== 'final_round') {
|
if (session?.matchMode !== 'final_round') {
|
||||||
@@ -117,7 +119,7 @@ function App() {
|
|||||||
setError('');
|
setError('');
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_URL}/api/sessions`, {
|
const response = await fetch(`${API_BASE}/sessions`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ name: hostName || 'Host' }),
|
body: JSON.stringify({ name: hostName || 'Host' }),
|
||||||
@@ -143,7 +145,7 @@ function App() {
|
|||||||
setError('');
|
setError('');
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_URL}/api/sessions/${code}/join`, {
|
const response = await fetch(`${API_BASE}/sessions/${code}/join`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ name: guestName || 'Gast' }),
|
body: JSON.stringify({ name: guestName || 'Gast' }),
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
|
||||||
|
const apiTarget = process.env.VITE_DEV_API_TARGET || 'http://backend:5000';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
server: {
|
||||||
|
host: '0.0.0.0',
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: apiTarget,
|
||||||
|
changeOrigin: true,
|
||||||
|
ws: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user