Files
ui/_docs/02_document/modules/src__api__endpoints.md
T
Oleksandr Bezdieniezhnykh 17d5bb45e7
ci/woodpecker/push/build-arm Pipeline was successful
[AZ-485] [AZ-486] Cycle 1 docs refresh (Step 13)
Phase B cycle 1 was a structural refactor only: F4 (barrel imports +
STC-ARCH-01) and F7 (endpoint builders + STC-ARCH-02). This commit
brings docs in line with source after the cycle, no code changes.

Module docs (12 consumers): swap every /api/<service>/... literal in
code snippets and integration tables for the matching endpoints.*
builder; note the barrel import migration in Dependencies.

New module doc: src__api__endpoints.md (public surface, F4 barrel
re-export note, STC-ARCH-02 enforcement, contract-test reference).

Architecture compliance baseline: mark F4 + F7 CLOSED with commit
hashes (23746ec, 8a461a2).

01_api-transport component description: add endpoints.ts + barrel to
Internal Interfaces, close the F7 caveat, extend Module Inventory.

ripple_log_cycle1.md: Task Step 0.5 reverse-dep analysis records the
import-graph closure (no extra docs needed beyond the direct set).

Carry-over reports landed alongside the docs:
- test_run_report_phase_b_cycle1.md (Step 11 outcome)
- implementation_report_refactor_phase_b_cycle1.md (cycle summary)

State file: trimmed to the autodev <30-line target; Steps 14 + 15
recorded as SKIPPED with rationale (no security or perf surface
changed in this cycle); pointer moved to Step 16 (Deploy).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 00:01:04 +03:00

9.3 KiB

Module: src/api/endpoints.ts

Source: src/api/endpoints.ts (79 lines) Topo batch: B2 (leaf — no internal imports) Introduced: AZ-486 (2026-05-11, commit 8a461a2), closing architecture baseline finding F7.

Purpose

Single source of truth for every /api/<service>/<path> URL the UI talks to. Replaces the hardcoded string literals that previously lived at each api.* / createSSE call site (and at every src={...} URL for API-served images / videos). The endpoints object is the canonical wire-contract documentation: each builder produces a character-identical string to the literal it superseded, so MSW handlers + e2e stubs + the nginx routing table all keep matching.

Together with the STC-ARCH-02 static gate (see Configuration), this module enforces "no hardcoded API path literals in src/" as a build-time invariant rather than a code-review aspiration.

Public interface

export const endpoints = {
  admin: {
    authRefresh: () => string
    authLogin:   () => string
    authLogout:  () => string
    users:       () => string
    user:        (id: string) => string
    classes:     () => string
    class:       (id: string | number) => string
  },
  annotations: {
    classes:                  () => string
    settingsUser:             () => string
    settingsSystem:           () => string
    settingsDirectories:      () => string
    annotations:              () => string
    annotationsByMedia:       (mediaId: string, pageSize?: number) => string  // pageSize default = 1000
    annotationImage:          (annotationId: string) => string
    annotationThumbnail:      (annotationId: string) => string
    annotationEvents:         () => string
    media:                    (queryString: string) => string
    mediaFile:                (mediaId: string) => string
    mediaItem:                (mediaId: string) => string
    mediaBatch:               () => string
    dataset:                  (queryString: string) => string
    datasetItem:              (annotationId: string) => string
    datasetBulkStatus:        () => string
    datasetClassDistribution: () => string
  },
  flights: {
    collection:       (queryString?: string) => string  // GET ?pageSize=... lists; POST (no qs) creates
    aircrafts:        () => string
    aircraft:         (id: string) => string
    flight:           (id: string) => string
    flightWaypoints:  (id: string) => string
    flightWaypoint:   (flightId: string, waypointId: string) => string
    flightLiveGps:    (id: string) => string
  },
  detect: {
    media: (mediaId: string) => string  // POST → trigger detection for a media item
  },
} as const

The whole object is as const, so each leaf's return type is the narrow string literal where possible (e.g. '/api/admin/auth/refresh') and the parameterised builders carry a string return.

Internal logic

  • Pure data + template strings. No side effects, no I/O, no caching. Every builder is a one-line () => '...' or arrow returning a template literal.
  • Function form (not constants), per direction at task-creation time:
    • Parameterised paths (e.g. flight(id)) need a function anyway. Keeping every entry as a function — even the constant ones — gives a single uniform call shape at every site (endpoints.x.y()) so reviewers don't have to remember which entries take parens and which don't.
    • Per-builder tree-shaking under Vite's production rollup remains intact.
  • Query strings owned by the caller for variable-shape paths. Where the query is dynamic (flights.collection, annotations.media, annotations.dataset), the caller builds a URLSearchParams.toString() and the builder owns only the path + ?. This keeps the wire contract identical to pre-refactor literals at every callsite.

Public API (barrel re-export)

