From d86cd4bfba9ddaaa8f116ac51fa8c3d2e5303894 Mon Sep 17 00:00:00 2001 From: Randy Hammond Date: Mon, 27 Apr 2026 02:35:16 +0000 Subject: [PATCH 1/2] feat(frontend): migrate REST/audio/SW to /api/v1 The frontend has been carrying legacy /api/* deprecation traffic since the native v1 surface landed. Move every client-side caller over to v1 so production logs stop showing self-inflicted hits in the legacy-usage report: - RTK Query base URL flips to /api/v1 - /api/v1/listener/tg-selection (was /api/auth/tg-selection) - /api/v1/admin/radioreference/preview (was /admin/radioreference/preview/csv) - /api/v1/admin/legacy-usage (was /v1/admin/legacy-usage relative to /api) - raw fetch() in main.tsx auth-recovery and ToolsPanel Swagger session - audio download URLs in player.ts, BookmarksPanel, SearchPanel - service worker passthrough regex accepts both /api/v1/{calls,shared} and the legacy variants during the transition window - vite dev proxy forwards /api/v1/ws WebSocket upgrades Legacy /api/* routes remain available with deprecation headers for external consumers; this only moves the embedded frontend. All 201 vitest specs pass. tsc clean. Backend unchanged. --- CHANGELOG.md | 1 + frontend/src/app/api.ts | 4 ++-- frontend/src/app/slices/admin/adminSlice.ts | 2 +- frontend/src/app/slices/shared/authSlice.ts | 4 ++-- frontend/src/components/admin/ToolsPanel.tsx | 4 ++-- frontend/src/components/scanner/BookmarksPanel.tsx | 4 ++-- frontend/src/components/scanner/SearchPanel.tsx | 2 +- frontend/src/main.tsx | 2 +- frontend/src/services/audio/player.ts | 4 ++-- frontend/src/types/api.ts | 2 +- frontend/sw.ts | 8 +++++--- frontend/vite.config.ts | 1 + 12 files changed, 21 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c337bb..8cbf8bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Frontend now talks to the native `/api/v1/*` surface for every REST call (RTK Query base URL, raw `fetch()` for audio downloads and silent token refresh, the service-worker passthrough rules, the dev-server proxy, and the Swagger UI bootstrap). Tg-selection moves from `/api/auth/tg-selection` to `/api/v1/listener/tg-selection`; RadioReference CSV preview moves to `/api/v1/admin/radioreference/preview`; legacy-usage report is consumed at `/api/v1/admin/legacy-usage`. Legacy `/api/*` routes remain available for non-frontend clients with the existing deprecation headers. - API-key authentication on `/api/v1/*` upload routes accepts only `Authorization: Bearer `; the legacy `X-API-Key` header, `?key=` query parameter, and `key=` form field continue to work on legacy routes only. JWT-shaped Bearer tokens on v1 API-key routes are rejected with `invalid_credentials`. ## [1.2.1] — 2026-04-25 diff --git a/frontend/src/app/api.ts b/frontend/src/app/api.ts index bf08318..87f394b 100644 --- a/frontend/src/app/api.ts +++ b/frontend/src/app/api.ts @@ -12,7 +12,7 @@ import type { } from "@/types"; const rawBaseQuery = fetchBaseQuery({ - baseUrl: "/api", + baseUrl: "/api/v1", prepareHeaders: (headers, { getState }) => { const state = getState() as { auth: { token: string | null } }; const token = state.auth?.token; @@ -149,7 +149,7 @@ export const api = createApi({ providesTags: ["Bookmarks"], }), getLegacyUsage: builder.query({ - query: () => "/v1/admin/legacy-usage", + query: () => "/admin/legacy-usage", providesTags: ["LegacyUsage"], }), }), diff --git a/frontend/src/app/slices/admin/adminSlice.ts b/frontend/src/app/slices/admin/adminSlice.ts index 180b3be..2dc357f 100644 --- a/frontend/src/app/slices/admin/adminSlice.ts +++ b/frontend/src/app/slices/admin/adminSlice.ts @@ -75,7 +75,7 @@ const adminApi = api.injectEndpoints({ // ── RadioReference CSV preview (multipart file upload) ── rrPreviewCSV: builder.mutation({ query: (body) => ({ - url: "/admin/radioreference/preview/csv", + url: "/admin/radioreference/preview", method: "POST", body, }), diff --git a/frontend/src/app/slices/shared/authSlice.ts b/frontend/src/app/slices/shared/authSlice.ts index 69c9aad..88f4c4b 100644 --- a/frontend/src/app/slices/shared/authSlice.ts +++ b/frontend/src/app/slices/shared/authSlice.ts @@ -110,14 +110,14 @@ const authApi = api.injectEndpoints({ { disabledTGs: number[]; avoidList?: AvoidEntry[] }, void >({ - query: () => "/auth/tg-selection", + query: () => "/listener/tg-selection", }), updateTGSelection: builder.mutation< { ok: boolean }, { disabledTGs: number[]; avoidList: AvoidEntry[] } >({ query: (body) => ({ - url: "/auth/tg-selection", + url: "/listener/tg-selection", method: "PUT", body, }), diff --git a/frontend/src/components/admin/ToolsPanel.tsx b/frontend/src/components/admin/ToolsPanel.tsx index 7c27db8..0c57e39 100644 --- a/frontend/src/components/admin/ToolsPanel.tsx +++ b/frontend/src/components/admin/ToolsPanel.tsx @@ -25,7 +25,7 @@ import { selectToken } from "@/app/slices/shared/authSlice"; import { useAppSelector } from "@/app/store"; import RadioReferenceCard from "@/components/admin/RadioReferenceCard"; -const SWAGGER_URL = "/api/admin/docs/index.html"; +const SWAGGER_URL = "/api/v1/admin/docs/index.html"; export default function ToolsPanel() { const token = useAppSelector(selectToken); @@ -611,7 +611,7 @@ export default function ToolsPanel() {