From 80f833b926493477b766f025be9b25e28ed4eb67 Mon Sep 17 00:00:00 2001 From: Tomas Dvorak Date: Fri, 24 Oct 2025 16:28:31 +0200 Subject: [PATCH] hot fix #2 dev day #70 --- .env | 4 +-- .env.example | 4 +-- cache/prefetch/articles.json | 1 + cache/prefetch/articles.json.hdr | 1 + cache/prefetch/competition_aliases.json | 1 + cache/prefetch/competition_aliases.json.hdr | 1 + cache/prefetch/events_upcoming.json | 1 + cache/prefetch/events_upcoming.json.hdr | 1 + cache/prefetch/meta.json | 1 + cache/prefetch/prefetch_status.json | 37 +++++++++++++++++++++ cache/prefetch/settings.json | 1 + cache/prefetch/settings.json.hdr | 1 + cache/prefetch/sponsors.json | 1 + cache/prefetch/sponsors.json.hdr | 1 + cache/prefetch/team_logo_overrides.json | 1 + cache/prefetch/team_logo_overrides.json.hdr | 1 + frontend/.env | 4 +-- frontend/.env.example | 8 +++-- frontend/nginx.conf | 32 ++++++++++++++++++ frontend/src/services/api.ts | 6 ++-- frontend/src/utils/url.ts | 24 +++++++++---- 21 files changed, 115 insertions(+), 17 deletions(-) create mode 100644 cache/prefetch/articles.json create mode 100644 cache/prefetch/articles.json.hdr create mode 100644 cache/prefetch/competition_aliases.json create mode 100644 cache/prefetch/competition_aliases.json.hdr create mode 100644 cache/prefetch/events_upcoming.json create mode 100644 cache/prefetch/events_upcoming.json.hdr create mode 100644 cache/prefetch/meta.json create mode 100644 cache/prefetch/prefetch_status.json create mode 100644 cache/prefetch/settings.json create mode 100644 cache/prefetch/settings.json.hdr create mode 100644 cache/prefetch/sponsors.json create mode 100644 cache/prefetch/sponsors.json.hdr create mode 100644 cache/prefetch/team_logo_overrides.json create mode 100644 cache/prefetch/team_logo_overrides.json.hdr diff --git a/.env b/.env index 0a190e4..b27ffb6 100644 --- a/.env +++ b/.env @@ -62,10 +62,10 @@ MAX_FILES=5 # Maximum number of files per upload ALLOWED_ORIGINS=http://localhost:3000,http://localhost:8080 # Frontend Configuration REACT_APP_NAME=Fotbal Club Manager -REACT_APP_API_URL=http://localhost:8080/api/v1 +REACT_APP_API_URL=/api/v1 # FACR API Configuration -REACT_APP_FACR_API_BASE_URL=http://localhost:8080/api/v1/facr +REACT_APP_FACR_API_BASE_URL=/api/v1/facr REACT_APP_FACR_API_TIMEOUT=5000 REACT_APP_FACR_CACHE_TTL=3600000 diff --git a/.env.example b/.env.example index 0a190e4..b27ffb6 100644 --- a/.env.example +++ b/.env.example @@ -62,10 +62,10 @@ MAX_FILES=5 # Maximum number of files per upload ALLOWED_ORIGINS=http://localhost:3000,http://localhost:8080 # Frontend Configuration REACT_APP_NAME=Fotbal Club Manager -REACT_APP_API_URL=http://localhost:8080/api/v1 +REACT_APP_API_URL=/api/v1 # FACR API Configuration -REACT_APP_FACR_API_BASE_URL=http://localhost:8080/api/v1/facr +REACT_APP_FACR_API_BASE_URL=/api/v1/facr REACT_APP_FACR_API_TIMEOUT=5000 REACT_APP_FACR_CACHE_TTL=3600000 diff --git a/cache/prefetch/articles.json b/cache/prefetch/articles.json new file mode 100644 index 0000000..b668362 --- /dev/null +++ b/cache/prefetch/articles.json @@ -0,0 +1 @@ +{"items":[],"page":1,"page_size":10,"total":0} \ No newline at end of file diff --git a/cache/prefetch/articles.json.hdr b/cache/prefetch/articles.json.hdr new file mode 100644 index 0000000..48f058c --- /dev/null +++ b/cache/prefetch/articles.json.hdr @@ -0,0 +1 @@ +{"etag":"","fetched_at":"2025-10-24T14:27:54Z","last_modified":""} \ No newline at end of file diff --git a/cache/prefetch/competition_aliases.json b/cache/prefetch/competition_aliases.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/cache/prefetch/competition_aliases.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/cache/prefetch/competition_aliases.json.hdr b/cache/prefetch/competition_aliases.json.hdr new file mode 100644 index 0000000..48f058c --- /dev/null +++ b/cache/prefetch/competition_aliases.json.hdr @@ -0,0 +1 @@ +{"etag":"","fetched_at":"2025-10-24T14:27:54Z","last_modified":""} \ No newline at end of file diff --git a/cache/prefetch/events_upcoming.json b/cache/prefetch/events_upcoming.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/cache/prefetch/events_upcoming.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/cache/prefetch/events_upcoming.json.hdr b/cache/prefetch/events_upcoming.json.hdr new file mode 100644 index 0000000..48f058c --- /dev/null +++ b/cache/prefetch/events_upcoming.json.hdr @@ -0,0 +1 @@ +{"etag":"","fetched_at":"2025-10-24T14:27:54Z","last_modified":""} \ No newline at end of file diff --git a/cache/prefetch/meta.json b/cache/prefetch/meta.json new file mode 100644 index 0000000..7bae4f7 --- /dev/null +++ b/cache/prefetch/meta.json @@ -0,0 +1 @@ +{"lastUpdated":"2025-10-24T14:27:54Z"} \ No newline at end of file diff --git a/cache/prefetch/prefetch_status.json b/cache/prefetch/prefetch_status.json new file mode 100644 index 0000000..5493da9 --- /dev/null +++ b/cache/prefetch/prefetch_status.json @@ -0,0 +1,37 @@ +{ + "baseURL": "http://127.0.0.1:8080/api/v1", + "duration_ms": 15, + "endpoints": [ + { + "path": "/settings", + "file": "settings.json", + "ok": true + }, + { + "path": "/articles?page=1\u0026page_size=10\u0026published=true", + "file": "articles.json", + "ok": true + }, + { + "path": "/sponsors", + "file": "sponsors.json", + "ok": true + }, + { + "path": "/events/upcoming", + "file": "events_upcoming.json", + "ok": true + }, + { + "path": "/public/team-logo-overrides", + "file": "team_logo_overrides.json", + "ok": true + }, + { + "path": "/competition-aliases", + "file": "competition_aliases.json", + "ok": true + } + ], + "lastUpdated": "2025-10-24T14:27:54Z" +} \ No newline at end of file diff --git a/cache/prefetch/settings.json b/cache/prefetch/settings.json new file mode 100644 index 0000000..cd26240 --- /dev/null +++ b/cache/prefetch/settings.json @@ -0,0 +1 @@ +{"about_html":"","accent_color":"#e53e3e","background_color":"#ffffff","club_id":"","club_logo_url":"","club_name":"","club_type":"","club_url":"","contact_address":"","contact_city":"","contact_country":"","contact_email":"","contact_phone":"","contact_zip":"","custom_nav":null,"facebook_url":"","font_body":"Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif","font_heading":"Poppins, sans-serif","gallery_label":"","gallery_url":"","instagram_url":"","location_latitude":0,"location_longitude":0,"map_style":"","map_zoom_level":0,"merch_items":null,"merch_limit":0,"merch_module_enabled":false,"merch_source":"","merch_style":"","primary_color":"#1a365d","secondary_color":"#2b6cb0","show_about_in_nav":false,"show_map_on_homepage":false,"sponsors_layout":"","sponsors_theme":"","text_color":"#1a202c","videos":null,"videos_items":null,"videos_limit":6,"videos_module_enabled":false,"videos_source":"auto","videos_style":"slider","youtube_url":""} \ No newline at end of file diff --git a/cache/prefetch/settings.json.hdr b/cache/prefetch/settings.json.hdr new file mode 100644 index 0000000..48f058c --- /dev/null +++ b/cache/prefetch/settings.json.hdr @@ -0,0 +1 @@ +{"etag":"","fetched_at":"2025-10-24T14:27:54Z","last_modified":""} \ No newline at end of file diff --git a/cache/prefetch/sponsors.json b/cache/prefetch/sponsors.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/cache/prefetch/sponsors.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/cache/prefetch/sponsors.json.hdr b/cache/prefetch/sponsors.json.hdr new file mode 100644 index 0000000..48f058c --- /dev/null +++ b/cache/prefetch/sponsors.json.hdr @@ -0,0 +1 @@ +{"etag":"","fetched_at":"2025-10-24T14:27:54Z","last_modified":""} \ No newline at end of file diff --git a/cache/prefetch/team_logo_overrides.json b/cache/prefetch/team_logo_overrides.json new file mode 100644 index 0000000..9f1fc78 --- /dev/null +++ b/cache/prefetch/team_logo_overrides.json @@ -0,0 +1 @@ +{"by_name":{}} \ No newline at end of file diff --git a/cache/prefetch/team_logo_overrides.json.hdr b/cache/prefetch/team_logo_overrides.json.hdr new file mode 100644 index 0000000..48f058c --- /dev/null +++ b/cache/prefetch/team_logo_overrides.json.hdr @@ -0,0 +1 @@ +{"etag":"","fetched_at":"2025-10-24T14:27:54Z","last_modified":""} \ No newline at end of file diff --git a/frontend/.env b/frontend/.env index 5abf770..4e0fd33 100644 --- a/frontend/.env +++ b/frontend/.env @@ -1,10 +1,10 @@ -REACT_APP_API_URL=http://localhost:8080/api/v1 +REACT_APP_API_URL=/api/v1 REACT_APP_NAME=Fotbal Club Manager REACT_APP_ENV=development # FACR API Configuration - Local instance # Backend exposes the FACR proxy under /api/v1/facr -REACT_APP_FACR_API_BASE_URL=http://localhost:8080/api/v1/facr +REACT_APP_FACR_API_BASE_URL=/api/v1/facr REACT_APP_FACR_API_TIMEOUT=5000 # 5 seconds REACT_APP_FACR_CACHE_TTL=3600000 # 1 hour in milliseconds diff --git a/frontend/.env.example b/frontend/.env.example index e0d7669..a753950 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -1,12 +1,14 @@ -REACT_APP_API_URL=http://localhost:8080/api/v1 +REACT_APP_API_URL=/api/v1 # Alternatively, you can set only the backend origin and the app will auto-append /api/v1 -# REACT_APP_API_BASE_URL=http://localhost:8080 +# REACT_APP_API_BASE_URL=https://api.example.com +# Optional: force assets (uploads, dist) to a specific domain (e.g., files.tdvorak.dev) +# REACT_APP_ASSET_BASE_URL=https://files.example.com REACT_APP_NAME=Fotbal Club Manager REACT_APP_ENV=development # FACR API Configuration - Local instance # Backend exposes the FACR proxy under /api/v1/facr -REACT_APP_FACR_API_BASE_URL=http://localhost:8080/api/v1/facr +REACT_APP_FACR_API_BASE_URL=/api/v1/facr REACT_APP_FACR_API_TIMEOUT=5000 # 5 seconds REACT_APP_FACR_CACHE_TTL=3600000 # 1 hour in milliseconds diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 504a10f..641ef3f 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -86,6 +86,38 @@ server { proxy_busy_buffers_size 8k; } + # Proxy backend-served assets so the frontend can use relative URLs + location /uploads/ { + proxy_pass http://backend:8080; + 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; + # Allow long cache; backend may set ETag/Last-Modified + add_header Cache-Control "public, max-age=2592000"; + } + + location /dist/ { + proxy_pass http://backend:8080; + 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; + add_header Cache-Control "public, max-age=2592000"; + } + + location /cache/ { + proxy_pass http://backend:8080; + 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; + add_header Cache-Control "public, max-age=600"; + } + # Error pages error_page 500 502 503 504 /50x.html; location = /50x.html { diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index 17144e9..de4ff11 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -4,15 +4,17 @@ import { getToken } from '../utils/auth'; // Resolve API URL. Some code uses REACT_APP_API_URL (full api path including /api/v1), // others set REACT_APP_API_BASE_URL (backend origin). Normalize so baseURL always points to API root. const envApiUrl = process.env.REACT_APP_API_URL || process.env.REACT_APP_API_BASE_URL; -let API_URL = envApiUrl || 'http://localhost:8080/api/v1'; +let API_URL = envApiUrl || '/api/v1'; // If the provided base looks like a backend origin (no /api/), append /api/v1 try { - const maybe = new URL(API_URL); + const maybe = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : undefined); if (!/\/api\//.test(maybe.pathname)) { // ensure single trailing slash then append api/v1 maybe.pathname = maybe.pathname.replace(/\/$/, '') + '/api/v1'; API_URL = maybe.toString(); + } else { + API_URL = maybe.toString(); } } catch { // If URL parsing fails, keep API_URL as-is diff --git a/frontend/src/utils/url.ts b/frontend/src/utils/url.ts index 5eede13..682caed 100644 --- a/frontend/src/utils/url.ts +++ b/frontend/src/utils/url.ts @@ -8,13 +8,25 @@ export function assetUrl(pathOrUrl?: string | null): string | undefined { if (/^(?:https?:)?\/\//i.test(pathOrUrl) || /^data:/i.test(pathOrUrl)) { return pathOrUrl; } - // Rewrite known backend-served asset paths (/uploads, /dist) + // Known backend-served asset paths (/uploads, optionally /dist) if (pathOrUrl.startsWith('/uploads') || pathOrUrl.startsWith('/dist')) { - const base = process.env.REACT_APP_API_BASE_URL || process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1'; - // We want the backend origin, not the API path. Construct from the base. - const baseUrl = new URL(base); - baseUrl.pathname = '/'; - return new URL(pathOrUrl, baseUrl).toString(); + // 1) Explicit override wins + const explicit = process.env.REACT_APP_ASSET_BASE_URL || process.env.REACT_APP_API_BASE_URL || process.env.REACT_APP_API_URL || ''; + if (explicit) { + const baseUrl = new URL(explicit, typeof window !== 'undefined' ? window.location.origin : undefined); + baseUrl.pathname = '/'; + return new URL(pathOrUrl, baseUrl).toString(); + } + // 2) Local dev/IP: talk to backend:8080 directly + if (typeof window !== 'undefined') { + const { protocol, hostname } = window.location; + const isLocal = hostname === 'localhost' || /^\d{1,3}(\.\d{1,3}){3}$/.test(hostname); + if (isLocal) { + return `${protocol}//${hostname}:8080${pathOrUrl}`; + } + } + // 3) Production/domain: keep relative so frontend Nginx/Cloudflared path proxy forwards to backend + return pathOrUrl; } // Otherwise return as-is (relative or other paths) return pathOrUrl;