API change

This commit is contained in:
Nik Afiq 2025-11-11 22:48:57 +09:00
parent c2d5d54592
commit 062c659cde
7 changed files with 47 additions and 21 deletions

View File

@ -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())

View File

@ -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!
}
}

View File

@ -0,0 +1,7 @@
export const API_ENDPOINT = {
v1: {
CURRENT: `/api/v1/current`,
SHOWS: `/api/v1/shows`,
TIME: `/api/v1/time`
},
} as const;

View File

@ -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<number | null>(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,
});

View File

@ -1,13 +1,14 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { API_ENDPOINT } from "../api/endpoint";
/** Uses /api/time => { now: <ms UTC> } 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;

View File

@ -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)$/;

View File

@ -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,
},
},
},