Files
veilix.app/docs/03-data-model.md
T
andreypr207andClaude Opus 5.5 515acfb69d Документация: этапы по фазам, решения D-026…D-033
- Новый порядок этапов (D-026): фаза 1 — концепт на dev без внешних
  сервисов (1b сайт, 1c вход, 1d ключи, 1e счёт и демо-оплата,
  1f демо-выдача, 1g продления, 1h рефералы); фаза 2 — Telegram;
  фаза 3 — ALTCHA, ЮKassa, почта, Twenty, Chatwoot, Umami, выдача
  через API и публичный запуск; фаза 4 — звёзды и эквайринги.
- Решения: демо-оплата по списку IP (D-027), выдача ключей через
  адаптер manual/demo/api (D-028), страницы сайта из блоков Payload
  (D-029), пересборка через revalidatePath (D-030), RU-адрес у mass
  (D-031), счётчик свободных серверов (D-032), переводы на 5 языков
  в каждом этапе (D-033).
- Тексты описывают работающий продукт; Вейликс — выделенный IP на
  общем сервере; Telegram поддержки — @veilix_support; сроки ответа
  не обещаем; VPN-серверы не ведут журналов.
- Ссылки на этапы во всех документах, Q-12 решён (ЮKassa для RUB),
  R-19 (библиотеки для headless Chromium) сделано.

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

28 KiB
Raw Blame History

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. Схема связей

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)
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)
topupPresets array: currency, amounts[] суммы пополнения в кабинете (04-billing.md, раздел 4)
graceDays number, по умолчанию 3 льготный период
reminderDays number, по умолчанию 3 за сколько дней напоминать
renewalLeadHours number, по умолчанию 24 за сколько часов до окончания пробовать продлить
expiringBadgeDays number, по умолчанию 7 когда показывать «Истекает»
provisioningMode select manual | demo | api режим выдачи ключей (D-028): demo — с этапа 1f, api — с 3g
seo group: titleTemplate (L), description (L), ogImage значения по умолчанию

На этапе 1a сделаны code (после создания не меняется), name, orderPrefix и locales. Остальные поля добавляют этапы, которые ими пользуются: 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); 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)
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).

Поле Тип Описание
email email, null уникален в пределах сайта, хранится в нижнем регистре
emailVerifiedAt date
telegramId text, null уникален в пределах сайта (этап 2a)
telegramUsername, telegramName text этап 2a
locale select язык писем и бота
currency select валюта аккаунта (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)
payload text, зашифрованное содержимое конфига или URL
fileName text имя файла при скачивании
status select active | revoked
source select manual | auto
createdBy relationship → admins

QR-коды строятся на лету из payload, не хранятся.

5.3 Статусы ключа

Статус Значение
provisioning оплачен, готовится. Срок ещё не идёт. Подключения создаёт адаптер выдачи (D-028) или администратор
active работает, срок идёт с activatedAt
grace срок истёк, оплата не прошла, ключ ещё работает до graceUntil
expired истёк, подключения отключаются
cancelled отменён (возврат)
suspended заблокирован администратором

Переходы — в 04-billing.md.

5.4 key-extensions — Продления без оплаты (сайт)

Реферальный бонус и «Продлить вручную» (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): slug (уникальное в сайте: home, pricing, locations, support, terms, privacy, refund, cookies), title (L), layout (блоки, поля блоков локализованы), seo (title (L), description (L), image). Черновики и версии Payload включены. Набор блоков — в src/payload/blocks/, описание — по итогам этапа 1b.

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); нужна ли коллекция как буфер перед 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.