# Инструкция агенту: реализация бэкенда по [PLAN.md](../PLAN.md) Документ для ИИ-агента или разработчика, который создаёт **backend** монорепозитория «календарь забегов». Продуктовые цели и модель данных — в корневом **PLAN.md**; здесь — порядок работ, ограничения и **обязательные итоговые артефакты для фронтенда**. --- ## 0. Ограничение: нет возможности проверить подключение к БД 1. **Всё равно реализовать полноценный бэкенд «как для прод»**: миграции SQL, пул подключений `pg`, переменные `DB_*`, `docker-compose.yml` с PostgreSQL в корне репозитория. 2. **Не блокировать работу отсутствием живой БД у исполнителя:** - код миграций и seed должен быть **валидным и согласованным** с PLAN; - при старте API при невозможности подключиться к БД — **ясное сообщение в лог** и **корректный HTTP-ответ** на зависящих от БД маршрутах (например 503 с телом `{"error":"database_unavailable",...}`) **или** падение процесса на старте с понятной ошибкой (выбрать одну стратегию и описать её в `docs/backend.md`). 3. **Не выдумывать обход БД** (in-memory «режим без Postgres»), если это не согласовано отдельно — фронт и план рассчитаны на PostgreSQL. 4. Автотесты, требующие Docker/Postgres, помечать как **опциональные** или давать инструкцию «как прогнать локально», не считая провал из-за отсутствия БД у агента блокером для merge кода. --- ## 1. Ветка и расположение в репо - Если репозиторий под git: создать ветку `feature/backend-api` (или аналог по соглашению команды). - Каталог **`backend/`** в корне проекта рядом с будущим `frontend/`. - Корневой **`docker-compose.yml`** — сервис `postgres` (порт, пользователь, БД, пароль — согласовать с `.env.example`). --- ## 2. Порядок реализации (обязательный) ### Шаг A. Каркас - Node **LTS**, **TypeScript**, **`backend/package.json`**. - Фреймворк: **Fastify** или **Express** (выбрать один, не смешивать). - Загрузка env: `dotenv` или встроенные средства; **валидация** наличия `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD` при старте (или при первом запросе к БД — но тогда задокументировать). - Сервер слушает порт из **`PORT`** или **`API_PORT`** (значение по умолчанию, напр. `3001`, указать в `.env.example`). ### Шаг B. CORS - Читать **`CORS_ORIGIN`** (например `http://localhost:5173` для Vite в dev). В prod — origin фронта. - Разрешить методы и заголовки, нужные для `GET/POST/PATCH` + `Content-Type: application/json`. ### Шаг C. Миграции - Каталог миграций, напр. `backend/migrations/` с нумерованными SQL-файлами **или** один миграционный runner (node-pg-migrate, graphile-migrate, собственный скрипт — на выбор, зафиксировать в `docs/backend.md`). - Первая миграция: таблица **`races`** со столбцами, соответствующими PLAN (см. раздел 3 ниже). - Команда **`npm run db:migrate`** (или эквивалент в `backend/`) — идемпотентное накатывание на чистую БД. ### Шаг D. Доступ к данным - Клиент **`pg`**: `Pool` с параметрами из `DB_*`. - Слой репозитория или прямые запросы в обработчиках — на усмотрение, без лишних абстракций сверх задачи. ### Шаг E. HTTP API Реализовать минимум: | Метод | Путь | Назначение | |--------|------|------------| | `GET` | `/health` | Liveness: процесс жив; **не обязан** проверять БД (или опционально — задокументировать). | | `GET` | `/ready` (опционально) | Readiness: проверка соединения с БД — полезно для оркестраторов. | | `GET` | `/races` | Список забегов; **query**: `year`, `month` (1–12) — фильтрация для экранов календаря; без параметров — все строки или разумный лимит + документация пагинации если добавите позже. | | `GET` | `/races/:id` | Одна запись по `id`. | | `POST` | `/races` | Создание; тело JSON в **camelCase** как в PLAN. | | `PATCH` | `/races/:id` | Частичное обновление; только переданные поля. | | `DELETE` | `/races/:id` | Опционально по PLAN; если не нужен фронту — можно не делать, но тогда явно написать в документации «удаление не поддерживается». | **Ошибки:** - 400 — валидация тела/параметров. - 404 — нет `id`. - 409 — конфликт уникального `id` при POST (если клиент прислал уже существующий). - 503 или 500 — недоступна БД (согласовать с разделом 0). Формат ошибки: JSON, единообразно, напр. `{"error":"validation_error","details":[...]}`. ### Шаг F. Seed (разовый скрипт) - Команда **`npm run seed`** в `backend/` (или корне монорепо с `workspace` — единообразно). - Читает **`import/races_2026_calendar.csv`** (путь от корня репо); парсинг с учётом кавычек в поле названия. - Генерирует **`id`** стабильно: например `{date}-{slug-from-title}`; при коллизии — суффикс или upsert по `id`. - **`INSERT ... ON CONFLICT (id) DO UPDATE`** (upsert) — удобно для повторного запуска seed. - Seed **не** вызывается из HTTP handlers. ### Шаг G. Корневой `.env.example` - Все переменные: `DB_*`, `PORT`/`API_PORT`, `CORS_ORIGIN`. - **Без** реальных паролей; комментарии к каждой переменной. --- ## 3. Соответствие полей PLAN ↔ SQL ↔ JSON API **В JSON API (запрос/ответ)** — **camelCase**, как в PLAN: `id`, `date`, `title`, `distanceKm`, `status`, `officialUrl`, `startTime`, `clusterSchedule`, `bibPickup`, `bibNumber`, `finishTime`, `notes`. **В PostgreSQL** — **snake_case**, например: | SQL column | Тип (рекомендация) | |------------|---------------------| | `id` | `TEXT` PRIMARY KEY | | `race_date` | `DATE` | | `title` | `TEXT` | | `distance_km` | `NUMERIC(6,3)` | | `status` | `TEXT` CHECK (опционально) | | `official_url` | `TEXT` | | `start_time` | `TEXT` | | `cluster_schedule` | `TEXT` | | `bib_pickup` | `TEXT` | | `bib_number` | `TEXT` | | `finish_time` | `TEXT` | | `notes` | `TEXT` | | `created_at` | `TIMESTAMPTZ` DEFAULT now() | | `updated_at` | `TIMESTAMPTZ` | Маппинг **строго** в одном модуле (например `backend/src/mappers/race.ts`), чтобы фронт всегда видел camelCase. **Типы `date`:** в API строка `YYYY-MM-DD`. **`distanceKm`:** число. **`finishTime`:** строка времени как в PLAN; бэкенд **не обязан** парсить для бизнес-логики (PR считает фронт), но может валидировать формат по желанию. --- ## 4. Обязательный итог для упрощения фронтенда После того как код бэкенда готов, агент **обязан** добавить в репозиторий документ: ### Файл: `docs/backend-api-for-frontend.md` В нём **кратко и без пробелов в фактах**: 1. **Base URL** — что подставлять во фронт (`VITE_API_BASE_URL`), пример для dev. 2. **CORS** — какой `CORS_ORIGIN` ожидается в dev. 3. **Таблица эндпоинтов** — метод, путь, query, тело запроса (пример JSON), пример успешного ответа, коды ошибок. 4. **Модель `Race`** — перечень полей в **camelCase** с типами и обязательностью для POST vs PATCH. 5. **Фильтр списка** — как именно работают `year` и `month` на `GET /races` (включая границы: пустой месяц, только год и т.д.). 6. **Идемпотентность seed** — одна фраза: upsert по `id`, откуда берётся CSV. 7. **Поведение при недоступной БД** — что возвращает API / что в логах (как в реализации). Дополнительно можно дублировать суть в **`docs/backend.md`** (общая эксплуатация: docker, migrate, seed, запуск), но **`backend-api-for-frontend.md`** — главная «шпаргалка» для разработчика SPA. --- ## 5. Критерии готовности (чеклист агента) - [ ] `docker-compose.yml` поднимает Postgres с параметрами, совместимыми с `.env.example`. - [ ] Миграция создаёт `races`; есть команда миграции. - [ ] Реализованы `GET /races`, `GET /races/:id`, `POST /races`, `PATCH /races/:id` согласно PLAN. - [ ] Реализован seed из `import/races_2026_calendar.csv`. - [ ] `GET /health` (и при наличии `/ready` — описано). - [ ] Корневой `.env.example` обновлён. - [ ] Написан **`docs/backend-api-for-frontend.md`** (раздел 4). - [ ] `docs/backend.md` содержит команды: установка зависимостей, migrate, seed, `npm run dev` для API. --- ## 6. Не входит в объём бэкенда (напоминание) - Авторизация, пользователи, сессии. - Парсинг сайтов организаторов. - Отдача статики фронта с того же процесса (фронт — отдельно Vite/build). --- *Конец инструкции. Источник требований к продукту — всегда [PLAN.md](../PLAN.md).*