Initial commit: project plan and seed data
Made-with: Cursor
This commit is contained in:
196
PLAN.md
Normal file
196
PLAN.md
Normal file
@@ -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 приоритет у этого файла.*
|
||||
161
docs/agent-backend-instruction.md
Normal file
161
docs/agent-backend-instruction.md
Normal file
@@ -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).*
|
||||
20
import/races_2026_calendar.csv
Normal file
20
import/races_2026_calendar.csv
Normal file
@@ -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
|
||||
|
Reference in New Issue
Block a user