Этап 1a: фундамент

- Next 16.4 и Payload 3.91 в одном приложении, PostgreSQL с UUID,
  TypeScript 6, ESLint 9, Prettier, Vitest
- Payload: администраторы с ролями, multi-tenant по сайтам, локализация
  en/ru/de/fr/es, интерфейс админки ru/en, языки сайта (sites.locales)
- Коллекции sites, domains, currencies, countries, locations, tags,
  tariffs, payment-systems (manual), media и начальные данные (pnpm seed)
- Proxy: хост → домен, язык и валюта, внутренние маршруты по хосту;
  /admin и /api закрыты на доменах брендов
- Токены и темы брендов, оболочки сайта и кабинета, переключатели языка,
  валюты и темы, заглушки страниц
- /svc/health, юнит systemd veilix-dev.service
- Документация: решения D-022…D-024, команды в CLAUDE.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
andreypr207andClaude Opus 5.5 committed 2026-10-21 05:08:05 +02:00
1 parent 3400f58a5a
commit 7e331c2c9d
140 files changed
+15885 -43

No files matched your search

+30 -18
View File
@@ -11,19 +11,22 @@
| Payload CMS | 3.91.x | Payload 4 в бете; переход после стабильного релиза ([D-002](decisions/D-002-payload-version.md)) |
| PostgreSQL | 18 (на хосте) | адаптер `@payloadcms/db-postgres`, `idType: 'uuid'` |
| Tailwind CSS | 4.x | токены из макета как CSS-переменные |
| TypeScript | 6.0 | не 7.0: typescript-eslint из eslint-config-next поддерживает только TS < 6.1 |
| ESLint | 9.39 | не 10: eslint-plugin-react, import и jsx-a11y не заявляют поддержку ESLint 10 |
| Vitest | 5.x | модульные тесты |
| next-intl | 4.x | переводы интерфейса, префиксы языков в URL |
| Nodemailer | через `@payloadcms/email-nodemailer` | транспорты SMTP, sendmail, «только журнал» |
| ALTCHA | 3.x | капча без третьих сторон |
| grammY | 1.x | Telegram-боты, этап 3 |
| Stripe SDK | актуальный | этап 4 |
Точные версии фиксируются в `package.json` на этапе 1a. Если к старту этапа 1a выйдет стабильный Payload 4, начинаем сразу на нём.
Точные версии закреплены в `package.json`: на этапе 1a — Next 16.4.2, Payload 3.91.0, React 19.3.1, next-intl 4.14.9, Tailwind 4.3.3, pnpm 12.14. На старте 1a Payload 4 был в бете (4.0.0-beta.3), поэтому начали на 3.91 ([D-002](decisions/D-002-payload-version.md)). Телеметрия Next.js и Payload выключена.
## 2. Одно приложение на все домены
1. Одно Next.js-приложение, Payload установлен внутрь как библиотека: админка, API и фронтенды — один процесс ([D-001](decisions/D-001-single-app.md)).
2. Proxy Next.js (`src/proxy.ts`, бывший middleware) по заголовку Host находит запись в коллекции «Домены» и узнаёт сайт (`dedicated` или `mass`) и тип фронтенда: сайт, кабинет или MiniApp. Список доменов кэшируется в памяти и сбрасывается при изменении записи.
3. Proxy переписывает запрос во внутренний маршрут вида `/{тип}/{сайт}/{язык}/…`, для маркетинговых страниц ещё и `/{валюта}/`. Снаружи эти пути недоступны: прямой запрос к ним даёт 404.
2. Proxy Next.js (`src/proxy.ts`, бывший middleware) по заголовку Host находит домен и узнаёт сайт (`dedicated` или `mass`) и тип фронтенда: сайт, кабинет или MiniApp. Список активных доменов proxy берёт у `/svc/internal/domains` (по токену из `.env`) и держит в памяти 15 секунд: правка домена в админке применяется в пределах 15 секунд ([D-022](decisions/D-022-proxy-routing.md)).
3. Proxy переписывает запрос во внутренний маршрут по хосту: `/m/{хост}/{язык}/{валюта}/…` — сайт, `/c/{хост}/{язык}/…` — кабинет, `/t/{хост}/…` — MiniApp. Снаружи эти пути недоступны: прямой запрос к ним получает ещё один префикс и попадает в 404. Решения proxy — чистая функция `src/core/routing/decide.ts` с модульными тестами.
4. Хост админки (`plc.veilix.cc`, `dev.plc.veilix.cc`) задаётся в `.env`. Только на нём работают `/admin` и REST API Payload. На доменах брендов эти пути закрыты.
5. Сайтам, кабинетам и MiniApp хост админки не нужен:
a. данные Payload они читают в том же процессе через Local API — это вызов функции, а не HTTP-запрос;
@@ -32,29 +35,38 @@
d. файлы медиатеки отдаются через домены сайтов.
6. Ядро общее (биллинг, ключи, вход, рефералы, почта), UI-кит общий, а страницы у каждого бренда свои: данные в дизайнах представлены по-разному.
## 3. Структура кода (план)
## 3. Структура кода
```
src/
proxy.ts — домен → сайт, тип фронтенда, язык, валюта
proxy.ts — хост → домен, язык, валюта, внутренний маршрут
payload.config.ts
payload-types.ts — типы Payload (pnpm generate:types)
app/
(payload)/ — админка и REST API Payload (стандартные файлы)
(front)/m/[site]/[locale]/[currency]/… — маркетинговые сайты
(front)/c/[site]/[locale]/… — кабинеты и оформление заказа
(front)/t/[site]/… — MiniApp (этап 3)
svc/… — свои эндпоинты: ALTCHA, вебхуки, health
(front)/m/[host]/[locale]/[currency]/(site)/… — маркетинговые сайты
(front)/c/[host]/[locale]/(shell)/… — кабинет с навигацией
(front)/c/[host]/[locale]/(plain)/… — вход и оформление заказа
(front)/t/[host]/… — MiniApp (этап 3)
(front)/context.ts — домен, язык и валюта страницы, данные оболочек
svc/… — свои эндпоинты: health, internal/domains; дальше ALTCHA, вебхуки
payload/
collections/ globals/ hooks/ access/ fields/
collections/ access/ fields/ hooks/ components/
core/ — бизнес-логика без привязки к UI
billing/ keys/ auth/ referral/ email/ payments/ currency/ geo/
sites.ts domains/ routing/ currencies/ payments/ — этап 1a
billing/ keys/ auth/ referral/ email/ geo/ — следующие этапы
brands/
dedicated/ mass/ — страницы и компоненты каждого бренда
ui/ — общие компоненты: кнопки, поля, шиты, переключатели, флаги, карта
i18n/ — конфиг next-intl и messages/{en,ru,de,fr,es}.json
styles/ — Tailwind, токены брендов, темы
dedicated/ mass/ — различия брендов; страницы и компоненты — с этапа 1b
ui/ — общие компоненты: оболочки сайта и кабинета, переключатели, тема, логотип, иконки
i18n/ — языки, конфиг next-intl и messages/{en,ru,de,fr,es}.json
styles/ — Tailwind, токены брендов
seed/ — начальные данные (pnpm seed)
ops/systemd/ — юниты systemd
scripts/ — служебные скрипты (очистка dev-базы)
```
Корневые layout сайта и кабинета дают только документ: `<html>` с `lang`, `data-site` и скриптом темы. Шапка, подвал и навигация — во вложенных группах `(site)`, `(shell)` и `(plain)`.
Бизнес-логика живёт в `core/` и вызывается одинаково из хуков Payload, server actions, вебхуков, фоновых задач и (на этапе 3) бота.
## 4. Окружения
@@ -66,7 +78,7 @@ src/
| Запуск | `pnpm dev` (горячая перезагрузка) | `pnpm start` из `current` после `pnpm build` |
| Порт | 3100 | 3000 |
| База | `veilix_dev` | `veilix_prod` |
| Схема базы | синхронизируется автоматически (push) | миграции Payload, применяются при выкладке |
| Схема базы | синхронизируется автоматически (push); если push задаёт вопросы — `pnpm db:reset` и `pnpm seed` | миграции Payload, применяются при выкладке |
| Домены | `dev.*`, `dev.plc.veilix.cc` | основные, `plc.veilix.cc` |
| Секреты | `/home/vx_dev/veilix_dev/.env` | `/home/vx_prod/veilix/shared/.env` |
| Файлы (media) | `/home/vx_dev/veilix_dev/media` | `/home/vx_prod/veilix/shared/media` |
@@ -77,7 +89,7 @@ src/
## 5. Выкладка в prod
1. В dev: изменения схемы оформляются миграцией (`payload migrate:create`), коммит, push в Gitea, тег релиза.
1. В dev: изменения схемы оформляются миграцией (`payload migrate:create`), коммит, push в Gitea, тег релиза. До первой выкладки (этап 1h) миграции не накапливаем: перед ней создаём одну начальную по итоговой схеме. Механизм проверен на этапе 1a: миграция, применённая к пустой схеме, создаёт те же таблицы, что push в dev.
2. Выкладка — команда `deploy <тег>` пользователя vx_prod, появится на этапе 1h. Запускает её пользователь; по желанию — sudo-правило, разрешающее vx_dev запускать только эту команду (Q-18). Шаги:
a. взять тег из Gitea (у vx_prod ключ только на чтение) в новую папку `releases/<дата-тег>`;
b. `pnpm install --frozen-lockfile` и `pnpm build`; ссылки на `shared/.env` и `shared/media`;
@@ -89,7 +101,7 @@ src/
## 6. Рендеринг и кэширование
1. Маркетинговые страницы собираются статически во всех вариантах «язык × валюта» и кэшируются. Proxy выбирает вариант по префиксу языка и cookie валюты.
1. Маркетинговые страницы собираются статически в вариантах «домен × язык × валюта» при первом запросе (ISR) и кэшируются. Proxy выбирает вариант по префиксу языка и cookie валюты.
2. Изменение в админке (цена, статус локации, FAQ) вызывает хук Payload, и Next.js пересобирает затронутые страницы за секунды (on-demand revalidation по тегам). Запасной вариант — плановое обновление раз в 5 минут.
3. При оформлении заказа наличие и цена проверяются заново на сервере. Устаревшая страница не может продать то, чего нет.
4. Кабинет рендерится на сервере (SSR) для каждого запроса, без кэша.