From 88a448dd8eacea7d9825370c80375d18ef11c0d0 Mon Sep 17 00:00:00 2001 From: Anton Date: Wed, 1 Apr 2026 13:42:37 +0300 Subject: [PATCH] Initial commit: project plan and seed data Made-with: Cursor --- PLAN.md | 196 ++++++++++++++++++++++++++++++ docs/agent-backend-instruction.md | 161 ++++++++++++++++++++++++ import/races_2026_calendar.csv | 20 +++ 3 files changed, 377 insertions(+) create mode 100644 PLAN.md create mode 100644 docs/agent-backend-instruction.md create mode 100644 import/races_2026_calendar.csv diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 0000000..c5ae5b6 --- /dev/null +++ b/PLAN.md @@ -0,0 +1,196 @@ +# План: календарь забегов (SPA + API + PostgreSQL) + +Консолидированный план реализации с актуальными решениями. После инициализации git — работать в отдельной ветке (например `feature/race-calendar-app`). + +## 1. Цели продукта + +1. **Расписание по месяцу** — выбор месяца/года, список забегов. +2. **Расписание на год** — календарная сетка года, отметки на датах со стартами; по клику на дату — модалка/панель со списком забегов и переход на карточку. +3. Добавлять забеги **запланированные** и **уже прошедшие**. +4. Для **прошедших** — ввод/редактирование **результата** и **стартового номера**; поля можно дописать позже. +5. **Личные рекорды** на главной по дистанциям: 1 км, 5 км, 10 км, 15 км, 21.1 км, 42.2 км — обновляются, если в забеге указан более быстрый результат на «подходящей» дистанции. +6. **Старты (организатор):** дата и время старта, расписание кластеров, выдача номеров — **ручной ввод**; обязательна возможность указать **официальную ссылку** на страницу организатора (автопарсинг сайтов не входит в объём). +7. **Авторизация не требуется.** + +## 2. Исходные данные в репозитории + +- Файл `import/races_2026_calendar.csv` — колонки `date`, `month`, `day`, `event`, `distance_km`. +- Назначение CSV: **один раз** — как вход для **seed-скрипта**, который пишет строки в PostgreSQL. +- **В рантайме** ни фронт, ни API этот CSV **не читают**. После успешного seed файл может оставаться в репо только как архив/референс. + +## 3. Стек и структура репозитория + +- **Монорепозиторий:** `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`, русская локаль там, где нужно. + +### 3.1 Переменные окружения + +**Только на сервере API (никогда не во фронт-бандле):** + +```env +DB_HOST= +DB_PORT= +DB_NAME= +DB_USER= +DB_PASSWORD= +``` + +Дополнительно по необходимости: `PORT` или `API_PORT`, `NODE_ENV`, `CORS_ORIGIN`. + +**Фронтенд (Vite):** только публичный адрес API, например: + +```env +VITE_API_BASE_URL=http://localhost:3001 +``` + +В коде: `import.meta.env.VITE_API_BASE_URL`. + +Шаблон без секретов — корневой `.env.example`; описание — в `docs/frontend.md` и `docs/backend.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 приоритет у этого файла.* diff --git a/docs/agent-backend-instruction.md b/docs/agent-backend-instruction.md new file mode 100644 index 0000000..a7c63c9 --- /dev/null +++ b/docs/agent-backend-instruction.md @@ -0,0 +1,161 @@ +# Инструкция агенту: реализация бэкенда по [PLAN.md](../PLAN.md) + +Документ для ИИ-агента или разработчика, который создаёт **backend** монорепозитория «календарь забегов». Продуктовые цели и модель данных — в корневом **PLAN.md**; здесь — порядок работ, ограничения и **обязательные итоговые артефакты для фронтенда**. + +--- + +## 0. Ограничение: нет возможности проверить подключение к БД + +1. **Всё равно реализовать полноценный бэкенд «как для прод»**: миграции SQL, пул подключений `pg`, переменные `DB_*`, `docker-compose.yml` с PostgreSQL в корне репозитория. +2. **Не блокировать работу отсутствием живой БД у исполнителя:** + - код миграций и seed должен быть **валидным и согласованным** с PLAN; + - при старте API при невозможности подключиться к БД — **ясное сообщение в лог** и **корректный HTTP-ответ** на зависящих от БД маршрутах (например 503 с телом `{"error":"database_unavailable",...}`) **или** падение процесса на старте с понятной ошибкой (выбрать одну стратегию и описать её в `docs/backend.md`). +3. **Не выдумывать обход БД** (in-memory «режим без Postgres»), если это не согласовано отдельно — фронт и план рассчитаны на PostgreSQL. +4. Автотесты, требующие Docker/Postgres, помечать как **опциональные** или давать инструкцию «как прогнать локально», не считая провал из-за отсутствия БД у агента блокером для merge кода. + +--- + +## 1. Ветка и расположение в репо + +- Если репозиторий под git: создать ветку `feature/backend-api` (или аналог по соглашению команды). +- Каталог **`backend/`** в корне проекта рядом с будущим `frontend/`. +- Корневой **`docker-compose.yml`** — сервис `postgres` (порт, пользователь, БД, пароль — согласовать с `.env.example`). + +--- + +## 2. Порядок реализации (обязательный) + +### Шаг A. Каркас + +- Node **LTS**, **TypeScript**, **`backend/package.json`**. +- Фреймворк: **Fastify** или **Express** (выбрать один, не смешивать). +- Загрузка env: `dotenv` или встроенные средства; **валидация** наличия `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD` при старте (или при первом запросе к БД — но тогда задокументировать). +- Сервер слушает порт из **`PORT`** или **`API_PORT`** (значение по умолчанию, напр. `3001`, указать в `.env.example`). + +### Шаг B. CORS + +- Читать **`CORS_ORIGIN`** (например `http://localhost:5173` для Vite в dev). В prod — origin фронта. +- Разрешить методы и заголовки, нужные для `GET/POST/PATCH` + `Content-Type: application/json`. + +### Шаг C. Миграции + +- Каталог миграций, напр. `backend/migrations/` с нумерованными SQL-файлами **или** один миграционный runner (node-pg-migrate, graphile-migrate, собственный скрипт — на выбор, зафиксировать в `docs/backend.md`). +- Первая миграция: таблица **`races`** со столбцами, соответствующими PLAN (см. раздел 3 ниже). +- Команда **`npm run db:migrate`** (или эквивалент в `backend/`) — идемпотентное накатывание на чистую БД. + +### Шаг D. Доступ к данным + +- Клиент **`pg`**: `Pool` с параметрами из `DB_*`. +- Слой репозитория или прямые запросы в обработчиках — на усмотрение, без лишних абстракций сверх задачи. + +### Шаг E. HTTP API + +Реализовать минимум: + +| Метод | Путь | Назначение | +|--------|------|------------| +| `GET` | `/health` | Liveness: процесс жив; **не обязан** проверять БД (или опционально — задокументировать). | +| `GET` | `/ready` (опционально) | Readiness: проверка соединения с БД — полезно для оркестраторов. | +| `GET` | `/races` | Список забегов; **query**: `year`, `month` (1–12) — фильтрация для экранов календаря; без параметров — все строки или разумный лимит + документация пагинации если добавите позже. | +| `GET` | `/races/:id` | Одна запись по `id`. | +| `POST` | `/races` | Создание; тело JSON в **camelCase** как в PLAN. | +| `PATCH` | `/races/:id` | Частичное обновление; только переданные поля. | +| `DELETE` | `/races/:id` | Опционально по PLAN; если не нужен фронту — можно не делать, но тогда явно написать в документации «удаление не поддерживается». | + +**Ошибки:** + +- 400 — валидация тела/параметров. +- 404 — нет `id`. +- 409 — конфликт уникального `id` при POST (если клиент прислал уже существующий). +- 503 или 500 — недоступна БД (согласовать с разделом 0). + +Формат ошибки: JSON, единообразно, напр. `{"error":"validation_error","details":[...]}`. + +### Шаг F. Seed (разовый скрипт) + +- Команда **`npm run seed`** в `backend/` (или корне монорепо с `workspace` — единообразно). +- Читает **`import/races_2026_calendar.csv`** (путь от корня репо); парсинг с учётом кавычек в поле названия. +- Генерирует **`id`** стабильно: например `{date}-{slug-from-title}`; при коллизии — суффикс или upsert по `id`. +- **`INSERT ... ON CONFLICT (id) DO UPDATE`** (upsert) — удобно для повторного запуска seed. +- Seed **не** вызывается из HTTP handlers. + +### Шаг G. Корневой `.env.example` + +- Все переменные: `DB_*`, `PORT`/`API_PORT`, `CORS_ORIGIN`. +- **Без** реальных паролей; комментарии к каждой переменной. + +--- + +## 3. Соответствие полей PLAN ↔ SQL ↔ JSON API + +**В JSON API (запрос/ответ)** — **camelCase**, как в PLAN: + +`id`, `date`, `title`, `distanceKm`, `status`, `officialUrl`, `startTime`, `clusterSchedule`, `bibPickup`, `bibNumber`, `finishTime`, `notes`. + +**В PostgreSQL** — **snake_case**, например: + +| SQL column | Тип (рекомендация) | +|------------|---------------------| +| `id` | `TEXT` PRIMARY KEY | +| `race_date` | `DATE` | +| `title` | `TEXT` | +| `distance_km` | `NUMERIC(6,3)` | +| `status` | `TEXT` CHECK (опционально) | +| `official_url` | `TEXT` | +| `start_time` | `TEXT` | +| `cluster_schedule` | `TEXT` | +| `bib_pickup` | `TEXT` | +| `bib_number` | `TEXT` | +| `finish_time` | `TEXT` | +| `notes` | `TEXT` | +| `created_at` | `TIMESTAMPTZ` DEFAULT now() | +| `updated_at` | `TIMESTAMPTZ` | + +Маппинг **строго** в одном модуле (например `backend/src/mappers/race.ts`), чтобы фронт всегда видел camelCase. + +**Типы `date`:** в API строка `YYYY-MM-DD`. **`distanceKm`:** число. **`finishTime`:** строка времени как в PLAN; бэкенд **не обязан** парсить для бизнес-логики (PR считает фронт), но может валидировать формат по желанию. + +--- + +## 4. Обязательный итог для упрощения фронтенда + +После того как код бэкенда готов, агент **обязан** добавить в репозиторий документ: + +### Файл: `docs/backend-api-for-frontend.md` + +В нём **кратко и без пробелов в фактах**: + +1. **Base URL** — что подставлять во фронт (`VITE_API_BASE_URL`), пример для dev. +2. **CORS** — какой `CORS_ORIGIN` ожидается в dev. +3. **Таблица эндпоинтов** — метод, путь, query, тело запроса (пример JSON), пример успешного ответа, коды ошибок. +4. **Модель `Race`** — перечень полей в **camelCase** с типами и обязательностью для POST vs PATCH. +5. **Фильтр списка** — как именно работают `year` и `month` на `GET /races` (включая границы: пустой месяц, только год и т.д.). +6. **Идемпотентность seed** — одна фраза: upsert по `id`, откуда берётся CSV. +7. **Поведение при недоступной БД** — что возвращает API / что в логах (как в реализации). + +Дополнительно можно дублировать суть в **`docs/backend.md`** (общая эксплуатация: docker, migrate, seed, запуск), но **`backend-api-for-frontend.md`** — главная «шпаргалка» для разработчика SPA. + +--- + +## 5. Критерии готовности (чеклист агента) + +- [ ] `docker-compose.yml` поднимает Postgres с параметрами, совместимыми с `.env.example`. +- [ ] Миграция создаёт `races`; есть команда миграции. +- [ ] Реализованы `GET /races`, `GET /races/:id`, `POST /races`, `PATCH /races/:id` согласно PLAN. +- [ ] Реализован seed из `import/races_2026_calendar.csv`. +- [ ] `GET /health` (и при наличии `/ready` — описано). +- [ ] Корневой `.env.example` обновлён. +- [ ] Написан **`docs/backend-api-for-frontend.md`** (раздел 4). +- [ ] `docs/backend.md` содержит команды: установка зависимостей, migrate, seed, `npm run dev` для API. + +--- + +## 6. Не входит в объём бэкенда (напоминание) + +- Авторизация, пользователи, сессии. +- Парсинг сайтов организаторов. +- Отдача статики фронта с того же процесса (фронт — отдельно Vite/build). + +--- + +*Конец инструкции. Источник требований к продукту — всегда [PLAN.md](../PLAN.md).* diff --git a/import/races_2026_calendar.csv b/import/races_2026_calendar.csv new file mode 100644 index 0000000..5c26a44 --- /dev/null +++ b/import/races_2026_calendar.csv @@ -0,0 +1,20 @@ +date,month,day,event,distance_km +2026-04-05,April,5,Забег «Апрель»,5.0 +2026-04-11,April,11,Кросс «Быстрый пёс»,2.0 +2026-04-12,April,12,Кросс «Лисья гора»,6.0 +2026-04-19,April,19,Love and Space Half Marathon 2026,5.0 +2026-05-03,May,3,Казанский марафон,42.195 +2026-05-17,May,17,Мышкинский полумарафон «По шести холмам»,21.1 +2026-05-23,May,23,Забег.РФ Москва,21.0975 +2026-05-31,May,31,Переславский марафон «Александровские вёрсты»,21.1 +2026-06-07,June,07,"Красочный забег",5.0 +2026-06-12,June,12,"Полумарафон «Здорово, Кострома!»",21.1 +2026-06-20,June,20,Ночной забег | Москва,10.0 +2026-07-04,July,4,СберПрайм марафон «Белые ночи»,42.2 +2026-07-11,July,11,Полумарафон «Сергиевым путём»,21.1 +2026-07-18,July,18,Ночной забег | Нижний Новгород,10.0 +2026-07-24,July,24,Suvorov Extreme,30.0 +2026-08-08,August,8,Рыбинский полумарафон «Великий хлебный путь»,21.1 +2026-08-16,August,16,Run & Eat,10.0 +2026-09-13,September,13,Ярославский полумарафон «Золотое кольцо»,21.1 +2026-10-04,October,4,Полумарафон «Моя столица»,21.1