Compare commits
2 Commits
feature/do
...
chore/clea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
007d899721 | ||
| 2cf01186e9 |
@@ -5,7 +5,6 @@
|
|||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
!.env.example
|
!.env.example
|
||||||
docker-compose.stack.env
|
|
||||||
**/*.md
|
**/*.md
|
||||||
.cursor
|
.cursor
|
||||||
*.log
|
*.log
|
||||||
|
|||||||
32
.env.example
32
.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_HOST=localhost
|
||||||
DB_PORT=5432
|
DB_PORT=5432
|
||||||
DB_NAME=calendar_run
|
DB_NAME=calendar_run
|
||||||
DB_USER=calendar_user
|
DB_USER=calendar_user
|
||||||
DB_PASSWORD=calendar_pass
|
DB_PASSWORD=replace_with_strong_secret
|
||||||
|
|
||||||
# ─── Backend API ──────────────────────────────────────────────
|
# ─── Backend API ───────────────────────────────────────────────
|
||||||
# Порт: сначала читается PORT (если задан), иначе API_PORT, иначе 3001.
|
# Порт процесса: сначала PORT, иначе API_PORT, иначе 3001.
|
||||||
# PORT=3001
|
# В docker-compose.stack.yml для контейнера задаётся PORT=3000 (см. compose).
|
||||||
|
# PORT=3000
|
||||||
API_PORT=3001
|
API_PORT=3001
|
||||||
|
|
||||||
# ─── Dev/CI: без PostgreSQL для smoke API (не для migrate/seed) ─
|
# ─── Режим без БД (только тесты / smoke API, не для migrate/seed) ─
|
||||||
# CALENDAR_RUN_MOCK_DB=1
|
# CALENDAR_RUN_MOCK_DB=1
|
||||||
|
|
||||||
# ─── CORS ─────────────────────────────────────────────────────
|
# ─── CORS ────────────────────────────────────────────────────
|
||||||
# Allowed origin for the frontend (Vite dev server default)
|
# Локальный Vite: http://localhost:5173
|
||||||
|
# Стек с фронтом на 3033: http://localhost:3033
|
||||||
CORS_ORIGIN=http://localhost:5173
|
CORS_ORIGIN=http://localhost:5173
|
||||||
|
|
||||||
# ─── Frontend (Vite) ─────────────────────────────────────────
|
# ─── Frontend (Vite, локально из каталога frontend/) ─────────
|
||||||
# Public URL of the API, used in SPA code via import.meta.env
|
# В Docker-образе фронта базовый URL API задаётся при сборке (/api), не из .env.
|
||||||
VITE_API_BASE_URL=http://localhost:3001
|
VITE_API_BASE_URL=http://localhost:3001
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,7 +1,4 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
.env
|
.env
|
||||||
docker-compose.stack.env
|
|
||||||
*.log
|
*.log
|
||||||
*plan*
|
|
||||||
*PLAN*
|
|
||||||
37
PLAN.md
37
PLAN.md
@@ -10,25 +10,27 @@
|
|||||||
|
|
||||||
## Модель данных `Race` (API — camelCase)
|
## Модель данных `Race` (API — camelCase)
|
||||||
|
|
||||||
|
|
||||||
| Поле | Тип | Описание |
|
| Поле | Тип | Описание |
|
||||||
|------|-----|----------|
|
| ----------------- | --------------------------------------------- | ----------------------------------------------- |
|
||||||
| `id` | string | Стабильный ключ, например `{YYYY-MM-DD}-{slug}` |
|
| `id` | string | Стабильный ключ, например `{YYYY-MM-DD}-{slug}` |
|
||||||
| `date` | string | `YYYY-MM-DD` |
|
| `date` | string | `YYYY-MM-DD` |
|
||||||
| `title` | string | Название |
|
| `title` | string | Название |
|
||||||
| `distanceKm` | number | Дистанция, км |
|
| `distanceKm` | number | Дистанция, км |
|
||||||
| `status` | `planned` \| `registered` \| `completed` \| null | Жизненный цикл старта |
|
| `status` | `planned` | `registered` | `completed` | null | Жизненный цикл старта |
|
||||||
| `officialUrl` | string \| null | Сайт организатора |
|
| `officialUrl` | string | null | Сайт организатора |
|
||||||
| `startTime` | string \| null | Время старта (строка, напр. `09:30`) |
|
| `startTime` | string | null | Время старта (строка, напр. `09:30`) |
|
||||||
| `clusterSchedule` | string \| null | Расписание кластеров |
|
| `clusterSchedule` | string | null | Расписание кластеров |
|
||||||
| `bibPickup` | string \| null | Выдача номеров |
|
| `bibPickup` | string | null | Выдача номеров |
|
||||||
| `bibNumber` | string \| null | Стартовый номер |
|
| `bibNumber` | string | null | Стартовый номер |
|
||||||
| `finishTime` | string \| null | Результат `H:MM:SS` или `MM:SS` |
|
| `finishTime` | string | null | Результат `H:MM:SS` или `MM:SS` |
|
||||||
| `finishPlace` | string \| null | Место на финише (текст: «3», «3/120» и т.п.) |
|
| `finishPlace` | string | null | Место на финише (текст: «3», «3/120» и т.п.) |
|
||||||
| `notes` | string \| null | Заметки |
|
| `notes` | string | null | Заметки |
|
||||||
| `createdAt` | string | ISO, read-only |
|
| `createdAt` | string | ISO, read-only |
|
||||||
| `updatedAt` | string \| null | ISO, read-only |
|
| `updatedAt` | string | null | ISO, read-only |
|
||||||
|
|
||||||
PostgreSQL: `snake_case` столбцы, маппинг в [`backend/src/mappers/race.ts`](backend/src/mappers/race.ts).
|
|
||||||
|
PostgreSQL: `snake_case` столбцы, маппинг в `[backend/src/mappers/race.ts](backend/src/mappers/race.ts)`.
|
||||||
|
|
||||||
## HTTP API (минимум)
|
## HTTP API (минимум)
|
||||||
|
|
||||||
@@ -37,25 +39,26 @@ PostgreSQL: `snake_case` столбцы, маппинг в [`backend/src/mappers
|
|||||||
- `GET /races` — список; query: `year`, `month` (целые; `month` 1–12).
|
- `GET /races` — список; query: `year`, `month` (целые; `month` 1–12).
|
||||||
- `GET /races/:id`, `POST /races`, `PATCH /races/:id`, `DELETE /races/:id`.
|
- `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
|
## Seed
|
||||||
|
|
||||||
- Файл [`import/races_2026_calendar.csv`](import/races_2026_calendar.csv).
|
- Файл `[import/races_2026_calendar.csv](import/races_2026_calendar.csv)`.
|
||||||
- Стабильный `id`, upsert по `id`. Повторный запуск безопасен.
|
- Стабильный `id`, upsert по `id`. Повторный запуск безопасен.
|
||||||
|
|
||||||
## Режим без PostgreSQL (dev/CI)
|
## Режим без 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)
|
## Frontend (SPA)
|
||||||
|
|
||||||
- Маршруты: дашборд (`/`), список стартов (`/races`), карточка (`/races/:id`).
|
- Маршруты: дашборд (`/`), список стартов (`/races`), карточка (`/races/:id`).
|
||||||
- Дашборд: ближайший старт, последний результат, PR, сезон, PR по ключевым дистанциям, сравнение завершённых стартов, при необходимости — лёгкая визуализация прогресса.
|
- Дашборд: ближайший старт, последний результат, PR, сезон, PR по ключевым дистанциям, сравнение завершённых стартов, при необходимости — лёгкая визуализация прогресса.
|
||||||
- Список: будущие / прошедшие; фильтрация по году и месяцу через API.
|
- Список: будущие / прошедшие; фильтрация по году и месяцу через 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-БД.
|
- Миграции и seed воспроизводимы; контракт API покрыт smoke-тестами в CI при необходимости с mock-БД.
|
||||||
|
|
||||||
|
|||||||
65
README.md
65
README.md
@@ -1,56 +1,47 @@
|
|||||||
# Calendar Run
|
# 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:
|
Там же перечислены **`DB_HOST`**, **`DB_PORT`**, **`DB_NAME`**, **`DB_USER`**, **`DB_PASSWORD`** (подключение бэкенда к БД), **`PORT`** / **`API_PORT`**, опционально **`CALENDAR_RUN_MOCK_DB`**, **`CORS_ORIGIN`**, а для локального Vite — **`VITE_API_BASE_URL`**.
|
||||||
- `cd backend`
|
|
||||||
- `npm install`
|
|
||||||
2. Configure environment variables. Copy the root template and edit:
|
|
||||||
|
|
||||||
```bash
|
## Backend — локально
|
||||||
cp .env.example .env
|
|
||||||
```
|
|
||||||
|
|
||||||
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.
|
Без PostgreSQL (только smoke API): в `.env` задайте `CALENDAR_RUN_MOCK_DB=1`; **`db:migrate` и `seed` с mock не использовать**.
|
||||||
- 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:
|
|
||||||
|
|
||||||
```bash
|
## Frontend — локально
|
||||||
npm run build
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## Frontend — quick start
|
|
||||||
|
|
||||||
1. Configure the API URL for Vite (file is read from `frontend/`):
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd frontend
|
cd frontend
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
```
|
|
||||||
|
|
||||||
Edit `VITE_API_BASE_URL` if the API is not on `http://localhost:3001`.
|
|
||||||
|
|
||||||
2. Install and run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install
|
npm install
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Default app URL: `http://localhost:5173`. The backend `CORS_ORIGIN` must match this origin (see root `.env.example`).
|
Значение `VITE_API_BASE_URL` см. в **корневом** [`.env.example`](.env.example); для dev по умолчанию `http://localhost:3001`. У бэкенда `CORS_ORIGIN` должен совпадать с origin приложения (например `http://localhost:5173`).
|
||||||
|
|
||||||
## Docs
|
## Docker: backend + frontend рядом с Postgres
|
||||||
|
|
||||||
- [Backend API for Frontend](docs/backend-api-for-frontend.md)
|
Используйте [`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.
|
||||||
- [Backend operations](docs/backend.md)
|
|
||||||
- [Backend agent instruction](docs/agent-backend-instruction.md)
|
```bash
|
||||||
- [Backend sync fix instruction](docs/agent-fix-backend-sync-instruction.md)
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
Фронт в браузере обращается к API по префиксу **`/api`** (nginx в образе фронта проксирует на backend).
|
||||||
|
|
||||||
|
## Документация API и бэкенда
|
||||||
|
|
||||||
|
- [Шпаргалка API для фронта](docs/backend-api-for-frontend.md)
|
||||||
|
- [Эксплуатация backend](docs/backend.md)
|
||||||
|
|||||||
@@ -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, но более спокойно и чисто
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 🧭 Финальный ориентир
|
|
||||||
|
|
||||||
Интерфейс должен вызывать ощущение:
|
|
||||||
|
|
||||||
> “Я контролирую свою беговую историю и прогресс”
|
|
||||||
|
|
||||||
а не:
|
|
||||||
|
|
||||||
> “Я заполняю таблицу результатов”
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
@@ -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
|
|
||||||
@@ -1,15 +1,19 @@
|
|||||||
# Запуск приложения в сети с уже поднятым PostgreSQL (как у family-budget).
|
# Backend + frontend в сети с уже поднятым PostgreSQL (external network).
|
||||||
#
|
#
|
||||||
# 1) Скопируйте пример переменных (не коммитьте файл с паролями):
|
# Подготовка: из корня репозитория скопируйте .env.example → .env и задайте DB_*
|
||||||
# cp docker-compose.stack.env.example docker-compose.stack.env
|
# и CORS_ORIGIN (для фронта на :3033 — http://localhost:3033).
|
||||||
# отредактируйте docker-compose.stack.env
|
|
||||||
#
|
#
|
||||||
# NPM / reverse proxy: проброс на порт фронта 3033 (внутри контейнера nginx слушает 80).
|
# Сеть (имя должно существовать, как у вашего Postgres):
|
||||||
# Фронт в браузере ходит на /api → nginx проксирует на backend:3000.
|
# 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/migrate.js
|
||||||
# docker compose -f docker-compose.stack.yml exec backend node dist/seed.js
|
# docker compose -f docker-compose.stack.yml exec backend node dist/seed.js
|
||||||
|
#
|
||||||
|
# NPM: проброс на порт 3033. Браузер ходит на /api → nginx во фронте → backend:3000.
|
||||||
|
|
||||||
services:
|
services:
|
||||||
backend:
|
backend:
|
||||||
@@ -18,7 +22,7 @@ services:
|
|||||||
dockerfile: Dockerfile.backend
|
dockerfile: Dockerfile.backend
|
||||||
container_name: runners-calendar-backend
|
container_name: runners-calendar-backend
|
||||||
env_file:
|
env_file:
|
||||||
- docker-compose.stack.env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
- PORT=3000
|
- PORT=3000
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -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).*
|
|
||||||
@@ -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/` не менялись для "закрытия" замечаний.
|
|
||||||
@@ -8,6 +8,8 @@ VITE_API_BASE_URL=http://localhost:3001
|
|||||||
|
|
||||||
В коде SPA: `import.meta.env.VITE_API_BASE_URL`.
|
В коде SPA: `import.meta.env.VITE_API_BASE_URL`.
|
||||||
|
|
||||||
|
В Docker-стеке из репозитория образ фронта собирается с **`VITE_API_BASE_URL=/api`**: запросы идут на тот же origin, nginx проксирует `/api` на backend (см. `docker/nginx.frontend.conf`).
|
||||||
|
|
||||||
## 2. CORS
|
## 2. CORS
|
||||||
|
|
||||||
В dev-режиме бэкенд ожидает переменную:
|
В dev-режиме бэкенд ожидает переменную:
|
||||||
|
|||||||
@@ -78,6 +78,8 @@ API слушает порт: **`PORT`** (если задан), иначе **`API
|
|||||||
| `API_PORT` | Порт API-сервера | `3001` |
|
| `API_PORT` | Порт API-сервера | `3001` |
|
||||||
| `CORS_ORIGIN` | Разрешённый origin для CORS | `http://localhost:5173` |
|
| `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: <NAME>`.
|
**Без mock:** при отсутствии любой из `DB_*` процесс падает при старте: `Missing required environment variable: <NAME>`.
|
||||||
|
|
||||||
**С `CALENDAR_RUN_MOCK_DB=1`:** переменные `DB_*` не обязательны; реальный пул не поднимается. **Не использовать** mock для `npm run db:migrate` и `npm run seed` — нужен настоящий Postgres и корректные `DB_*`.
|
**С `CALENDAR_RUN_MOCK_DB=1`:** переменные `DB_*` не обязательны; реальный пул не поднимается. **Не использовать** mock для `npm run db:migrate` и `npm run seed` — нужен настоящий Postgres и корректные `DB_*`.
|
||||||
@@ -111,3 +113,9 @@ backend/
|
|||||||
├── package.json
|
├── package.json
|
||||||
└── tsconfig.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-файле).
|
||||||
|
|||||||
@@ -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
|
VITE_API_BASE_URL=http://localhost:3001
|
||||||
|
|||||||
Reference in New Issue
Block a user