Files
runners-calendar/docs/backend-api-for-frontend.md
Vaka.pro a2dcf67396
Some checks failed
CI / build-and-test (pull_request) Has been cancelled
feat: align docs with code, finish_place, registered status, UI filters, tests, CI
- Add PLAN.md and sync backend docs, .env.example, API doc (404 details)
- Document mock DB and PORT/API_PORT in docs/backend.md; README monorepo + frontend/.env.example
- Migration 002: finish_place column, status registered; mapper and mock DB updated
- Frontend: registered status, finishPlace, calendar year/month filters, pace sparkline
- Extract createApp for tests; supertest + tsx; GitHub Actions CI

Made-with: Cursor
2026-04-06 22:20:31 +03:00

7.0 KiB
Raw Permalink Blame History

Backend API — шпаргалка для фронтенда

1. Base URL

VITE_API_BASE_URL=http://localhost:3001

В коде SPA: import.meta.env.VITE_API_BASE_URL.

2. CORS

В dev-режиме бэкенд ожидает переменную:

CORS_ORIGIN=http://localhost:5173

Разрешены методы GET, POST, PATCH, DELETE и заголовок Content-Type: application/json.

3. Эндпоинты

GET /health

Liveness-проверка (без обращения к БД).

Ответ 200:

{ "status": "ok" }

GET /ready

Readiness-проверка (проверяет подключение к БД).

Ответ 200:

{ "status": "ready", "db": "connected" }

Ответ 503:

{ "error": "database_unavailable", "db": "disconnected" }

GET /races

Список забегов, отсортированных по дате.

Query-параметры (опциональные):

Параметр Тип Описание
year number Фильтр по году (напр. 2026)
month number Фильтр по месяцу (112)
  • Без параметров — возвращает все забеги.
  • Можно указать только year, только month или оба.
  • month без year фильтрует по месяцу всех лет.

Пример запроса:

GET /races?year=2026&month=5

Ответ 200:

[
  {
    "id": "2026-05-03-kazanskii-marafon",
    "date": "2026-05-03",
    "title": "Казанский марафон",
    "distanceKm": 42.195,
    "status": "planned",
    "officialUrl": null,
    "startTime": null,
    "clusterSchedule": null,
    "bibPickup": null,
    "bibNumber": null,
    "finishTime": null,
    "finishPlace": null,
    "notes": null,
    "createdAt": "2026-03-31T12:00:00.000Z",
    "updatedAt": null
  }
]

GET /races/:id

Одна запись по id.

Ответ 200: объект Race (см. модель ниже).

Ответ 404: тело JSON, поле details — массив пояснений (можно показывать в UI или игнорировать).

{ "error": "not_found", "details": ["Race not found"] }

POST /races

Создание забега.

Тело запроса (JSON):

{
  "id": "2026-06-01-my-race",
  "date": "2026-06-01",
  "title": "Мой забег",
  "distanceKm": 10,
  "status": "planned",
  "officialUrl": "https://example.com",
  "startTime": "09:30",
  "clusterSchedule": null,
  "bibPickup": null,
  "bibNumber": null,
  "finishTime": null,
  "finishPlace": null,
  "notes": null
}

Обязательные поля: id, date, title, distanceKm.

Ответ 201: созданный объект Race.

Ответ 400:

{ "error": "validation_error", "details": ["Fields id, date, title, distanceKm are required"] }

Ответ 409:

{ "error": "conflict", "details": ["Race with this id already exists"] }

PATCH /races/:id

Частичное обновление — передавать только изменяемые поля.

Тело запроса (JSON):

{
  "finishTime": "1:45:30",
  "finishPlace": "12/340",
  "bibNumber": "1234",
  "status": "completed"
}

Допустимые поля: date, title, distanceKm, status, officialUrl, startTime, clusterSchedule, bibPickup, bibNumber, finishTime, finishPlace, notes.

Ответ 200: обновлённый объект Race.

Ответ 400:

{ "error": "validation_error", "details": ["No updatable fields provided"] }

Ответ 404:

{ "error": "not_found", "details": ["Race not found"] }

DELETE /races/:id

Удаление забега.

Ответ 204: пустое тело.

Ответ 404:

{ "error": "not_found", "details": ["Race not found"] }

4. Модель Race (camelCase)

Поле Тип POST обяз. PATCH Описание
id string да Стабильный ключ, напр. 2026-05-03-kazan-marathon
date string да да YYYY-MM-DD
title string да да Название забега
distanceKm number да да Дистанция в км
status string | null нет да "planned" / "registered" / "completed"
officialUrl string | null нет да URL организатора
startTime string | null нет да Время старта, напр. "09:30"
clusterSchedule string | null нет да Расписание кластеров
bibPickup string | null нет да Выдача номеров
bibNumber string | null нет да Стартовый номер
finishTime string | null нет да Финишное время H:MM:SS или MM:SS
finishPlace string | null нет да Место на финише (произвольная строка)
notes string | null нет да Заметки
createdAt string ISO timestamp (read-only)
updatedAt string | null ISO timestamp (read-only)

5. Фильтрация списка (GET /races)

  • year — целое число, фильтрует по EXTRACT(YEAR FROM race_date).
  • month — целое число 112, фильтрует по EXTRACT(MONTH FROM race_date).
  • Параметры можно комбинировать (?year=2026&month=5) или указывать по одному.
  • Без параметров — все забеги без ограничения (пагинация пока не реализована).
  • Если по фильтру нет результатов — пустой массив [], статус 200.

6. Идемпотентность seed

Seed-скрипт (npm run seed в backend/) выполняет upsert по полю id (INSERT … ON CONFLICT (id) DO UPDATE). Источник данных — import/races_2026_calendar.csv из корня репозитория. Повторный запуск безопасен.

7. Поведение при недоступной БД

  • GET /health — всегда 200 (не проверяет БД).
  • GET /ready — при недоступной БД: 503 { "error": "database_unavailable", "db": "disconnected" }. В режиме CALENDAR_RUN_MOCK_DB (dev/CI без Postgres) readiness возвращает успех без реального подключения — см. docs/backend.md.
  • Все остальные маршруты — 503 { "error": "database_unavailable" }.
  • В логах сервера: строка ошибки с контекстом маршрута.