src/api/index.ts re-exports endpoints alongside api, createSSE, setToken, getToken, getApiBase, setNavigateToLogin. Consumers OUTSIDE the 01_api-transport component MUST import from the barrel (import { endpoints } from '@/api' or from '../api') — direct imports of src/api/endpoints from other components are blocked by STC-ARCH-01 (F4 closure, see src__api__client.md).

Dependencies

  • Internal: none.
  • External: none.

Consumers (intra-repo)

After the AZ-486 migration, endpoints is imported by:

  • src/api/client.ts — internal refreshToken() helper uses endpoints.admin.authRefresh().
  • src/auth/AuthContext.tsxauthRefresh, authLogin, authLogout.
  • src/components/FlightContext.tsxflights.collection, flights.flight, annotations.settingsUser.
  • src/components/DetectionClasses.tsxadmin.classes, admin.class.
  • src/features/admin/AdminPage.tsxadmin.users, admin.user.
  • src/features/annotations/AnnotationsPage.tsx — annotation CRUD endpoints, detect.media.
  • src/features/annotations/AnnotationsSidebar.tsxannotations.annotationEvents (SSE), bulk-status, dataset endpoints.
  • src/features/annotations/CanvasEditor.tsxannotations.annotationImage, annotations.annotationThumbnail.
  • src/features/annotations/MediaList.tsxannotations.media, annotations.mediaFile, annotations.mediaItem, annotations.mediaBatch.
  • src/features/annotations/VideoPlayer.tsxannotations.mediaFile.
  • src/features/dataset/DatasetPage.tsxannotations.dataset* family, annotations.classes, annotations.annotationImage.
  • src/features/flights/FlightsPage.tsx — full flights.* surface + annotations.settingsUser.
  • src/features/settings/SettingsPage.tsxannotations.settings*, flights.aircrafts.

This is the full intra-repo consumer list — STC-ARCH-02 guarantees no production-source caller falls outside it.

Data models

None defined here. Path-string output only.

Configuration

The module IS the API-path configuration. The only "config" is the nginx routing table that maps each /api/<service>/... prefix to a concrete backend service — see src__api__client.md → External integrations for the live table.

Static enforcement (STC-ARCH-02):

  • Script: scripts/check-arch-imports.mjs --mode=api-literals.
  • Wired into: scripts/run-tests.sh (functional profile, static group) — runs before any unit test.
  • What it forbids: any /api/<service>/... literal in ['"]quoting undersrc/`.
  • Exempt files: this file (src/api/endpoints.ts) and src/**/*.test.ts(x) only.
  • Bypass policy: none. Adding a new exempt path requires updating the exempt regex in the script AND a module-layout.md rule revision in the same commit.

External integrations

This module integrates nothing directly. It documents — as TypeScript values — the wire contract for every external integration the SPA has, as routed by nginx.conf. See the routing table in src__api__client.md → External integrations for the per-prefix backend mapping.

Security

  • No bearer plumbing here. Token injection still happens in client.ts (Authorization header) and sse.ts (access_token query parameter). Builders return URLs without the token.
  • No URL-encoding of interpolated id / mediaId / queryString parameters. All current callsites pass already-safe values (UUIDs, ints, pre-built URLSearchParams.toString() output). If any future caller passes user-controlled text, the builder must add encodeURIComponent (see open question below).
  • No CSRF surface change — same posture as the pre-refactor literals.

Tests

  • src/api/endpoints.test.ts (36 Vitest assertions): pins every builder's output to its exact pre-refactor URL string. This is the contract documentation — any wire-contract change MUST update this test in the same commit as the backend / MSW / e2e stub change. Includes one barrel-re-export assertion (endpoints is reachable via import { endpoints } from '../api').
  • tests/architecture_imports.test.ts (AZ-486 / STC-ARCH-02 suite, 6 cases): verifies the static gate passes on the migrated codebase AND fails when a synthetic single-quoted / double-quoted / template-literal /api/<service>/... literal is introduced in src/. Also verifies the *.test.ts and // comment exemptions.

Notes / open questions

  • detect.media only exposes the single-segment path that the UI uses today (POST /api/detect/<mediaId>). The full detect/ service has more endpoints (per the nginx table) but no UI callsite consumes them. Add new builders only when a real callsite needs them — don't pre-populate.
  • flights.collection overloads its return on whether queryString is provided. Acceptable while the contract is "GET with ?pageSize, POST without" — but if a third flights-collection verb (DELETE? PUT?) is ever added with its own query shape, split into named builders rather than threading more conditional logic through one.
  • No URL-encoding of interpolated params (see Security). Add encodeURIComponent at the first callsite that needs it, plus a contract-test case in endpoints.test.ts. Currently safe across all 36 pinned URLs.
  • Wire-contract test coverage is exact-string, not shape. This is deliberate: a "looks like a path" matcher would silently accept a hyphen-to-underscore change that breaks the backend. Updating these strings IS a wire-contract change — treat the test as a release-gate.