Эмулятор реестра ФСП
fsp-mock/ — сервис, моделирующий реестр достижений Федерации спортивного программирования. По проектным материалам у команды нет производственного доступа к реальному реестру для хакатона, поэтому определён собственный контракт и создан заменяемый источник данных. Это контракт команды, а не опубликованная спецификация реального API ФСП.
Источники: документация эмулятора, сущности, API, план, каталог публичных источников, seed, сценарии интерфейса. Репозиторий: https://gitlab.geek.moscow/geek.moscow/fsp-mock.
Место в продукте
Продуктовый сценарий состоит из поиска записи человека, подтверждения владения и импорта достижений. Привязкой владеет Candidates. Он создаёт фоновое задание через outbox, worker обращается к реестру через адаптер. В архитектурных материалах порт называется FspGateway, реализация для эмулятора — FspMockAdapter; фактический HTTP-адаптер также рассматривается в спецификации seed.
Каждый импортированный факт сохраняет источник, внешний идентификатор и время обновления. Повторная синхронизация должна обновлять существующий факт, включая исправление или отзыв результата. Продуктовый frontend не вызывает эмулятор напрямую. При недоступности ФСП профиль продолжает работать с последними полученными данными и видимым временем синхронизации.
Подтверждение владения кодом — проектная гипотеза о возможном механизме связи с реестром. Наличие такого механизма у настоящей ФСП не подтверждено. Для подключения реального источника потребуется согласовать контракт и заменить либо адаптировать интеграционный слой.
Реализация
Сервис написан на NestJS 11. База данных не используется: JSON-наборы загружаются в память при старте. Модули: Seed, Auth, Dictionaries, Competitions, Persons, Dossier, Ownership, Health. OpenAPI 3 выдаётся самим приложением.
Глобальная проверка bearer-токена защищает /v1/*; /health и /openapi.json открыты. Токен хранится в creds-store под именем FSP_MOCK_BEARER_TOKEN, пространство project/geek.moscow. Глобальный фильтр ошибок возвращает {code, message} с кодами person_not_found, unauthorized, not_found, bad_request, internal_error.
API
| Метод | Путь | Назначение |
|---|---|---|
| GET | /health |
Проверка работоспособности, {status: "ok"} |
| GET | /openapi.json |
Описание OpenAPI |
| GET | /v1/persons |
Поиск по fsp_person_id либо full_name и birth_date; необязательный regional_branch_code; результат unique, ambiguous или 404 |
| GET | /v1/persons/{id}/dossier |
Досье, необязательный курсор since; поля history, summary, participations, results, ranks, national_teams |
| POST | /v1/persons/{id}/ownership-challenges |
Создание проверки владения; 202 с challenge_id и замаскированным каналом |
| POST | /v1/persons/{id}/ownership-challenges/{challengeId}/confirm |
Проверка {code}; 200 с fsp_person_id либо 401 |
| GET | /v1/disciplines |
Дисциплины |
| GET | /v1/competitions, /v1/competitions/{id} |
Соревнования; фильтры discipline_code, year |
| GET | /v1/dictionaries/{levels,series,ranks,roles,categories,age-groups,branches} |
Семь справочников |
Коды подтверждения хранятся в памяти. Обновление выполняется периодическим запросом досье с since; webhook в контракте эмулятора отсутствует.
Набор данных
Набор генерируется скриптом scripts/generate-seed.mjs из спецификации seed. Приведённые количества описывают зафиксированный набор, а не реальную численность участников ФСП.
| Сущность | Записей |
|---|---|
| Люди | 50 |
| Участия | 135 |
| Результаты | 129 |
| Спортивные разряды | 80 |
| Соревнования | 38 |
| Команды | 20 |
| Сборные | 6 |
| Региональные отделения | 89 |
| Дисциплины | 5 |
Пять дисциплин: алгоритмическое программирование, продуктовое программирование и хакатоны, беспилотники, робототехника, информационная безопасность. Справочники содержат 5 уровней, 6 серий, 8 разрядов, 5 ролей, 3 категории и 8 возрастных групп.
Специальные примеры включают две пары полных тёзок для неоднозначного поиска, людей без истории соревнований, исправленный и отозванный результаты, участника сборной и идентификатор с гарантированным 404. Эти случаи нужны для контрактных тестов и демонстрации пограничных состояний. Синтетические записи с датами после даты подготовки стенда не являются утверждением о реально состоявшихся достижениях.
Исследовательский интерфейс
fsp-mock/frontend/ — React-приложение для разработчиков, QA и демонстраций. Оно использует Vite, React Router, TanStack Query и дизайн-систему проекта. Позволяет искать человека, просматривать карточку и досье, проходить сценарий подтверждения владения, исследовать соревнования и справочники.
Адрес API и bearer-токен сохраняются локально в исследовательском интерфейсе. Его сценарии проверяются с MSW. По текущему обзору этот интерфейс является локальным инструментом и не развёрнут в кластерах; опубликованный API эмулятора не означает публикацию explorer.
Проверки и упаковка
HTTP-контракт покрыт набором jest/supertest. Dockerfile выполняет двухэтапную сборку на node:22-alpine, запускает приложение непривилегированным пользователем и открывает порт 3000. При подготовке русской документации тесты сервиса повторно не запускались.
Размещение
| Окружение | Адрес | Кластер и образ |
|---|---|---|
| Внутренний стенд | Служебный доступ | namespace fsp-mock, сертификат внутреннего CA, образ из локальной сборки |
| Публичный API | fsp.geek.moscow | Тот же публичный узел, что и платформа; namespace fsp-mock, Let's Encrypt; образ fsp-mock:f5788d1a |
Манифесты находятся в infra/k3s/fsp-mock/; infra/k3s/fsp-mock-yc/ описывает прежнее размещение в Yandex Cloud. Оба развёртывания используют /health для проб. Публичная доступность адреса не отменяет bearer-защиту /v1/*; токен на сайте документации не публикуется.
Известное ограничение интеграции
В документе о seed backend зафиксировано, что FspHttpAdapter.fetchDossier ожидает плоское поле achievements, а эмулятор возвращает history, summary, participations, results, ranks, national_teams. Кроме того, требуется полное согласованное отображение исправленных и отозванных фактов на состояния достижений backend.
Поэтому сквозной импорт ФСП нельзя объявить рабочим только на основании доступности API или ответа с нулём записей. Нужны преобразование формата и проверка семантики состояний. Демонстрационные достижения, добавленные локальными фикстурами, должны быть явно помечены fixture-only и не выдаваться за результат реального импорта. Менять внешний контракт эмулятора только ради сокрытия несовпадения не следует.