--- title: Restructure api.js into per-domain service modules status: draft epic: https://github.com/ccatobs/ops-db-ui/issues/72 repos: - ccatobs/ops-db-ui --- # PRD 0001: Restructure api.js into per-domain service modules > Source of truth for this epic. Tracker: [epic issue](https://github.com/ccatobs/ops-db-ui/issues/72). > The epic links here; this file is not embedded in the issue body. ## Problem `src/services/api.js` is meant to be the central transport layer: it creates the Axios instance, resolves the base URL (runtime config → Vite env), attaches the bearer token, and auto-logs-out on 401. That part is genuinely cross-cutting — every feature depends on it and it carries no domain knowledge. Over time the file has also accumulated **domain-specific endpoint catalogs** exported as named objects: - `diagnose` — 7 admin-diagnose methods (includes the 404 → `not_found` normalization that is the single home for that response shape). - `tokens` — 12 token-management methods. - `plan` — ~38 write/mutation methods spanning eight distinct domains (sources, instruments/modules/lines, obs-units, programs, pre-scheduled slots, obs-modes, setup/switch-plan, obs-config/CHAI). - `scheduler` — 1 method (`check_observable`). `plan` in particular is the planner feature's entire write surface parked in the central module, so every new planner endpoint edits the same file that owns auth and 401 handling — coupling that has nothing to do with transport. A second smell compounds it: the abstraction is only half-applied. The planner components still issue ~30 raw inline `api.get('/obs/…')`, `api.get('/instruments/…')`, `api.get('/sources/…')` **reads** that never made it into the `plan` object, so `plan` wraps only the mutations while the matching reads stay scattered across the components. A reader of `api.js` sees the writes and has no idea the reads exist. This diverges from the repo's own convention of grouping by domain everywhere else — `src/store/modules/`, `src/components//`. ## Solution Return `api.js` to a **transport-only** module and give each domain its own service file, mirroring the store/component layout the repo already uses: ``` src/services/ api.js → Axios instance + interceptors only; `export default api` diagnose.js → export const diagnose = { … } (404→not_found kept intact) tokens.js → export const tokens = { … } plan.js → export const plan = { …writes AND reads for the planner domain… } scheduler.js → export const scheduler = { checkObservable } ``` Each domain file does `import api from './api'` and exports the same named object under the same name, so importers change only their **import path**, not their call sites (e.g. `import { plan as planApi } from '@/services/api'` becomes `from '@/services/plan'`). Behavior is unchanged — no endpoint URL, param, or logic changes — with one deliberate deepening: the planner domain's inline reads are folded into `plan.js` so the module owns reads and writes together and the components stop calling raw `api.get(...)`. This is a mechanical, behavior-preserving refactor. Its value is structural: `api.js` stops being a change-magnet coupled to every feature, and each feature's endpoint surface lives with that feature. ### Precondition This epic is a **follow-up to PR #71** (branch `22-include-scheduler_loop-options-in-operational_config`), which introduces `plan`/`scheduler` and itself edits `api.js`. All slices assume post-#71 `develop`; starting earlier would only collide with that PR. ## Implementation decisions - **`api.js` keeps only transport:** the `axios.create(...)` instance, `baseURL` resolution, `paramsSerializer`, the bearer-token request interceptor, the 401 auto-logout response interceptor, and `export default api`. Nothing else. - **One file per domain**, each exporting the existing named object verbatim so call sites are untouched. Names stay `diagnose`, `tokens`, `plan`, `scheduler`. - **`diagnose.js` preserves the `getOperationReport` 404 → `not_found` normalization** as-is — it is the single home for the `not_found` report shape, shared by `FailureAlertPanel.vue` and `DiagnoseView.vue`; it must not drift. - **Importer updates are path-only.** Known importers to migrate: - `diagnose`: `store/modules/transfer.js` (`import api, { diagnose }`), `components/transfer/FailureAlertPanel.vue`, `views/DiagnoseView.vue`. - `tokens`: `store/modules/tokens.js`. - `plan`: the 9 planner components under `src/components/planner/` (ChaiInparPanel, ChaiLinesPanel, InstrumentsPanel, ObsConfigDialog, ObsSetupPanel, ObsUnitsPanel, ProgramsPanel, SlotsPanel, SourcesPanel). - `scheduler`: `components/observer/CheckObservablePanel.vue`. - The many `import api from '@/services/api'` default-import consumers are unaffected — the default export stays put. - **Sequencing:** every extraction removes its block from the same `api.js`, so the slices form a linear chain (diagnose → tokens → plan+scheduler → fold reads) rather than parallel PRs that would conflict on `api.js`. The plan+scheduler slice is the one that leaves `api.js` transport-only. - **Reads-folding (final slice):** move the ~30 inline planner `api.get(...)` reads into `plan.js` as named read methods (e.g. `getSources`, `getObsUnits`, `getLines`, `getInstrumentModules`, …); planner components call those instead of raw `api.get`. Endpoint strings and params are copied verbatim. ## Testing decisions The repo has no test framework configured, so verification is behavioral via the build/lint gates plus a targeted smoke of the affected screens: - `npm run lint` and `npm run build` must pass for every slice (a moved/renamed import that misses a call site fails the build — the primary safety net for a path-only refactor). - Manual smoke of each affected surface after its slice: the Diagnose drawer and `/admin/diagnose` view (diagnose), token management (tokens), the planner panels and observer check-observable panel (plan/scheduler), confirming the same requests fire with the same params (network tab) and the 404 → not_found path still renders. - Prior art for "behavior lives at the boundary, not the file layout": the existing `diagnose`/`tokens` exports and their consumers (`FailureAlertPanel.vue`, `DiagnoseView.vue`, `store/modules/tokens.js`) — their call sites should be byte-for-byte identical after migration. ## Out of scope - No change to endpoint URLs, request params, response handling, or any runtime behavior. - No migration of the default-import `api` consumers (they already depend only on transport and stay as-is). - No new nesting/reshaping of the `plan` object's method names beyond adding the folded read methods (no `plan.sources.create` restructuring). - No backend (`ops-db-api`) changes. - Doing the refactor before PR #71 merges.