117 lines
11 KiB
Markdown
117 lines
11 KiB
Markdown
# 02. Архитектура
|
||
|
||
## 1. Стек
|
||
|
||
| Что | Версия на 20.10.2026 | Комментарий |
|
||
|---|---|---|
|
||
| Node.js | 24 LTS (на сервере 24.22) | |
|
||
| pnpm | 12.x | ставится root через `corepack enable pnpm`; версия фиксируется полем `packageManager` |
|
||
| Next.js | 16.4.x | App Router, React Server Components |
|
||
| React | 19.x | |
|
||
| 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-переменные |
|
||
| 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, начинаем сразу на нём.
|
||
|
||
## 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.
|
||
4. Хост админки (`plc.veilix.cc`, `dev.plc.veilix.cc`) задаётся в `.env`. Только на нём работают `/admin` и REST API Payload. На доменах брендов эти пути закрыты.
|
||
5. Сайтам, кабинетам и MiniApp хост админки не нужен:
|
||
a. данные Payload они читают в том же процессе через Local API — это вызов функции, а не HTTP-запрос;
|
||
b. запросы браузера (формы, вход, действия в кабинете) идут на тот домен, где открыта страница: server actions и эндпоинты `/svc/…`;
|
||
c. вебхуки платёжных систем и Telegram приходят на публичные домены, не на хост админки;
|
||
d. файлы медиатеки отдаются через домены сайтов.
|
||
6. Ядро общее (биллинг, ключи, вход, рефералы, почта), UI-кит общий, а страницы у каждого бренда свои: данные в дизайнах представлены по-разному.
|
||
|
||
## 3. Структура кода (план)
|
||
|
||
```
|
||
src/
|
||
proxy.ts — домен → сайт, тип фронтенда, язык, валюта
|
||
payload.config.ts
|
||
app/
|
||
(payload)/ — админка и REST API Payload (стандартные файлы)
|
||
(front)/m/[site]/[locale]/[currency]/… — маркетинговые сайты
|
||
(front)/c/[site]/[locale]/… — кабинеты и оформление заказа
|
||
(front)/t/[site]/… — MiniApp (этап 3)
|
||
svc/… — свои эндпоинты: ALTCHA, вебхуки, health
|
||
payload/
|
||
collections/ globals/ hooks/ access/ fields/
|
||
core/ — бизнес-логика без привязки к UI
|
||
billing/ keys/ auth/ referral/ email/ payments/ currency/ geo/
|
||
brands/
|
||
dedicated/ mass/ — страницы и компоненты каждого бренда
|
||
ui/ — общие компоненты: кнопки, поля, шиты, переключатели, флаги, карта
|
||
i18n/ — конфиг next-intl и messages/{en,ru,de,fr,es}.json
|
||
styles/ — Tailwind, токены брендов, темы
|
||
```
|
||
|
||
Бизнес-логика живёт в `core/` и вызывается одинаково из хуков Payload, server actions, вебхуков, фоновых задач и (на этапе 3) бота.
|
||
|
||
## 4. Окружения
|
||
|
||
| | dev | prod |
|
||
|---|---|---|
|
||
| Пользователь ОС | vx_dev (под ним работают редактор и сессии Claude) | vx_prod (без редактора и ИИ-агентов) |
|
||
| Папка | `/home/vx_dev/veilix_dev` — рабочая копия git | `/home/vx_prod/veilix` — `releases/`, симлинк `current`, `shared/` |
|
||
| Запуск | `pnpm dev` (горячая перезагрузка) | `pnpm start` из `current` после `pnpm build` |
|
||
| Порт | 3100 | 3000 |
|
||
| База | `veilix_dev` | `veilix_prod` |
|
||
| Схема базы | синхронизируется автоматически (push) | миграции 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` |
|
||
|
||
1. Без Docker: оба окружения — обычные процессы Node, каждый под своим пользователем ОС, запускаются одинаково — systemd user-юнитами: `veilix-dev.service` у vx_dev, `veilix.service` у vx_prod. У vx_dev нет доступа к файлам, секретам, логам и базе prod. Подробно — [D-003](decisions/D-003-hosting.md), [D-004](decisions/D-004-environments.md).
|
||
2. Dev закрыт от посторонних в nginx и запрещён к индексации, см. [09-infrastructure.md](09-infrastructure.md).
|
||
3. Платёжные системы в dev — только в тестовом режиме.
|
||
|
||
## 5. Выкладка в prod
|
||
|
||
1. В dev: изменения схемы оформляются миграцией (`payload migrate:create`), коммит, push в Gitea, тег релиза.
|
||
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`;
|
||
c. применить миграции к `veilix_prod`;
|
||
d. переключить симлинк `current` на новый релиз и перезапустить `veilix.service`;
|
||
e. проверить `/svc/health`; хранить 5 последних релизов.
|
||
3. Откат: переключить `current` на прошлый релиз и перезапустить. Миграции пишем так, чтобы прошлый релиз продолжал работать с новой схемой (сначала добавляем, удаляем позже).
|
||
4. Простой при выкладке — только время перезапуска процесса, несколько секунд.
|
||
|
||
## 6. Рендеринг и кэширование
|
||
|
||
1. Маркетинговые страницы собираются статически во всех вариантах «язык × валюта» и кэшируются. Proxy выбирает вариант по префиксу языка и cookie валюты.
|
||
2. Изменение в админке (цена, статус локации, FAQ) вызывает хук Payload, и Next.js пересобирает затронутые страницы за секунды (on-demand revalidation по тегам). Запасной вариант — плановое обновление раз в 5 минут.
|
||
3. При оформлении заказа наличие и цена проверяются заново на сервере. Устаревшая страница не может продать то, чего нет.
|
||
4. Кабинет рендерится на сервере (SSR) для каждого запроса, без кэша.
|
||
5. Маркетинговые домены не ставят cookies, кроме выбора языка, валюты и темы.
|
||
|
||
## 7. Фоновые задачи
|
||
|
||
Встроенная очередь Payload (Jobs Queue) с расписанием cron. Исполнитель работает внутри процесса приложения (`autoRun`), в dev его можно отключить переменной окружения.
|
||
|
||
| Задача | Расписание | Что делает |
|
||
|---|---|---|
|
||
| `renewals` | каждые 15 минут | продление ключей, у которых срок подходит (см. [04-billing.md](04-billing.md)) |
|
||
| `reminders` | каждый час | напоминания за 3 дня до окончания |
|
||
| `expiry` | каждые 15 минут | перевод в льготный период и в «истёк» |
|
||
| `send-email` | по событию | отправка письма с повторами при ошибке |
|
||
| `cleanup` | раз в сутки | старые коды входа, сессии, записи журнала писем |
|
||
| `reconcile` | раз в сутки | сверка кэша баланса с суммой движений по счёту |
|
||
|
||
## 8. Чего не используем
|
||
|
||
1. Docker — не нужен ни в dev, ни в prod ([D-003](decisions/D-003-hosting.md)).
|
||
2. n8n — боты пишем в коде на grammY ([D-015](decisions/D-015-telegram-bots.md)).
|
||
3. listmonk — шаблоны писем в Payload, отправка своим транспортом ([D-014](decisions/D-014-email.md)).
|
||
4. Redis — стоит на сервере, но пока не нужен: лимиты запросов и очередь задач работают на PostgreSQL.
|