Initial commit
This commit is contained in:
@@ -0,0 +1,395 @@
|
||||
# arch-02. API-контракты и связность взаимодействий
|
||||
|
||||
> Термины — в [`arch-00-glossary.md`](arch-00-glossary.md). Общая схема — в [`arch-01-system-architecture.md`](arch-01-system-architecture.md).
|
||||
|
||||
## Назначение
|
||||
|
||||
Этот документ — канонический реестр API-контрактов между frontend, backend-сервисами и внешними системами. Его цель — контролировать связность: если сервис описан как участник сценария, здесь должен быть указан контракт, направление вызова, владелец и потребитель. Когда появятся профильные спецификации модулей, они могут дублировать здесь зафиксированные контракты для удобства разработки.
|
||||
|
||||
## Правила связности
|
||||
|
||||
- Любой новый endpoint, webhook, worker-contract или внешний вызов сначала добавляется в этот файл; при появлении профильного документа модуля-владельца — дублируется там для детализации реализации.
|
||||
- Публичные пользовательские API находятся под `/api/v1`; internal API не публикуются наружу через `nginx`.
|
||||
- Internal HTTP API между backend-сервисами используют единую маску: **`/internal/{service_mnemonic}/v1/{resource}`**, где `{service_mnemonic}` — короткое имя владельца endpoint (см. [`arch-00-glossary.md`](arch-00-glossary.md), «Мнемоники internal API»). Health-check остаётся на `/health/*`.
|
||||
- OpenAPI 3.1 обязателен для HTTP-контрактов `api-backend`, `message-safety`, `bitrix-sync` и `bitrix-local-app` — файлы `{service}/openapi.yaml` в репозитории сервиса (см. раздел «OpenAPI»); для Bitrix24 REST фиксируются используемые методы и payload-мэппинг.
|
||||
- Все service-to-service вызовы передают `X-Request-ID` и по возможности W3C `traceparent`.
|
||||
- Frontend передаёт **`X-Ux-Session-Id`** во всех запросах к `api-backend`, когда UX-сессия активна (рекомендуется для аналитики и логов; **не** является auth).
|
||||
- Все internal API защищаются service token и закрытой Docker/VPC-сетью.
|
||||
|
||||
## Service tokens (internal API)
|
||||
|
||||
Все internal endpoint (`/internal/*`) доступны **только** из Docker/VPC-сети и требуют service token. Endpoint не публикуются через `nginx` (исключение — ops внутри VPC).
|
||||
|
||||
| Переменная | Кто проверяет | Кто передаёт | Endpoint | Заголовок |
|
||||
|---|---|---|---|---|
|
||||
| `MESSAGE_SAFETY_SERVICE_TOKEN` | `message-safety` | `api-backend` | `POST/GET /internal/safety/v1/*` | `X-Service-Token` |
|
||||
| `BITRIX_INTERNAL_API_TOKEN` | `bitrix-local-app` | `api-backend` | `POST/GET /internal/openlines/v1/*` | `Authorization: Bearer` |
|
||||
| `BITRIX_LOCAL_APP_INTERNAL_TOKEN` | — | `api-backend` (исходящий) | то же | `Authorization: Bearer` |
|
||||
| `BITRIX_API_INBOX_TOKEN` | `api-backend` | `bitrix-local-app` | `POST /internal/openlines/v1/inbox` | `Authorization: Bearer` |
|
||||
| `BITRIX_API_FORWARD_TOKEN` | — | `bitrix-local-app` (исходящий) | то же | `Authorization: Bearer` |
|
||||
| `BITRIX_SYNC_SERVICE_TOKEN` | `bitrix-sync` | ops / мониторинг | `GET /internal/sync/v1/*` | `Authorization: Bearer` или `X-Service-Token` |
|
||||
|
||||
Пары значений (должны совпадать):
|
||||
|
||||
- `BITRIX_LOCAL_APP_INTERNAL_TOKEN` (api-backend) = `BITRIX_INTERNAL_API_TOKEN` (bitrix-local-app)
|
||||
- `BITRIX_API_FORWARD_TOKEN` (bitrix-local-app) = `BITRIX_API_INBOX_TOKEN` (api-backend)
|
||||
|
||||
Генерация: `openssl rand -hex 32`. Секреты не коммитить.
|
||||
|
||||
**Не путать с webhook-токенами** (публичные callback от Bitrix24, не internal service API):
|
||||
|
||||
| Переменная | Назначение |
|
||||
|---|---|
|
||||
| `BITRIX_APPLICATION_TOKEN` | проверка событий Bitrix24 → `bitrix-local-app` `/bitrix/handler` |
|
||||
| `BITRIX_SYNC_WEBHOOK_TOKEN` | проверка webhook Bitrix24 → `bitrix-sync` `/bitrix/sync/webhook/contact` |
|
||||
|
||||
## Frontend ↔ api-backend
|
||||
|
||||
| Контракт | Владелец | Потребитель | Назначение | Auth |
|
||||
|---|---|---|---|---|
|
||||
| `GET /api/v1/public/app-config` | `api-backend` | Expo frontend | Публичные настройки: OTP, оператор, лимиты, файлы, **UX idle timeout** | public + CORS/rate limit |
|
||||
| `GET /api/v1/public/content` | `api-backend` | Expo frontend | Тексты по мнемоникам и популярные вопросы | public + CORS/rate limit |
|
||||
| `POST /api/v1/consents` | `api-backend` | Expo frontend | Сохранение согласий перед OTP; тело включает `guest_session_id`, версии документов, device metadata | public + `guest_session_id` + rate limit |
|
||||
| `POST /api/v1/analytics/session-start` | `api-backend` | Expo frontend | Событие `session_start`, новая `UxSession` | public + rate limit |
|
||||
| `POST /api/v1/auth/bootstrap` | `api-backend` | Expo frontend | После OTP: `find-or-create` пользователя, связь согласий, привязка `user_id` к `UxSession` | JWT |
|
||||
| `POST /api/v1/dialogs` | `api-backend` | Expo frontend | Создание диалога перед первым сообщением (в т.ч. после популярного вопроса) | JWT + idempotency |
|
||||
| `GET /api/v1/me` | `api-backend` | Expo frontend | Профиль текущего клиента | JWT |
|
||||
| `GET /api/v1/me/documents` | `api-backend` | Expo frontend | Список документов (MVP: может быть пустым; доставка — post-MVP) | JWT |
|
||||
| `GET /api/v1/documents/{document_id}` | `api-backend` | Expo frontend | Метаданные документа (post-MVP) | JWT |
|
||||
| `GET /api/v1/documents/{document_id}/download-url` | `api-backend` | Expo frontend | Presigned URL; обязателен audit | JWT |
|
||||
| `GET /api/v1/dialogs` | `api-backend` | Expo frontend | История диалогов | JWT |
|
||||
| `GET /api/v1/dialogs/{dialog_id}` | `api-backend` | Expo frontend | Карточка диалога | JWT |
|
||||
| `GET /api/v1/dialogs/{dialog_id}/messages` | `api-backend` | Expo frontend | История сообщений, polling fallback | JWT |
|
||||
| `POST /api/v1/dialogs/{dialog_id}/messages` | `api-backend` | Expo frontend | Отправка сообщения клиента (MVP: `content_kind` `text` или `file`, см. ниже) | JWT + idempotency + safety |
|
||||
| `POST /api/v1/dialogs/{dialog_id}/attachments/init` | `api-backend` | Expo frontend | Инициализация загрузки в S3-quarantine | JWT |
|
||||
| `POST /api/v1/dialogs/{dialog_id}/attachments/{attachment_id}/complete` | `api-backend` | Expo frontend | Завершение загрузки и фиксация checksum/metadata | JWT |
|
||||
| `WS /api/v1/realtime` | `api-backend` | Expo frontend | Realtime-события чата, статусы доставки, unread | JWT |
|
||||
|
||||
Единый формат ошибки:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "profile_not_found",
|
||||
"message": "Profile was not found",
|
||||
"request_id": "01J00000000000000000000000",
|
||||
"details": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `POST /api/v1/consents` (тело запроса)
|
||||
|
||||
```json
|
||||
{
|
||||
"guest_session_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"consents": {
|
||||
"personal_data": { "accepted": true, "version": "2026-06-10" },
|
||||
"user_agreement": { "accepted": true, "version": "2026-06-10" },
|
||||
"marketing": { "accepted": false, "version": "2026-06-10" }
|
||||
},
|
||||
"device": {
|
||||
"platform": "ios",
|
||||
"app_version": "1.0.0",
|
||||
"device_id": "..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
После OTP api-backend связывает запись с `UserIdentity` по `guest_session_id` (в рамках `POST /api/v1/auth/bootstrap`). TTL guest-записи — 24 ч.
|
||||
|
||||
### `POST /api/v1/analytics/session-start` (событие `session_start`)
|
||||
|
||||
Вызывается frontend **только** при начале **новой** UX-сессии (см. arch-01, «Аналитическая UX-сессия»). **Не** привязан к OTP и JWT.
|
||||
|
||||
**Заголовки:** `X-Request-ID` (опционально).
|
||||
|
||||
**Тело:**
|
||||
|
||||
```json
|
||||
{
|
||||
"start_reason": "first_launch",
|
||||
"guest_session_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"device": {
|
||||
"platform": "web",
|
||||
"app_version": "1.0.0",
|
||||
"device_id": "..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `start_reason` — обязательно: `first_launch` | `cold_start` | `idle_timeout`;
|
||||
- `guest_session_id` — опционально (если уже создан в гостевом режиме).
|
||||
|
||||
**Ответ `201`:**
|
||||
|
||||
```json
|
||||
{
|
||||
"ux_session_id": "660e8400-e29b-41d4-a716-446655440001",
|
||||
"started_at": "2026-07-08T12:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
Frontend сохраняет `ux_session_id` **в памяти** и передаёт **`X-Ux-Session-Id`** в последующих запросах.
|
||||
|
||||
**Повторный вызов в рамках той же UX-сессии не требуется** (возврат из фона в пределах idle timeout).
|
||||
|
||||
### `POST /api/v1/auth/bootstrap` (после OTP)
|
||||
|
||||
Вызывается **один раз** после успешного OTP и получения JWT. **Не** создаёт UX-сессию.
|
||||
|
||||
**Заголовки:** `Authorization: Bearer <access_token>`, `X-Ux-Session-Id` (рекомендуется).
|
||||
|
||||
**Тело:**
|
||||
|
||||
```json
|
||||
{
|
||||
"guest_session_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"ux_session_id": "660e8400-e29b-41d4-a716-446655440001"
|
||||
}
|
||||
```
|
||||
|
||||
**Ответ `200`:** `{ "user_id": "uuid", "profile_ready": true }`.
|
||||
|
||||
**Ошибки:** `401` (JWT), `403` (согласия не приняты / guest session истёк).
|
||||
|
||||
### Создание диалога
|
||||
|
||||
- `POST /api/v1/dialogs` — idempotency key в заголовке; ответ `{ "dialog_id": "uuid", "status": "open" }`.
|
||||
- Обязателен перед первым `POST .../messages` (включая популярный вопрос после auth).
|
||||
- `dialog_id` = `external_chat_id` (см. [`arch-00-glossary.md`](arch-00-glossary.md)).
|
||||
|
||||
### Формат исходящего сообщения клиента (MVP)
|
||||
|
||||
Имена `content_kind`, полей — [`arch-00-glossary.md`](arch-00-glossary.md). Правила:
|
||||
|
||||
| `content_kind` | Тело `POST .../messages` | `Message.text` | `MessageAttachment` |
|
||||
|---|---|---|---|
|
||||
| `text` | непустой `text`; без вложения | текст | 0 записей |
|
||||
| `file` | `attachment_id` + `checksum`; `text` пустой | пустая строка | ровно 1 запись |
|
||||
|
||||
- непустой `text` **и** вложение → **`400`** `mixed_content_not_allowed` (до `message-safety`);
|
||||
- пустое сообщение → **`403`** `empty_message`;
|
||||
- более одного вложения → **`400`** `too_many_attachments`;
|
||||
- файловое сообщение в Bitrix24: `message.files` (signed URL), `message.text` пустой.
|
||||
|
||||
Post-MVP: допускается «текст + файлы» отдельной версией API.
|
||||
|
||||
## Realtime (`WS /api/v1/realtime`)
|
||||
|
||||
Transport: **WebSocket** over HTTPS (`wss://`), JWT в query `?access_token=` или subprotocol (реализация — в модуле `api-backend`).
|
||||
|
||||
**Подключение:**
|
||||
|
||||
1. Клиент открывает WS с валидным access token.
|
||||
2. Сервер отправляет `{ "type": "connected", "server_time": "ISO8601" }`.
|
||||
3. Клиент отправляет подписку:
|
||||
|
||||
```json
|
||||
{ "type": "subscribe", "dialog_ids": ["uuid"] }
|
||||
```
|
||||
|
||||
4. Сервер отвечает `{ "type": "subscribed", "dialog_ids": ["uuid"] }`.
|
||||
|
||||
**События сервер → клиент:**
|
||||
|
||||
| `type` | Назначение | Ключевые поля |
|
||||
|---|---|---|
|
||||
| `message.new` | Новое сообщение в диалоге | `dialog_id`, `message` (DTO как в REST) |
|
||||
| `message.status` | Смена статуса доставки/safety | `dialog_id`, `message_id`, `status` |
|
||||
| `dialog.status` | Смена статуса диалога | `dialog_id`, `status` |
|
||||
|
||||
**Reconnect:**
|
||||
|
||||
- exponential backoff: 1s → 2s → 4s → … max 30s;
|
||||
- после reconnect — повтор `subscribe` с актуальным списком `dialog_ids`;
|
||||
- при недоступности WS > 30s — fallback на polling `GET .../messages?after=<cursor>`.
|
||||
|
||||
**Ping:** сервер может слать `{ "type": "ping" }` каждые 30s; клиент отвечает `{ "type": "pong" }`.
|
||||
|
||||
## OpenAPI
|
||||
|
||||
| Сервис | Файл | Публикуется наружу |
|
||||
|---|---|---|
|
||||
| `api-backend` | `api-backend/openapi.yaml` | да (`/api/v1/*`, health) |
|
||||
| `message-safety` | `message-safety/openapi.yaml` | нет (internal) |
|
||||
| `bitrix-local-app` | `bitrix-local-app/openapi.yaml` | частично (`/bitrix/*`, health) |
|
||||
| `bitrix-sync` | `bitrix-sync/openapi.yaml` | нет (internal + webhook) |
|
||||
|
||||
Правила:
|
||||
|
||||
- breaking change публичного API → новый path-prefix (`/api/v2`) + запись в arch-02;
|
||||
- internal API версионируется тем же правилом (`/internal/{mnemonic}/v2/...`);
|
||||
- OpenAPI генерируется или поддерживается вручную — на усмотрение модуля, но файл обязателен в DoD (arch-05).
|
||||
|
||||
## Frontend ↔ Keycloak
|
||||
|
||||
| Контракт | Владелец | Потребитель | Назначение |
|
||||
|---|---|---|---|
|
||||
| OIDC Authorization Code Flow with PKCE | Keycloak | Expo frontend | OTP-only login, token issue, refresh |
|
||||
| OIDC Refresh Token Grant | Keycloak | Expo frontend | Обновление access token без OTP при действующем refresh token |
|
||||
| OIDC logout | Keycloak | Expo frontend | Завершение сессии Keycloak, очистка tokens |
|
||||
| JWKS / discovery | Keycloak | Expo frontend, `api-backend` | Проверка issuer, audience и ключей |
|
||||
|
||||
Frontend не обращается напрямую к Keycloak DB и не хранит парольные credentials. Парольная авторизация в MVP отключена.
|
||||
|
||||
**OTP (Keycloak):** единственный канал первичной авторизации — телефон. OTP-flow нужен, когда refresh token отсутствует или истёк. При действующем refresh token frontend использует **Refresh Token Grant** и не показывает OTP. После ввода кода **Keycloak проверяет OTP**: при `KEYCLOAK_OTP_MOCK_ENABLED=true` — сверка с `KEYCLOAK_OTP_MOCK_CODE` (`.env`); при `false` — сверка с OTP от SMS-провайдера (post-MVP, [`!Backlog.md`](../../HAN_chat/!Backlog.md)). `api-backend` OTP не проверяет, только JWT.
|
||||
|
||||
### Жизненный цикл access token (frontend)
|
||||
|
||||
Детали — arch-01, «Обновление access token (frontend)». Кратко:
|
||||
|
||||
| Механизм | Когда | Действие |
|
||||
|---|---|---|
|
||||
| **Scheduler** | за ~60 с до `exp` access token | Refresh Token Grant → новые tokens, перепланировать таймер |
|
||||
| **401 interceptor** | `api-backend` / WS отклонил access token | single-flight refresh → **один** retry запроса |
|
||||
| **Открытие приложения** | cold start / resume | silent refresh, если refresh token ещё действителен |
|
||||
|
||||
Правила:
|
||||
|
||||
- refresh выполняет **только frontend** (Keycloak token endpoint); `api-backend` на `401` **не** обновляет токен;
|
||||
- параллельные запросы при refresh — очередь + single-flight;
|
||||
- провал refresh → очистка tokens, гостевой режим, OTP при следующем защищённом действии;
|
||||
- успешный refresh **не** создаёт UX-сессию (`session_start`).
|
||||
|
||||
**401 от `api-backend`:** единый формат ошибки (см. выше); типичный `code`: `unauthorized` / `token_expired` — frontend трактует как сигнал к refresh+retry (если refresh token ещё валиден).
|
||||
|
||||
## api-backend ↔ message-safety
|
||||
|
||||
| Контракт | Владелец | Потребитель | Назначение | Защита |
|
||||
|---|---|---|---|---|
|
||||
| `POST /internal/safety/v1/messages/check` | `message-safety` | `api-backend` | Синхронная проверка текста, ссылок и файлов по cache/rules | internal network + `X-Service-Token` |
|
||||
| `GET /internal/safety/v1/messages/tasks/{task_id}` | `message-safety` | `api-backend` | Опрос async-проверки файлов | internal network + `X-Service-Token` |
|
||||
| Read S3-quarantine | Selectel S3 | `message-safety` | Чтение файла worker-ом при cache miss | read-only key |
|
||||
|
||||
HTTP-семантика: `200 allow`, `403 deny`, `203 pending`. `api-backend` не выбирает sync/async режим, а только интерпретирует ответ.
|
||||
|
||||
Маппинг в App DB (`Message.safety_status` — см. [`arch-00-glossary.md`](arch-00-glossary.md)):
|
||||
|
||||
| HTTP / `message-safety` | `Message.safety_status` | Финальный? |
|
||||
|---|---|---|
|
||||
| `200` / `allow` | `allowed` | да |
|
||||
| `403` / `deny` | `blocked` | да |
|
||||
| `203` / `pending` | `pending` | нет |
|
||||
|
||||
## api-backend ↔ bitrix-local-app (Open Lines)
|
||||
|
||||
Мнемоника сервиса: **`openlines`**. Endpoint Open Lines на стороне `bitrix-local-app` и приёмник событий на стороне `api-backend` используют один префикс `/internal/openlines/v1/`.
|
||||
|
||||
| Контракт | Владелец | Потребитель | Назначение | Защита |
|
||||
|---|---|---|---|---|
|
||||
| `POST /internal/openlines/v1/messages` | `bitrix-local-app` | `api-backend` | Отправка сообщения клиента в Bitrix24 Open Lines | Bearer `BITRIX_INTERNAL_API_TOKEN` |
|
||||
| `GET /internal/openlines/v1/dialogs/{external_chat_id}` | `bitrix-local-app` | `api-backend` | Получение маппинга `dialog_id` ↔ `bitrix_chat_id` | Bearer token |
|
||||
| `GET /internal/openlines/v1/status` | `bitrix-local-app` | ops / `api-backend` | Статус OAuth и `imconnector.status` | Bearer token |
|
||||
| `POST /internal/openlines/v1/setup/retry` | `bitrix-local-app` | ops | Повтор register/activate/event.bind | Bearer token |
|
||||
| `POST /internal/openlines/v1/inbox` | `api-backend` | `bitrix-local-app` | Forward нормализованных событий оператора | Bearer `BITRIX_API_INBOX_TOKEN` |
|
||||
|
||||
`external_chat_id` всегда равен `dialog_id` приложения. `bitrix-sync` не участвует в hot path чата.
|
||||
|
||||
## api-backend ↔ bitrix-sync
|
||||
|
||||
**без синхронного HTTP** в пользовательских сценариях. Связь — PostgreSQL-триггеры в App DB → очередь `han_app.sync_queue` + прямой доступ `bitrix-sync` к `han_app` для write-back. `api-backend` **не создаёт** задачи синхронизации вручную.
|
||||
|
||||
### Очередь, триггеры и write-back (основной контракт MVP)
|
||||
|
||||
| Контракт | Тип | Владелец | Потребитель | Назначение |
|
||||
|---|---|---|---|---|
|
||||
| `han_app.sync_queue` | PostgreSQL | триггеры `han_app` (миграции App DB) | `bitrix-sync` | Асинхронная очередь App DB → Bitrix24: триггер ставит задачу при изменении отслеживаемых полей |
|
||||
| `han.sync_suppress` (GUC) | PostgreSQL session | `bitrix-sync` | триггеры `han_app` | Подавление эхо-задач при записи данных от Bitrix24 в App DB |
|
||||
| `ClientProfile.bitrix_contact_id` | PostgreSQL | `bitrix-sync` | App DB | Маппинг профиля на CRM Contact после map/create |
|
||||
| `han_app.entity_external_mapping` | PostgreSQL | `bitrix-sync` | App DB | Универсальный маппинг App entity ↔ Bitrix entity (MVP: Contact) |
|
||||
| Обновление `sync_queue.status` | PostgreSQL | `bitrix-sync` | App DB | `processed` / `failed` / `dead_letter`, retry metadata |
|
||||
|
||||
Типы задач MVP (`sync_queue.task_type`):
|
||||
|
||||
- `contact.map_or_create` — матчинг/создание Contact, запись `bitrix_contact_id`, флаг регистрации в Bitrix24;
|
||||
- `contact.update` — push изменений профиля в Bitrix24.
|
||||
|
||||
`bitrix-sync` **не создаёт** `UserIdentity` / `ClientProfile` в auth-flow; вход worker — задачи из `sync_queue`, созданные триггерами.
|
||||
|
||||
### Internal HTTP `bitrix-sync` (ops, не hot path)
|
||||
|
||||
| Контракт | Владелец | Потребитель | Назначение | Защита |
|
||||
|---|---|---|---|---|
|
||||
| `GET /internal/sync/v1/status` | `bitrix-sync` | ops / мониторинг | Глубина очереди, dead letter, последний успешный run | internal network + `BITRIX_SYNC_SERVICE_TOKEN` |
|
||||
|
||||
Повтор dead letter и ручной replay в MVP — через БД/ops-процедуры; отдельный HTTP replay-endpoint — post-MVP.
|
||||
|
||||
## bitrix-local-app ↔ Bitrix24
|
||||
|
||||
| Контракт | Направление | Назначение |
|
||||
|---|---|---|
|
||||
| `GET/POST /bitrix/install` | Bitrix24 → `bitrix-local-app` | Установка local app, OAuth lifecycle |
|
||||
| `GET/POST /bitrix/handler` | Bitrix24 → `bitrix-local-app` | `ONIMCONNECTOR*`, `ONAPPINSTALL`, `ONAPPUNINSTALL` |
|
||||
| `imconnector.register` | `bitrix-local-app` → Bitrix24 | Регистрация `han_mobile_app` |
|
||||
| `imconnector.activate` | `bitrix-local-app` → Bitrix24 | Привязка к линии 8 |
|
||||
| `event.bind` | `bitrix-local-app` → Bitrix24 | Подписка на события коннектора |
|
||||
| `imconnector.send.messages` | `bitrix-local-app` → Bitrix24 | Доставка сообщения клиента оператору |
|
||||
| `imconnector.send.status.delivery` | `bitrix-local-app` → Bitrix24 | Подтверждение доставки входящего события |
|
||||
|
||||
## bitrix-sync ↔ Bitrix24 CRM
|
||||
|
||||
| Контракт | Направление | Назначение |
|
||||
|---|---|---|
|
||||
| `crm.contact.get/list/add/update` | `bitrix-sync` → Bitrix24 | Поиск, создание и обновление Contact |
|
||||
| `POST /bitrix/sync/webhook/contact` | Bitrix24 (робот) → `bitrix-sync` | Исходящий webhook при изменении полей Contact, зарегистрированного в приложении |
|
||||
| PostgreSQL schema `bitrix_sync` | `bitrix-sync` ↔ PostgreSQL | Worker state, field mapping, retry/dead letter audit |
|
||||
| PostgreSQL schema `han_app` | `bitrix-sync` ↔ PostgreSQL | Очередь `sync_queue`, маппинг ID, обновление профиля (Bitrix → App) |
|
||||
|
||||
Очередь `han_app.sync_queue` и write-back — в разделе «api-backend ↔ bitrix-sync» выше.
|
||||
|
||||
`bitrix-sync` использует `BITRIX_SYNC_APP_DATABASE_URL` для `han_app` + `bitrix_sync`, только `BITRIX_SYNC_CRM_*` для Bitrix24 CRM REST и не читает OAuth-токены `bitrix-local-app`.
|
||||
|
||||
## api-backend ↔ внешние хранилища
|
||||
|
||||
| Контракт | Внешний сервис | Назначение |
|
||||
|---|---|---|
|
||||
| PostgreSQL schema `han_app` | Managed PostgreSQL | App DB: пользователи, профили, диалоги, сообщения, настройки, sync_queue, audit |
|
||||
| Selectel S3 `han-chat-quarantine` | Selectel S3 | Временное хранение вложений клиента до verdict |
|
||||
| Selectel S3 `han-chat-attachments` | Selectel S3 | Проверенные файлы чата |
|
||||
| Selectel S3 `han-chat-documents` | Selectel S3 | Документы компании для клиента |
|
||||
| Redis | `redis` | rate limits, OTP counters, coordination/realtime state |
|
||||
|
||||
## Observability-контракты
|
||||
|
||||
| Контракт | Владелец | Потребители | Назначение |
|
||||
|---|---|---|---|
|
||||
| OTLP gRPC/HTTP | `otel-collector` (`observability`) | backend-сервисы | Приём traces/logs/metrics |
|
||||
| JSON stdout logs | каждый сервис | platform logs / оператор | Техническая диагностика; **`ux_session_id`** из `X-Ux-Session-Id`, если передан |
|
||||
| Audit / analytics events в App DB | `api-backend` | аналитика, расследования | `session_start` и чувствительные действия без PII |
|
||||
|
||||
### Analytics: `session_start`
|
||||
|
||||
При `POST /api/v1/analytics/session-start` api-backend создаёт запись:
|
||||
|
||||
| Поле | Пример |
|
||||
|---|---|
|
||||
| `event_type` | `session_start` |
|
||||
| `ux_session_id` | UUID новой UX-сессии |
|
||||
| `start_reason` | `first_launch` / `cold_start` / `idle_timeout` |
|
||||
| `guest_session_id` | UUID или null |
|
||||
| `user_id` | null (до auth bootstrap) |
|
||||
| `request_id` | из `X-Request-ID` |
|
||||
| `ip`, `user_agent` | из proxy headers |
|
||||
|
||||
Raw OTP и полный номер телефона в audit **не** пишутся.
|
||||
|
||||
### Audit: выдача download URL
|
||||
|
||||
При `GET /api/v1/documents/{document_id}/download-url` и аналогичных endpoint вложений чата api-backend создаёт запись:
|
||||
|
||||
| Поле | Пример |
|
||||
|---|---|
|
||||
| `event_type` | `document.download_url_issued`, `attachment.download_url_issued` |
|
||||
| `user_id` | UUID пользователя |
|
||||
| `resource_type` | `document` / `attachment` |
|
||||
| `resource_id` | UUID ресурса |
|
||||
| `ux_session_id` | из `X-Ux-Session-Id` |
|
||||
| `request_id` | из `X-Request-ID` |
|
||||
| `ip`, `user_agent` | из proxy headers |
|
||||
|
||||
Presigned URL и содержимое файла в audit **не** пишутся. Структура таблицы — модуль `database`.
|
||||
|
||||
## Health-контракты
|
||||
|
||||
Все backend-сервисы имеют `GET /health/live` и `GET /health/ready`. Наружу публикуются только health endpoints, которые нужны `nginx`/Bitrix24; internal services проверяются через Docker/VPC-сеть.
|
||||
Reference in New Issue
Block a user