mirror of
https://github.com/azaion/ui.git
synced 2026-06-21 13:31:10 +00:00
70fb452805
Replace the broken `GET /api/admin/auth/refresh` (no `credentials:'include'`) mount-time bootstrap with `POST /api/admin/auth/refresh` (with credentials) chained to `GET /api/admin/users/me`. Returning users with a valid HttpOnly refresh cookie no longer flash through `/login`. Closes Finding B3 / Vision P3. - Add module-scoped `bootstrapInflight` guard (StrictMode double-mount safety) + test-only reset hook exported via the `src/auth` barrel; `tests/setup.ts` resets it in `afterEach` to prevent pending-promise leakage between tests. - Defensive `hasPermission` against legacy `/users/me` payloads omitting `permissions`; default MSW handler now seeds `permissions` explicitly. - Add `endpoints.admin.usersMe()` builder (STC-ARCH-02 forbids the literal). - Bulk-swap 15 test files from `http.get` -> `http.post` for the refresh override so intentional bootstrap-fail tests still fail correctly. - Update auth component description; mark B3 closed. - Code review verdict PASS; static + fast suites green (231 / 13 skipped). Batch report: _docs/03_implementation/batch_13_cycle3_report.md Co-authored-by: Cursor <cursoragent@cursor.com>
84 lines
4.5 KiB
TypeScript
84 lines
4.5 KiB
TypeScript
// AZ-486 / F7 — Single source of truth for every `/api/<service>/<path>` URL
|
|
// the UI talks to today. Closes architecture baseline finding F7.
|
|
//
|
|
// Every UI callsite of `api.*`, `createSSE`, and raw image/video `src` URLs
|
|
// pointing at an API resource MUST go through one of these builders. The
|
|
// STC-ARCH-02 static gate (scripts/check-arch-imports.mjs `--mode=api-literals`,
|
|
// wired into scripts/run-tests.sh) enforces it.
|
|
//
|
|
// **Wire-contract invariant**: the strings produced here are character-identical
|
|
// to the literals that lived in the source before this refactor and that MSW
|
|
// handlers + e2e stubs intercept. Any change to a builder's output is a wire-
|
|
// contract change and MUST be coordinated with the backend + the MSW handler
|
|
// surface + e2e stubs in the same commit. The accompanying test file
|
|
// (`endpoints.test.ts`) pins every URL string and is the contract documentation.
|
|
//
|
|
// **Why function form (not constants)**: per user direction at task-creation
|
|
// time; allows parameter interpolation without callsite re-introducing template
|
|
// literals and keeps tree-shaking per-builder under Vite's production rollup.
|
|
|
|
export const endpoints = {
|
|
admin: {
|
|
authRefresh: () => '/api/admin/auth/refresh',
|
|
authLogin: () => '/api/admin/auth/login',
|
|
authLogout: () => '/api/admin/auth/logout',
|
|
users: () => '/api/admin/users',
|
|
// AZ-510 — chained from POST authRefresh() during AuthProvider bootstrap
|
|
// (the POST refresh response is `{ token }` only; the user shape comes
|
|
// from this GET). Keeps `01_api-transport` as the single source of truth
|
|
// for `/api/admin/...` literals (STC-ARCH-02).
|
|
usersMe: () => '/api/admin/users/me',
|
|
user: (id: string) => `/api/admin/users/${id}`,
|
|
classes: () => '/api/admin/classes',
|
|
// DetectionClass.id is `number` in the type system; widened to accept
|
|
// string for forward-compat if the backend switches the column to UUID.
|
|
class: (id: string | number) => `/api/admin/classes/${id}`,
|
|
},
|
|
annotations: {
|
|
classes: () => '/api/annotations/classes',
|
|
settingsUser: () => '/api/annotations/settings/user',
|
|
settingsSystem: () => '/api/annotations/settings/system',
|
|
settingsDirectories: () => '/api/annotations/settings/directories',
|
|
annotations: () => '/api/annotations/annotations',
|
|
// page-size is currently always 1000 at every callsite; expose it as an
|
|
// optional param so future tuning is a single-file change.
|
|
annotationsByMedia: (mediaId: string, pageSize: number = 1000) =>
|
|
`/api/annotations/annotations?mediaId=${mediaId}&pageSize=${pageSize}`,
|
|
annotationImage: (annotationId: string) =>
|
|
`/api/annotations/annotations/${annotationId}/image`,
|
|
annotationThumbnail: (annotationId: string) =>
|
|
`/api/annotations/annotations/${annotationId}/thumbnail`,
|
|
annotationEvents: () => '/api/annotations/annotations/events',
|
|
// Callers pre-build a URLSearchParams.toString() and pass it through; the
|
|
// builder owns the path + `?` only so the wire-contract stays identical.
|
|
media: (queryString: string) => `/api/annotations/media?${queryString}`,
|
|
mediaFile: (mediaId: string) => `/api/annotations/media/${mediaId}/file`,
|
|
mediaItem: (mediaId: string) => `/api/annotations/media/${mediaId}`,
|
|
mediaBatch: () => '/api/annotations/media/batch',
|
|
dataset: (queryString: string) => `/api/annotations/dataset?${queryString}`,
|
|
datasetItem: (annotationId: string) =>
|
|
`/api/annotations/dataset/${annotationId}`,
|
|
datasetBulkStatus: () => '/api/annotations/dataset/bulk-status',
|
|
datasetClassDistribution: () =>
|
|
'/api/annotations/dataset/class-distribution',
|
|
},
|
|
flights: {
|
|
// GET (with `?pageSize=...`) lists flights; POST (no query) creates one.
|
|
// The query string is owned by the caller (URLSearchParams.toString()) so
|
|
// the wire-contract stays identical to today.
|
|
collection: (queryString?: string) =>
|
|
queryString ? `/api/flights?${queryString}` : '/api/flights',
|
|
aircrafts: () => '/api/flights/aircrafts',
|
|
aircraft: (id: string) => `/api/flights/aircrafts/${id}`,
|
|
flight: (id: string) => `/api/flights/${id}`,
|
|
flightWaypoints: (id: string) => `/api/flights/${id}/waypoints`,
|
|
flightWaypoint: (flightId: string, waypointId: string) =>
|
|
`/api/flights/${flightId}/waypoints/${waypointId}`,
|
|
flightLiveGps: (id: string) => `/api/flights/${id}/live-gps`,
|
|
},
|
|
detect: {
|
|
// Trigger detection for a media item. Single-segment service path.
|
|
media: (mediaId: string) => `/api/detect/${mediaId}`,
|
|
},
|
|
} as const
|