diff --git a/.env b/.env index 93f62c6..d3794d8 100644 --- a/.env +++ b/.env @@ -6,13 +6,16 @@ DATABASE_URL=postgres://bierbaron_casino:7g61eWrF8TqjkHOD7wRADAQOEEDiMYAk@markus # App APP_PORT=3000 -APP_BASE_URL=http://localhost:3000 -FRONTEND_ORIGIN=http://localhost:5173 +APP_BASE_URL=http://localhost:187 +FRONTEND_ORIGIN=http://localhost:187 # Discord OAuth (Platzhalter, trägst du später ein) DISCORD_CLIENT_ID=1431281331551211712 DISCORD_CLIENT_SECRET=TwqM5vjKzAD1lwC1cHs7CWgj-0taugJd -DISCORD_REDIRECT_URI=http://localhost:3000/auth/discord/callback +DISCORD_REDIRECT_URI=http://localhost:187/auth/discord/callback # Sessions / JWT SESSION_SECRET=qquZXyTC9e8wNTvVrIVqMarfQ92HX9tt +# Lokale Umgebung: HTTP only -> false +# Später hinter echter HTTPS-Domain: true +COOKIE_SECURE=false \ No newline at end of file diff --git a/.env.example b/.env.example index 4b3f4c0..8479202 100644 --- a/.env.example +++ b/.env.example @@ -6,8 +6,8 @@ DATABASE_URL=postgres://bierbaron:verysecret@db:5432/bierbaron_casino # App APP_PORT=3000 -APP_BASE_URL=http://localhost:3000 -FRONTEND_ORIGIN=http://localhost:5173 +APP_BASE_URL=http://localhost +FRONTEND_ORIGIN=http://localhost # Discord OAuth (Platzhalter, trägst du später ein) DISCORD_CLIENT_ID=your_discord_client_id @@ -16,3 +16,6 @@ DISCORD_REDIRECT_URI=http://localhost:3000/auth/discord/callback # Sessions / JWT SESSION_SECRET=change_me_please +# Lokale Umgebung: HTTP only -> false +# Später hinter echter HTTPS-Domain: true +COOKIE_SECURE=false \ No newline at end of file diff --git a/backend/src/config.ts b/backend/src/config.ts index 46c9907..5457aa9 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -4,11 +4,13 @@ dotenv.config(); export const config = { port: parseInt(process.env.APP_PORT || "3000", 10), databaseUrl: process.env.DATABASE_URL as string, + appBaseUrl: process.env.APP_BASE_URL || "http://localhost", discord: { clientId: process.env.DISCORD_CLIENT_ID as string, clientSecret: process.env.DISCORD_CLIENT_SECRET as string, redirectUri: process.env.DISCORD_REDIRECT_URI as string }, sessionSecret: process.env.SESSION_SECRET || "dev-secret", - frontendOrigin: process.env.FRONTEND_ORIGIN || "http://localhost:5173" -}; + frontendOrigin: process.env.FRONTEND_ORIGIN || "http://localhost:5173", + cookieSecure: process.env.COOKIE_SECURE === "true" +}; \ No newline at end of file diff --git a/backend/src/index.ts b/backend/src/index.ts index 9083d8f..5d5cfa9 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,4 +1,3 @@ -// backend/src/index.ts import express from "express"; import session from "express-session"; import cors from "cors"; @@ -13,10 +12,7 @@ import { BalanceLeaderboardEntry, BigWinLeaderboardEntry} from "./routes/leaderb const app = express(); -// --- KORREKTUR: Reverse Proxy --- -// Dem Server mitteilen, dass er hinter einem Proxy läuft (z.B. Nginx). -// '1' bedeutet, dass wir dem ersten vorgeschalteten Proxy vertrauen. -// Dies ist entscheidend, damit `req.secure` und `req.protocol` korrekt funktionieren. +// Reverse Proxy (NGINX) vertrauen app.set("trust proxy", 1); app.use(cors({ @@ -32,12 +28,8 @@ app.use( saveUninitialized: false, cookie: { httpOnly: true, - // --- KORREKTUR: Reverse Proxy --- - // Das Cookie wird nur über HTTPS gesendet, wenn die App in Produktion läuft. - // Die `trust proxy` Einstellung oben sorgt dafür, dass dies auch hinter - // einem HTTPS-Proxy korrekt erkannt wird. - secure: process.env.NODE_ENV === "production", - // 'lax' ist ein guter Standard für die SameSite-Policy. + // WICHTIG: per ENV steuerbar, nicht stumpf NODE_ENV + secure: config.cookieSecure, sameSite: "lax", maxAge: 1000 * 60 * 60 * 24 * 7 } diff --git a/backend/src/services/walletService.ts b/backend/src/services/walletService.ts index f4a4f72..0c60e18 100644 --- a/backend/src/services/walletService.ts +++ b/backend/src/services/walletService.ts @@ -1,3 +1,4 @@ +// path: backend/src/services/walletService.ts import { pool, query } from "../db"; export interface Wallet { @@ -9,6 +10,10 @@ export interface Wallet { const HOURLY_RATE = 25; const CLAIM_INTERVAL_MS = 60 * 60 * 1000; // 1 Stunde +// Begrenzung: maximal so viele Stunden werden nachträglich gutgeschrieben. +// Beispiel: 24 => max 24 * 25 = 600 Bierkästen pro Claim. +const MAX_OFFLINE_HOURS = 24; + export async function getWalletForUser(userId: number): Promise { const rows = await query( ` @@ -82,10 +87,16 @@ export async function claimHourlyForUser(userId: number): Promise { claimedAmount = HOURLY_RATE; } else { const diffMs = now.getTime() - lastClaim.getTime(); - const intervals = Math.floor(diffMs / CLAIM_INTERVAL_MS); // volle Stunden - if (intervals >= 1) { - claimedAmount = intervals * HOURLY_RATE; + if (diffMs >= CLAIM_INTERVAL_MS) { + const rawIntervals = Math.floor(diffMs / CLAIM_INTERVAL_MS); + // Begrenzung, um Unreal-Sprünge (75k etc.) zu verhindern + const effectiveIntervals = Math.min(rawIntervals, MAX_OFFLINE_HOURS); + + claimedAmount = effectiveIntervals * HOURLY_RATE; + + // Nach einem erfolgreichen Claim: nächster in 1h + nextClaimInMs = CLAIM_INTERVAL_MS; } else { claimedAmount = 0; nextClaimInMs = CLAIM_INTERVAL_MS - diffMs; @@ -148,4 +159,4 @@ export function computeNextClaimMs(last_claim_at: string | null): number { if (diffMs >= CLAIM_INTERVAL_MS) return 0; return CLAIM_INTERVAL_MS - diffMs; -} +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index d355cae..ac6e66b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,29 +1,37 @@ services: - backend: + backend: build: ./backend container_name: bierbaron_backend restart: unless-stopped env_file: - .env - ports: - - "3000:3000" networks: - bierbaron_net - frontend: + + frontend: build: ./frontend container_name: bierbaron_frontend restart: unless-stopped environment: - - VITE_API_BASE_URL=http://localhost:3000 - ports: - - "5173:5173" + - VITE_API_BASE_URL=http://localhost:187 depends_on: - backend networks: - bierbaron_net -networks: - bierbaron_net: + nginx: + image: nginx:alpine + container_name: bierbaron_nginx + restart: unless-stopped + ports: + - "187:80" + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro + depends_on: + - frontend + - backend + networks: + - bierbaron_net -volumes: - db_data: \ No newline at end of file +networks: + bierbaron_net: \ No newline at end of file diff --git a/frontend/src/api.ts b/frontend/src/api.ts index bf677f5..d42532c 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -1,10 +1,8 @@ -// frontend/src/api.ts -// API-Calls (JSON) const API_BASE = - // --- KORREKTUR: Falscher Variablenname --- - // Die Variable in docker-compose.yml heißt VITE_API_BASE_URL. - import.meta.env.VITE_API_BASE_URL || - `http://localhost:3000`; + typeof import.meta.env.VITE_API_BASE_URL === "string" && + import.meta.env.VITE_API_BASE_URL.length > 0 + ? import.meta.env.VITE_API_BASE_URL + : ""; async function apiGet(path: string, options: RequestInit = {}): Promise { const res = await fetch(`${API_BASE}${path}`, { @@ -94,8 +92,7 @@ export async function getMe(): Promise { } export function getLoginUrl(): string { - // --- KORREKTUR: Falsche Login-URL --- - // Die Route im Backend lautet /auth/discord, nicht /auth/login/discord. + // Route im Backend: /auth/discord return `${API_BASE}/auth/discord`; } diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 0000000..4ae06d4 --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,101 @@ +# path: nginx/nginx.conf +worker_processes 1; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + sendfile on; + keepalive_timeout 65; + + upstream bierbaron_backend { + server backend:3000; + } + + upstream bierbaron_frontend { + server frontend:5173; + } + + # Standard-Server: alles über diesen einen Entry-Point + server { + listen 80; + server_name _; + + # Common Proxy-Header + set $proxy_backend bierbaron_backend; + + # Backend-Routen: + # - Auth (Discord OAuth) + # - API/Leaderboards + # - Wallet + # - Slot + # - Me + # - Healthcheck + + location /health { + proxy_pass http://bierbaron_backend; + 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 /auth/ { + proxy_pass http://bierbaron_backend; + proxy_http_version 1.1; + 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://bierbaron_backend; + proxy_http_version 1.1; + 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 /wallet { + proxy_pass http://bierbaron_backend; + proxy_http_version 1.1; + 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 /slot/ { + proxy_pass http://bierbaron_backend; + proxy_http_version 1.1; + 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 /me { + proxy_pass http://bierbaron_backend; + proxy_http_version 1.1; + 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; + } + + # Alles andere -> Frontend (Vite / React) + location / { + proxy_pass http://bierbaron_frontend; + proxy_http_version 1.1; + 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; + } + } +}