feat(api): Swagger UI + docs/integration для Альтума и других consumer'ов

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.
This commit is contained in:
Zimin A.N.
2026-05-04 15:45:09 +03:00
parent e284fa85be
commit 930df5b888
5 changed files with 580 additions and 0 deletions
+249
View File
@@ -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 <jwt>` от 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,
})
<Select options={spacecrafts.map(s => ({ value: s.businessKey, label: s.data.name }))} />
```
- Skeleton при первой загрузке.
- Fallback с retry-кнопкой при недоступности backend.
- `POST /api/orders` — отправляет businessKey'и (не локальные id).
- Каскадные селекты: instrument → отфильтрованы по выбранному spacecraft через
`instrument.spacecraft_codes`; processing_level + data_format → по
`instrument.supported_level_codes` / `supported_format_codes`.
### TTL vs webhook
5 минут TTL достаточно для v1 — справочники меняются редко (новый КА — раз в
месяцы). На v2, если потребуется realtime: подписка altum-backend через
`aiokafka` на топик **`nsi.dictionary.changed`** (Ordinis опубликует) с
payload `{name, businessKey, action: created|updated|closed}`. Не блокер.
### CORS
Фронт ходит через altum-backend, прямой CORS не нужен. Для отладки добавим в
allowlist:
- `https://altum.nstart.cloud`
- `https://altum.k8s.264.nstart.cloud`
## 5. Что делает команда Ordinis (todo)
- [ ] Сидинг 4 справочников (`mission`, `instrument`, `processing_level`,
`data_format`) согласно §3 в bundle `cuod`. Отдельный changelog для
добавления схем + начальные записи.
- [ ] Service account `altum-backend` в Keycloak realm `nstart` с ролью
`ordinis-public`. Передать `ORDINIS_CLIENT_ID` / `ORDINIS_CLIENT_SECRET`
команде Альтума.
- [ ] CORS allowlist для отладочных доменов Альтума на ingress.
- [ ] Включить `ORDINIS_AUTH_REQUIRED=true` на staging когда altum-backend
будет готов тестировать с m2m JWT.
- [ ] Swagger URL read-api в `docs/tech/api-integration.md` (создать).
- [ ] (v2) Kafka топик `nsi.dictionary.changed` с change events.
## 6. Acceptance criteria
- [ ] Оператор НСИ заводит новый КА в Ordinis admin — через ≤5 минут он
появляется в выпадашке заказа Альтума без редеплоя.
- [ ] Оператор закрывает КА (`PUT /records/{key}/close`) — через ≤5 минут
пропадает из выпадашки активных, но в существующих заказах (snapshot)
всё видно.
- [ ] Заказ съёмки PDF/выгрузка — берёт name КА из snapshot, не из live read-api.
- [ ] При недоступности Ordinis Альтум продолжает работать со stale-кэшом,
выводит баннер «справочники могут быть устаревшими».
- [ ] Lang switching — оператор переключает на EN, КА называются «Resurs-P № 3».
- [ ] Cascading select работает: выбираешь инструмент — список форматов и
уровней обработки сужается.
## 7. Сроки
- Ordinis: сидинг + Keycloak service account + CORS — **1 день после ответа на §5**.
- Альтум: BFF + 4 ref-эндпоинта + миграция БД (`reference_snapshot`) + UI с 4
каскадными селектами + тесты — **2-3 дня** после готовности Ordinis.
---
**Repo cross-refs:**
- Read-api routing: `ordinis-read-api/`
- Auth/scope: `ordinis-auth/.../{ScopeContext, SecurityConfig}.java`
- Bitemporal model: `ordinis-domain/.../record/DictionaryRecord.java`
- Audit log: `ordinis-rest-api/.../audit/AuditLogger.java` (фиксирует все
изменения справочников — Альтум по trace_id может проследить «когда оператор
закрыл КА»).
+243
View File
@@ -0,0 +1,243 @@
# Ordinis API — гайд для интеграторов
Документ для команд которые хотят читать/писать справочники Ordinis из своих
сервисов (Альтум, геопортал, BI и т.д.).
**Owner:** Ordinis team — zimin.an@nstart.space.
## 1. Где живёт сервис
| Среда | URL | Auth |
|---------|--------------------------------------------------|-------------------------------------------|
| staging | `https://ordinis.k8s.265.nstart.cloud` | пока выключена (`ORDINIS_AUTH_REQUIRED=false`) |
| prod | `https://ordinis.k8s.264.nstart.cloud` | будет JWT (после prod stand-up) |
| local | `http://localhost:8080` | выключена |
## 2. Swagger UI / OpenAPI spec
- **Swagger UI:** `https://ordinis.k8s.265.nstart.cloud/swagger-ui.html`
- **Spec JSON:** `https://ordinis.k8s.265.nstart.cloud/v3/api-docs`
- **Spec YAML:** `https://ordinis.k8s.265.nstart.cloud/v3/api-docs.yaml`
Из spec можно генерировать клиент для Python (openapi-generator-cli),
TypeScript, Go и т.д. Пример:
```bash
openapi-generator-cli generate \
-i https://ordinis.k8s.265.nstart.cloud/v3/api-docs \
-g python \
-o ./altum-backend/clients/ordinis
```
## 3. Авторизация
Ordinis принимает JWT от Keycloak realm `nstart`:
- Issuer: `https://auth.nstart.space/realms/nstart`
- Алгоритм: RS256
- JWK Set discoverится через `/.well-known/openid-configuration`.
### Service account (m2m)
Для интеграции backend↔backend (например altum-backend → ordinis read-api)
получить service account клиент в Keycloak:
1. Запросить у Ordinis team создание клиента в realm `nstart`.
2. Назначить роль `ordinis-public` (или выше, если нужен RESTRICTED scope).
3. Получить `client_id` + `client_secret`.
**Получение access token (client_credentials):**
```bash
curl -X POST https://auth.nstart.space/realms/nstart/protocol/openid-connect/token \
-d "grant_type=client_credentials" \
-d "client_id=altum-backend" \
-d "client_secret=<SECRET>"
```
Ответ:
```json
{ "access_token": "eyJhbGciOi...", "expires_in": 300, "token_type": "Bearer" }
```
Кэшировать токен с TTL = `expires_in - 30` (буфер на retries) в backend, не
держать в фронте.
### Маппинг ролей в DataScope
| Keycloak роль | Ordinis DataScope |
|-------------------------------------------------|-------------------|
| `ordinis-public` / `public` | PUBLIC |
| `ordinis-internal` / `internal` | PUBLIC + INTERNAL |
| `ordinis-restricted` / `restricted` | + RESTRICTED |
Любая роль с суффиксом `-PUBLIC|INTERNAL|RESTRICTED` тоже работает (см.
`ordinis-auth/.../ScopeContext.java`). Это сделано чтобы не плодить orgaroles
для каждого consumer'а.
## 4. Read-side endpoints
### `GET /api/v1/dictionaries`
Список доступных справочников.
```bash
curl -H "Authorization: Bearer $TOKEN" \
https://ordinis.k8s.265.nstart.cloud/api/v1/dictionaries
```
```json
[
{ "name": "spacecraft", "displayName": "Космические аппараты", "scope": "PUBLIC", "schemaVersion": "1.0.0" },
{ "name": "mission", "displayName": "Миссии / программы", "scope": "PUBLIC", "schemaVersion": "1.0.0" },
...
]
```
### `GET /api/v1/dictionaries/{name}/records`
Активные записи на момент `at` (default: `now`). Параметры:
| параметр | тип | пример | примечание |
|----------|-----------|------------------------------|------------------------------------------------|
| `lang` | string | `ru`, `en` | flatten i18n полей до строки на этом языке |
| `at` | ISO 8601 | `2026-05-04T12:00:00+03:00` | bitemporal query — вернёт активные на момент |
| `as_scope` | string | `PUBLIC,INTERNAL` | для anonymous + `allowQueryScope=true` (legacy) |
```bash
curl -H "Authorization: Bearer $TOKEN" \
"https://ordinis.k8s.265.nstart.cloud/api/v1/dictionaries/spacecraft/records?lang=ru"
```
### `GET /api/v1/dictionaries/{name}/records/{businessKey}`
Одна запись. Параметры те же.
### `GET /api/v1/dictionaries/{name}/records/{businessKey}/history`
Все версии записи (включая закрытые). Используется UI-историей, для consumer'ов
обычно не нужно.
## 5. Стратегии кэширования
### A) Простой TTL (рекомендуется для v1)
```ts
// TanStack Query
useQuery({
queryKey: ['ordinis', 'spacecraft', lang],
queryFn: () => fetch(`/api/reference/spacecraft?lang=${lang}`).then(r => r.json()),
staleTime: 5 * 60_000, // 5 минут — справочники меняются редко
retry: 3,
})
```
### B) Webhook / Kafka push (v2)
Когда нужны realtime инвалидации — подписаться на топик
`nsi.dictionary.changed` (планируется в Ordinis v2). Payload:
```json
{
"name": "spacecraft",
"businessKey": "RESURS-P-3",
"action": "updated", // created / updated / closed
"at": "2026-05-15T10:30:00+03:00"
}
```
Consumer вычищает соответствующий ключ из своего кэша и pull'ит свежую версию.
### C) Snapshot at order time (для аудит-кейсов)
Когда нужен «как было на момент заказа» — сохранить полную копию использованных
справочников рядом с заказом:
```sql
CREATE TABLE imaging_order (
id UUID PRIMARY KEY,
spacecraft_business_key VARCHAR(256),
instrument_business_key VARCHAR(256),
reference_snapshot JSONB, -- копии записей, не FK
created_at TIMESTAMPTZ DEFAULT NOW()
);
```
Альтернатива — запрашивать read-api с `?at=<order.created_at>` (bitemporal
поддерживает это). Snapshot надёжнее: read-only к prod, не зависит от наличия
старых версий после retention.
## 6. Best practices
1. **Не делайте FK на копию справочника**. Храните `business_key VARCHAR`
это стабильный идентификатор, валидный сквозь версии.
2. **Кэшируйте per `(dictionary, lang)`**. Inline в коде — `staleTime`/TTL.
3. **Refresh JWT перед TTL**. Не дожидайтесь 401 — большинство Keycloak SDK
рефрешит автоматически.
4. **Stale cache на 5xx/timeout**. Если Ordinis недоступен, отдавайте старые
данные с warn'ом в лог. Не падайте каскадно.
5. **Используйте businessKey, не UUID**. UUID — внутренний идентификатор записи
и меняется при создании новой версии (через update). businessKey стабилен.
6. **Не пишите массовых update'ов**. Ordinis оптимизирован под редкие записи и
частые чтения. Bulk import через bundle (`POST /admin/bundle/import`) или
через ETL.
## 7. Bitemporal модель — что значит `validFrom` / `validTo`
Каждая запись имеет:
- `validFrom` — с какого момента запись активна (бизнес-время).
- `validTo` — до какого момента (или `9999-12-31` если бессрочно).
- `version` — порядковый номер версии (JPA optimistic lock).
При update создаётся **новая версия**: старая `validTo` ставится в `now`,
новая создаётся с `validFrom = now`. Запись по `businessKey` всегда активна на
любой момент времени (не считая периодов когда она закрыта).
Для consumer'ов это значит: `GET /records/{key}?at=X` всегда возвращает
актуальную версию (или 404 если `key` был закрыт на этот момент).
## 8. Текущие справочники в bundle `cuod` (anchor v1)
| Имя | Scope | Записей (на staging) | Назначение |
|--------------------|--------|----------------------|-----------------------------------------------|
| `spacecraft` | PUBLIC | 4 | Каталог КА |
| `satellite_type` | PUBLIC | 5 | Классификация типов |
| `ground_station` | PUBLIC | 3 | Наземные пункты |
| `mission` | PUBLIC | 9 | Программы (Ресурс-П, Канопус, Sentinel...) |
| `instrument` | PUBLIC | 10 | Бортовые сенсоры (Геотон, MODIS, C-SAR...) |
| `processing_level` | PUBLIC | 8 | Уровни L0-L4 |
| `data_format` | PUBLIC | 8 | GeoTIFF, NITF, SAFE и т.д. |
Все справочники имеют `i18n` поля (`name`, `description`) с RU + EN. Используйте
`?lang=ru` для русского flatten, `?lang=en` для английского.
## 9. Cross-references между справочниками
- `spacecraft.satellite_type_code``satellite_type.code`
- `mission.spacecraft_codes``spacecraft.businessKey[]`
- `instrument.spacecraft_codes``spacecraft.businessKey[]`
- `instrument.supported_format_codes``data_format.businessKey[]`
- `instrument.supported_level_codes``processing_level.businessKey[]`
Эти связи **не enforced** на уровне БД (это NoSQL-style references поверх
JSONB). Consumer должен сам подтягивать связанные записи параллельными
запросами либо JOIN'ить в своей БД через snapshot.
Cascading select для UI:
```ts
const instrument = ... // выбран MSI-S2
const formats = await fetch(`/dictionaries/data_format/records?lang=${lang}`)
.then(r => r.json())
const allowedFormats = formats.filter(f =>
instrument.data.supported_format_codes.includes(f.businessKey))
// allowedFormats: SAFE, GEOTIFF, COG, JPEG2000
```
## 10. Поддержка / эскалация
- Bug в API: `git@git.nstart.local:2-6/2-6-4/terravault/ordinis` issues.
- Запрос новой роли в Keycloak / нового scope: zimin.an@nstart.space.
- Запрос нового справочника / схемы: создать issue в репо ordinis с label `dictionary-request`.
@@ -42,6 +42,14 @@ public class SecurityConfig {
.authorizeHttpRequests(auth -> { .authorizeHttpRequests(auth -> {
auth.requestMatchers("/actuator/**").permitAll(); auth.requestMatchers("/actuator/**").permitAll();
auth.requestMatchers("/error").permitAll(); auth.requestMatchers("/error").permitAll();
// Swagger UI + OpenAPI spec — открыты для интеграторов (Альтум, ...).
auth.requestMatchers(
"/swagger-ui.html",
"/swagger-ui/**",
"/v3/api-docs",
"/v3/api-docs/**",
"/v3/api-docs.yaml"
).permitAll();
if (props.requireAuthentication()) { if (props.requireAuthentication()) {
auth.anyRequest().authenticated(); auth.anyRequest().authenticated();
} else { } else {
+10
View File
@@ -51,5 +51,15 @@
<groupId>io.micrometer</groupId> <groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing</artifactId> <artifactId>micrometer-tracing</artifactId>
</dependency> </dependency>
<!--
springdoc генерирует OpenAPI 3 spec из аннотаций контроллеров и публикует
Swagger UI на /swagger-ui.html. Нужно для интеграторов (Альтум) — не
хочется поддерживать markdown-ТЗ синхронно с кодом.
-->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.7.0</version>
</dependency>
</dependencies> </dependencies>
</project> </project>
@@ -0,0 +1,70 @@
package cloud.nstart.terravault.ordinis.restapi.config;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
/**
* OpenAPI 3 spec для Swagger UI на {@code /swagger-ui.html}. Spec — на
* {@code /v3/api-docs}.
*
* <p>Авторизация — JWT bearer от Keycloak ({@code auth.nstart.space}).
* Service account для интеграторов (Альтум — {@code altum-backend}).
*/
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI ordinisOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("Ordinis НСИ ЦУОД — Admin / Read API")
.version("v1")
.description("""
MDM сервис для НСИ ЦУОД ОДХ.
**Endpoints:**
- `/api/v1/dictionaries` — справочники (admin write).
- `/api/v1/dictionaries/{name}/records` — записи с bitemporal моделью.
- `/api/v1/admin/audit` — журнал аудита (paginated + CSV export).
- `/api/v1/admin/outbox` — статистика и DLQ.
**Read-only API** (Альтум, геопортал, другие consumer'ы) — отдельный
read-сервис на том же базовом URL: те же `GET /dictionaries/...`,
но с поддержкой `?lang=ru/en` для flatten i18n и `?at=<iso>` для
bitemporal queries.
**Auth:** Bearer JWT от Keycloak (`auth.nstart.space/realms/nstart`).
Роль `ordinis-public` достаточна для чтения. На staging авторизация
может быть выключена (`ORDINIS_AUTH_REQUIRED=false`).
**Документация интеграции:** `docs/integration/altum-imaging-order.md`,
`docs/tech/api-integration.md`.
""")
.contact(new Contact()
.name("Ordinis team")
.email("zimin.an@nstart.space"))
.license(new License().name("Internal").url("https://nstart.space")))
.servers(List.of(
new Server().url("https://ordinis.k8s.265.nstart.cloud").description("staging (cluster.265)"),
new Server().url("https://ordinis.k8s.264.nstart.cloud").description("prod (cluster.142)"),
new Server().url("http://localhost:8080").description("local dev")))
.components(new Components()
.addSecuritySchemes("bearerAuth",
new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
.description("Keycloak realm `nstart`. Service account для m2m: попросить у Ordinis team.")))
.addSecurityItem(new SecurityRequirement().addList("bearerAuth"));
}
}