Docs security / security (push) Successful in 7s
Docs verify / verify (push) Successful in 11s
Conventional commits / commits (pull_request) Successful in 11s
Docs security / security (pull_request) Successful in 6s
Docs verify / verify (pull_request) Successful in 12s
124 lines
9.9 KiB
Markdown
124 lines
9.9 KiB
Markdown
# HTTP API и ошибки
|
||
|
||
## Источник контракта
|
||
|
||
Backend генерирует OpenAPI из собственного публичного API. Этот сгенерированный
|
||
OpenAPI — исполняемый источник HTTP-контракта. Frontend использует
|
||
предоставленный OpenAPI snapshot как актуальный вход и локально генерирует
|
||
типизированный клиент. Backend владеет контрактом, а подготовка и доставка
|
||
схемы находятся вне frontend. Это разделение принято в
|
||
[ADR 002](../adr/002-nest-generated-openapi-contract.md) и
|
||
[ADR 014](../adr/014-trusted-openapi-input.md).
|
||
|
||
Backend командой `npm run openapi:generate` создаёт `openapi.json` без запуска
|
||
сервера и не хранит результат в Git. Contract test сравнивает вывод генератора
|
||
с Nest runtime schema настроенного приложения. Frontend хранит полученный
|
||
artifact у себя и проверяет соответствие generated types этой копии. CI не
|
||
скачивает соседние репозитории и не сравнивает их текущие revisions. Обновление
|
||
входного artifact потребителя выполняется отдельно с review; совместимость
|
||
развёрнутых версий не доказывается одним зелёным локальным CI.
|
||
|
||
API имеет prefix `/api/v1`; Swagger публикуется для локальной разработки по
|
||
environment policy, а не используется frontend в runtime.
|
||
|
||
## Скринер акций
|
||
|
||
`POST /api/v1/stocks/screener/search` всегда создаёт новый результат по
|
||
условиям скринера, включая пустой подтверждённый список. Ответ первой страницы
|
||
содержит непрозрачный `searchId`, время получения каталога и признак следующей
|
||
страницы. `GET /api/v1/stocks/screener/search/{searchId}?page=...` читает
|
||
последующую страницу того же результата, не запускает новый поиск и сохраняет
|
||
состав и время списка. Идентификатор результата принадлежит текущему пользователю
|
||
и его подключению; недоступный или чужой результат возвращает ошибку, а не
|
||
пустой список. Точные поля и статусы определяет backend-generated OpenAPI.
|
||
|
||
## Инвестиционные данные и синхронизация
|
||
|
||
Затронутые GET инвестиционных ресурсов возвращают сгенерированный contract
|
||
`{ data, meta }`. `data` nullable: `null` означает отсутствие complete result,
|
||
а подтверждённый пустой массив является available complete result. `meta`
|
||
независимо описывает freshness, completeness, `lastSuccessfulAt`,
|
||
resource-relevant `lastAttempt` и availability. `lastAttempt` имеет состояния
|
||
`none`, `running`, `succeeded`, `partial` и `failed`; его неуспех не заменяет
|
||
сохранённый complete result или его время. `Portfolio.isStale` остаётся
|
||
специфичным для snapshot полем, а не заменой состояния попытки.
|
||
|
||
Только Accounts дополнительно возвращает top-level `sync`: `null`, если у
|
||
текущего подключения ещё не было часовой попытки, иначе connection-wide
|
||
наблюдаемый результат. Это наблюдение, не команда. Обычный GET и retry после
|
||
проверки доступа читают локальное состояние и не начинают личную работу.
|
||
Устаревший `sync=observe`, пока он сохранён для совместимости маршрута, также
|
||
только читает. Request-started screen-sync command/status не является текущей
|
||
AS-IS моделью.
|
||
|
||
Безопасные ошибки завершённой часовой попытки передаются в `lastAttempt` и
|
||
Accounts `sync`; HTTP transport/access/input errors сохраняют Problem Details.
|
||
Старые данные при временной source failure сохраняют исходное успешное время.
|
||
Ни одно поле не раскрывает connection revision. Детали поведения —
|
||
[свежесть и полнота](cross-cutting/data-freshness.md).
|
||
|
||
Envelope несовместим с прежними bare-array ответами: backend/frontend
|
||
поставляются согласованной парой. Финальный artifact передаётся явно;
|
||
локальная генерация и CI frontend читают только собственный snapshot.
|
||
|
||
## Validation и identity
|
||
|
||
Backend преобразует и проверяет входные данные на общей HTTP-границе.
|
||
Неизвестные свойства запроса удаляются; сам факт их передачи не обещает отказа.
|
||
Проверка формата не заменяет бизнес-ограничения и проверку доступа к счёту.
|
||
Аутентификация подтверждает личность пользователя; accountId из URL сам по себе
|
||
не доказывает доступ к брокерским данным.
|
||
|
||
## Error contract
|
||
|
||
Общая граница обработки HTTP-ошибок возвращает `application/problem+json`
|
||
со `status` и публичным `code`.
|
||
Неожиданный exception возвращает `500` / `INTERNAL_SERVER_ERROR`, без исходного
|
||
message/stack. Типичный ответ:
|
||
|
||
```json
|
||
{ "status": 401, "code": "UNAUTHORIZED" }
|
||
```
|
||
|
||
Для input validation контракт требует `400`, `VALIDATION_ERROR` и `errors` как
|
||
массив `{ pointer, detail }`, где pointer адресует поле запроса. Это требование
|
||
проверяется реальным HTTP-ответом после всех стадий обработки запроса. Глобальная
|
||
валидация передаёт фильтру структурированные ошибки без исходных значений и DTO,
|
||
а фильтр формирует JSON Pointer, включая вложенные поля. HTTP suites подтверждают
|
||
детали ошибок регистрации, банковской карты и синхронизации; OpenAPI отдельно
|
||
фиксирует форму ответа.
|
||
|
||
Ответ не включает автоматически все поля RFC (`type`, `title`, `instance`).
|
||
Добавление обязательного поля является изменением публичного контракта.
|
||
Клиент выбирает поведение по публичному коду, а не по внутреннему тексту ошибки.
|
||
|
||
## Domain errors и auth
|
||
|
||
Специальный code определяется сценарием; общее преобразование ошибок и безопасная
|
||
форма ответа принадлежат HTTP-границе. Auth endpoints используют свои status и cookie
|
||
operations: [Authentication](../domains/authentication/overview.md).
|
||
T-Bank token failure и временная недоступность имеют разные значения:
|
||
[Connection API](../integrations/tbank.md#состояние-и-ошибки). Raw upstream/Prisma errors
|
||
и secrets не передаются frontend.
|
||
|
||
## Наблюдаемость
|
||
|
||
Backend пишет структурированные JSON-события в stdout через Pino. Обработка
|
||
HTTP-запроса получает серверный request ID, который возвращается заголовком
|
||
`X-Request-ID`; допустимый входящий идентификатор сохраняется отдельно как
|
||
client request ID. Это позволяет сопоставить публичную ошибку с обработкой
|
||
запроса без передачи внутреннего exception в ответ API.
|
||
|
||
Журнал завершения запроса содержит шаблон маршрута, метод, статус,
|
||
длительность и при наличии публичный error code. События внешней границы
|
||
добавляют операцию, результат и число попыток. Разрешённые поля ограничены;
|
||
полные запросы и ответы, token, cookie и Authorization не являются данными
|
||
журнала. Фильтрация полей и redaction не защищают секрет, вручную включённый
|
||
в свободный текст сообщения или stack.
|
||
|
||
Выбор logger и граница ответственности описаны в
|
||
[ADR 008](../adr/008-pino-backend-logging.md); согласованное поведение — в
|
||
workspace-спецификации
|
||
[Structured logging](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/backend-structured-logging/spec.md).
|
||
Сборщик, срок хранения журналов и alerting не определяются приложением.
|