// AZ-486 / F7 — Single source of truth for every `/api//` 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