Этап 0: документация и макеты
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
3400f58a5a
75 files changed
+14283
No files matched your search
@@ -0,0 +1,374 @@
|
||||
# 03. Модель данных
|
||||
|
||||
## 1. Соглашения
|
||||
|
||||
1. Все `id` — UUID (`idType: 'uuid'` в адаптере Postgres). Номера для людей (заказы, ключи, платежи, реферальные коды) — случайные коды, не последовательные.
|
||||
2. Сайт — тенант плагина `@payloadcms/plugin-multi-tenant`. Поле тенанта называется `site`. Коллекции с пометкой «сайт» содержат его автоматически; в админке у них есть переключатель сайта.
|
||||
3. «(L)» — локализованное поле Payload: значение хранится на каждом языке, если перевода нет — показывается EN.
|
||||
4. Деньги — целое число в минимальных единицах (`amount`: 249900 = 2 499,00 ₽) плюс `currency` — код ISO 4217 (`RUB`, `USD`). Код проверяется по справочнику «Валюты».
|
||||
5. Даты — `timestamptz`, в UTC.
|
||||
6. Секреты в полях (конфиги WG, ссылки подписок, SMTP-пароль, id сохранённых карт) шифруются приложением (AES-256-GCM, ключ в `.env`).
|
||||
|
||||
## 2. Схема связей
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
SITES ||--o{ DOMAINS : "домены"
|
||||
SITES ||--o{ LOCATIONS : "локации"
|
||||
SITES ||--o{ TARIFFS : "тарифы"
|
||||
SITES ||--o{ CUSTOMERS : "пользователи"
|
||||
COUNTRIES ||--o{ LOCATIONS : "страна"
|
||||
TARIFFS }o--o{ COUNTRIES : "страны тарифа"
|
||||
TARIFFS }o--o{ TAGS : "теги"
|
||||
DOMAINS }o--o{ CURRENCIES : "валюты домена"
|
||||
PAYMENT_SYSTEMS }o--o{ CURRENCIES : "валюты системы"
|
||||
CUSTOMERS ||--o{ KEYS : "ключи"
|
||||
TARIFFS ||--o{ KEYS : "тариф"
|
||||
LOCATIONS ||--o{ KEYS : "локация (dedicated)"
|
||||
KEYS ||--o{ CONNECTIONS : "подключения"
|
||||
CUSTOMERS ||--o{ ORDERS : "заказы"
|
||||
KEYS ||--o{ ORDERS : "продления"
|
||||
ORDERS ||--o{ PAYMENTS : "оплаты"
|
||||
CUSTOMERS ||--o{ PAYMENTS : "платежи"
|
||||
PAYMENT_SYSTEMS ||--o{ PAYMENTS : "через"
|
||||
CUSTOMERS ||--o{ LEDGER_ENTRIES : "движения"
|
||||
PAYMENTS ||--o{ LEDGER_ENTRIES : "зачисление"
|
||||
ORDERS ||--o{ LEDGER_ENTRIES : "списание, возврат"
|
||||
CUSTOMERS ||--o{ PAYMENT_METHODS : "карты"
|
||||
CUSTOMERS ||--o{ REFERRALS : "пригласил"
|
||||
CUSTOMERS ||--o{ CUSTOMER_SESSIONS : "сессии"
|
||||
```
|
||||
|
||||
## 3. Настройки и справочники
|
||||
|
||||
### 3.1 `sites` — Сайты (тенанты)
|
||||
|
||||
| Поле | Тип | Описание |
|
||||
|---|---|---|
|
||||
| code | select `dedicated` \| `mass`, уникальное | бренд: по нему выбираются тема и страницы |
|
||||
| name | text (L) | «Вейликс» / «Veilix» |
|
||||
| orderPrefix | text | префикс номеров: `VX`, `VV` |
|
||||
| supportEmail | email | почта поддержки (Q-08) |
|
||||
| emailFrom | group: name, address | отправитель писем, напр. «Вейликс» <no-reply@veilix.app> |
|
||||
| supportBot, mainBot | text | имена ботов (этап 3) |
|
||||
| referralEnabled | checkbox | |
|
||||
| referralBonus | array: currency, amount | бонус «месяц в подарок» в каждой валюте |
|
||||
| topupPresets | array: currency, amounts[] | суммы пополнения в кабинете |
|
||||
| graceDays | number, по умолчанию 3 | льготный период |
|
||||
| reminderDays | number, по умолчанию 3 | за сколько дней напоминать |
|
||||
| renewalLeadHours | number, по умолчанию 24 | за сколько часов до окончания пробовать продлить |
|
||||
| expiringBadgeDays | number, по умолчанию 7 | когда показывать «Истекает» |
|
||||
| seo | group: titleTemplate (L), description (L), ogImage | значения по умолчанию |
|
||||
|
||||
### 3.2 `domains` — Домены (сайт)
|
||||
|
||||
| Поле | Тип | Описание |
|
||||
|---|---|---|
|
||||
| hostname | text, уникальное | `veilix.app`, `my.veilix.app`, … |
|
||||
| app | select `marketing` \| `cabinet` \| `miniapp` | тип фронтенда |
|
||||
| locales | multi-select из языков конфига | включённые языки |
|
||||
| defaultLocale | select | обычно `en` |
|
||||
| currencies | multi-select валют | включённые валюты |
|
||||
| defaultCurrency | select | валюта по умолчанию |
|
||||
| localeCurrency | array: locale, currency | валюта по умолчанию для языка: ru → RUB |
|
||||
| isPrimary | checkbox | основной домен этого типа; используется в ссылках и sitemap |
|
||||
| indexable | checkbox | разрешена ли индексация (для кабинета и MiniApp — нет) |
|
||||
| umamiWebsiteId | text | этап 1h |
|
||||
| active | checkbox | |
|
||||
|
||||
### 3.3 `currencies` — Валюты
|
||||
|
||||
| Поле | Тип | Описание |
|
||||
|---|---|---|
|
||||
| code | text, уникальное | ISO 4217: `RUB`, `USD` |
|
||||
| symbol | text | `₽`, `$` |
|
||||
| name | text (L) | |
|
||||
| minorUnits | number | 2 |
|
||||
| active, sort | | |
|
||||
|
||||
### 3.4 `payment-systems` — Платёжные системы
|
||||
|
||||
Платёжная система привязана только к валютам. Где она доступна, следует из цепочки: валюты → домены, на которых они включены → сайты. В оформлении заказа и пополнении показываются системы в валюте аккаунта.
|
||||
|
||||
| Поле | Тип | Описание |
|
||||
|---|---|---|
|
||||
| name | text | название в админке |
|
||||
| adapter | select | реализация в коде: `manual`; на этапе 4 — `stripe`, `yookassa`, `crypto_*`, `rucard_*` |
|
||||
| title | text (L) | что видит пользователь: «Банковская карта», «Карта МИР», «СБП», «Криптовалюта» |
|
||||
| description | text (L) | подсказка: «USDT, BTC, ETH, курс фиксируется на 15 минут» |
|
||||
| icon | upload | |
|
||||
| currencies | multi-select валют | в каких валютах доступна |
|
||||
| purposes | multi-select `order` \| `topup` | оплата заказа, пополнение |
|
||||
| visibleToCustomers | checkbox | у `manual` выключено: ей пользуются только администраторы |
|
||||
| mode | select `test` \| `live` | |
|
||||
| limits | array: currency, min, max | |
|
||||
| capabilities | только чтение, из адаптера | `recurring`, `refunds`, `saveMethod` |
|
||||
| active, sort | | |
|
||||
|
||||
Ключи доступа к провайдерам — только в `.env` (`PAYMENT_<КОД>_…`), не в базе.
|
||||
|
||||
### 3.5 `countries` — Страны (общие)
|
||||
|
||||
| Поле | Тип | Описание |
|
||||
|---|---|---|
|
||||
| iso2 | text, уникальное | `NL`, `DE`, … |
|
||||
| name | text (L) | |
|
||||
| lat, lng | number | точка на карте по умолчанию |
|
||||
| sort | number | |
|
||||
|
||||
### 3.6 `locations` — Локации (сайт)
|
||||
|
||||
| Поле | Тип | Описание |
|
||||
|---|---|---|
|
||||
| country | relationship → countries | уникальна в пределах сайта |
|
||||
| city | text (L) | |
|
||||
| lat, lng | number | точка города на карте |
|
||||
| status | select `available` \| `on_demand` \| `soon` | dedicated: «Серверы в наличии» / «Установим за 24 часа»; mass: «Включено» / «Скоро» |
|
||||
| freeServers | number, необязательное | счётчик (решение о показе — этап 1b) |
|
||||
| visible, sort | | |
|
||||
|
||||
### 3.7 `tags` — Теги (общие)
|
||||
|
||||
`name`, `code` (уникальное). Для фильтрации тарифов и будущей автоматизации выдачи ключей.
|
||||
|
||||
### 3.8 `tariffs` — Тарифы (сайт)
|
||||
|
||||
| Поле | Тип | Описание |
|
||||
|---|---|---|
|
||||
| name | text (L) | |
|
||||
| code | text, уникальное в сайте | |
|
||||
| type | select `dedicated` \| `mass` | поведение ключа: dedicated — страна выбирается при покупке, mass — все страны тарифа |
|
||||
| description | text (L) | |
|
||||
| features | array of text (L) | пункты «что входит» |
|
||||
| period | group: unit `month` \| `year`, count | |
|
||||
| devicesLimit | number, по умолчанию 5 | |
|
||||
| prices | array: currency, firstPeriodAmount, renewalAmount | строка на валюту; нет строки — тариф в этой валюте не продаётся |
|
||||
| countries | relationship → countries, many | страны тарифа |
|
||||
| tags | relationship → tags, many | |
|
||||
| badge | text (L) | «выгоднее» |
|
||||
| isDefault | checkbox | выбран по умолчанию на странице цен |
|
||||
| active, sort | | |
|
||||
|
||||
## 4. Люди
|
||||
|
||||
### 4.1 `admins` — Администраторы
|
||||
|
||||
Стандартная авторизация Payload (email и пароль), `maxLoginAttempts: 5`, `lockTime: 15 мин`.
|
||||
|
||||
| Поле | Тип | Описание |
|
||||
|---|---|---|
|
||||
| name | text | |
|
||||
| roles | multi-select `owner` \| `admin` \| `support` \| `content` | см. раздел 8 |
|
||||
| sites | доступ к тенантам | owner и admin видят все сайты |
|
||||
|
||||
### 4.2 `customers` — Пользователи (сайт)
|
||||
|
||||
Коллекция авторизации Payload со своей стратегией (вход по коду, [05-auth.md](05-auth.md)).
|
||||
|
||||
| Поле | Тип | Описание |
|
||||
|---|---|---|
|
||||
| email | email, null | уникален в пределах сайта, хранится в нижнем регистре |
|
||||
| emailVerifiedAt | date | |
|
||||
| telegramId | text, null | уникален в пределах сайта (этап 3) |
|
||||
| telegramUsername, telegramName | text | этап 3 |
|
||||
| locale | select | язык писем и бота |
|
||||
| currency | select | валюта аккаунта ([04-billing.md](04-billing.md), раздел 2) |
|
||||
| balance | number, только чтение | кэш суммы движений по счёту |
|
||||
| referralCode | text, уникальное в сайте | случайный |
|
||||
| referredBy | relationship → customers | |
|
||||
| status | select `active` \| `blocked` | |
|
||||
| registeredVia | select `email` \| `telegram` \| `admin` | |
|
||||
| registrationIpHash | text | HMAC от IP: только для сравнения при проверке самоприглашений |
|
||||
| marketingConsent | checkbox | |
|
||||
| lastLoginAt | date | |
|
||||
| defaultPaymentMethod | relationship → payment-methods | |
|
||||
| adminNotes | textarea | видят только администраторы |
|
||||
|
||||
### 4.3 `customer-sessions` — Сессии (служебная)
|
||||
|
||||
`customer`, `kind` (`web` \| `miniapp`), `tokenHash` (SHA-256, уникальное), `createdAt`, `expiresAt`, `lastSeenAt`, `ipHash`, `userAgent`, `revokedAt`.
|
||||
|
||||
### 4.4 `auth-codes` — Коды входа (служебная)
|
||||
|
||||
`site`, `email`, `purpose` (`login` \| `link_email`), `customer` (для привязки почты), `codeHash`, `expiresAt`, `attempts`, `consumedAt`, `requestIpHash`.
|
||||
|
||||
## 5. Ключи
|
||||
|
||||
### 5.1 `keys` — Ключи (сайт)
|
||||
|
||||
| Поле | Тип | Описание |
|
||||
|---|---|---|
|
||||
| code | text, уникальное | публичный код, напр. `K-7F2C9Q` |
|
||||
| customer | relationship | |
|
||||
| tariff | relationship | |
|
||||
| type | select `dedicated` \| `mass` | копия из тарифа |
|
||||
| location | relationship → locations | только dedicated |
|
||||
| status | select | см. 5.3 |
|
||||
| autoRenew | checkbox, по умолчанию да | |
|
||||
| activatedAt, expiresAt, graceUntil, cancelledAt | date | |
|
||||
| renewalAmount, currency | | цена продления, зафиксированная при покупке; администратор может изменить |
|
||||
| period, devicesLimit | | копии из тарифа на момент покупки |
|
||||
| ipAddress, ruIpAddress | text | для показа (dedicated) |
|
||||
| adminNotes | textarea | |
|
||||
|
||||
### 5.2 `connections` — Подключения
|
||||
|
||||
| Поле | Тип | Описание |
|
||||
|---|---|---|
|
||||
| key | relationship | |
|
||||
| kind | select `wireguard` \| `amneziawg` \| `happ` \| `xray` \| `other` | `happ` — ссылка подписки или crypt-ссылка Happ, `xray` — ссылка `vless://` и подобные |
|
||||
| title | text | «Устройство 1», «Ноутбук» |
|
||||
| deviceSlot | number | номер устройства для WG/AWG |
|
||||
| country | relationship → countries | для WG у mass — страна конфига; для RU-адреса — RU |
|
||||
| isRu | checkbox | подключение к RU-адресу (dedicated) |
|
||||
| payload | text, зашифрованное | содержимое конфига или URL |
|
||||
| fileName | text | имя файла при скачивании |
|
||||
| status | select `active` \| `revoked` | |
|
||||
| source | select `manual` \| `auto` | |
|
||||
| createdBy | relationship → admins | |
|
||||
|
||||
QR-коды строятся на лету из `payload`, не хранятся.
|
||||
|
||||
### 5.3 Статусы ключа
|
||||
|
||||
| Статус | Значение |
|
||||
|---|---|
|
||||
| `provisioning` | оплачен, готовится. Срок ещё не идёт. На этапе 1 так создаются все ключи: подключения добавляет администратор |
|
||||
| `active` | работает, срок идёт с `activatedAt` |
|
||||
| `grace` | срок истёк, оплата не прошла, ключ ещё работает до `graceUntil` |
|
||||
| `expired` | истёк, подключения отключаются |
|
||||
| `cancelled` | отменён (возврат) |
|
||||
| `suspended` | заблокирован администратором |
|
||||
|
||||
Переходы — в [04-billing.md](04-billing.md).
|
||||
|
||||
## 6. Деньги
|
||||
|
||||
### 6.1 `orders` — Заказы (сайт)
|
||||
|
||||
| Поле | Тип | Описание |
|
||||
|---|---|---|
|
||||
| number | text, уникальное | `VX-7K3M9Q` |
|
||||
| customer | relationship | |
|
||||
| type | select `new_key` \| `renewal` | |
|
||||
| tariff | relationship | |
|
||||
| location | relationship | для `new_key` у dedicated |
|
||||
| key | relationship | для `renewal`; для `new_key` заполняется после оплаты |
|
||||
| renewalFor | date | `expiresAt` ключа, который продлеваем; уникально вместе с `key` |
|
||||
| currency, amount | | полная стоимость |
|
||||
| status | select `pending` \| `paid` \| `cancelled` \| `expired` \| `refunded` \| `partially_refunded` | |
|
||||
| source | select `web` \| `miniapp` \| `bot` \| `admin` \| `auto_renewal` | |
|
||||
| paidAt, cancelledAt, validUntil | date | `validUntil` — неоплаченный заказ истекает через 24 часа |
|
||||
| refundedAmount | number | |
|
||||
| adminNotes | textarea | |
|
||||
|
||||
### 6.2 `payments` — Платежи (сайт)
|
||||
|
||||
| Поле | Тип | Описание |
|
||||
|---|---|---|
|
||||
| number | text, уникальное | |
|
||||
| customer | relationship | |
|
||||
| paymentSystem | relationship | |
|
||||
| adapter | text | копия из платёжной системы |
|
||||
| purpose | select `order` \| `topup` | |
|
||||
| order | relationship | для `order` |
|
||||
| currency, amount | | |
|
||||
| status | select `pending` \| `succeeded` \| `failed` \| `cancelled` \| `refunded` \| `partially_refunded` | |
|
||||
| providerPaymentId | text | уникально вместе с `adapter` |
|
||||
| providerStatus, providerData | text, json | только для администраторов |
|
||||
| methodSummary | group: type, brand, last4 | «Карта ···· 4242», «USDT (TRC-20)» |
|
||||
| savedPaymentMethod | relationship | если карта сохранена |
|
||||
| idempotencyKey | text, уникальное | |
|
||||
| succeededAt, processedAt, failedAt | date | `processedAt` — цепочка действий выполнена |
|
||||
| failureReason | text | |
|
||||
| createdBy | relationship → admins | для ручных |
|
||||
| comment | textarea | |
|
||||
|
||||
### 6.3 `ledger-entries` — Движения по счёту (сайт)
|
||||
|
||||
Только создание. Изменять и удалять нельзя никому, ошибка исправляется встречной записью.
|
||||
|
||||
| Поле | Тип | Описание |
|
||||
|---|---|---|
|
||||
| customer | relationship | |
|
||||
| currency | | валюта аккаунта |
|
||||
| amount | number со знаком | `+` зачисление, `−` списание |
|
||||
| type | select `topup` \| `order_charge` \| `refund` \| `referral_bonus` \| `adjustment` \| `payout` | `payout` — возврат денег с баланса на карту (этап 4) |
|
||||
| payment, order, key, referral | relationship | источник записи |
|
||||
| balanceAfter | number | баланс после записи |
|
||||
| comment | text | обязателен для `adjustment` |
|
||||
| createdBy | relationship → admins, null | null — система |
|
||||
|
||||
Уникальность: (`payment`, `topup`), (`order`, `order_charge`), (`referral`, `customer`, `referral_bonus`). Это защищает от двойного проведения.
|
||||
|
||||
### 6.4 `payment-methods` — Сохранённые способы оплаты (сайт)
|
||||
|
||||
`customer`, `paymentSystem`, `providerMethodId` (зашифрованное), `type`, `brand`, `last4`, `expMonth`, `expYear`, `isDefault`, `status` (`active` \| `detached`), `detachedAt`.
|
||||
|
||||
### 6.5 `referrals` — Приглашения (сайт)
|
||||
|
||||
`referrer`, `referee` (уникальное), `status` (`pending` \| `review` \| `rewarded` \| `rejected`), `qualifyingOrder`, `rewardedAt`, `rejectReason`.
|
||||
|
||||
## 7. Контент и коммуникации
|
||||
|
||||
### 7.1 `pages` — Страницы (сайт)
|
||||
|
||||
`slug` (уникальное в сайте: `terms`, `privacy`, `refund`, `cookies`), `title` (L), `content` (L, Lexical), `seo` (title (L), description (L)). Черновики и версии Payload включены.
|
||||
|
||||
### 7.2 `faq` — Вопросы и ответы (сайт)
|
||||
|
||||
`question` (L), `answer` (L, rich text), `placements` (multi: `home`, `pricing`, `support_connection`, `support_billing`, `support_locations`, `cabinet`, `miniapp`), `sort`, `active`. Один вопрос можно показать в нескольких местах.
|
||||
|
||||
### 7.3 `email-templates` — Шаблоны писем (сайт)
|
||||
|
||||
`code` (уникальное в сайте), `subject` (L), `body` (L, rich text с переменными `{{code}}`, `{{keyTitle}}`, `{{expiresAt}}`, `{{amount}}` …), `active`. Шапка и подвал письма — в коде, для каждого бренда свои.
|
||||
|
||||
Коды шаблонов: `login_code`, `link_email_code`, `order_paid`, `key_provisioning`, `key_ready`, `renewal_reminder`, `expiry_reminder`, `renewal_succeeded`, `renewal_failed`, `grace_started`, `key_expired`, `topup_succeeded`, `referral_reward`, `refund_issued`, `admin_new_order`, `admin_provisioning_needed`, `admin_new_lead`.
|
||||
|
||||
### 7.4 `leads` — Заявки (сайт)
|
||||
|
||||
`type` (`individual_terms` \| `contact`), `name`, `email`, `telegram`, `whatsapp`, `topic`, `message`, `locale`, `pageUrl`, `customer`, `status` (`new` \| `in_progress` \| `done` \| `spam`), `crmId` и `chatwootId` (этап 2), `ipHash`.
|
||||
|
||||
### 7.5 `media` — Файлы
|
||||
|
||||
Загрузки Payload: изображения для контента, иконки платёжных систем, картинки для соцсетей.
|
||||
|
||||
## 8. Журналы
|
||||
|
||||
### 8.1 `email-log` — Журнал писем
|
||||
|
||||
`site`, `to`, `templateCode`, `subject`, `transport` (`smtp` \| `sendmail` \| `log_only`), `status` (`queued` \| `sent` \| `failed`), `error`, `messageId`, `sentAt`, `customer`, `debugCode`.
|
||||
|
||||
`debugCode` — код входа открытым текстом. Заполняется, только если в настройках почты включено «Записывать коды входа в журнал». Видят роли owner и admin. Записи с кодом удаляются через сутки, остальные — через `logRetentionDays`.
|
||||
|
||||
### 8.2 `audit-log` — Журнал действий
|
||||
|
||||
`actor` (администратор или система), `action` (`payment.manual`, `ledger.adjustment`, `key.activate`, `order.refund`, `customer.merge`, …), `entityType`, `entityId`, `before`, `after`, `ipHash`, `createdAt`. Только создание.
|
||||
|
||||
## 9. Глобальные настройки
|
||||
|
||||
### 9.1 `email-settings` — Почта
|
||||
|
||||
| Поле | Описание |
|
||||
|---|---|
|
||||
| transport | `smtp` \| `sendmail` \| `log_only` (только журнал, без отправки) |
|
||||
| smtp | host, port, secure, user, password (зашифрованный) |
|
||||
| sendmailPath | по умолчанию `/usr/sbin/sendmail` |
|
||||
| logLoginCodes | «Записывать коды входа в журнал». В prod по умолчанию выключено; когда включено, в админке висит предупреждение |
|
||||
| logRetentionDays | по умолчанию 30 |
|
||||
| Кнопка | «Отправить тестовое письмо» на указанный адрес |
|
||||
|
||||
## 10. Доступ
|
||||
|
||||
| Коллекция | owner, admin | support | content | пользователь (через приложение) |
|
||||
|---|---|---|---|---|
|
||||
| Настройки и справочники | всё | чтение | чтение | — |
|
||||
| pages, faq, email-templates, локализация | всё | чтение | всё | — |
|
||||
| customers | всё | чтение, правка заметок, блокировка | — | только свой профиль |
|
||||
| keys, connections | всё | всё | — | только свои, чтение |
|
||||
| orders, payments | всё | чтение, ручной платёж | — | только свои, чтение |
|
||||
| ledger-entries | создание корректировок, чтение | чтение | — | только свои, чтение |
|
||||
| leads | всё | всё | — | — |
|
||||
| email-log, audit-log | чтение | чтение без `debugCode` | — | — |
|
||||
|
||||
Пользователь никогда не ходит в REST Payload напрямую: кабинет читает данные на сервере через Local API с `overrideAccess: false` и `user`.
|
||||
Reference in new issue
Block a user