Files
runners-calendar/docs/backend-api-for-frontend.md
Vaka.pro 3c6baa66a1
Some checks failed
CI / build-and-test (pull_request) Has been cancelled
feat(frontend): race form, start time selects, calendar views, day page
- Hide org schedule fields when editing a past race; isRaceDateInPast helper
- StartTimeSelects (HH:mm:ss) and optional ?date= prefill on new race
- Full-card link to edit for races needing result entry; shadow token
- List/calendar toggle (sessionStorage); year grid and month focus views
- Date hover popover and /races/day/:ymd page with Add button
- Docs plan-korrektirovok-starty.md and startTime API note; client 0.4.0

Made-with: Cursor
2026-04-13 22:07:37 +03:00

7.4 KiB
Raw Permalink Blame History

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

1. Base URL

SPA всегда отправляет запросы на относительный префикс /api текущего origin.

  • В dev (npm run dev): Vite proxy отправляет /api/* на http://localhost:3001/api/*.
  • В Docker/проде: nginx фронта проксирует /api/* на хост runners-calendar-backend:3000 в той же сети (уникальное имя сервиса Compose, без коллизий с чужими стеками).

2. CORS

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

CORS_ORIGIN=http://localhost:5173

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

3. Эндпоинты

GET /api/health

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

Ответ 200:

{ "status": "ok" }

GET /api/ready

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

Ответ 200:

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

Ответ 503:

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

GET /api/races

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

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

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

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

GET /api/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 /api/races/:id

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

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

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

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

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