From 062c659cded31d05a1b8184850fe27b215954761 Mon Sep 17 00:00:00 2001 From: Nik Afiq Date: Tue, 11 Nov 2025 22:48:57 +0900 Subject: [PATCH] API change --- backend/internal/http/handlers.go | 7 ++++--- frontend/ops/nginx.conf.template | 13 ++++++------- frontend/src/api/endpoint.ts | 7 +++++++ frontend/src/components/Timer.tsx | 6 +++--- frontend/src/hooks/useServerClock.ts | 9 +++++---- frontend/src/pages/ShowsPage.tsx | 5 +++-- frontend/vite.config.ts | 21 +++++++++++++++++++-- 7 files changed, 47 insertions(+), 21 deletions(-) create mode 100644 frontend/src/api/endpoint.ts diff --git a/backend/internal/http/handlers.go b/backend/internal/http/handlers.go index e3874b1..e83b949 100644 --- a/backend/internal/http/handlers.go +++ b/backend/internal/http/handlers.go @@ -18,7 +18,10 @@ func NewRouter(svc service.EpisodeService) *gin.Engine { c.JSON(http.StatusOK, gin.H{"status": "ok"}) }) - r.GET("/time", func(c *gin.Context) { + api := r.Group("/api") + v1 := api.Group("/v1") + + v1.GET("/time", func(c *gin.Context) { now := time.Now().UTC().UnixMilli() c.Header("Cache-Control", "no-store, max-age=0, must-revalidate") @@ -27,8 +30,6 @@ func NewRouter(svc service.EpisodeService) *gin.Engine { }) }) - v1 := r.Group("/v1") - // GET /v1/current v1.GET("/current", func(c *gin.Context) { cur, err := svc.GetCurrent(c.Request.Context()) diff --git a/frontend/ops/nginx.conf.template b/frontend/ops/nginx.conf.template index 7cde7c2..88bc3f0 100644 --- a/frontend/ops/nginx.conf.template +++ b/frontend/ops/nginx.conf.template @@ -2,24 +2,23 @@ server { listen 80; server_name _; + # Static files for the SPA root /usr/share/nginx/html; index index.html; - # (Optional) redirect bare root to /watch-party/ - # Helps avoid loading "/" by mistake (your log shows that happened once) + # Redirect bare root to /watch-party/ location = / { return 302 /watch-party/; } - # Redirect no-trailing-slash to trailing slash + # Ensure trailing slash location = /watch-party { return 302 /watch-party/; } - # Serve the SPA under /watch-party/ using alias - # /watch-party/... -> /usr/share/nginx/html/... + # Serve SPA under /watch-party/ location /watch-party/ { alias /usr/share/nginx/html/; try_files $uri $uri/ /index.html; } - # Proxy API unchanged (your React app should request /api/current) + # Backend API (preserve /api/v1/… path) location /api/ { proxy_http_version 1.1; proxy_set_header Host $host; @@ -27,6 +26,6 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Connection ""; - proxy_pass ${BACKEND_ORIGIN}/; + proxy_pass ${BACKEND_ORIGIN}; # no trailing slash! } } \ No newline at end of file diff --git a/frontend/src/api/endpoint.ts b/frontend/src/api/endpoint.ts new file mode 100644 index 0000000..456e6d7 --- /dev/null +++ b/frontend/src/api/endpoint.ts @@ -0,0 +1,7 @@ +export const API_ENDPOINT = { + v1: { + CURRENT: `/api/v1/current`, + SHOWS: `/api/v1/shows`, + TIME: `/api/v1/time` + }, +} as const; \ No newline at end of file diff --git a/frontend/src/components/Timer.tsx b/frontend/src/components/Timer.tsx index 54224e0..75d5d70 100644 --- a/frontend/src/components/Timer.tsx +++ b/frontend/src/components/Timer.tsx @@ -1,9 +1,10 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { useServerClock } from "../hooks/useServerClock"; +import { API_ENDPOINT } from "../api/endpoint"; // ===== Config & fallbacks ===== const TIMEZONE = "Asia/Tokyo"; // JST (UTC+09) -const API_URL = "/api/v1/current"; +const API_URL_CURRENT = API_ENDPOINT.v1.CURRENT const FALLBACK_START_HOUR = 19; const FALLBACK_START_MINUTE = 25; const FALLBACK_END_SECONDS = 300; @@ -74,7 +75,7 @@ function jstToUtcMs(y: number, m: number, d: number, hh: number, mm: number, ss } async function loadSchedule(signal?: AbortSignal) { - const res = await fetch(API_URL, { cache: "no-store", signal }); + const res = await fetch(API_URL_CURRENT, { cache: "no-store", signal }); if (!res.ok) throw new Error(`HTTP ${res.status}`); return (await res.json()) as ApiSchedule; } @@ -97,7 +98,6 @@ export default function Timer() { const [untilStart, setUntilStart] = useState(0); const timerRef = useRef(null); const { nowMs, ready, error: timeError } = useServerClock({ - endpoint: `${import.meta.env.BASE_URL}api/time`, // works under /watch-party/ in prod, /api in dev refreshMs: TIME_SYNC_INTERVAL, }); diff --git a/frontend/src/hooks/useServerClock.ts b/frontend/src/hooks/useServerClock.ts index 076327a..98d119e 100644 --- a/frontend/src/hooks/useServerClock.ts +++ b/frontend/src/hooks/useServerClock.ts @@ -1,13 +1,14 @@ import { useCallback, useEffect, useRef, useState } from "react"; +import { API_ENDPOINT } from "../api/endpoint"; /** Uses /api/time => { now: } and returns a server-correct "nowMs()" */ +const TIME_URL_ENDPOINT = API_ENDPOINT.v1.TIME; + export function useServerClock(opts?: { - endpoint?: string; refreshMs?: number; enabled?: boolean; }) { const { - endpoint = "/api/time", refreshMs = 60_000, enabled = true, } = opts || {}; @@ -20,7 +21,7 @@ export function useServerClock(opts?: { try { setError(null); const t0 = Date.now(); - const res = await fetch(endpoint, { cache: "no-store" }); + const res = await fetch(TIME_URL_ENDPOINT, { cache: "no-store" }); const t1 = Date.now(); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); @@ -31,7 +32,7 @@ export function useServerClock(opts?: { } catch (e: any) { setError(e?.message || "time sync failed"); } - }, [endpoint]); + }, []); useEffect(() => { if (!enabled) return; diff --git a/frontend/src/pages/ShowsPage.tsx b/frontend/src/pages/ShowsPage.tsx index 79c3e2d..83e5c39 100644 --- a/frontend/src/pages/ShowsPage.tsx +++ b/frontend/src/pages/ShowsPage.tsx @@ -1,4 +1,5 @@ import { useEffect, useMemo, useState } from "react"; +import { API_ENDPOINT } from "../api/endpoint"; type Show = { id: number; @@ -10,8 +11,8 @@ type Show = { date_created: string; }; -const GET_URL = "/api/v1/shows"; -const POST_URL = "/api/v1/current"; +const GET_URL = API_ENDPOINT.v1.SHOWS; +const POST_URL = API_ENDPOINT.v1.CURRENT; const HHMM = /^(\d{1,2}):([0-5]\d)$/; diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 2bd5a82..da6dba3 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,11 +1,19 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; +// Use PUBLIC_BASE_PATH="/watch-party/" for prod container builds. +// In dev, keep "/" so local paths are simple. export default defineConfig(({ mode }) => { - const base = mode === "development" ? "/" : process.env.PUBLIC_BASE_PATH || "/"; + const base = + mode === "development" + ? "/" + : process.env.PUBLIC_BASE_PATH || "/watch-party/"; + return { base, plugins: [react()], + + // DEV: proxy /api/* directly to Go backend server: { port: 5173, open: true, @@ -13,7 +21,16 @@ export default defineConfig(({ mode }) => { "/api": { target: "http://localhost:8082", changeOrigin: true, - rewrite: p => p.replace(/^\/api/, ""), + }, + }, + }, + + // For `vite preview`, keep the same proxy + preview: { + proxy: { + "/api": { + target: "http://localhost:8082", + changeOrigin: true, }, }, },