# API Surface ## Health - `GET /health` ## Registry - `GET /assets` - `POST /assets` - `GET /registry/assets/{id}` - `PUT /registry/assets/{id}` - `DELETE /registry/assets/{id}` - `GET /components` - `POST /components` - `GET /registry/components/{id}` - `PUT /registry/components/{id}` Registry invariants: - Assets do not carry `project_id` in request/response contracts. - Registry `PUT` mutations are history-backed (event + snapshot + projection update), not direct projection writes. ## Ingest - `POST /ingest/hardware` - `POST /ingest/manual/csv` - `GET /ingest/manual/csv/jobs/{id}` Manual CSV ingest contract: - `POST /ingest/manual/csv` is asynchronous and returns `202 Accepted` with `job_id`. - Final result is read from `GET /ingest/manual/csv/jobs/{id}`. ## Timeline - `GET /assets/{id}/timeline` - `GET /components/{id}/timeline` - These routes now return the same structured **timeline cards** contract as history timeline endpoints (breaking change accepted). ## History API - `POST /api/history/admin/backfill/current-from-observations` - idempotent operational backfill: copies `component_type` and open `installation.slot_name` from historical `observations.details` into history-first projections (`parts`, `installations`) - `POST /api/history/admin/repair/all` - best-effort admin repair flow (single action) that can restore missing timeline slots from `observations` and enqueue recompute jobs for affected entities - `POST /api/history/admin/cleanup-orphaned-projections/preview` - lightweight count preview for orphan cleanup candidates - `POST /api/history/admin/cleanup-orphaned-projections` - destructive cleanup of registry/projection/raw rows for assets/components that no longer have active history (`is_deleted = FALSE`) - `POST /api/history/admin/cancel-by-source/preview` - previews soft-delete of history events by `source_type` (`ingest_json`, `ingest_csv`, `user`, `system`) and optional date/source_ref filters - `POST /api/history/admin/cancel-by-source` - soft-deletes matched history events and queues recompute jobs for affected assets/components - `GET /api/history/jobs/{id}` - `GET /api/history/components/{id}/events` - `GET /api/history/assets/{id}/events` - `POST /api/history/components/{id}/apply` - `POST /api/history/assets/{id}/apply` - `GET /api/history/components/{id}/timeline` - `GET /api/history/assets/{id}/timeline` - `GET /api/history/components/{id}/timeline/cards/{card_id}/events` - `GET /api/history/assets/{id}/timeline/cards/{card_id}/events` - `GET /api/history/components/{id}/events/{timeline_event_id}/detail` - `GET /api/history/assets/{id}/events/{timeline_event_id}/detail` - `DELETE /api/history/components/{id}/events/{event_id}` - `DELETE /api/history/assets/{id}/events/{event_id}` - `POST /api/history/components/{id}/rollback` - `POST /api/history/assets/{id}/rollback` ## Asset Page Component Management API (Current Components) - `GET /api/assets/{asset_id}/current-components` - returns enriched current component rows for asset page table (`status`, `type`, `slot`, `vendor/model`, firmware) and filter options - `POST /api/assets/{asset_id}/components/add/check` - duplicate preflight by `vendor_serial` and `vendor_serial + model` - `POST /api/assets/{asset_id}/components/actions/add` - `mode = create_and_attach | attach_existing` - supports `force=true` for moving an existing component from another asset to current asset - `POST /api/assets/{asset_id}/components/actions/edit` - bulk edit selected components on current asset (history-backed) - multi-select rejects unique fields (`vendor_serial`, `slot`) - `POST /api/assets/{asset_id}/components/actions/remove` - remove is **de-assert only** - requires `deassert_status = working | not_working | unknown` - `GET /api/components/search-lite` - lightweight component search for Add modal (`vendor_serial`, vendor/model, current installation, status) History API invariants: - `POST /api/history/*/apply` uses semantic dedupe (no-op changes do not create events/snapshots/timeline rows). - Delete/rollback/hard-restore are asynchronous and return `202 Accepted` with `job_id`. - Timeline endpoints are grouped by day by default (`UTC`, optional `tz` override). - Timeline endpoints return **cards** (single/dedup/bulk), not raw rows. - Timeline supports server-side filters via query parameters: `date_from`, `date_to`, `action`, `source`, `device`, `slot`, `part_number` (mapped to model), `serial`. - Card drilldown endpoint (`.../timeline/cards/{card_id}/events`) accepts `tz` and must use the same timezone as the timeline cards response used to render the card. - `DELETE .../events/{event_id}` is soft-delete + recompute enqueue (not physical delete). - `POST .../rollback` supports `compensating` and `hard_restore` modes. - Asset page component mutating actions (`add/edit/remove`) are history-backed and must not bypass history transaction flows. ## Failures - `GET /failures` - `POST /failures` `POST /failures` (manual UI registration) request: - `component_serial` (required, exact component serial) - `server_serial` (optional, used for validation/binding; submit without component serial is rejected) - `failure_date` (required, `YYYY-MM-DD`) - `description` (optional) `POST /failures` behavior: - resolves component by serial - records component failure status via history (`COMPONENT_STATUS_SET` -> `FAILED`) - writes/updates `failure_events` projection row with source `manual_ui` - returns resolved component/server summary and created IDs ## UI Routes - `GET /ui` - `GET /ui/search` - `GET /ui/asset` - `GET /ui/asset/{vendor}` - `GET /ui/asset/{vendor}/{model}` - `GET /ui/asset/{vendor}/{model}/{vendor_serial}` - `GET /ui/component` - `GET /ui/component/{vendor}` - `GET /ui/component/models` - `GET /ui/component/{vendor}/{model}` (model statistics page) - `GET /ui/component/{vendor}/{model}/{vendor_serial}` - `GET /ui/failure` - `GET /ui/failure/{failure_id}` (failure detail page) - `GET /ui/failures` - `GET /ui/failures/{failure_id}` (compatibility alias to failure detail page) - `GET /ui/ingest` - `GET /ui/ingest/manual-template.csv` - `GET /ui/history-admin` UI/admin notes: - Destructive registry action `DELETE /registry/assets/{id}` ("Asset -> Delete with details") is operated from the Data Admin UI, not from the regular asset page. - UI routes are **semantic-first and singular** (`/ui/asset`, `/ui/component`, `/ui/failure`); legacy plural/ID-based UI fallback routes are intentionally removed. - Failure detail page supports both singular and plural ID routes for compatibility (`/ui/failure/{id}` primary, `/ui/failures/{id}` alias). ## CSV Export Contract Route: `GET /ui/ingest/manual-template.csv` Rules: - Encoding: UTF-8. - UTF-8 BOM is included at file start for Excel compatibility. - Delimiter: `;`. - Line endings: `\r\n`. - Escaping: RFC4180-compatible (fields with `;`, `"`, `\n`, `\r` are quoted; inner `"` is doubled). - Header row is always present. - Stable column order (deterministic, no map-driven order). - Exported header order: - `дата_осмотра` - `серийный_номер_сервера` - `вендор` - `p/n_устройства` - `s/n_устройства` - `локейшн_в_сервере` - `версия_прошивки` - `состояние_оборудования` - Identifier-like columns are exported with Excel-safe text protection (to preserve leading zeros/codes). - Empty values are exported as empty cells (no `null`/`undefined`). - HTTP headers: - `Content-Type: text/csv; charset=utf-8` - `Content-Disposition: attachment; filename="manual-import-template.csv"` ## Routing Notes - API router is registered in `internal/api/server.go`. - Registry, ingest, failures, asset/component pages, and UI routes are attached to `http.ServeMux`. - History API routes and background history worker are also wired from `internal/api/server.go`.