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:
229
PLAN.md
229
PLAN.md
@@ -1,202 +1,61 @@
|
||||
# План: календарь забегов (SPA + API + PostgreSQL)
|
||||
# Calendar Run — план продукта
|
||||
|
||||
Консолидированный план реализации с актуальными решениями. После инициализации git — работать в отдельной ветке (например `feature/race-calendar-app`).
|
||||
Монорепозиторий: **backend** (Express + PostgreSQL) и **frontend** (React + Vite). Цель — календарь стартов с метриками бегуна: планирование, результаты, PR и сравнение.
|
||||
|
||||
## 1. Цели продукта
|
||||
## Вне объёма (намеренно)
|
||||
|
||||
1. **Расписание по месяцу** — выбор месяца/года, список забегов.
|
||||
2. **Расписание на год** — календарная сетка года, отметки на датах со стартами; по клику на дату — модалка/панель со списком забегов и переход на карточку.
|
||||
3. Добавлять забеги **запланированные** и **уже прошедшие**.
|
||||
4. Для **прошедших** — ввод/редактирование **результата** и **стартового номера**; поля можно дописать позже.
|
||||
5. **Личные рекорды** на главной по дистанциям: 1 км, 5 км, 10 км, 15 км, 21.1 км, 42.2 км — обновляются, если в забеге указан более быстрый результат на «подходящей» дистанции.
|
||||
6. **Старты (организатор):** дата и время старта, расписание кластеров, выдача номеров — **ручной ввод**; обязательна возможность указать **официальную ссылку** на страницу организатора (автопарсинг сайтов не входит в объём).
|
||||
7. **Авторизация не требуется.**
|
||||
- Авторизация, мультипользовательность, личные кабинеты.
|
||||
- Парсинг сайтов организаторов и автозагрузка результатов.
|
||||
- Отдача статики SPA с того же процесса, что и API (фронт — отдельный Vite/build).
|
||||
|
||||
## 2. Исходные данные в репозитории
|
||||
## Модель данных `Race` (API — camelCase)
|
||||
|
||||
- Файл `import/races_2026_calendar.csv` — колонки `date`, `month`, `day`, `event`, `distance_km`.
|
||||
- Назначение CSV: **один раз** — как вход для **seed-скрипта**, который пишет строки в PostgreSQL.
|
||||
- **В рантайме** ни фронт, ни API этот CSV **не читают**. После успешного seed файл может оставаться в репо только как архив/референс.
|
||||
| Поле | Тип | Описание |
|
||||
|------|-----|----------|
|
||||
| `id` | string | Стабильный ключ, например `{YYYY-MM-DD}-{slug}` |
|
||||
| `date` | string | `YYYY-MM-DD` |
|
||||
| `title` | string | Название |
|
||||
| `distanceKm` | number | Дистанция, км |
|
||||
| `status` | `planned` \| `registered` \| `completed` \| null | Жизненный цикл старта |
|
||||
| `officialUrl` | string \| null | Сайт организатора |
|
||||
| `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 | Место на финише (текст: «3», «3/120» и т.п.) |
|
||||
| `notes` | string \| null | Заметки |
|
||||
| `createdAt` | string | ISO, read-only |
|
||||
| `updatedAt` | string \| null | ISO, read-only |
|
||||
|
||||
## 3. Стек и структура репозитория
|
||||
PostgreSQL: `snake_case` столбцы, маппинг в [`backend/src/mappers/race.ts`](backend/src/mappers/race.ts).
|
||||
|
||||
- **Монорепозиторий:** `frontend/` (Vite + React 18 + TypeScript + react-router-dom) и `backend/` (Node.js + Fastify или Express).
|
||||
- **БД:** PostgreSQL; схема — SQL-миграции в `backend/` (или согласованный каталог миграций).
|
||||
- **Локально:** `docker-compose.yml` с сервисом Postgres.
|
||||
- **Стили:** CSS + **BEM** + CSS variables (минимальная дизайн-система: цвета, отступы, типографика).
|
||||
- **Даты/время:** `date-fns` или `Intl`, русская локаль там, где нужно.
|
||||
## HTTP API (минимум)
|
||||
|
||||
### 3.1 Переменные окружения
|
||||
- `GET /health` — liveness без БД.
|
||||
- `GET /ready` — readiness (подключение к БД; в режиме mock считается доступной — только для dev/CI).
|
||||
- `GET /races` — список; query: `year`, `month` (целые; `month` 1–12).
|
||||
- `GET /races/:id`, `POST /races`, `PATCH /races/:id`, `DELETE /races/:id`.
|
||||
|
||||
**Только на сервере API (никогда не во фронт-бандле):**
|
||||
Ошибки: JSON, единый стиль (`validation_error`, `not_found`, `conflict`, `database_unavailable`). Подробности — [`docs/backend-api-for-frontend.md`](docs/backend-api-for-frontend.md).
|
||||
|
||||
```env
|
||||
DB_HOST=
|
||||
DB_PORT=
|
||||
DB_NAME=
|
||||
DB_USER=
|
||||
DB_PASSWORD=
|
||||
```
|
||||
## Seed
|
||||
|
||||
Дополнительно по необходимости: `PORT` или `API_PORT`, `NODE_ENV`, `CORS_ORIGIN`.
|
||||
- Файл [`import/races_2026_calendar.csv`](import/races_2026_calendar.csv).
|
||||
- Стабильный `id`, upsert по `id`. Повторный запуск безопасен.
|
||||
|
||||
**Фронтенд (Vite):** только публичный адрес API, например:
|
||||
## Режим без PostgreSQL (dev/CI)
|
||||
|
||||
```env
|
||||
VITE_API_BASE_URL=http://localhost:3001
|
||||
```
|
||||
Переменная `CALENDAR_RUN_MOCK_DB=1` (или `true`): HTTP-обработчики используют заглушку пула **без** реальной БД. **Не использовать** для `npm run db:migrate` и `npm run seed` — нужен настоящий Postgres и `DB_*`.
|
||||
|
||||
В коде: `import.meta.env.VITE_API_BASE_URL`.
|
||||
## Frontend (SPA)
|
||||
|
||||
Шаблон без секретов — корневой `.env.example`; описание — в `docs/frontend.md` и `docs/backend.md`.
|
||||
- Маршруты: дашборд (`/`), список стартов (`/races`), карточка (`/races/:id`).
|
||||
- Дашборд: ближайший старт, последний результат, PR, сезон, PR по ключевым дистанциям, сравнение завершённых стартов, при необходимости — лёгкая визуализация прогресса.
|
||||
- Список: будущие / прошедшие; фильтрация по году и месяцу через API.
|
||||
- Стили: BEM и дизайн-токены; ориентир по духу — [`agent-frontend-ui-instructions.md`](agent-frontend-ui-instructions.md).
|
||||
|
||||
## 4. Источник правды и поток данных
|
||||
## Критерии готовности текущей итерации
|
||||
|
||||
### 4.1 Рантайм
|
||||
|
||||
Единственный источник правды для календаря и карточек в работающем приложении — **PostgreSQL**. SPA общается **только с HTTP API**.
|
||||
|
||||
**localStorage не используется** для хранения забегов или «дельт».
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
User[User]
|
||||
SPA[React SPA]
|
||||
API[Node API]
|
||||
DB[(PostgreSQL)]
|
||||
User --> SPA
|
||||
SPA -->|"fetch VITE_API_BASE_URL"| API
|
||||
API -->|"DB_* connection"| DB
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 4.2 Вне рантайма (разово)
|
||||
|
||||
**Seed-скрипт** (запуск вручную при развёртывании/обновлении стартового набора):
|
||||
|
||||
- Читает `import/*.csv` и/или опционально `public/data/races.json`.
|
||||
- Выполняет `INSERT` / upsert в таблицу `races`.
|
||||
- Не вызывается из SPA и не выполняется на каждом HTTP-запросе.
|
||||
|
||||
Промежуточная ступень **CSV → races.json для работы SPA не обязательна**: seed может писать в БД напрямую из CSV и/или из JSON.
|
||||
|
||||
### 4.3 Разовый перенос CSV → БД
|
||||
|
||||
- Парсинг: заголовок; кавычки в `event`; `distance_km` — число; `date` — `YYYY-MM-DD`.
|
||||
- После переноса приложение в обычном режиме **не использует** `import/races_2026_calendar.csv`. Новые старты — через UI/API (или отдельная админ-фича импорта, если появится позже).
|
||||
|
||||
## 5. Модель данных (БД и API)
|
||||
|
||||
Один набор полей — в таблице, в JSON тел запросов/ответов API и в опциональном файле для seed. Согласовать **camelCase в API** и **snake_case в SQL** в `docs/backend.md`.
|
||||
|
||||
|
||||
| Поле | Обяз. | Примечание |
|
||||
| ----------------- | ----- | ---------------------------------------------------- |
|
||||
| `id` | да | Стабильный ключ, напр. `2026-05-03-kazan-marathon` |
|
||||
| `date` | да | День старта `YYYY-MM-DD` |
|
||||
| `title` | да | Название |
|
||||
| `distanceKm` | да | Число км |
|
||||
| `status` | нет | `planned` / `completed`; иначе можно вывести из даты |
|
||||
| `officialUrl` | нет | Ссылка на организатора |
|
||||
| `startTime` | нет | Напр. `09:30` |
|
||||
| `clusterSchedule` | нет | Многострочный текст |
|
||||
| `bibPickup` | нет | Выдача номеров |
|
||||
| `bibNumber` | нет | Стартовый номер |
|
||||
| `finishTime` | нет | Время H:MM:SS / HH:MM:SS; для PR — перевод в секунды |
|
||||
| `notes` | нет | Заметки |
|
||||
|
||||
|
||||
Опционально в миграциях: `created_at`, `updated_at`.
|
||||
|
||||
**Операции:** `GET` список/фильтры по году-месяцу при необходимости, `GET :id`, `POST`, `PATCH :id`, при необходимости `DELETE` — зафиксировать в `docs/backend.md`.
|
||||
|
||||
## 6. Поведение SPA
|
||||
|
||||
### 6.1 Форма «Добавить забег»
|
||||
|
||||
- Обязательно: дата, название, дистанция.
|
||||
- Если дата **строго до сегодня** (локальная дата пользователя) — показать **результат** и **стартовый номер** (необязательны при первом сохранении).
|
||||
- Если дата в будущем — поля результата скрыты.
|
||||
- Опционально: чекбокс «Уже прошёл» для случая **сегодняшней** даты, чтобы открыть поля результата.
|
||||
- Сохранение: `**POST`** на API → запись в БД.
|
||||
|
||||
### 6.2 Страница забега
|
||||
|
||||
- Все поля модели; для прошедших — акцент на результат и номер.
|
||||
- Редактирование результата и номера **в любой момент**: `**PATCH`** на API, затем обновление состояния на клиенте (refetch / инвалидация кэша).
|
||||
|
||||
### 6.3 Личные рекорды
|
||||
|
||||
- Дистанции: 1, 5, 10, 15, 21.1, 42.2 км.
|
||||
- Учитывать забеги с заполненным `finishTime` и дистанцией в пределах допуска к целевой (напр. 21.0975 → 21.1, 42.195 → 42.2).
|
||||
- «Чужие» дистанции (2 км, 6 км, 30 км…) в таблицу PR по умолчанию **не** включаются.
|
||||
- Расчёт на клиенте по данным, полученным с API.
|
||||
|
||||
### 6.4 Экраны
|
||||
|
||||
1. Главная — PR, навигация «Месяц» / «Год», ближайшие старты.
|
||||
2. Месяц — селектор, список.
|
||||
3. Год — сетка, маркеры, клик по дате → модалка со списком → карточка.
|
||||
4. Карточка забега, форма добавления.
|
||||
5. Доступность модалки: фокус, Esc, контраст.
|
||||
|
||||
## 7. Документация
|
||||
|
||||
Создать каталог `**docs/`**:
|
||||
|
||||
|
||||
| Файл | Назначение |
|
||||
| ------------------ | ------------------------------------------------------- |
|
||||
| `docs/backend.md` | Docker, `DB`_*, миграции, seed, REST API, CORS |
|
||||
| `docs/frontend.md` | Структура `frontend/`, `VITE`_*, сборка, контракт с API |
|
||||
| `docs/ux-spa.md` | Сценарии, экраны, BEM, кратко про a11y |
|
||||
|
||||
|
||||
Корневой `**README.md`**: описание проекта, быстрый старт, **ссылки на три файла выше**.
|
||||
|
||||
## 8. Риски и ограничения
|
||||
|
||||
- `**DB`_* и пароли** — только в окружении сервера API; в git — только `.env.example` без реальных секретов.
|
||||
- Актуальные данные после работы в UI — в **БД**; файлы в git не синхронизируются с правками автоматически. Надёжный бэкап — **резервное копирование PostgreSQL**.
|
||||
- Деплой: Postgres + процесс API с env + статическая раздача фронта (или иная схема хостинга — описать в README).
|
||||
- Данные организаторов (расписание стартов) — **вручную** + ссылка; без парсинга чужих сайтов в объёме первой версии.
|
||||
|
||||
## 9. Ключевые пути после реализации
|
||||
|
||||
- `docker-compose.yml`, `.env.example`
|
||||
- `backend/` — сервер, миграции, роуты
|
||||
- `scripts/` или `backend/scripts/` — **разовый** seed из CSV/JSON
|
||||
- `import/races_2026_calendar.csv` — только вход seed (не рантайм)
|
||||
- `public/data/races.json` — опциональный вход seed
|
||||
- `frontend/src/api/` — клиент API
|
||||
- `frontend/src/pages/`, `frontend/src/components/` — UI (BEM)
|
||||
- `frontend/src/lib/distances.ts` — PR
|
||||
- `docs/*.md`, `README.md`
|
||||
|
||||
## 10. Чеклист задач (implementation todos)
|
||||
|
||||
1. Монорепо: `frontend/` + `backend/`, BEM, токены, роутер.
|
||||
2. Postgres в docker-compose, миграции таблицы `races`, бэкенд читает `DB`_*.
|
||||
3. REST CRUD + разовый seed (CSV и/или JSON) → БД.
|
||||
4. Клиент API на фронте, типы, загрузка данных для экранов и PR.
|
||||
5. Экраны месяц и год, модалка по дате.
|
||||
6. Форма добавления с условными полями; карточка с PATCH результата/номера.
|
||||
7. Поля организатора на карточке.
|
||||
8. A11y модалки, мобильная вёрстка, смоук-сценарии.
|
||||
9. `docs/` + README + `.env.example`.
|
||||
|
||||
## 11. Идеи на будущее (тематика бега)
|
||||
|
||||
- Недельный объём / простой план подготовки.
|
||||
- Импорт/экспорт JSON для бэкапа через API.
|
||||
- Фильтры (город, дистанция), погода по дню старта.
|
||||
- График динамики PR.
|
||||
- Загрузка GPX / тренировки.
|
||||
- Браузерные напоминания за N дней до старта.
|
||||
- Сравнение результатов одной дистанции по годам.
|
||||
- PWA для офлайн-просмотра (read-only кэш не заменяет БД без продуманной синхронизации).
|
||||
|
||||
---
|
||||
|
||||
*Документ создан как единый актуальный план в корне репозитория. При расхождениях с черновиками в IDE приоритет у этого файла.*
|
||||
- Документация согласована с кодом: [`README.md`](README.md), [`docs/backend.md`](docs/backend.md), [`docs/backend-api-for-frontend.md`](docs/backend-api-for-frontend.md).
|
||||
- Миграции и seed воспроизводимы; контракт API покрыт smoke-тестами в CI при необходимости с mock-БД.
|
||||
|
||||
Reference in New Issue
Block a user