From 930df5b888d9b9c12fd3e7d032c1be99c37468da Mon Sep 17 00:00:00 2001 From: "Zimin A.N." Date: Mon, 4 May 2026 15:45:09 +0300 Subject: [PATCH] =?UTF-8?q?feat(api):=20Swagger=20UI=20+=20docs/integratio?= =?UTF-8?q?n=20=D0=B4=D0=BB=D1=8F=20=D0=90=D0=BB=D1=8C=D1=82=D1=83=D0=BC?= =?UTF-8?q?=D0=B0=20=D0=B8=20=D0=B4=D1=80=D1=83=D0=B3=D0=B8=D1=85=20consum?= =?UTF-8?q?er'=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Springdoc-openapi генерирует OpenAPI 3 spec из аннотаций контроллеров — интеграторам не нужно поддерживать markdown-ТЗ синхронно с кодом. - /swagger-ui.html (UI), /v3/api-docs (JSON), /v3/api-docs.yaml. - OpenApiConfig: title/description/contact/servers (staging+prod+local), bearerAuth security scheme (Keycloak JWT). - SecurityConfig: permitAll на swagger paths. Документация: - docs/integration/altum-imaging-order.md — ТЗ интеграции «Заказ съёмки». Согласованы: m2m service account через Keycloak, BFF в altum-backend (FastAPI), 4 справочника со схемами, cascading select через instrument.supported_*_codes, snapshot strategy для аудита. - docs/tech/api-integration.md — общий гайд для интеграторов: auth flow, endpoints, кэш-стратегии, bitemporal модель, current dictionaries, cross-refs, best practices. Скрипты codegen openapi-generator-cli работают off /v3/api-docs. --- docs/integration/altum-imaging-order.md | 249 ++++++++++++++++++ docs/tech/api-integration.md | 243 +++++++++++++++++ .../ordinis/auth/SecurityConfig.java | 8 + ordinis-rest-api/pom.xml | 10 + .../ordinis/restapi/config/OpenApiConfig.java | 70 +++++ 5 files changed, 580 insertions(+) create mode 100644 docs/integration/altum-imaging-order.md create mode 100644 docs/tech/api-integration.md create mode 100644 ordinis-rest-api/src/main/java/cloud/nstart/terravault/ordinis/restapi/config/OpenApiConfig.java diff --git a/docs/integration/altum-imaging-order.md b/docs/integration/altum-imaging-order.md new file mode 100644 index 0000000..4f9dff2 --- /dev/null +++ b/docs/integration/altum-imaging-order.md @@ -0,0 +1,249 @@ +# Интеграция «Заказ съёмки» (Альтум) ↔ Ordinis НСИ + +**Status:** схемы согласованы, ожидаем service-account Keycloak и сидинг. +**Owner со стороны Ordinis:** zimin.an@nstart.space. +**Owner со стороны Альтума:** altum-backend (FastAPI BFF). + +## 1. Зачем + +В форме «Заказ съёмки» (greenfield на стороне Альтума — поля mission / +instrument / processing_level / data_format пока отсутствуют в БД и UI) поля +будут читаться из Ordinis НСИ — единый источник истины. Когда оператор НСИ +заводит новый КА «Ресурс-П 4» — он сразу появляется в выпадашке заказа без +релиза Альтума. + +**Польза:** новый спутник / станция / прибор появляется в заказе через 5 минут +после ввода в админке Ordinis вместо релизного цикла Альтума. + +## 2. Контракт API (Ordinis → Альтум) + +**Base URL:** + +- staging: `https://ordinis.k8s.265.nstart.cloud/api/v1` +- prod: `https://ordinis.k8s.264.nstart.cloud/api/v1` (после prod stand-up) + +**Авторизация:** + +- На staging пока выключена (`ORDINIS_AUTH_REQUIRED=false`), на prod включится. +- Header `Authorization: Bearer ` от Keycloak (`auth.nstart.space/realms/nstart`). +- Альтум использует **service account (m2m client)** в реалме `nstart`, роль + `ordinis-public`. JWT issued через `client_credentials` flow, рефрешится в + altum-backend перед TTL. +- Ordinis маппит роль `ordinis-public` → DataScope.PUBLIC. Маппинг — см. + `ordinis-auth/.../ScopeContext.java`. + +**Endpoints (read-only):** + +``` +GET /dictionaries + → список доступных справочников. + +GET /dictionaries/{name}/records?lang=ru&at=2026-05-04T12:00:00Z + → активные записи на момент `at` (default: now). + ?lang=ru/en — i18n поля приходят строкой на выбранном языке. + +GET /dictionaries/{name}/records/{businessKey}?lang=ru&at=... + → одна запись по businessKey. +``` + +**Пример:** + +```bash +curl https://ordinis.k8s.265.nstart.cloud/api/v1/dictionaries/spacecraft/records?lang=ru +``` + +```json +[ + { + "businessKey": "RESURS-P-3", + "validFrom": "2025-01-01T00:00:00+03:00", + "validTo": "9999-12-31T23:59:59+00:00", + "data": { + "code": "RESURS-P-3", + "name": "Ресурс-П № 3", + "norad_id": 41386, + "satellite_type": "OPTICAL", + "launched_at": "2016-03-13", + "operator": "ROSCOSMOS", + "status": "ACTIVE" + } + } +] +``` + +## 3. Согласованные схемы 4 справочников + +Все справочники имеют общую базовую структуру: `code` (string, businessKey), +`status` (enum: active/archived). Поля name/description — i18n localized. +Дальше описаны специфические поля. + +### `mission` — программа / семейство КА + +| поле | тип | пример | примечание | +|-----------------------|--------------------|---------------------------------------|---------------------------------------| +| `code` | string | `RESURS` | businessKey, x-id-source | +| `name` | i18n | `Ресурс` / `Resurs` | для UI | +| `description` | i18n | `Российская программа оптического ДЗЗ среднего разрешения` | tooltip | +| `spacecraft_codes` | array of string | `["RESURS-P-3","RESURS-P-4"]` | businessKeys из spacecraft | +| `status` | enum | `active` / `archived` | | + +### `instrument` — конкретный сенсор на борту + +| поле | тип | пример | примечание | +|----------------------------|-----------------|-----------------------------------------|----------------------------------------| +| `code` | string | `GEOTON-L1` | businessKey, x-id-source | +| `name` | i18n | `Геотон-Л1` | | +| `spacecraft_codes` | array of string | `["RESURS-P-3","RESURS-P-4"]` | many-to-many | +| `type` | enum | `optical` / `sar` / `infrared` / `multispectral` / `hyperspectral` | UI-фильтр | +| `spatial_resolution_m` | number | `1.0` | для сортировки/фильтра | +| `swath_km` | number | `38` | | +| `spectral_bands` | array of string | `["PAN","B","G","R","NIR"]` | опц. | +| `supported_format_codes` | array of string | `["GEOTIFF","NITF"]` | businessKeys из data_format | +| `supported_level_codes` | array of string | `["L1A","L2"]` | businessKeys из processing_level | +| `status` | enum | `active` / `archived` | | + +Связи instrument → format/level в самом instrument'е — даёт фронту cascading +select без отдельного эндпоинта совместимости. + +### `processing_level` — уровень обработки + +| поле | тип | пример | +|----------------|--------|-------------------------------------------------| +| `code` | string | `L1A` (businessKey, x-id-source) | +| `name` | i18n | `Радиометрически скорректированный` | +| `description` | i18n | (детали обработки) | +| `sort_order` | integer | `20` (для упорядочивания L0→L1→L2) | +| `status` | enum | `active` / `archived` | + +### `data_format` — формат файлов + +| поле | тип | пример | +|--------------------|--------|-----------------------------------------| +| `code` | string | `GEOTIFF` (businessKey, x-id-source) | +| `name` | i18n | `GeoTIFF` | +| `mime_type` | string | `image/tiff` | +| `file_extension` | string | `.tif` | +| `description` | i18n | опц. | +| `status` | enum | `active` / `archived` | + +## 4. Что делает команда Альтум + +### Backend (altum-backend, FastAPI, единая точка интеграций) + +- `backend/app/clients/ordinis.py` — httpx-клиент с retry + cache: + - retry 3× с exponential backoff + - timeout 5 сек + - кэш с TTL 5 мин per `(dictionary, lang)` (in-process LRU + ETag когда + появится; пока без webhook → достаточно TTL) + - на 401 — refresh service-account JWT через Keycloak + - на 5xx/таймаут — stale cache + warn в лог. Не падать. +- `backend/app/routers/reference.py` — единая точка для фронта: + ``` + GET /api/reference/spacecraft + GET /api/reference/spacecraft/{business_key} + GET /api/reference/instrument + GET /api/reference/mission + GET /api/reference/processing_level + GET /api/reference/data_format + ``` + Каждый внутри дёргает Ordinis read-api с кэшом. Авторизация JWT-инжекта на + стороне backend — фронт ничего не знает про Keycloak Ordinis. +- Таблица `orders` получает новые колонки: + ``` + spacecraft_business_key VARCHAR(256) + instrument_business_key VARCHAR(256) + processing_level_code VARCHAR(256) + data_format_code VARCHAR(256) + reference_snapshot JSONB + ``` +- Снэпшот на момент создания заказа — копия всех использованных справочных + записей (для аудита: «через 2 года КА вывели из эксплуатации, но мы должны + помнить какой был на момент заказа»): + ```json + { + "reference_snapshot": { + "spacecraft": {"RESURS-P-3": {"version": 2, "data": {...}}}, + "instrument": {"GEOTON-L1": {"version": 1, "data": {...}}} + }, + "captured_at": "2026-05-15T10:30:00Z" + } + ``` + +### Frontend (TanStack Query) + +```ts +const { data: spacecrafts } = useQuery({ + queryKey: ['reference', 'spacecraft', lang], + queryFn: () => fetch(`/api/reference/spacecraft?lang=${lang}`).then(r => r.json()), + staleTime: 5 * 60_000, +}) + +