From 007d899721d9e4c66001e5f7706351ded1a6b7d6 Mon Sep 17 00:00:00 2001 From: "Vaka.pro" Date: Tue, 7 Apr 2026 00:30:29 +0300 Subject: [PATCH] chore: drop agent/plan docs, unify .env for Docker stack - Remove PLAN/agent instruction files; single root .env.example for DB + API - Stack compose uses env_file .env; delete stack env example duplicate - Refresh README, backend docs, API doc; trim gitignore/dockerignore Made-with: Cursor --- .dockerignore | 1 - .env.example | 32 +- .gitignore | 3 - PLAN.md | 49 +-- README.md | 69 ++--- agent-frontend-ui-instructions.md | 336 --------------------- docker-compose.stack.env.example | 11 - docker-compose.stack.yml | 20 +- docs/agent-backend-instruction.md | 162 ---------- docs/agent-fix-backend-sync-instruction.md | 77 ----- docs/backend-api-for-frontend.md | 2 + docs/backend.md | 8 + frontend/.env.example | 2 +- 13 files changed, 100 insertions(+), 672 deletions(-) delete mode 100644 agent-frontend-ui-instructions.md delete mode 100644 docker-compose.stack.env.example delete mode 100644 docs/agent-backend-instruction.md delete mode 100644 docs/agent-fix-backend-sync-instruction.md diff --git a/.dockerignore b/.dockerignore index 9768b3b..e399006 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,7 +5,6 @@ .env .env.* !.env.example -docker-compose.stack.env **/*.md .cursor *.log diff --git a/.env.example b/.env.example index e789991..cc42396 100644 --- a/.env.example +++ b/.env.example @@ -1,23 +1,33 @@ -# ─── PostgreSQL ─────────────────────────────────────────────── -# Не обязательны, если CALENDAR_RUN_MOCK_DB=1 (только HTTP API без БД). +# ============================================================================= +# Корневой шаблон окружения: локальная разработка, docker-compose.yml (Postgres) +# и docker-compose.stack.yml (backend + frontend в общей сети с Postgres). +# Скопируйте в .env и подставьте значения. Файл .env не коммитьте. +# ============================================================================= + +# ─── Подключение бэкенда к PostgreSQL ────────────────────────── +# Локально + docker-compose из этого репо: DB_HOST=localhost, DB_PORT=5432 +# Backend в Docker рядом с Postgres: DB_HOST = имя контейнера/сервиса в той же +# docker-сети (например postgres_budget), DB_PORT=5432 (внутренний порт Postgres). DB_HOST=localhost DB_PORT=5432 DB_NAME=calendar_run DB_USER=calendar_user -DB_PASSWORD=calendar_pass +DB_PASSWORD=replace_with_strong_secret -# ─── Backend API ────────────────────────────────────────────── -# Порт: сначала читается PORT (если задан), иначе API_PORT, иначе 3001. -# PORT=3001 +# ─── Backend API ─────────────────────────────────────────────── +# Порт процесса: сначала PORT, иначе API_PORT, иначе 3001. +# В docker-compose.stack.yml для контейнера задаётся PORT=3000 (см. compose). +# PORT=3000 API_PORT=3001 -# ─── Dev/CI: без PostgreSQL для smoke API (не для migrate/seed) ─ +# ─── Режим без БД (только тесты / smoke API, не для migrate/seed) ─ # CALENDAR_RUN_MOCK_DB=1 -# ─── CORS ───────────────────────────────────────────────────── -# Allowed origin for the frontend (Vite dev server default) +# ─── CORS ──────────────────────────────────────────────────── +# Локальный Vite: http://localhost:5173 +# Стек с фронтом на 3033: http://localhost:3033 CORS_ORIGIN=http://localhost:5173 -# ─── Frontend (Vite) ───────────────────────────────────────── -# Public URL of the API, used in SPA code via import.meta.env +# ─── Frontend (Vite, локально из каталога frontend/) ───────── +# В Docker-образе фронта базовый URL API задаётся при сборке (/api), не из .env. VITE_API_BASE_URL=http://localhost:3001 diff --git a/.gitignore b/.gitignore index a82fb9e..aa0926a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,4 @@ node_modules/ dist/ .env -docker-compose.stack.env *.log -*plan* -*PLAN* \ No newline at end of file diff --git a/PLAN.md b/PLAN.md index 034e7ea..d769140 100644 --- a/PLAN.md +++ b/PLAN.md @@ -10,25 +10,27 @@ ## Модель данных `Race` (API — camelCase) -| Поле | Тип | Описание | -|------|-----|----------| -| `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 | -PostgreSQL: `snake_case` столбцы, маппинг в [`backend/src/mappers/race.ts`](backend/src/mappers/race.ts). +| Поле | Тип | Описание | +| ----------------- | --------------------------------------------- | ----------------------------------------------- | +| `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 | + + +PostgreSQL: `snake_case` столбцы, маппинг в `[backend/src/mappers/race.ts](backend/src/mappers/race.ts)`. ## HTTP API (минимум) @@ -37,25 +39,26 @@ PostgreSQL: `snake_case` столбцы, маппинг в [`backend/src/mappers - `GET /races` — список; query: `year`, `month` (целые; `month` 1–12). - `GET /races/:id`, `POST /races`, `PATCH /races/:id`, `DELETE /races/:id`. -Ошибки: JSON, единый стиль (`validation_error`, `not_found`, `conflict`, `database_unavailable`). Подробности — [`docs/backend-api-for-frontend.md`](docs/backend-api-for-frontend.md). +Ошибки: JSON, единый стиль (`validation_error`, `not_found`, `conflict`, `database_unavailable`). Подробности — `[docs/backend-api-for-frontend.md](docs/backend-api-for-frontend.md)`. ## Seed -- Файл [`import/races_2026_calendar.csv`](import/races_2026_calendar.csv). +- Файл `[import/races_2026_calendar.csv](import/races_2026_calendar.csv)`. - Стабильный `id`, upsert по `id`. Повторный запуск безопасен. ## Режим без PostgreSQL (dev/CI) -Переменная `CALENDAR_RUN_MOCK_DB=1` (или `true`): HTTP-обработчики используют заглушку пула **без** реальной БД. **Не использовать** для `npm run db:migrate` и `npm run seed` — нужен настоящий Postgres и `DB_*`. +Переменная `CALENDAR_RUN_MOCK_DB=1` (или `true`): HTTP-обработчики используют заглушку пула **без** реальной БД. **Не использовать** для `npm run db:migrate` и `npm run seed` — нужен настоящий Postgres и `DB_`*. ## Frontend (SPA) - Маршруты: дашборд (`/`), список стартов (`/races`), карточка (`/races/:id`). - Дашборд: ближайший старт, последний результат, PR, сезон, PR по ключевым дистанциям, сравнение завершённых стартов, при необходимости — лёгкая визуализация прогресса. - Список: будущие / прошедшие; фильтрация по году и месяцу через API. -- Стили: BEM и дизайн-токены; ориентир по духу — [`agent-frontend-ui-instructions.md`](agent-frontend-ui-instructions.md). +- Стили: BEM и дизайн-токены; ориентир по духу — `[agent-frontend-ui-instructions.md](agent-frontend-ui-instructions.md)`. ## Критерии готовности текущей итерации -- Документация согласована с кодом: [`README.md`](README.md), [`docs/backend.md`](docs/backend.md), [`docs/backend-api-for-frontend.md`](docs/backend-api-for-frontend.md). +- Документация согласована с кодом: `[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-БД. + diff --git a/README.md b/README.md index 95ee042..086af1c 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,47 @@ # Calendar Run -Calendar Run is a races calendar project: a **backend API** (Express + PostgreSQL) and a **React SPA** for viewing and analyzing your race schedule. +Монорепозиторий: **backend** (Express + PostgreSQL) и **frontend** (React + Vite) — календарь забегов. -Product scope and data model: [PLAN.md](PLAN.md). +## Переменные окружения -## Backend — quick start +Один шаблон для локальной разработки и для Docker-стека: **[`.env.example`](.env.example)** → скопируйте в **`.env`** в корне репозитория. -1. Install dependencies: - - `cd backend` - - `npm install` -2. Configure environment variables. Copy the root template and edit: +Там же перечислены **`DB_HOST`**, **`DB_PORT`**, **`DB_NAME`**, **`DB_USER`**, **`DB_PASSWORD`** (подключение бэкенда к БД), **`PORT`** / **`API_PORT`**, опционально **`CALENDAR_RUN_MOCK_DB`**, **`CORS_ORIGIN`**, а для локального Vite — **`VITE_API_BASE_URL`**. - ```bash - cp .env.example .env - ``` +## Backend — локально - Use the **repository root** `.env` (the backend loads it via `backend/src/config.ts`). +1. `cd backend && npm install` +2. Корень: `cp .env.example .env`, задайте `DB_*` (и при необходимости `CORS_ORIGIN`). +3. Postgres: из корня `docker compose up -d` (см. [`docker-compose.yml`](docker-compose.yml)) — в compose используются те же `DB_NAME`, `DB_USER`, `DB_PASSWORD` из `.env`. +4. `cd backend && npm run db:migrate && npm run seed` +5. `npm run build && npm run dev` - - `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD` — required unless mock mode is on. - - API port: `PORT` (takes precedence) or `API_PORT` (default `3001`). - - **No PostgreSQL (CI / local smoke):** set `CALENDAR_RUN_MOCK_DB=1` (or `true`). Real `DB_*` values are not required; the API uses in-memory stubs for SQL used by the HTTP routes. **Do not** use mock mode for `npm run db:migrate` or `npm run seed`. -3. With a real database: from repo root, `docker-compose up -d`, then `cd backend && npm run db:migrate && npm run seed`. -4. Build and run API: +Без PostgreSQL (только smoke API): в `.env` задайте `CALENDAR_RUN_MOCK_DB=1`; **`db:migrate` и `seed` с mock не использовать**. - ```bash - npm run build - npm run dev - ``` +## Frontend — локально -## Frontend — quick start +```bash +cd frontend +cp .env.example .env +npm install +npm run dev +``` -1. Configure the API URL for Vite (file is read from `frontend/`): +Значение `VITE_API_BASE_URL` см. в **корневом** [`.env.example`](.env.example); для dev по умолчанию `http://localhost:3001`. У бэкенда `CORS_ORIGIN` должен совпадать с origin приложения (например `http://localhost:5173`). - ```bash - cd frontend - cp .env.example .env - ``` +## Docker: backend + frontend рядом с Postgres - Edit `VITE_API_BASE_URL` if the API is not on `http://localhost:3001`. +Используйте [`docker-compose.stack.yml`](docker-compose.stack.yml): общая **внешняя** сеть с контейнером Postgres (как в вашей инфраструктуре). В корне должен быть **`.env`** (из `.env.example`): `DB_HOST` — имя сервиса/контейнера Postgres в этой сети, `DB_PORT=5432`, плюс остальные `DB_*` и **`CORS_ORIGIN=http://localhost:3033`**, если заходите на фронт с хоста на порту 3033. -2. Install and run: +```bash +docker compose -f docker-compose.stack.yml up -d --build +docker compose -f docker-compose.stack.yml exec backend node dist/migrate.js +docker compose -f docker-compose.stack.yml exec backend node dist/seed.js +``` - ```bash - npm install - npm run dev - ``` +Фронт в браузере обращается к API по префиксу **`/api`** (nginx в образе фронта проксирует на backend). - Default app URL: `http://localhost:5173`. The backend `CORS_ORIGIN` must match this origin (see root `.env.example`). +## Документация API и бэкенда -## Docs - -- [Backend API for Frontend](docs/backend-api-for-frontend.md) -- [Backend operations](docs/backend.md) -- [Backend agent instruction](docs/agent-backend-instruction.md) -- [Backend sync fix instruction](docs/agent-fix-backend-sync-instruction.md) +- [Шпаргалка API для фронта](docs/backend-api-for-frontend.md) +- [Эксплуатация backend](docs/backend.md) diff --git a/agent-frontend-ui-instructions.md b/agent-frontend-ui-instructions.md deleted file mode 100644 index a1de325..0000000 --- a/agent-frontend-ui-instructions.md +++ /dev/null @@ -1,336 +0,0 @@ -Ниже — инструкция для дизайн-агента, которая задаёт стиль, структуру и принципы интерфейса для твоего SPA. Она ориентирована на **минимализм, читаемость данных и удовольствие от использования**, а не на “перегруженный фитнес-дэшборд”. - -# 🎯 Общая задача - -Создать **чистый, современный, минималистичный интерфейс** для SPA-приложения бегуна, где пользователь: - -- отслеживает свои старты (прошлые и будущие), -- видит прогресс и личные рекорды, -- сравнивает результаты, -- получает ощущение “контроля над своей беговой историей”. - ---- - -# 🧭 Общая философия дизайна - -### 1. “Спокойная сила”, а не “спортивная агрессия” - -Не: - -- кислотные цвета, -- перегруженные графики, -- “фитнес-клуб стиль 2015”. - -Да: - -- чистый интерфейс, -- воздух, -- акцент на данных, -- ощущение премиальности. - ---- - -### 2. Минимум визуального шума - -Каждый экран должен отвечать на вопрос: - -> “Что здесь главное?” - -Если не ясно — убрать лишнее. - ---- - -### 3. Данные — главный герой - -Интерфейс не про “украшения”, а про: - -- результаты, -- прогресс, -- сравнение, -- числа. - ---- - -# 🎨 Визуальный стиль - -## Цвета - -### Основа: - -- фон: светлый (#F7F8FA или близко) -- карточки: белые -- текст: - - основной: почти чёрный (#111) - - вторичный: серый (#666) - -### Акцент: - -- 1 основной цвет: - - глубокий синий или изумрудный (например #2F6BFF или #1FA37A) - -### Дополнительно: - -- PR / успех: мягкий зелёный -- предупреждения: нейтральный жёлтый -- ошибки: мягкий красный - -❗ Никаких “радуг”. - ---- - -## Типографика - -- современный sans-serif (Inter / SF Pro / аналог) -- крупные числа: - - результаты (время) — большие, жирные -- иерархия: - - H1 — экран - - H2 — блок - - body — данные - - caption — мета - ---- - -## Отступы и сетка - -- много воздуха -- карточки с padding 16–24px -- равномерная вертикальная ритмика -- закругления: 12–16px - ---- - -# 🧱 Основные экраны - -## 1. Главный экран (Dashboard) - -### Цель: - -дать быстрый ответ: - -- что впереди -- где я сейчас -- какой прогресс - -### Блоки: - -#### 🔹 Ближайший старт - -- название -- дата -- дистанция -- countdown (“через 12 дней”) - -#### 🔹 Последний результат - -- время -- дистанция -- место -- дата - -#### 🔹 Личный рекорд - -- по ключевой дистанции -- выделен визуально - -#### 🔹 Сезон - -- количество стартов -- лучший результат -- краткая статистика - ---- - -## 2. Календарь стартов - -### Два раздела: - -- будущие -- прошедшие - -### Карточка старта: - -- название -- дата -- дистанция -- статус: - - планирую - - зарегистрирован - - пробежал - -Минимум кликов. - ---- - -## 3. Карточка старта - -- название -- дата -- дистанция -- время -- темп -- место (если есть) -- заметки (опционально) - ---- - -## 4. Личные рекорды (PR) - -- список дистанций: - - 5K - - 10K - - 21.1 - - 42.2 - -Для каждой: - -- лучшее время -- дата -- старт - ---- - -## 5. Сравнение стартов (ВАЖНО) - -Это ключевая фича. - -### UI: - -таблица или карточки: - - -| Год | Время | Темп | Место | -| --- | ----- | ---- | ----- | - - -Можно добавить: - -- стрелки (лучше/хуже) -- визуальный прогресс - ---- - -# 📊 Графики - -Минимально и аккуратно: - -- линия прогресса по дистанции -- без перегруза -- без 10 линий сразу - ---- - -# 🧩 Компоненты - -- карточки -- таблицы (очень аккуратно) -- фильтры -- переключатели (tabs) -- кнопки (primary / secondary) - ---- - -# 🧠 UX принципы - -### 1. Минимум кликов - -Любая частая задача: -→ максимум 2–3 клика - ---- - -### 2. Быстрое считывание - -Пользователь должен за 2 секунды понять: - -- что это -- где он -- что делать - ---- - -### 3. Консистентность - -- одинаковые карточки -- одинаковые статусы -- одинаковые действия - ---- - -# 🧪 Для аналитики (важно) - -Заложить: - -- зоны кликов -- понятные CTA -- отсутствие “пустых зон” - ---- - -# 🖼️ Использование изображений - -## Когда использовать: - -- пустые состояния -- onboarding -- вдохновение - -## Когда НЕ использовать: - -- в данных -- в списках стартов -- в результатах - ---- - -## Если генерировать изображения - -Стиль: - -- минимализм -- спорт без пафоса -- одиночный бегун -- город / парк -- мягкий свет -- утро / вечер - -### Пример prompt: - -> “minimalist photo of a runner jogging alone in a city park at sunrise, soft light, calm mood, no crowd, modern aesthetic” - ---- - -# 🚫 Чего избегать - -- перегруженных дашбордов -- 100 метрик сразу -- ярких кислотных цветов -- сложных графиков -- таблиц как в Excel -- лишних иконок -- “геймификации ради геймификации” - ---- - -# 💡 Вдохновение (по духу) - -Если бы нужно было описать стиль: - -- как Notion, но для бегунов -- как Apple Fitness, но менее ярко -- как Strava, но более спокойно и чисто - ---- - -# 🧭 Финальный ориентир - -Интерфейс должен вызывать ощущение: - -> “Я контролирую свою беговую историю и прогресс” - -а не: - -> “Я заполняю таблицу результатов” - ---- - diff --git a/docker-compose.stack.env.example b/docker-compose.stack.env.example deleted file mode 100644 index ee11060..0000000 --- a/docker-compose.stack.env.example +++ /dev/null @@ -1,11 +0,0 @@ -# Скопируйте в docker-compose.stack.env и подставьте свои значения. -# Файл docker-compose.stack.env не должен попадать в git (см. .gitignore). - -DB_HOST=postgres -DB_PORT=5432 -DB_NAME=calendar_run -DB_USER=calendar_user -DB_PASSWORD=replace_with_strong_secret - -# Origin браузера для CORS (если заходите на фронт не с localhost — поменяйте) -CORS_ORIGIN=http://localhost:3033 diff --git a/docker-compose.stack.yml b/docker-compose.stack.yml index 7cefe19..cabc703 100644 --- a/docker-compose.stack.yml +++ b/docker-compose.stack.yml @@ -1,15 +1,19 @@ -# Запуск приложения в сети с уже поднятым PostgreSQL (как у family-budget). +# Backend + frontend в сети с уже поднятым PostgreSQL (external network). # -# 1) Скопируйте пример переменных (не коммитьте файл с паролями): -# cp docker-compose.stack.env.example docker-compose.stack.env -# отредактируйте docker-compose.stack.env +# Подготовка: из корня репозитория скопируйте .env.example → .env и задайте DB_* +# и CORS_ORIGIN (для фронта на :3033 — http://localhost:3033). # -# NPM / reverse proxy: проброс на порт фронта 3033 (внутри контейнера nginx слушает 80). -# Фронт в браузере ходит на /api → nginx проксирует на backend:3000. +# Сеть (имя должно существовать, как у вашего Postgres): +# docker network ls # -# Первая инициализация БД (один раз, когда Postgres уже доступен по DB_HOST): +# Запуск: +# docker compose -f docker-compose.stack.yml up -d --build +# +# Миграции и seed (один раз после появления БД): # docker compose -f docker-compose.stack.yml exec backend node dist/migrate.js # docker compose -f docker-compose.stack.yml exec backend node dist/seed.js +# +# NPM: проброс на порт 3033. Браузер ходит на /api → nginx во фронте → backend:3000. services: backend: @@ -18,7 +22,7 @@ services: dockerfile: Dockerfile.backend container_name: runners-calendar-backend env_file: - - docker-compose.stack.env + - .env environment: - PORT=3000 ports: diff --git a/docs/agent-backend-instruction.md b/docs/agent-backend-instruction.md deleted file mode 100644 index 12cbacf..0000000 --- a/docs/agent-backend-instruction.md +++ /dev/null @@ -1,162 +0,0 @@ -# Инструкция агенту: реализация бэкенда по [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. **Режим без 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 кода. - ---- - -## 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` (`planned` \| `registered` \| `completed`), `officialUrl`, `startTime`, `clusterSchedule`, `bibPickup`, `bibNumber`, `finishTime`, `finishPlace`, `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` | -| `finish_place` | `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/docs/agent-fix-backend-sync-instruction.md b/docs/agent-fix-backend-sync-instruction.md deleted file mode 100644 index ab03f44..0000000 --- a/docs/agent-fix-backend-sync-instruction.md +++ /dev/null @@ -1,77 +0,0 @@ -# Инструкция агенту: устранение рассинхронизации backend с планом/контрактом - -Документ описывает, как выполнить план исправлений **только через изменения кода** (без правок существующей документации в `docs/`). - -## 1) Ветка и границы задачи - -- Создать отдельную ветку по best practice, например: `fix/backend-api-validation-and-runtime-sync`. -- Не менять существующие файлы документации в `docs/` как способ "починить" замечания. -- Исправления вносятся в runtime-код и обязательные артефакты репозитория. - -## 2) Обязательные изменения в коде - -### A. Строгая валидация `GET /races` - -Файл: `backend/src/routes/races.ts` - -- Добавить явную проверку `year`: - - целое число; - - при невалидном значении вернуть `400`: - - `{"error":"validation_error","details":[...]}` -- Добавить явную проверку `month`: - - целое число в диапазоне `1..12`; - - при невалидном значении вернуть `400` в том же формате. -- Исключить передачу `NaN`/некорректных значений в SQL-параметры. - -### B. Разделение ошибок валидации и ошибок БД - -Файл: `backend/src/routes/races.ts` - -- `400` использовать только для ошибок входа (query/body/params). -- `503 {"error":"database_unavailable"}` использовать только для технической недоступности БД. -- Сохранить единый JSON-формат ошибок во всех CRUD-маршрутах. - -### C. Выравнивание конфигурации порта - -Файл: `backend/src/config.ts` - -- Поддержать оба env-подхода: - - приоритет `PORT`, - - затем `API_PORT`, - - затем fallback `3001`. - -### D. Обязательный root-артефакт - -Файл: `README.md` (в корне) - -- Создать базовый `README.md` с: - - кратким описанием проекта, - - минимальным quick start, - - ссылками на текущие документы backend/API. - -## 3) Допустимая реорганизация кода - -- Можно добавить небольшие локальные helper-функции в `backend/src/routes/races.ts`. -- При необходимости можно вынести общие mapper/validator-хелперы в `backend/src/mappers/race.ts`, если это уменьшает дублирование. -- Не усложнять архитектуру: только то, что нужно для контракта и устойчивого поведения API. - -## 4) Проверка результата - -Минимум выполнить: - -1. `npm run build` в `backend/` (типизация/сборка). -2. Проверка диагностики/линтов по измененным backend-файлам. -3. Smoke-сценарии API: - - `GET /health` -> `200`, - - `GET /ready` -> `200` при доступной БД или `503` при недоступной, - - `GET /races?year=bad` -> `400`, - - `GET /races?month=13` -> `400`, - - `GET /races?year=2026&month=5` -> корректный `200` и данные/пустой массив. - -## 5) Критерии готовности (Definition of Done) - -- Контракт валидации `GET /races` соблюден в runtime. -- Валидационные ошибки не маскируются под `database_unavailable`. -- Конфиг порта поддерживает `PORT` и `API_PORT` с правильным приоритетом. -- В репозитории есть корневой `README.md`. -- Никакие существующие документы в `docs/` не менялись для "закрытия" замечаний. diff --git a/docs/backend-api-for-frontend.md b/docs/backend-api-for-frontend.md index c9f4537..2c25dee 100644 --- a/docs/backend-api-for-frontend.md +++ b/docs/backend-api-for-frontend.md @@ -8,6 +8,8 @@ VITE_API_BASE_URL=http://localhost:3001 В коде SPA: `import.meta.env.VITE_API_BASE_URL`. +В Docker-стеке из репозитория образ фронта собирается с **`VITE_API_BASE_URL=/api`**: запросы идут на тот же origin, nginx проксирует `/api` на backend (см. `docker/nginx.frontend.conf`). + ## 2. CORS В dev-режиме бэкенд ожидает переменную: diff --git a/docs/backend.md b/docs/backend.md index f8b382d..709186a 100644 --- a/docs/backend.md +++ b/docs/backend.md @@ -78,6 +78,8 @@ API слушает порт: **`PORT`** (если задан), иначе **`API | `API_PORT` | Порт API-сервера | `3001` | | `CORS_ORIGIN` | Разрешённый origin для CORS | `http://localhost:5173` | +Для локального Vite в корневом `.env.example` также указан **`VITE_API_BASE_URL`** (читает только фронт из `frontend/`). В Docker-стеке базовый URL API задаётся при **сборке** образа фронта (`/api`), не через этот файл. + **Без mock:** при отсутствии любой из `DB_*` процесс падает при старте: `Missing required environment variable: `. **С `CALENDAR_RUN_MOCK_DB=1`:** переменные `DB_*` не обязательны; реальный пул не поднимается. **Не использовать** mock для `npm run db:migrate` и `npm run seed` — нужен настоящий Postgres и корректные `DB_*`. @@ -111,3 +113,9 @@ backend/ ├── package.json └── tsconfig.json ``` + +## Docker: стек backend + frontend + +Файл [`docker-compose.stack.yml`](../docker-compose.stack.yml) поднимает API и nginx со статикой SPA в **внешней** сети Docker (рядом с уже запущенным Postgres). Переменные — в **корневом** `.env` (шаблон [`.env.example`](../.env.example)): как минимум `DB_*`, `CORS_ORIGIN` (для выдачи фронта на порту 3033 задайте `http://localhost:3033`). Перед первым `up` файл `.env` должен существовать. + +Порядок после старта контейнеров: `node dist/migrate.js` и `node dist/seed.js` внутри контейнера `backend` (см. комментарии в compose-файле). diff --git a/frontend/.env.example b/frontend/.env.example index 93496e6..a241963 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -1,2 +1,2 @@ -# Base URL of the Calendar Run API (must match CORS_ORIGIN on the backend) +# Для локального npm run dev. Полный список переменных — в корневом .env.example репозитория. VITE_API_BASE_URL=http://localhost:3001