feat: align docs with code, finish_place, registered status, UI filters, tests, CI
Some checks failed
CI / build-and-test (pull_request) Has been cancelled
Some checks failed
CI / build-and-test (pull_request) Has been cancelled
- 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
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
2. **Не блокировать работу отсутствием живой БД у исполнителя:**
|
||||
- код миграций и seed должен быть **валидным и согласованным** с PLAN;
|
||||
- при старте API при невозможности подключиться к БД — **ясное сообщение в лог** и **корректный HTTP-ответ** на зависящих от БД маршрутах (например 503 с телом `{"error":"database_unavailable",...}`) **или** падение процесса на старте с понятной ошибкой (выбрать одну стратегию и описать её в `docs/backend.md`).
|
||||
3. **Не выдумывать обход БД** (in-memory «режим без Postgres»), если это не согласовано отдельно — фронт и план рассчитаны на PostgreSQL.
|
||||
3. **Режим без Postgres для dev/CI** согласован с [PLAN.md](../PLAN.md) и `docs/backend.md`: переменная `CALENDAR_RUN_MOCK_DB=1` (или `true`) включает in-memory заглушку пула **только** для HTTP-слоя. Для **`npm run db:migrate`** и **`npm run seed`** нужен реальный PostgreSQL и `DB_*`; mock для миграций/seed не используется.
|
||||
4. Автотесты, требующие Docker/Postgres, помечать как **опциональные** или давать инструкцию «как прогнать локально», не считая провал из-за отсутствия БД у агента блокером для merge кода.
|
||||
|
||||
---
|
||||
@@ -90,7 +90,7 @@
|
||||
|
||||
**В JSON API (запрос/ответ)** — **camelCase**, как в PLAN:
|
||||
|
||||
`id`, `date`, `title`, `distanceKm`, `status`, `officialUrl`, `startTime`, `clusterSchedule`, `bibPickup`, `bibNumber`, `finishTime`, `notes`.
|
||||
`id`, `date`, `title`, `distanceKm`, `status` (`planned` \| `registered` \| `completed`), `officialUrl`, `startTime`, `clusterSchedule`, `bibPickup`, `bibNumber`, `finishTime`, `finishPlace`, `notes`.
|
||||
|
||||
**В PostgreSQL** — **snake_case**, например:
|
||||
|
||||
@@ -107,6 +107,7 @@
|
||||
| `bib_pickup` | `TEXT` |
|
||||
| `bib_number` | `TEXT` |
|
||||
| `finish_time` | `TEXT` |
|
||||
| `finish_place` | `TEXT` |
|
||||
| `notes` | `TEXT` |
|
||||
| `created_at` | `TIMESTAMPTZ` DEFAULT now() |
|
||||
| `updated_at` | `TIMESTAMPTZ` |
|
||||
|
||||
@@ -87,6 +87,7 @@ GET /races?year=2026&month=5
|
||||
"bibPickup": null,
|
||||
"bibNumber": null,
|
||||
"finishTime": null,
|
||||
"finishPlace": null,
|
||||
"notes": null,
|
||||
"createdAt": "2026-03-31T12:00:00.000Z",
|
||||
"updatedAt": null
|
||||
@@ -102,10 +103,10 @@ GET /races?year=2026&month=5
|
||||
|
||||
**Ответ 200:** объект `Race` (см. модель ниже).
|
||||
|
||||
**Ответ 404:**
|
||||
**Ответ 404:** тело JSON, поле `details` — массив пояснений (можно показывать в UI или игнорировать).
|
||||
|
||||
```json
|
||||
{ "error": "not_found" }
|
||||
{ "error": "not_found", "details": ["Race not found"] }
|
||||
```
|
||||
|
||||
---
|
||||
@@ -129,6 +130,7 @@ GET /races?year=2026&month=5
|
||||
"bibPickup": null,
|
||||
"bibNumber": null,
|
||||
"finishTime": null,
|
||||
"finishPlace": null,
|
||||
"notes": null
|
||||
}
|
||||
```
|
||||
@@ -160,12 +162,13 @@ GET /races?year=2026&month=5
|
||||
```json
|
||||
{
|
||||
"finishTime": "1:45:30",
|
||||
"finishPlace": "12/340",
|
||||
"bibNumber": "1234",
|
||||
"status": "completed"
|
||||
}
|
||||
```
|
||||
|
||||
**Допустимые поля:** `date`, `title`, `distanceKm`, `status`, `officialUrl`, `startTime`, `clusterSchedule`, `bibPickup`, `bibNumber`, `finishTime`, `notes`.
|
||||
**Допустимые поля:** `date`, `title`, `distanceKm`, `status`, `officialUrl`, `startTime`, `clusterSchedule`, `bibPickup`, `bibNumber`, `finishTime`, `finishPlace`, `notes`.
|
||||
|
||||
**Ответ 200:** обновлённый объект `Race`.
|
||||
|
||||
@@ -178,7 +181,7 @@ GET /races?year=2026&month=5
|
||||
**Ответ 404:**
|
||||
|
||||
```json
|
||||
{ "error": "not_found" }
|
||||
{ "error": "not_found", "details": ["Race not found"] }
|
||||
```
|
||||
|
||||
---
|
||||
@@ -192,7 +195,7 @@ GET /races?year=2026&month=5
|
||||
**Ответ 404:**
|
||||
|
||||
```json
|
||||
{ "error": "not_found" }
|
||||
{ "error": "not_found", "details": ["Race not found"] }
|
||||
```
|
||||
|
||||
## 4. Модель `Race` (camelCase)
|
||||
@@ -203,13 +206,14 @@ GET /races?year=2026&month=5
|
||||
| `date` | string | да | да | `YYYY-MM-DD` |
|
||||
| `title` | string | да | да | Название забега |
|
||||
| `distanceKm` | number | да | да | Дистанция в км |
|
||||
| `status` | string \| null | нет | да | `"planned"` / `"completed"` |
|
||||
| `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` |
|
||||
| `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) |
|
||||
@@ -229,6 +233,6 @@ Seed-скрипт (`npm run seed` в `backend/`) выполняет **upsert**
|
||||
## 7. Поведение при недоступной БД
|
||||
|
||||
- `GET /health` — всегда `200` (не проверяет БД).
|
||||
- `GET /ready` — `503 { "error": "database_unavailable", "db": "disconnected" }`.
|
||||
- `GET /ready` — при недоступной БД: `503 { "error": "database_unavailable", "db": "disconnected" }`. В режиме **`CALENDAR_RUN_MOCK_DB`** (dev/CI без Postgres) readiness возвращает успех без реального подключения — см. `docs/backend.md`.
|
||||
- Все остальные маршруты — `503 { "error": "database_unavailable" }`.
|
||||
- В логах сервера: строка ошибки с контекстом маршрута.
|
||||
|
||||
@@ -62,27 +62,31 @@ npm run build # компиляция в dist/
|
||||
npm start # запуск из dist/
|
||||
```
|
||||
|
||||
API слушает порт из `API_PORT` (по умолчанию `3001`).
|
||||
API слушает порт: **`PORT`** (если задан), иначе **`API_PORT`**, иначе **`3001`**.
|
||||
|
||||
## Переменные окружения
|
||||
|
||||
| Переменная | Описание | По умолчанию |
|
||||
|---|---|---|
|
||||
| `DB_HOST` | Хост PostgreSQL | — (обязательна) |
|
||||
| `DB_PORT` | Порт PostgreSQL | — (обязательна) |
|
||||
| `DB_NAME` | Имя базы данных | — (обязательна) |
|
||||
| `DB_USER` | Пользователь БД | — (обязательна) |
|
||||
| `DB_PASSWORD` | Пароль БД | — (обязательна) |
|
||||
| `DB_HOST` | Хост PostgreSQL | — (обязательна без mock, см. ниже) |
|
||||
| `DB_PORT` | Порт PostgreSQL | — (обязательна без mock) |
|
||||
| `DB_NAME` | Имя базы данных | — (обязательна без mock) |
|
||||
| `DB_USER` | Пользователь БД | — (обязательна без mock) |
|
||||
| `DB_PASSWORD` | Пароль БД | — (обязательна без mock) |
|
||||
| `CALENDAR_RUN_MOCK_DB` | `1` или `true` — режим без PostgreSQL для HTTP API (dev/CI) | выкл. |
|
||||
| `PORT` | Порт API (приоритетнее `API_PORT`) | — |
|
||||
| `API_PORT` | Порт API-сервера | `3001` |
|
||||
| `CORS_ORIGIN` | Разрешённый origin для CORS | `http://localhost:5173` |
|
||||
|
||||
При отсутствии любой из `DB_*` процесс падает при старте с сообщением `Missing required environment variable: <NAME>`.
|
||||
**Без mock:** при отсутствии любой из `DB_*` процесс падает при старте: `Missing required environment variable: <NAME>`.
|
||||
|
||||
**С `CALENDAR_RUN_MOCK_DB=1`:** переменные `DB_*` не обязательны; реальный пул не поднимается. **Не использовать** mock для `npm run db:migrate` и `npm run seed` — нужен настоящий Postgres и корректные `DB_*`.
|
||||
|
||||
## Поведение при недоступной БД
|
||||
|
||||
- **Старт сервера** — проходит успешно (env валидирован, Express слушает порт).
|
||||
- **`GET /health`** — всегда `200 { "status": "ok" }` (liveness, без обращения к БД).
|
||||
- **`GET /ready`** — пробует подключиться к БД; возвращает `200` если ОК, `503 { "error": "database_unavailable" }` если нет.
|
||||
- **`GET /ready`** — при обычном режиме пробует подключиться к БД; `200` если ОК, `503 { "error": "database_unavailable", ... }` если нет. В режиме **`CALENDAR_RUN_MOCK_DB`** readiness считается успешным без реального подключения (удобно для CI/smoke API).
|
||||
- **Все остальные маршруты** при ошибке БД возвращают `503 { "error": "database_unavailable" }`.
|
||||
|
||||
## Структура каталога
|
||||
@@ -90,11 +94,13 @@ API слушает порт из `API_PORT` (по умолчанию `3001`).
|
||||
```
|
||||
backend/
|
||||
├── migrations/
|
||||
│ └── 001_create_races.sql
|
||||
│ ├── 001_create_races.sql
|
||||
│ └── 002_finish_place_and_registered_status.sql
|
||||
├── src/
|
||||
│ ├── app.ts # фабрика Express (тесты)
|
||||
│ ├── config.ts # загрузка и валидация env
|
||||
│ ├── db.ts # pg Pool
|
||||
│ ├── index.ts # точка входа Express
|
||||
│ ├── db.ts # pg Pool или mock
|
||||
│ ├── index.ts # запуск сервера
|
||||
│ ├── migrate.ts # раннер миграций
|
||||
│ ├── seed.ts # разовый импорт CSV
|
||||
│ ├── mappers/
|
||||
|
||||
Reference in New Issue
Block a user