Files
runners-calendar/docs/backend-api-for-frontend.md
Vakanaut 793d51fdce
Some checks failed
CI / build-and-test (pull_request) Has been cancelled
feat: complete account and dashboard sprint
2026-07-12 15:56:26 +03:00

7.4 KiB
Raw Blame History

Backend API — контракт для frontend

Base URL и авторизация

SPA обращается к относительному префиксу /api. В dev Vite проксирует его на http://localhost:3001/api; в Docker nginx проксирует на runners-calendar-backend:3000.

Все маршруты /api/races требуют сессионную cookie авторизованного пользователя с подтверждённым email. Записи принадлежат текущему пользователю: чужая запись не видна и отвечает 404.

После POST /api/auth/login или GET /api/auth/me клиент получает csrfToken. Для каждого POST, PATCH и DELETE с активной сессией его нужно передавать в заголовке X-CSRF-Token. Cookie передаётся браузером автоматически; при отдельном origin запрос должен использовать credentials.

Аккаунт и сессии

Все маршруты этого раздела требуют авторизации и подтверждённого email. Для изменяющих состояние запросов обязателен X-CSRF-Token.

GET /api/auth/sessions

Возвращает активные сессии текущего пользователя. Хеши и сами токены не возвращаются.

{
  "sessions": [{
    "id": "UUID",
    "createdAt": "ISO datetime",
    "lastSeenAt": "ISO datetime",
    "expiresAt": "ISO datetime",
    "current": true
  }]
}

POST /api/auth/password

{ "currentPassword": "…", "newPassword": "не менее 15 символов" }

Проверяет текущий пароль, сохраняет новый и отзывает все остальные активные сессии. Успех: 200 { "ok": true }; неверный текущий пароль: 400 invalid_current_password.

DELETE /api/auth/sessions/:id

Отзывает указанную активную сессию текущего пользователя. Успех: 204. Если отозвана текущая сессия, cookie очищается. Чужая, неактивная или отсутствующая сессия отвечает 404.

POST /api/auth/sessions/revoke-others

Отзывает все активные сессии, кроме текущей. Успех: 200 { "ok": true }.

Служебные маршруты

GET /api/health

Liveness без проверки БД:

{ "status": "ok", "version": "<backend version>" }

GET /api/meta

Версия для footer:

{ "version": "<backend version>" }

GET /api/ready

Проверяет БД. Успех: { "status": "ready", "db": "connected" }. Недоступная БД: 503 { "error": "database_unavailable", "db": "disconnected" }.

Забеги

GET /api/races

Возвращает только забеги текущего пользователя, отсортированные по дате. Опциональные query-параметры: year (целое число) и month (112); без результатов возвращается [].

GET /api/races/:id

Возвращает один забег. id — UUID, который создаёт сервер. Не путайте его с slug: slug создаёт клиент как читаемый ключ и он уникален в рамках одного пользователя.

POST /api/races

Создаёт забег. Обязательные поля: slug, date, title, distanceKm. Неизвестные поля отклоняются.

{
  "slug": "2026-06-01-my-race",
  "date": "2026-06-01",
  "title": "Мой забег",
  "distanceKm": 10,
  "status": "planned",
  "officialUrl": "https://example.com",
  "startTime": "09:30"
}

Успех: 201 и объект Race. Одинаковый slug у того же пользователя: 409 { "error": "conflict", "details": ["Race with this slug already exists"] }. Если coverImageUrl не передан, backend может найти Open Graph-обложку по officialUrl; это best-effort и не мешает созданию при ошибке внешнего сайта.

PATCH /api/races/:id

Частично обновляет забег. Передайте хотя бы одно поле из модели ниже; неизвестные поля и пустой объект отклоняются. Успех: 200 и обновлённый Race.

DELETE /api/races/:id

Удаляет забег. Успех: 204 без тела.

Модель Race

Поле Тип POST PATCH Ограничение
id UUID сервер Read-only UUID сервера
slug string да да 1120 символов: буквы/цифры, разделённые -
date string да да Реальная дата YYYY-MM-DD
title string да да 1200 символов
distanceKm number да да Больше 0, не более 999.999
status string/null нет да planned, registered или completed
officialUrl HTTP(S) URL/null нет да До 2048 символов
coverImageUrl HTTP(S) URL/null нет да До 2048 символов
startTime string/null нет да HH:MM или HH:MM:SS
clusterSchedule string/null нет да До 1000 символов
bibPickup string/null нет да До 500 символов
bibNumber string/null нет да До 100 символов
finishTime string/null нет да SS, MM:SS или H:MM:SS
finishPlace string/null нет да До 100 символов
notes string/null нет да До 5000 символов
createdAt ISO datetime сервер Read-only
updatedAt ISO datetime/null сервер Read-only

Ошибки

Статус Тело Когда
400 { "error": "validation_error", "details": ["…"] } Неверный UUID, query или тело запроса
401 { "error": "unauthorized", "details": ["Authentication required"] } Нет сессии
403 csrf_error / email_not_verified Неверный CSRF-токен или email не подтверждён
404 { "error": "not_found", "details": ["Race not found"] } Забега нет либо он принадлежит другому пользователю
409 conflict Дублирующий slug пользователя
500 unknown_error Непредвиденная ошибка приложения
503 database_unavailable База недоступна

Некорректный JSON также отвечает 400 validation_error.

Seed

npm run seed выполняет upsert по slug и не перезаписывает пользовательские изменения. После включения авторизации ему нужен SEED_OWNER_USER_ID или SEED_OWNER_EMAIL.