Files
docs/architecture/http-api.md
T
ksv741 2c34023af8
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
docs: document stock screener
2026-10-11 11:18:40 +03:00

9.9 KiB
Raw Blame History

HTTP API и ошибки

Источник контракта

Backend генерирует OpenAPI из собственного публичного API. Этот сгенерированный OpenAPI — исполняемый источник HTTP-контракта. Frontend использует предоставленный OpenAPI snapshot как актуальный вход и локально генерирует типизированный клиент. Backend владеет контрактом, а подготовка и доставка схемы находятся вне frontend. Это разделение принято в ADR 002 и ADR 014.

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. Детали поведения — свежесть и полнота.

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. Типичный ответ:

{ "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. T-Bank token failure и временная недоступность имеют разные значения: Connection API. 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; согласованное поведение — в workspace-спецификации Structured logging. Сборщик, срок хранения журналов и alerting не определяются приложением.