# Read-side endpoints

Read-API в Ordinis — отдельный deployable (`ordinis-read-api`, scaled 2× в проде)
работающий на Postgres replica. Запросы из read-api **не блокируют** writer'а.

Base URL:

| Среда | URL |
|---|---|
| prod | `https://ordinis.k8s.264.nstart.cloud/api/v1` |
| staging | `https://ordinis.k8s.265.nstart.cloud/api/v1` |

Все endpoints требуют JWT (см. [Авторизация](auth.md)).

## OpenAPI / Swagger UI

Interactive playground с **Keycloak login** (PKCE flow, без client_secret).

**Используй staging для exploration, не prod.** Прод — для реальных
пользователей и боевых данных. Запросы из Swagger UI создают
audit-записи и могут менять данные если ты случайно нажмёшь POST/PUT/DELETE.

- **Swagger UI (staging):** `https://ordinis.k8s.265.nstart.cloud/swagger-ui.html`
  → нажми **Authorize** → выбери `oauth2` → войдёшь через Keycloak.
  После login каждый "Try it out" автоматически добавит JWT.
- **Spec JSON (staging):** `https://ordinis.k8s.265.nstart.cloud/v3/api-docs`
- **Spec YAML (staging):** `https://ordinis.k8s.265.nstart.cloud/v3/api-docs.yaml`

OpenAPI spec для clients (SDK generation, code-first integration) можно
также pull from prod (`/v3/api-docs` на k8s.264) — это статичный файл,
безопасный для machine consumption. Для interactive exploration — только
staging.

{% note tip %}

Если у тебя уже есть pre-issued service token (например для CI scripts) —
выбери схему `bearerAuth` в Authorize dialog и paste'ни raw JWT. PKCE flow
тогда не нужен.

{% endnote %}

Из spec'а можно generate'ить клиент:

```bash
openapi-generator-cli generate \
  -i https://ordinis.k8s.264.nstart.cloud/v3/api-docs \
  -g python \
  -o ./altum-backend/clients/ordinis
```

Поддерживаются: Python, TypeScript, Go, Java, Kotlin, Ruby и др.

## `GET /api/v1/dictionaries`

Список доступных справочников (фильтрован по scope JWT'а).

```bash
curl -H "Authorization: Bearer $TOKEN" \
  https://ordinis.k8s.264.nstart.cloud/api/v1/dictionaries
```

Response:

```json
[
  {
    "name": "spacecraft",
    "displayName": "Космические аппараты",
    "scope": "PUBLIC",
    "schemaVersion": "1.0.0",
    "recordsCount": 4
  },
  {
    "name": "mission",
    "displayName": "Миссии / программы",
    "scope": "PUBLIC",
    "schemaVersion": "1.0.0",
    "recordsCount": 9
  }
]
```

## `GET /api/v1/{name}/records`

{% note warning %}

**БЕЗ префикса `/dictionaries/`!** Read-api сидит на `/api/v1/{name}/records`.
Префикс `/api/v1/dictionaries/...` — это admin write controller (CRUD), у него
нет bulk-list эндпоинта.

{% endnote %}

Активные записи на момент `at` (default: сейчас).

### Параметры

| Параметр | Тип | Пример | Назначение |
|---|---|---|---|
| `lang` | string | `ru-RU`, `en-US` | Flatten i18n полей в строку. Короткие `ru`/`en` тоже. |
| `at` | ISO 8601 | `2026-05-04T12:00:00+03:00` | Bitemporal query — активные на момент. |
| `as_scope` | string | `PUBLIC,INTERNAL` | Legacy для anonymous + `allowQueryScope=true`. |

### Пример

```bash
curl -H "Authorization: Bearer $TOKEN" \
  "https://ordinis.k8s.264.nstart.cloud/api/v1/spacecraft/records?lang=ru-RU"
```

Response:

```json
[
  {
    "businessKey": "RESURS-P-3",
    "displayKey": "Ресурс-П № 3",
    "scope": "PUBLIC",
    "validFrom": "2025-01-01T00:00:00+03:00",
    "validTo": "9999-12-31T23:59:59+03:00",
    "data": {
      "name": "Ресурс-П № 3",
      "satelliteTypeCode": "EARTH-OBSERVATION-OPTICAL",
      "launchDate": "2024-12-25",
      "status": "active"
    },
    "version": 2
  }
]
```

### Без `?lang` (raw i18n)

```json
"name": { "ru-RU": "Ресурс-П № 3", "en-US": "Resurs-P No.3" }
```

С `?lang=ru-RU`:

```json
"name": "Ресурс-П № 3"
```

Если запрошенного языка нет — fallback к `default_locale` (обычно `ru-RU`).

## `GET /api/v1/{name}/records/{businessKey}`

Одна запись. Параметры те же что у list.

```bash
curl -H "Authorization: Bearer $TOKEN" \
  "https://ordinis.k8s.264.nstart.cloud/api/v1/spacecraft/records/RESURS-P-3?lang=ru"
```

`404 Not Found` — запись либо не существует, либо закрыта на момент `at`,
либо вне scope JWT'а (Ordinis скрывает существование).

## `GET /api/v1/{name}/records/{businessKey}/history`

Все версии записи (включая закрытые) для построения истории изменений в UI.

```json
[
  {
    "version": 1,
    "validFrom": "2024-06-01T00:00:00+03:00",
    "validTo": "2025-01-01T00:00:00+03:00",
    "data": {...},
    "txTime": "2024-06-01T10:23:45+03:00",
    "changedBy": "admin@nstart.space",
    "changeReason": "initial"
  },
  {
    "version": 2,
    "validFrom": "2025-01-01T00:00:00+03:00",
    "validTo": "9999-12-31T23:59:59+03:00",
    "data": {...},
    "txTime": "2024-12-30T14:11:22+03:00",
    "changedBy": "admin@nstart.space",
    "changeReason": "Обновление статуса после успешного запуска"
  }
]
```

Для consumers history endpoint обычно не нужен — это UI-feature.

## Spatial filters (geometry)

Если справочник имеет geometry поля (`spacecraft.aoi`, `mission.coverage_area`),
поддерживаются bbox + polygon фильтры:

### `?bbox=` (bounding box)

```
GET /api/v1/spacecraft/records?bbox=37.0,55.0,38.0,56.0
```

Format: `min_lon,min_lat,max_lon,max_lat` (WGS84). Возвращает записи где
`geometry` пересекает bbox через PostGIS `ST_Intersects` + GiST index.

### `?polygon=` (GeoJSON)

```
GET /api/v1/spacecraft/records?polygon={"type":"Polygon","coordinates":[[[37,55],[38,55],[38,56],[37,56],[37,55]]]}
```

Polygon — GeoJSON object (URL-encoded). Сложнее bbox, точнее.

## Дальше

- [События (Kafka outbox)](events.md) — push-обновления вместо polling
- [Bitemporal](bitemporal.md) — детали `validFrom`/`validTo` модели
- [Caching](caching.md) — стратегии актуализации
