InGame: прототип административной системы
Статус: локальный технический прототип. Это не production-система и не готовый личный кабинет клиента.
Репозиторий содержит рабочую основу будущей административной системы InGame: нормализованную PostgreSQL-базу, односторонний импорт исторических данных из WordPress, экраны админки и часть серверной логики бронирований. Документ фиксирует фактическое состояние кода на текущий момент, чтобы следующий разработчик понимал границу между уже реализованным и запланированным.
Что уже реализовано
Основа и данные
- Next.js 16, React 19, TypeScript и PostgreSQL 17 в Docker Compose.
- Нормализованная схема данных для клиентов, заказов, бронирований, ресурсов, расписания, оплат, транзакций, товаров, дополнительных услуг, промокодов, сертификатов, отчётов и аудита.
- Суммы хранятся целыми центами; важные связи защищены внешними ключами, ограничениями статусов и индексами.
- Запрет пересечений бронирований и блокировок одного ресурса обеспечивается на уровне PostgreSQL: exclusion constraints и триггер берут блокировку ресурса внутри транзакции.
- Односторонний локальный импорт WordPress-данных с SHA-256-чексуммами, идемпотентностью, карантином некорректных записей и сверкой результатов.
- Сохраняются legacy ID, WPML-алиасы и исходные снимки данных, поэтому исторические строки разных языков связываются с одной канонической сущностью без дублирования каталога.
Работа администратора
- Расписание: календарь по дням, временная шкала квестов и лаунжей, создание и изменение заказа, блокировка слота и дня квеста, фильтрация по
utm_source, итоги по способам оплаты. - Заказы и бронирования: список, поиск, фильтры по статусу/дате/UTM; в одном заказе может быть несколько бронирований. При сохранении проверяются конфликты ресурсов, тарифы, количество игроков, скидки и промокоды.
- Клиенты: поиск, сортировка, создание, редактирование, удаление, история заказов и транзакционное объединение дублей.
- Квесты: список и редактор; тарифы, цены для числа игроков, недельное расписание, связанные услуги, локализации
et/ru/en, активность. - Продукты и дополнительные услуги: создание и редактирование, категории, атрибуты, варианты, цены, локализации и медиа из импортированного хранилища.
- Промокоды: CRUD, фиксированная или процентная скидка, период действия, лимит, использованное количество и вычисляемый статус.
- Магазин и сертификаты: просмотр и редактирование заказов магазина, импортированные сертификаты и файлы, журнал действий, тестовая отправка сертификата и тестовый возврат платежа.
- Статистика: доходы по квестам, услугам, товарам и способам оплаты; предупреждения о расхождении суммы заказа и оплаты; фильтры и CSV-экспорт с защитой от CSV-injection.
- Публичное API прототипа: получение доступности и создание одного простого бронирования с серверной валидацией и защитой от устаревшего/занятого слота.
flowchart LR
WP["WordPress-архив\nлокальный источник"] -->|"односторонний импорт"| DB[("PostgreSQL")]
DB --> ADMIN["Админка Next.js"]
DB --> API["Публичное API\nдоступность и бронь"]
STATIC["Статический прототип сайта"] --> SITE["/site"]
Что пока не реализовано
Следующие пункты не должны восприниматься как готовый функционал:
- нет авторизации, персональных учётных записей, ролей и проверки прав доступа;
- нет полноценного публичного сайта, регистрации или личного кабинета клиента; маршрут
/siteтолько отдаёт внешний статический HTML-файл; - нет боевой интеграции с amoCRM, Montonio, email или Telegram; в Docker включены только симуляции email/Montonio, а amoCRM не вызывается;
- нет очереди/outbox, обработчика webhooks, повторных попыток, журналов доставки и production-секретов;
- нет реального проведения платежей, выдачи возвратов или отправки сертификатов: эти действия помечены как симуляция;
- нет production-развёртывания, резервного копирования и мониторинга;
- импорт — это снимок исторических данных в одну сторону. Удаления, выполненные позднее в WordPress, намеренно не реплицируются в локальную базу.
Структура проекта
| Путь | Назначение |
|---|---|
app/admin/ |
Страницы админки: расписание, заказы, клиенты, квесты, каталог, сертификаты, промокоды и статистика. |
app/admin/actions/ |
Server Actions, которые принимают формы, проверяют входные данные и обновляют нужные страницы. |
app/api/public/ |
Публичные прототипные API: availability и bookings. |
components/admin/ |
Переиспользуемые компоненты навигации, таблиц, календаря и редактора заказа. |
lib/server/ |
Серверная бизнес-логика и SQL-запросы. Новые правила брони/денег следует добавлять сюда, а не в React-компоненты. |
lib/import/normalize.mjs |
Чистые функции нормализации WordPress-данных; покрываются отдельными тестами. |
db/001_init.sql |
Начальная схема PostgreSQL, ограничения и индексы. |
scripts/import-legacy.mjs |
Односторонний импорт локального WordPress-снимка. |
scripts/reconcile.mjs |
Сверка итогов последнего импорта. |
tests/ |
Тесты схемы, импорта, бронирований, каталога, клиентов, статистики и shell админки. |
docker-compose.yml |
Локальные контейнеры PostgreSQL и Next.js. |
Локальный запуск
Требования
- Docker Desktop / Docker Compose;
- Node.js 24 (для запуска импортера и тестов на хосте);
- локальная копия WordPress-архива и HTML-прототипа сайта, если требуется импорт и маршрут
/site.
Подготовка источников для импорта
Для работы админки без импорта достаточно PostgreSQL. Для db:import и db:reconcile дополнительно обязателен соседний локальный проект WordPress по пути ../../Админка и сайт:
- в нём есть
docker-compose.ymlи запущен сервисwordpress— импортёр вызывает в нём PHP-экспортёр черезdocker compose exec; - доступен каталог
../../Админка и сайт/runtime/htdocsс PDF и медиа исторических заказов; - в текущем
docker-compose.ymlпроверены read-only mount:../../ingame-prototype-v5-rewrite.html→/app/site-prototype.htmlи../Админка и сайт/original/htdocs→/legacy/htdocs.
db:reconcile рассчитан на контрольный исторический снимок и содержит ожидаемые количества записей. На другой выгрузке он намеренно сообщит о расхождении; для новой миграции сначала согласуйте новые контрольные значения. Не подключайте импортёр к удалённой БД: скрипты намеренно принимают только локальные адреса.
Если HTML-файла сайта нет, /site вернёт 503, а healthcheck контейнера приложения станет unhealthy; PostgreSQL и страницы админки при этом могут быть доступны.
Запуск демонстрационного контура в Docker
npm ci
docker compose up -d --build
docker compose exec postgres pg_isready -U ingame -d ingame_admin
npm run db:import
npm run db:reconcile
Этот режим запускает уже собранную версию через next start, а не режим разработки с hot reload. После изменения кода пересоберите и перезапустите контейнер: docker compose up -d --build.
После запуска:
- сайт-прототип: http://127.0.0.1:3100/site;
- админка: http://127.0.0.1:3100/admin;
- PostgreSQL с хоста:
postgresql://ingame:ingame_local@127.0.0.1:5433/ingame_admin.
Активная разработка на хосте
Сначала поднимите PostgreSQL. Затем создайте .env.local на основе .env.local.example: DATABASE_URL обязателен для серверных страниц; SITE_PROTOTYPE_PATH нужен только для маршрута /site. После этого выполните npm run dev.
Скрипты импорта и сверки используют тот же локальный адрес PostgreSQL по умолчанию. Схема из db/001_init.sql применяется Postgres только при первом создании тома postgres_data; изменение этого файла не меняет уже созданную базу. Для чистого локального запуска требуется новый том либо отдельная миграция.
Контракты и важные правила
Публичное API
| Метод | Маршрут | Назначение |
|---|---|---|
GET |
/api/public/availability?date=YYYY-MM-DD |
Доступные слоты квестов на дату. |
POST |
/api/public/bookings |
Создаёт одно бронирование источника SITE. Требует дату, время, questId, число игроков, имя, телефон, email и язык et/ru/en. |
Маршруты предназначены только для локального прототипа: CORS разрешён для 127.0.0.1:3100, localhost:3100 и null. Production-эксплуатация, авторизация и защита от злоупотреблений для этих API пока не реализованы.
Бронь и деньги
- Бронь, заказ и оплата — разные сущности. Не добавляйте в код проверку доступности только на уровне UI: окончательная проверка обязана оставаться в транзакции и в ограничениях PostgreSQL.
- Ручная скидка требует причины; промокод проверяется на активность, срок и лимит.
- Денежные суммы принимаются и сохраняются в центах; не используйте floating point для расчётов.
- Исторические данные могут быть неполными. Сохраняйте legacy ID и снимки строк заказа, а спорные записи отправляйте в
import_quarantineвместо молчаливого удаления. - Все операции, которые меняют деньги или историю, должны быть аудируемыми. В текущем прототипе аудит уже ведётся для части операций; при расширении не обходите этот механизм.
Локализация и каталог
- Допустимые локали:
et,ru,en. - Каноническая сущность каталога одна; языковые варианты и WPML-ссылки — это алиасы/переводы, а не отдельные товары.
- Вариации товара синхронизируются через
lib/server/catalog-variations.ts. Не дублируйте логику комбинаций атрибутов в страницах.
Проверка перед изменением
Тесты разделены по внешним зависимостям. Не запускайте все команды как один «минимальный набор» — часть из них специально требует локальный WordPress-контур и исторические файлы.
| Проверка | Команда | Что требуется |
|---|---|---|
| Сборка | npm run build |
Установленные зависимости; база и WordPress не нужны. |
| Схема PostgreSQL | npm run test:db |
Docker в PATH и локальный Postgres-контур. |
| Импорт | npm run test:import |
Docker, локальный Postgres, соседний WordPress-проект с сервисом wordpress и его файлами. |
| Весь набор | node --test tests/*.test.mjs |
Все перечисленные зависимости, плюс HTML-файл сайта ../../ingame-prototype-v5-rewrite.html. |
Для изменений бронирования, денег, промокодов, импорта или схемы дополнительно вручную проверьте: создание слота, попытку конкурирующей брони, блокировку/разблокировку, изменение заказа с несколькими бронями, корректность итогов и строку аудита.
Как продолжать разработку
- Сначала зафиксируйте правило в ТЗ и решите, влияет ли оно на деньги, бронь, миграцию или интеграции.
- Внесите изменение в типы и серверную бизнес-логику, затем при необходимости — в схему PostgreSQL и миграцию данных.
- Добавьте тест на обычную и ошибочную ветку. Для конфликтов брони используйте настоящий PostgreSQL, а не только мок.
- После серверной части добавьте интерфейс и ручную проверку сценария.
- Внешние интеграции проектируйте через отдельные адаптеры, inbox/outbox, идемпотентные ключи, проверку подписи webhook и журнал событий. Не встраивайте вызовы Montonio или amoCRM непосредственно в обработчик формы.