Files
veilix.app/docs/03-data-model.md
T
andreypr207andClaude Opus 5.5 20491d1ba4 Этап 1b: сайт
- Страницы обоих брендов из блоков Payload (D-029): главная, цены,
  локации, поддержка, 4 юридические, 404 и 500. Коллекции pages
  (черновики и версии) и faq с местами показа, 12 типов блоков; поля
  sites — почта и Telegram поддержки, часы работы, SEO по умолчанию.
- Карта и глобус рисуются на сервере (d3-geo), контуры во внешнем
  SVG через <use>; выбор локации синхронизирован между картой и сеткой.
- Тексты сайта и юридические черновики на 5 языках: de, fr, es —
  перевод с глоссарием; seed создаёт страницы и FAQ, только если их
  нет, SEED_CONTENT=reset пересоздаёт.
- SEO: title и описания, canonical, hreflang с x-default,
  sitemap.xml и robots.txt на каждый домен, картинки для соцсетей.
- Статическая сборка и пересборка через revalidatePath из хуков
  Payload (D-030); /svc/internal/revalidate для проверки.
- 404 — статичная страница со статусом 404 через proxy; на сайте
  только опубликованные страницы.
- Производительность: Client Hints темы Payload только в админке,
  предзагрузка 2 файлов шрифтов вместо 6; Lighthouse mobile 97–99.
- Контраст --ink-3 по WCAG AA, радиокнопки вместо div role=button.
- Документация: итоги 1b, глоссарий de/fr/es, трактовки юридических
  черновиков подтверждены, переносимые вопросы — в этапах 3e–3h.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 04:53:15 +02:00

