Merge branch 'docs/api-quickstart-cheatsheet' into 'main'
docs(api): Quick Start guide + Cheat Sheet для интеграторов See merge request 2-6/2-6-4/terravault/ordinis!241
This commit is contained in:
@@ -0,0 +1,188 @@
|
|||||||
|
# Ordinis API · Cheat Sheet
|
||||||
|
|
||||||
|
Одностраничный референс. Полный гайд — `api-quickstart.md`.
|
||||||
|
|
||||||
|
**Base:** `https://ordinis.k8s.264.nstart.cloud/api/v1` · **Version:** `v2.36.0` · **OpenAPI:** `/swagger-ui/index.html`
|
||||||
|
|
||||||
|
## Auth
|
||||||
|
|
||||||
|
```bash
|
||||||
|
TOKEN=$(curl -s -X POST https://auth.nstart.space/realms/nstart/protocol/openid-connect/token \
|
||||||
|
-d client_id=ordinis -d grant_type=password -d scope=openid \
|
||||||
|
-d username=$USER -d password=$PASS | jq -r .access_token)
|
||||||
|
|
||||||
|
curl -H "Authorization: Bearer $TOKEN" $ORDINIS/api/v1/me/notifications/settings
|
||||||
|
```
|
||||||
|
|
||||||
|
Scopes: `PUBLIC` (anon ok) · `INTERNAL` (auth) · `RESTRICTED` (admin).
|
||||||
|
|
||||||
|
## Read (no auth для PUBLIC)
|
||||||
|
|
||||||
|
| Verb | Path | Что |
|
||||||
|
|---|---|---|
|
||||||
|
| `GET` | `/version` | Версия + commit + builtAt |
|
||||||
|
| `GET` | `/dictionaries` | Список всех справочников |
|
||||||
|
| `GET` | `/dictionaries/{name}` | Детали + JSON Schema |
|
||||||
|
| `GET` | `/dictionaries/templates` | 6 шаблонов схем |
|
||||||
|
| `GET` | `/dictionaries/templates/{id}` | Один шаблон со schemaJson |
|
||||||
|
| `GET` | `/dictionaries/graph/outgoing` | FK-граф между справочниками |
|
||||||
|
| `GET` | `/dictionaries/{name}/dependents` | Кто ссылается на этот dict |
|
||||||
|
| `GET` | `/dictionaries/{name}/changelog` | Список изменений схемы |
|
||||||
|
| `GET` | `/dictionaries/{name}/changelog/{id}/diff` | Diff между версиями |
|
||||||
|
| `GET` | `/dictionaries/{name}/snapshots?asOf=` | Snapshot на дату (ISO-8601) |
|
||||||
|
| `GET` | `/{name}/records` | Список записей (⚠️ не `/dictionaries/{name}/records`) |
|
||||||
|
| `GET` | `/{name}/records/{businessKey}` | Одна запись |
|
||||||
|
| `GET` | `/{name}/records/{businessKey}/history` | История версий записи |
|
||||||
|
| `GET` | `/{name}/records/scheduled-summary` | Будущие версии (count + nearestValidFrom) |
|
||||||
|
| `GET` | `/{name}/records/export.csv` | Bulk export |
|
||||||
|
| `GET` | `/search?q=&limit=` | Smart search (≥3 chars, trigram) |
|
||||||
|
| `GET` | `/ai/info` | `{enabled: bool}` AI feature flag |
|
||||||
|
|
||||||
|
### Query params (records)
|
||||||
|
|
||||||
|
```
|
||||||
|
?limit=50&offset=0 pagination
|
||||||
|
?status=ACTIVE&country=RU фильтр по атрибутам схемы
|
||||||
|
?scope=INTERNAL если есть права
|
||||||
|
?asOf=2026-03-15T00:00:00Z bitemporal cut (где поддержано)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Write (требует auth)
|
||||||
|
|
||||||
|
| Verb | Path | Что |
|
||||||
|
|---|---|---|
|
||||||
|
| `POST` | `/{name}/records` | Создать запись (если approval не нужен) |
|
||||||
|
| `PUT` | `/{name}/records/{businessKey}` | Обновить |
|
||||||
|
| `DELETE` | `/{name}/records/{businessKey}` | Soft-delete (tombstone) |
|
||||||
|
| `POST` | `/{name}/records/bulk-close` | Массовое закрытие |
|
||||||
|
| `POST` | `/{name}/records/{businessKey}/cascade-close` | Закрыть с зависимыми |
|
||||||
|
| `GET` | `/{name}/records/{businessKey}/cascade-preview` | Preview каскада |
|
||||||
|
| `PATCH` | `/dictionaries/{name}/schema` | Изменить схему inline (non-breaking) |
|
||||||
|
| `PUT` | `/dictionaries/{name}` | Полностью пересоздать (breaking) |
|
||||||
|
|
||||||
|
## Workflow (drafts + approvals)
|
||||||
|
|
||||||
|
| Verb | Path | Что |
|
||||||
|
|---|---|---|
|
||||||
|
| `POST` | `/dictionaries/{name}/records/drafts` | Создать record draft |
|
||||||
|
| `POST` | `/dictionaries/{name}/drafts` | Создать schema draft |
|
||||||
|
| `GET` | `/dictionaries/{name}/drafts` | Список schema drafts по dict |
|
||||||
|
| `GET` | `/dictionaries/{name}/drafts/active` | Активный draft (200+null если нет) |
|
||||||
|
| `GET` | `/dictionaries/{name}/drafts/{id}` | Один draft |
|
||||||
|
| `DELETE` | `/dictionaries/{name}/drafts/{id}` | Withdraw |
|
||||||
|
| `POST` | `/dictionaries/{name}/drafts/{id}/review` | Назначить ревью |
|
||||||
|
| `POST` | `/dictionaries/{name}/drafts/{id}/decision` | Approve/reject schema draft |
|
||||||
|
| `POST` | `/dictionaries/{name}/drafts/{id}/publish` | Опубликовать после approve |
|
||||||
|
| `GET` | `/drafts/me` | Мои record-drafts (status filter) |
|
||||||
|
| `GET` | `/drafts/{id}` | Один record draft |
|
||||||
|
| `DELETE` | `/drafts/{id}` | Withdraw record draft |
|
||||||
|
| `POST` | `/drafts/{id}/approve` | Одобрить (body: `{comment}`) |
|
||||||
|
| `POST` | `/drafts/{id}/reject` | Отклонить (body: `{comment}`) |
|
||||||
|
| `GET` | `/admin/reviews/pending` | Очередь ревьюера (records) |
|
||||||
|
| `GET` | `/admin/schema-reviews/pending` | Очередь ревьюера (schemas) |
|
||||||
|
| `GET` | `/admin/schema-drafts/me` | Мои schema drafts |
|
||||||
|
|
||||||
|
## AI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
|
||||||
|
$ORDINIS/api/v1/ai/suggest-field \
|
||||||
|
-d '{"prompt":"контактный email оператора"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Limits: **30 req/min** на IP, circuit breaker **10 fails / 60s → open 5min** (`503 circuit_open`).
|
||||||
|
|
||||||
|
## Notifications (per-user, auth)
|
||||||
|
|
||||||
|
| Verb | Path | Что |
|
||||||
|
|---|---|---|
|
||||||
|
| `GET` | `/me/notifications` | Bell-bar feed |
|
||||||
|
| `POST` | `/me/notifications/{id}/read` | Mark as read |
|
||||||
|
| `POST` | `/me/notifications/read-all` | Mark all as read |
|
||||||
|
| `GET` | `/me/notifications/preferences` | Per-event matrix |
|
||||||
|
| `PUT` | `/me/notifications/preferences` | Сохранить матрицу |
|
||||||
|
| `GET` | `/me/notifications/settings` | Quiet hours config |
|
||||||
|
| `PUT` | `/me/notifications/settings` | Сохранить quiet hours |
|
||||||
|
|
||||||
|
## Webhooks (admin)
|
||||||
|
|
||||||
|
| Verb | Path | Что |
|
||||||
|
|---|---|---|
|
||||||
|
| `GET` | `/admin/webhooks/subscriptions` | Список подписок |
|
||||||
|
| `POST` | `/admin/webhooks/subscriptions` | Создать (`secret` в ответе, **один раз**) |
|
||||||
|
| `GET` | `/admin/webhooks/subscriptions/{id}` | Детали |
|
||||||
|
| `PUT` | `/admin/webhooks/subscriptions/{id}` | Обновить |
|
||||||
|
| `DELETE` | `/admin/webhooks/subscriptions/{id}` | Удалить |
|
||||||
|
| `POST` | `/admin/webhooks/subscriptions/{id}/rotate-secret` | Перевыпуск ключа |
|
||||||
|
| `POST` | `/admin/webhooks/subscriptions/{id}/test` | Test delivery |
|
||||||
|
| `GET` | `/admin/webhooks/subscriptions/{id}/stats` | Histogram (24h/7d) |
|
||||||
|
| `GET` | `/admin/webhooks/subscriptions/{id}/deliveries` | Recent deliveries |
|
||||||
|
| `GET` | `/admin/webhooks/deliveries/dlq` | DLQ list |
|
||||||
|
| `POST` | `/admin/webhooks/deliveries/{id}/retry` | Manual retry |
|
||||||
|
|
||||||
|
### Headers delivered
|
||||||
|
|
||||||
|
```
|
||||||
|
X-Ordinis-Event: RecordCreated
|
||||||
|
X-Ordinis-Signature: sha256=<hex> HMAC-SHA256(body, secret)
|
||||||
|
X-Ordinis-Delivery: del_01HXY...
|
||||||
|
User-Agent: ordinis-alerts
|
||||||
|
```
|
||||||
|
|
||||||
|
### Event types
|
||||||
|
|
||||||
|
`RecordCreated · RecordUpdated · RecordClosed · RecordRestored · BulkClosed · SchemaDraftSubmitted · SchemaDraftApproved · SchemaDraftRejected · SchemaDraftPublished · RecordDraftSubmitted · RecordDraftApproved`
|
||||||
|
|
||||||
|
## Audit (admin)
|
||||||
|
|
||||||
|
| Verb | Path | Что |
|
||||||
|
|---|---|---|
|
||||||
|
| `GET` | `/admin/audit?dictionary=&action=&user=&dateFrom=&dateTo=` | Фильтрованный аудит |
|
||||||
|
| `GET` | `/admin/audit/export.csv` | Полный экспорт |
|
||||||
|
| `GET` | `/admin/audit/export-selected.csv` | Selected ids экспорт |
|
||||||
|
|
||||||
|
Action types (13): `DICTIONARY_CREATED · SCHEMA_UPDATED · SCHEMA_PUBLISHED · RECORD_CREATED · RECORD_UPDATED · RECORD_CLOSED · RECORD_RESTORED · DRAFT_CREATED · DRAFT_SUBMITTED · DRAFT_APPROVED · DRAFT_REJECTED · DRAFT_WITHDRAWN · REVIEWER_ASSIGNED`
|
||||||
|
|
||||||
|
## Outbox (admin)
|
||||||
|
|
||||||
|
| Verb | Path | Что |
|
||||||
|
|---|---|---|
|
||||||
|
| `GET` | `/admin/outbox/stats` | Pending/DLQ counters |
|
||||||
|
| `GET` | `/admin/outbox/dlq` | DLQ events list |
|
||||||
|
|
||||||
|
## Errors — единая форма
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "status": 422, "code": "validation_failed", "message": "...",
|
||||||
|
"details": {}, "traceId": "...", "timestamp": "..." }
|
||||||
|
```
|
||||||
|
|
||||||
|
| HTTP | code | |
|
||||||
|
|---|---|---|
|
||||||
|
| 400 | `bad_request` | Невалидный body |
|
||||||
|
| 401 | `unauthorized` | Нет/просрочен токен |
|
||||||
|
| 403 | `forbidden` | Scope/role не пускает |
|
||||||
|
| 404 | `not_found` | Нет такого ресурса |
|
||||||
|
| 409 | `conflict` | Concurrent update / unique violation |
|
||||||
|
| 422 | `validation_failed` | Schema validation fail |
|
||||||
|
| 429 | `rate_limited` | Лимит запросов |
|
||||||
|
| 500 | `internal_error` | См. traceId |
|
||||||
|
| 502 | `llm_error` | AI: LLM upstream |
|
||||||
|
| 503 | `circuit_open` | AI: circuit breaker |
|
||||||
|
|
||||||
|
## 60-second smoke
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ORDINIS=https://ordinis.k8s.264.nstart.cloud
|
||||||
|
|
||||||
|
curl -s $ORDINIS/api/v1/version | jq # alive
|
||||||
|
curl -s $ORDINIS/api/v1/dictionaries | jq 'length' # >= 40
|
||||||
|
curl -s $ORDINIS/api/v1/dictionaries/spacecraft | jq '.scope' # "PUBLIC"
|
||||||
|
curl -s "$ORDINIS/api/v1/spacecraft/records?limit=1" | jq '.[0].businessKey'
|
||||||
|
curl -s "$ORDINIS/api/v1/search?q=Sentinel" | jq '.total' # 12
|
||||||
|
curl -s $ORDINIS/api/v1/ai/info | jq '.enabled' # true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Связанные доки
|
||||||
|
|
||||||
|
`api-quickstart.md` · `bundle-format-spec.md` · `docs/changelog.md` · Swagger UI `/swagger-ui/index.html`
|
||||||
@@ -0,0 +1,572 @@
|
|||||||
|
# Ordinis API — Quick Start
|
||||||
|
|
||||||
|
Practical guide to НСИ-ordinis REST API. Тут не справочник всех endpoint'ов
|
||||||
|
(для этого есть OpenAPI / Swagger UI), а минимально-достаточный путь от
|
||||||
|
«первый запрос» до «подписался на webhook».
|
||||||
|
|
||||||
|
**Версия:** v2.36.0 (`/api/v1`)
|
||||||
|
**Swagger UI:** `https://ordinis.k8s.265.nstart.cloud/swagger-ui/index.html`
|
||||||
|
(staging — на prod env-flagged)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. Принципы
|
||||||
|
|
||||||
|
- REST + JSON. UTF-8. Все datetime в ISO-8601 UTC (`Z` suffix).
|
||||||
|
- Базовый префикс — `/api/v1`. Версионирование URL (breaking changes → `/v2`).
|
||||||
|
- Auth — Keycloak OIDC, Bearer-токен в `Authorization`. Anon разрешён для
|
||||||
|
`PUBLIC` scope.
|
||||||
|
- Pagination — query-params `?limit=N&offset=M`. По умолчанию `limit=50`.
|
||||||
|
- Ошибки — `application/json` с `{status, code, message, traceId, timestamp}`.
|
||||||
|
|
||||||
|
## 1. Base URL и проверка живости
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Production
|
||||||
|
ORDINIS=https://ordinis.k8s.264.nstart.cloud
|
||||||
|
|
||||||
|
# Staging
|
||||||
|
# ORDINIS=https://ordinis.k8s.265.nstart.cloud
|
||||||
|
|
||||||
|
curl "$ORDINIS/api/v1/version"
|
||||||
|
```
|
||||||
|
|
||||||
|
Ответ:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": "0.1.0-SNAPSHOT",
|
||||||
|
"commit": "2ca8f053",
|
||||||
|
"branch": "main",
|
||||||
|
"tag": "v2.36.0",
|
||||||
|
"builtAt": "2026-05-17T08:18:07.437Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Аутентификация
|
||||||
|
|
||||||
|
Большинство read-эндпоинтов работают без авторизации в `PUBLIC` scope.
|
||||||
|
Mutations, INTERNAL/RESTRICTED data, workflow и `/me/*` требуют JWT.
|
||||||
|
|
||||||
|
### 2.1 Получение токена через Keycloak (password grant)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
TOKEN=$(curl -s -X POST \
|
||||||
|
"https://auth.nstart.space/realms/nstart/protocol/openid-connect/token" \
|
||||||
|
-d "client_id=ordinis" \
|
||||||
|
-d "grant_type=password" \
|
||||||
|
-d "username=user@example.com" \
|
||||||
|
-d "password=...." \
|
||||||
|
-d "scope=openid profile email" \
|
||||||
|
| jq -r .access_token)
|
||||||
|
```
|
||||||
|
|
||||||
|
> Password grant — для CLI/CI/тестов. В production — OIDC Authorization Code
|
||||||
|
> с PKCE через ваш BFF.
|
||||||
|
|
||||||
|
### 2.2 Использование токена
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -H "Authorization: Bearer $TOKEN" \
|
||||||
|
"$ORDINIS/api/v1/me/notifications/settings"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Скоупы данных
|
||||||
|
|
||||||
|
- `PUBLIC` — открыто всем, включая anon
|
||||||
|
- `INTERNAL` — только для авторизованных с ролью
|
||||||
|
- `RESTRICTED` — узкий круг, как правило admin
|
||||||
|
|
||||||
|
Sidebar и search автоматически фильтруют по scope текущей сессии.
|
||||||
|
|
||||||
|
## 3. Справочники (dictionaries)
|
||||||
|
|
||||||
|
### 3.1 Список всех справочников
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "$ORDINIS/api/v1/dictionaries"
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "58ba2127-...",
|
||||||
|
"name": "spacecraft",
|
||||||
|
"displayName": "Космические аппараты",
|
||||||
|
"description": "Каталог КА (космических аппаратов) ДЗЗ-миссий",
|
||||||
|
"scope": "PUBLIC",
|
||||||
|
"schemaVersion": "1.0.0",
|
||||||
|
"bundle": "cuod",
|
||||||
|
"supportedLocales": ["ru-RU", "en-US"],
|
||||||
|
"defaultLocale": "ru-RU",
|
||||||
|
"createdAt": "2026-05-07T21:20:51.623195Z",
|
||||||
|
"updatedAt": "2026-05-07T21:20:51.623195Z"
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Детали одного справочника (со схемой)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "$ORDINIS/api/v1/dictionaries/spacecraft"
|
||||||
|
```
|
||||||
|
|
||||||
|
`schemaJson` — полный JSON Schema draft-07 с x-расширениями:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "spacecraft",
|
||||||
|
"scope": "PUBLIC",
|
||||||
|
"schemaJson": {
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"$id": "https://ordinis.nstart.cloud/schemas/cuod/spacecraft/1.0.0",
|
||||||
|
"type": "object",
|
||||||
|
"required": ["designator", "name", "satellite_type_code", "status"],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["ru-RU"],
|
||||||
|
"x-localized": true,
|
||||||
|
"patternProperties": { "^[a-z]{2}-[A-Z]{2}$": { "type": "string" } }
|
||||||
|
},
|
||||||
|
"satellite_type_code": {
|
||||||
|
"type": "string",
|
||||||
|
"x-references": "satellite_type.code"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Граф зависимостей (FK)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "$ORDINIS/api/v1/dictionaries/graph/outgoing"
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"outgoing": {
|
||||||
|
"spacecraft": { "fkCount": 2, "targets": ["satellite_type", "spacecraft_status"] },
|
||||||
|
"antenna": { "fkCount": 1, "targets": ["ground_station"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Записи (records)
|
||||||
|
|
||||||
|
> **Внимание к URL:** записи живут под `/api/v1/{dictName}/records`
|
||||||
|
> (а не `/api/v1/dictionaries/{name}/records` — это путь для drafts schema'ы).
|
||||||
|
|
||||||
|
### 4.1 Список записей
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "$ORDINIS/api/v1/spacecraft/records?limit=2"
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "a211eb15-...",
|
||||||
|
"businessKey": "1999-068A",
|
||||||
|
"data": {
|
||||||
|
"name": "Terra (EOS AM-1)",
|
||||||
|
"orbit": { "type": "SSO", "inclination_deg": 98.2 },
|
||||||
|
"status": "OPERATIONAL",
|
||||||
|
"country": "US",
|
||||||
|
"operator": "НАСА",
|
||||||
|
"satellite_type_code": "OPT_MIDRES_MS"
|
||||||
|
},
|
||||||
|
"validFrom": "2026-05-08T00:00:00Z",
|
||||||
|
"validTo": null,
|
||||||
|
"dataScope": "PUBLIC"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Одна запись по business key
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "$ORDINIS/api/v1/spacecraft/records/1999-068A"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 Фильтры
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# По атрибутам схемы (через query-params)
|
||||||
|
curl "$ORDINIS/api/v1/spacecraft/records?status=OPERATIONAL&country=RU"
|
||||||
|
|
||||||
|
# По scope (если у тебя есть доступ к INTERNAL)
|
||||||
|
curl -H "Authorization: Bearer $TOKEN" \
|
||||||
|
"$ORDINIS/api/v1/spacecraft/records?scope=INTERNAL"
|
||||||
|
|
||||||
|
# Pagination
|
||||||
|
curl "$ORDINIS/api/v1/spacecraft/records?limit=20&offset=40"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.4 Сводка по запланированным записям
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "$ORDINIS/api/v1/spacecraft/records/scheduled-summary"
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"count": 0,
|
||||||
|
"nearestValidFrom": null,
|
||||||
|
"upcomingValidFroms": [],
|
||||||
|
"scheduledBusinessKeys": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Smart Search
|
||||||
|
|
||||||
|
Поиск по содержимому всех справочников. Использует trigram-индекс.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "$ORDINIS/api/v1/search?q=Sentinel&limit=10"
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"query": "Sentinel",
|
||||||
|
"total": 12,
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"dictName": "mission",
|
||||||
|
"dictDisplayName": "Миссии / программы",
|
||||||
|
"count": 2,
|
||||||
|
"items": [
|
||||||
|
{ "businessKey": "SENTINEL_1", "dataScope": "PUBLIC", "createdAt": "..." },
|
||||||
|
{ "businessKey": "SENTINEL_2", "dataScope": "PUBLIC", "createdAt": "..." }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dictName": "instrument",
|
||||||
|
"dictDisplayName": "Инструменты съёмки",
|
||||||
|
"count": 2,
|
||||||
|
"items": [ ... ]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Минимальная длина запроса — **3 символа**. Search ограничен текущим scope сессии.
|
||||||
|
|
||||||
|
## 6. Bitemporal queries (TimeTravel)
|
||||||
|
|
||||||
|
### 6.1 История изменений записи
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "$ORDINIS/api/v1/spacecraft/records/1999-068A/history"
|
||||||
|
```
|
||||||
|
|
||||||
|
Возвращает все версии (по `valid_from`) и transaction history (`system_time`).
|
||||||
|
|
||||||
|
### 6.2 Snapshot справочника на дату
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "$ORDINIS/api/v1/dictionaries/spacecraft/snapshots?asOf=2026-03-15T00:00:00Z"
|
||||||
|
```
|
||||||
|
|
||||||
|
Точная реконструкция справочника на момент `asOf`. Все записи, которые
|
||||||
|
**действовали** (`valid_from ≤ asOf < valid_to`) и были **известны системе**
|
||||||
|
(`system_time ≤ now()`).
|
||||||
|
|
||||||
|
### 6.3 Changelog схемы (структурные изменения)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "$ORDINIS/api/v1/dictionaries/spacecraft/changelog?limit=10"
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dictionary": "spacecraft",
|
||||||
|
"currentVersion": "1.0.0",
|
||||||
|
"entries": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Диff между двумя версиями схемы:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "$ORDINIS/api/v1/dictionaries/spacecraft/changelog/{entryId}/diff"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. Шаблоны схем
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "$ORDINIS/api/v1/dictionaries/templates"
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "antenna",
|
||||||
|
"name": "Антенна",
|
||||||
|
"description": "Скелет: код, имя, диаметр, FK на наземную станцию.",
|
||||||
|
"icon": "WifiHighIcon",
|
||||||
|
"tags": ["space", "hardware", "cuod"]
|
||||||
|
},
|
||||||
|
{ "id": "blank", "name": "Пустой каркас", ... },
|
||||||
|
{ "id": "spacecraft", "name": "Космический аппарат", ... }
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Детали шаблона:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "$ORDINIS/api/v1/dictionaries/templates/spacecraft"
|
||||||
|
```
|
||||||
|
|
||||||
|
Вернёт `{ id, name, description, schemaJson }` со sample JSON Schema.
|
||||||
|
|
||||||
|
## 8. AI: suggest field (требует auth)
|
||||||
|
|
||||||
|
Проверка фичи:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "$ORDINIS/api/v1/ai/info"
|
||||||
|
# { "enabled": true }
|
||||||
|
```
|
||||||
|
|
||||||
|
Если `enabled: false` — endpoint `/suggest-field` отдаст 404.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST -H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"$ORDINIS/api/v1/ai/suggest-field" \
|
||||||
|
-d '{"prompt": "контактный email оператора"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"fieldName": "operator_contact_email",
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "email",
|
||||||
|
"description": "Контактный email оператора",
|
||||||
|
"x-references": "operator.code"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Лимиты:** 30 запросов/мин на IP. Circuit breaker открывается на 5 минут
|
||||||
|
после 10 ошибок за 60 секунд (`503 circuit_open`).
|
||||||
|
|
||||||
|
## 9. Workflow согласований (требует auth)
|
||||||
|
|
||||||
|
### 9.1 Создать draft записи
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST -H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"$ORDINIS/api/v1/dictionaries/spacecraft/records/drafts" \
|
||||||
|
-d '{
|
||||||
|
"operation": "CREATE",
|
||||||
|
"businessKey": "2026-099X",
|
||||||
|
"data": { "name": "Новый-1", "status": "PLANNED", ... },
|
||||||
|
"validFrom": "2026-06-01T00:00:00Z"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Если `approvalRequired=true` у справочника — draft уходит ревьюеру.
|
||||||
|
Иначе — record создаётся напрямую (если у тебя есть права).
|
||||||
|
|
||||||
|
### 9.2 Мои drafts
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -H "Authorization: Bearer $TOKEN" \
|
||||||
|
"$ORDINIS/api/v1/drafts/me"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.3 Очередь ревьюера
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -H "Authorization: Bearer $TOKEN" \
|
||||||
|
"$ORDINIS/api/v1/admin/reviews/pending"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.4 Approve / reject
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Approve
|
||||||
|
curl -X POST -H "Authorization: Bearer $TOKEN" \
|
||||||
|
"$ORDINIS/api/v1/drafts/{id}/approve" \
|
||||||
|
-d '{ "comment": "ok, merging" }'
|
||||||
|
|
||||||
|
# Reject
|
||||||
|
curl -X POST -H "Authorization: Bearer $TOKEN" \
|
||||||
|
"$ORDINIS/api/v1/drafts/{id}/reject" \
|
||||||
|
-d '{ "comment": "не хватает источника данных" }'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. Webhooks (admin)
|
||||||
|
|
||||||
|
### 10.1 Создать подписку
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST -H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"$ORDINIS/api/v1/admin/webhooks/subscriptions" \
|
||||||
|
-d '{
|
||||||
|
"name": "my-consumer",
|
||||||
|
"url": "https://my-app.example.com/ordinis-hook",
|
||||||
|
"eventTypes": ["RecordCreated", "RecordUpdated", "SchemaDraftPublished"],
|
||||||
|
"scopeFilter": ["PUBLIC", "INTERNAL"],
|
||||||
|
"dictionaries": null,
|
||||||
|
"textWrap": false
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Ответ содержит **`secret`** — HMAC-SHA256 ключ для верификации подписи.
|
||||||
|
Покажется один раз, сохрани.
|
||||||
|
|
||||||
|
### 10.2 Структура payload (события)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"eventId": "evt_01HXY...",
|
||||||
|
"eventType": "RecordCreated",
|
||||||
|
"occurredAt": "2026-05-17T13:00:00Z",
|
||||||
|
"dictionary": "spacecraft",
|
||||||
|
"businessKey": "2026-099X",
|
||||||
|
"actor": "user-uuid-...",
|
||||||
|
"scope": "PUBLIC",
|
||||||
|
"after": { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Заголовки:
|
||||||
|
- `X-Ordinis-Event: RecordCreated`
|
||||||
|
- `X-Ordinis-Signature: sha256=<hex>` (HMAC-SHA256 от body)
|
||||||
|
- `X-Ordinis-Delivery: del_01HXY...`
|
||||||
|
- `User-Agent: ordinis-alerts`
|
||||||
|
|
||||||
|
### 10.3 Верификация подписи (Node.js)
|
||||||
|
|
||||||
|
```js
|
||||||
|
import crypto from 'crypto'
|
||||||
|
|
||||||
|
function verify(body, signatureHeader, secret) {
|
||||||
|
const [algo, hex] = signatureHeader.split('=')
|
||||||
|
if (algo !== 'sha256') return false
|
||||||
|
const expected = crypto
|
||||||
|
.createHmac('sha256', secret)
|
||||||
|
.update(body)
|
||||||
|
.digest('hex')
|
||||||
|
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(hex))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.4 Retry + DLQ
|
||||||
|
|
||||||
|
Доставка ретраится с backoff (1m, 5m, 30m, 2h, 12h). После 5 неудач —
|
||||||
|
в DLQ. Ручной retry:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST -H "Authorization: Bearer $TOKEN" \
|
||||||
|
"$ORDINIS/api/v1/admin/webhooks/deliveries/{id}/retry"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 11. Errors
|
||||||
|
|
||||||
|
Все ошибки — JSON одинаковой формы:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 422,
|
||||||
|
"code": "validation_failed",
|
||||||
|
"message": "name.ru-RU is required",
|
||||||
|
"details": { "field": "data.name.ru-RU" },
|
||||||
|
"traceId": "abc123...",
|
||||||
|
"timestamp": "2026-05-17T13:00:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| HTTP | code | Когда |
|
||||||
|
|---|---|---|
|
||||||
|
| 400 | `bad_request` | Невалидный JSON, отсутствует обязательный field |
|
||||||
|
| 401 | `unauthorized` | Нет или просрочен токен |
|
||||||
|
| 403 | `forbidden` | Недостаточно прав / scope |
|
||||||
|
| 404 | `not_found` | Нет такого справочника / записи / endpoint |
|
||||||
|
| 409 | `conflict` | Concurrent update / unique constraint |
|
||||||
|
| 422 | `validation_failed` | Schema validation fail |
|
||||||
|
| 429 | `rate_limited` | Превышен лимит запросов |
|
||||||
|
| 500 | `internal_error` | Серверная ошибка (см. `traceId`) |
|
||||||
|
| 502 | `llm_error` | AI suggest — LLM недоступен |
|
||||||
|
| 503 | `circuit_open` | AI suggest — circuit breaker открыт |
|
||||||
|
|
||||||
|
## 12. Готовые сниппеты
|
||||||
|
|
||||||
|
### curl + jq (CI/CD)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Все записи активных операторов
|
||||||
|
curl -s "$ORDINIS/api/v1/operator/records?status=ACTIVE" \
|
||||||
|
| jq '.[] | {key: .businessKey, name: .data.name}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### TypeScript / Fetch
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const ORDINIS = 'https://ordinis.k8s.264.nstart.cloud'
|
||||||
|
|
||||||
|
type SpacecraftRecord = {
|
||||||
|
businessKey: string
|
||||||
|
data: { name: string; status: string; orbit: { type: string } }
|
||||||
|
validFrom: string
|
||||||
|
dataScope: 'PUBLIC' | 'INTERNAL' | 'RESTRICTED'
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSpacecraft(): Promise<SpacecraftRecord[]> {
|
||||||
|
const r = await fetch(`${ORDINIS}/api/v1/spacecraft/records?limit=100`, {
|
||||||
|
headers: { Accept: 'application/json' },
|
||||||
|
})
|
||||||
|
if (!r.ok) throw new Error(`Ordinis ${r.status}`)
|
||||||
|
return r.json()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Python (requests)
|
||||||
|
|
||||||
|
```python
|
||||||
|
import requests
|
||||||
|
|
||||||
|
ORDINIS = "https://ordinis.k8s.264.nstart.cloud"
|
||||||
|
|
||||||
|
def smart_search(query: str, limit: int = 10) -> dict:
|
||||||
|
r = requests.get(f"{ORDINIS}/api/v1/search",
|
||||||
|
params={"q": query, "limit": limit},
|
||||||
|
timeout=10)
|
||||||
|
r.raise_for_status()
|
||||||
|
return r.json()
|
||||||
|
|
||||||
|
result = smart_search("Sentinel")
|
||||||
|
for group in result["groups"]:
|
||||||
|
print(f'{group["dictDisplayName"]}: {group["count"]} hits')
|
||||||
|
```
|
||||||
|
|
||||||
|
### Java (HttpClient)
|
||||||
|
|
||||||
|
```java
|
||||||
|
HttpClient http = HttpClient.newHttpClient();
|
||||||
|
|
||||||
|
HttpRequest req = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(ORDINIS + "/api/v1/dictionaries/spacecraft"))
|
||||||
|
.header("Accept", "application/json")
|
||||||
|
.GET()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpResponse<String> resp = http.send(req, BodyHandlers.ofString());
|
||||||
|
DictionaryDetail detail = mapper.readValue(resp.body(), DictionaryDetail.class);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 13. Дальше
|
||||||
|
|
||||||
|
- **OpenAPI:** `$ORDINIS/v3/api-docs` (staging) / Swagger UI выше
|
||||||
|
- **Bundle spec (для интеграторов):** [`bundle-format-spec.md`](./bundle-format-spec.md)
|
||||||
|
- **CHANGELOG:** [`docs/changelog.md`](../changelog.md) — что меняется по версиям
|
||||||
|
- **Webhooks deep-dive:** в admin UI → `/webhooks` → Detail page
|
||||||
|
показывает histogram доставок, DLQ, signature test
|
||||||
Reference in New Issue
Block a user