422 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`).
7. Валюта везде хранится кодом ISO 4217 — и в деньгах, и в настройках (валюты домена и платёжной системы, строки цен). Код проверяется по справочнику, в админке это выпадающий список из «Валют».
8. Переводы полей Payload хранятся в таблицах `<коллекция>_l10n` (настройка адаптера `localesSuffix`). Имя по умолчанию `<коллекция>_locales` совпало бы с таблицей поля `locales` у сайтов и доменов.
## 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 : "пригласил"
KEYS ||--o{ KEY_EXTENSIONS : "продления без оплаты"
REFERRALS ||--o{ KEY_EXTENSIONS : "бонус"
CUSTOMERS ||--o{ CUSTOMER_SESSIONS : "сессии"
```
## 3. Настройки и справочники
### 3.1 `sites` — Сайты (тенанты)
| Поле | Тип | Описание |
|---|---|---|
| code | select `dedicated` \| `mass`, уникальное | бренд: по нему выбираются тема и страницы |
| name | text (L) | «Вейликс» / «Veilix» |
| orderPrefix | text | префикс номеров: `VX`, `VV` |
| locales | multi-select из языков конфига | языки контента сайта; en обязателен; домены показывают часть этих языков ([D-023](decisions/D-023-site-locales.md)) |
| supportEmail | email | почта поддержки: `help@veilix.app`, `help@veilixvpn.com` |
| emailFrom | group: name, address | отправитель писем, напр. «Вейликс» <no-reply@veilix.app> |
| supportBot | text | Telegram поддержки — имя для ссылки `t.me/…`: пока `veilix_support` у обоих брендов, с этапа 3e — бот поддержки бренда |
| supportHours | text (L) | часы поддержки: «с 10 до 20 по московскому времени в рабочие дни» |
| mainBot | text | имя основного бота (этап 2a) |
| referralEnabled | checkbox | |
| referralBonusDays | number, по умолчанию 30 | бонус «месяц в подарок»: дни срока ключа обоим ([D-025](decisions/D-025-referral-days.md)) |
| topupPresets | array: currency, amounts[] | суммы пополнения в кабинете ([04-billing.md](04-billing.md), раздел 4) |
| graceDays | number, по умолчанию 3 | льготный период |
| reminderDays | number, по умолчанию 3 | за сколько дней напоминать |
| renewalLeadHours | number, по умолчанию 24 | за сколько часов до окончания пробовать продлить |
| expiringBadgeDays | number, по умолчанию 7 | когда показывать «Истекает» |
| provisioningMode | select `manual` \| `demo` \| `api` | режим выдачи ключей ([D-028](decisions/D-028-provisioning-adapter.md)): `demo` — с этапа 1f, `api` — с 3g |
| seo | group: titleTemplate (L), description (L), ogImage | значения по умолчанию |
На этапе 1a сделаны `code` (после создания не меняется), `name`, `orderPrefix` и `locales`, на этапе 1b — `supportEmail`, `supportBot`, `supportHours` и `seo`. Остальные поля добавляют этапы, которые ими пользуются: SEO, контакты и часы поддержки — 1b, отправитель писем — 1c, порог «Истекает» — 1d, пресеты пополнения — 1e, режим выдачи — 1f, сроки продлений и напоминаний — 1g, рефералы — 1h, основной бот — 2a.
### 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 | этап 3f |
| 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`, `demo` (1e, [D-027](decisions/D-027-demo-payments.md)); `yookassa` (3b); `stripe`, `crypto_*` (4b) |
| title | text (L) | что видит пользователь: «Банковская карта», «Карта МИР», «СБП», «Криптовалюта» |
| description | text (L) | подсказка: «USDT, BTC, ETH, курс фиксируется на 15 минут» |
| icon | upload | |
| currencies | multi-select валют | в каких валютах доступна |
| purposes | multi-select `order` \| `topup` | оплата заказа, пополнение |
| visibleToCustomers | checkbox | у `manual` всегда выключено (проверяется при сохранении): ей пользуются только администраторы |
| allowedIps | text | для `demo`: IP и подсети, с которых система видна; пусто — не видна никому |
| mode | select `test` \| `live` | |
| limits | array: currency, min, max | |
| capabilities | только чтение, из адаптера | `recurring`, `refunds`, `saveMethod`; объявлены в `src/core/payments/adapters.ts`, в базе не хранятся |
| 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, необязательное | счётчик; на сайте показывается, если заполнен ([D-032](decisions/D-032-free-servers.md)) |
| 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 мин`. Первый администратор окружения (экран первого входа) становится owner. Последнего owner нельзя лишить роли и удалить. Роли и сайты меняет только owner, остальные правят только своё имя и пароль.
| Поле | Тип | Описание |
|---|---|---|
| name | text | |
| roles | multi-select `owner` \| `admin` \| `support` \| `content` | см. раздел 8 |
| sites | доступ к тенантам | owner и admin видят все сайты, поэтому поле показывается только ролям support и content; на экране первого входа скрыто |
### 4.2 `customers` — Пользователи (сайт)
Коллекция авторизации Payload со своей стратегией (вход по коду, [05-auth.md](05-auth.md)).
| Поле | Тип | Описание |
|---|---|---|
| email | email, null | уникален в пределах сайта, хранится в нижнем регистре |
| emailVerifiedAt | date | |
| telegramId | text, null | уникален в пределах сайта (этап 2a) |
| telegramUsername, telegramName | text | этап 2a |
| 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-адресу; у обоих брендов ([D-031](decisions/D-031-mass-ru-address.md)) |
| payload | text, зашифрованное | содержимое конфига или URL |
| fileName | text | имя файла при скачивании |
| status | select `active` \| `revoked` | |
| source | select `manual` \| `auto` | |
| createdBy | relationship → admins | |
QR-коды строятся на лету из `payload`, не хранятся.
### 5.3 Статусы ключа
| Статус | Значение |
|---|---|
| `provisioning` | оплачен, готовится. Срок ещё не идёт. Подключения создаёт адаптер выдачи ([D-028](decisions/D-028-provisioning-adapter.md)) или администратор |
| `active` | работает, срок идёт с `activatedAt` |
| `grace` | срок истёк, оплата не прошла, ключ ещё работает до `graceUntil` |
| `expired` | истёк, подключения отключаются |
| `cancelled` | отменён (возврат) |
| `suspended` | заблокирован администратором |
Переходы — в [04-billing.md](04-billing.md).
### 5.4 `key-extensions` — Продления без оплаты (сайт)
Реферальный бонус и «Продлить вручную» ([04-billing.md](04-billing.md), раздел 6.4). Только создание, как у движений по счёту.
| Поле | Тип | Описание |
|---|---|---|
| key | relationship | |
| days | number | сколько дней добавлено |
| reason | select `referral` \| `compensation` | реферальный бонус или компенсация из админки |
| referral | relationship → referrals | для `referral` |
| appliedAt | date, null | когда сдвинут срок; пусто — ключ готовится, дни добавятся при выдаче |
| expiresAtBefore, expiresAtAfter | date | срок до и после |
| comment | text | обязателен для `compensation` |
| createdBy | relationship → admins, null | null — система |
Уникальность: (`referral`, `key`) — бонус не начисляется дважды.
## 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` \| `adjustment` \| `payout` | `payout` — возврат денег с баланса на карту (этап 4b). Реферальный бонус — не деньги, а дни (5.4) |
| payment, order, key | relationship | источник записи |
| balanceAfter | number | баланс после записи |
| comment | text | обязателен для `adjustment` |
| createdBy | relationship → admins, null | null — система |
Уникальность: (`payment`, `topup`), (`order`, `order_charge`). Это защищает от двойного проведения.
### 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`, `days` (копия `sites.referralBonusDays` при начислении), `refereeKey`, `referrerKey` (пусто, пока у пригласившего нет подходящего ключа: дни ждут его следующего ключа), `rewardedAt`, `rejectReason`.
## 7. Контент и коммуникации
### 7.1 `pages` — Страницы (сайт)
Страницы сайта из блоков ([D-029](decisions/D-029-pages-blocks.md)): `slug` (уникальное в сайте: `home`, `pricing`, `locations`, `support`, `terms`, `privacy`, `refund`, `cookies`), `title` (L), `layout` (блоки, поля блоков локализованы), `seo` (title (L), description (L), image). Черновики и версии Payload включены. Блоки (`src/payload/blocks/`), поля блоков локализованы, в текстах — подстановки `{priceFirst}`, `{priceRenewal}`, `{priceMonth}`, `{priceYear}`, `{pricePerMonth}`, `{savings}`, `{countries}`, `{devices}`:
| Блок | Секция макета |
|---|---|
| `hero` | первый экран главной: плашки, H1, две кнопки, плашка под глобусом |
| `advantages` | «Что это даёт на практике»: большая карточка, малые карточки, карточка маршрутов |
| `pricing` | цены на главной: у dedicated — выбор локации и заявка, у mass — планы «Месяц» и «Год» |
| `crossLink` | ссылка на сайт другого бренда |
| `faq` | вопросы из мест показа: одно место — список, несколько — вкладки |
| `pricingHero` | верх страницы цен: крупная цена, «Что входит» |
| `locationGrid` | сетка локаций: у dedicated с ценами и «Купить», у mass — со статусами |
| `cards` | карточки внизу страниц: текст, ссылка на другой бренд, заявка |
| `mapHero` | верх страницы локаций: H1, карта, легенда |
| `contacts` | верх поддержки: H1, Telegram и почта из настроек сайта, часы |
| `contactForm` | «Написать нам» с темами обращения |
| `richText` | текст: юридические страницы |
### 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` — Заявки (сайт)
Этап 3d. До него формы — только интерфейс ([D-026](decisions/D-026-stage-order.md)); нужна ли коллекция как буфер перед Twenty — решаем на этапе. Поля: `type` (`individual_terms` \| `contact`), `name`, `email`, `telegram`, `whatsapp`, `topic`, `message`, `locale`, `pageUrl`, `customer`, `status` (`new` \| `in_progress` \| `done` \| `spam`), `crmId` (3d) и `chatwootId` (3e), `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 | пользователь (через приложение) |
|---|---|---|---|---|
| Настройки и справочники | всё (удаление сайта — только owner) | чтение | чтение | — |
| admins | owner — всё, admin — только себя | только себя | только себя | — |
| media | всё | чтение | всё | файлы открыты всем: их показывают сайты |
| pages, faq, email-templates, локализация | всё | чтение | всё | — |
| customers | всё | чтение, правка заметок, блокировка | — | только свой профиль |
| keys, connections | всё | всё | — | только свои, чтение |
| orders, payments | всё | чтение, ручной платёж | — | только свои, чтение |
| ledger-entries, key-extensions | создание корректировок и компенсаций, чтение | чтение | — | только свои, чтение |
| leads | всё | всё | — | — |
| email-log, audit-log | чтение | чтение без `debugCode` | — | — |
Пользователь никогда не ходит в REST Payload напрямую: кабинет читает данные на сервере через Local API с `overrideAccess: false` и `user`.