Various models
Обзор
Various Models — это динамические пользовательские таблицы в платформе Accelera Loyalty. Позволяют создавать произвольные модели данных без изменения кода, управлять их схемой, записями, а также выполнять массовые операции через API.
Юзкейсы
Хранение справочных данных
Каталоги товаров, справочники городов, тарифные планы — любые данные, по которым нужен быстрый поиск и фильтрация из игрового клиента.
Хранение результатов рассылок
Статусы доставки push-уведомлений, SMS, email — с возможностью фильтрации по client_id, каналу, статусу.
Промо-механики
Списки участников акций, промо-коды, купоны — с поддержкой batch-загрузки и upsert по уникальному ключу.
Аналитические витрины
Агрегированные данные для отображения в клиентском приложении — лидерборды, статистика, рейтинги.
Интеграционные буферы
Промежуточное хранение данных из внешних систем перед обработкой бизнес-логикой платформы.
Поддерживаемые типы данных
| Тип | PostgreSQL | Описание | Пример |
|---|---|---|---|
string | varchar | Строка до 2^31-1 байт | "Hello" |
integer | integer | Целое число (32-бит) | 42 |
float | real | Число с плавающей точкой | 3.14 |
boolean | boolean | Логическое значение | true / false |
json | json | JSON-документ | {"key": "value"} |
text | text | Большой текст | длинные строки |
date | date | Только дата | "2026-01-15" |
time | time | Только время | "14:30:00" |
datetime | timestamp | Дата и время | "2026-01-15T14:30:00" |
Системные колонки (создаются автоматически)
| Колонка | Тип | Описание |
|---|---|---|
uid | BIGINT (PK, auto-increment) | Уникальный идентификатор записи |
createdAt | timestamp | Время создания, заполняется автоматически |
updatedAt | timestamp | Время последнего обновления |
Системные колонки нельзя переименовать или удалить.
Валидаторы полей
При описании атрибутов модели можно указать ограничения:
| Параметр | Тип | Описание |
|---|---|---|
allowNull | boolean | Разрешить NULL-значения (по умолчанию true) |
unique | boolean | Уникальное значение (по умолчанию false) |
defaultValue | any | Значение по умолчанию (должно соответствовать типу) |
min | number | Минимальное числовое значение |
max | number | Максимальное числовое значение |
minLength | number | Минимальная длина строки |
maxLength | number | Максимальная длина строки |
pattern | string | Регулярное выражение для валидации |
enum | array | Список допустимых значений |
Пример описания атрибута
{
"name": "email",
"type": "string",
"allowNull": false,
"unique": true,
"maxLength": 255,
"minLength": 3,
"pattern": "^[^@]+@[^@]+\\.[^@]+$"
}Индексы
- Индексы ускоряют поиск по указанным колонкам
- Задаются как массив имён колонок при создании или обновлении модели
- Автоматически именуются
{modelName}_{columnName}_idx - Ограничение: JSON-колонки не поддерживают индексирование
{
"indexes": ["email", "status", "createdAt"]
}API: Управление моделями (Management)
Аутентификация: сессионная (cookie) Базовый путь: /v1/management/various
Получить список моделей
GET /v1/management/various/Ответ: массив определений моделей.
Получить модель по ID
GET /v1/management/various/{id}Создать модель
POST /v1/management/various/
Content-Type: application/jsonТело запроса:
{
"modelName": "products",
"title": "Каталог товаров",
"description": "Справочник товаров для программы лояльности",
"attributes": [
{
"name": "product_id",
"type": "string",
"allowNull": false,
"unique": true
},
{
"name": "name",
"type": "string",
"allowNull": false,
"maxLength": 200
},
{
"name": "price",
"type": "float",
"allowNull": false,
"min": 0
},
{
"name": "category",
"type": "string",
"allowNull": true
},
{
"name": "active",
"type": "boolean",
"defaultValue": true
}
],
"indexes": ["product_id", "category"]
}Ограничения:
modelName: 2-50 символов, уникальноеattributes: непустой массив- Имя не должно совпадать с существующей таблицей
Обновить метаданные модели
PUT /v1/management/various/{id}
Content-Type: application/jsonТело запроса (только метаданные, не структура):
{
"title": "Новое название",
"description": "Новое описание"
}Изменить схему модели
POST /v1/management/various/update-schema
Content-Type: application/jsonТело запроса:
{
"modelName": "products",
"attributes": [
{"name": "product_id", "type": "string", "allowNull": false, "unique": true},
{"name": "name", "type": "string", "allowNull": false},
{"name": "price", "type": "float", "allowNull": false},
{"name": "category", "type": "string"},
{"name": "brand", "type": "string", "allowNull": true}
],
"removedColumns": ["old_column"],
"renamedColumns": [
{"old": "category", "new": "product_category"}
],
"indexes": ["product_id", "product_category"]
}Возможности:
- Добавление новых колонок
- Удаление существующих колонок
- Переименование колонок
- Изменение типа данных (с проверкой совместимости)
- Изменение constraints (
allowNull,unique,defaultValue) - Управление индексами
Ограничения:
- Нельзя переименовать/удалить
uid,createdAt,updatedAt - Нельзя добавить
NOT NULLесли в колонке есть NULL-значения - Нельзя добавить
UNIQUEесли есть дубликаты - Нельзя индексировать JSON-колонки
Валидация изменений схемы (dry run)
POST /v1/management/various/validate-schema
Content-Type: application/jsonТело запроса аналогично update-schema. Возвращает результат валидации без применения изменений:
{"valid": true}или
{
"valid": false,
"errors": [
{
"column": "status",
"constraint": "NOT NULL",
"message": "Column has 42 null values",
"nullCount": 42
}
]
}Удалить модель
DELETE /v1/management/various/{model}Таблица не удаляется физически, а переименовывается с добавлением timestamp (soft delete).
API: Управление записями (Management)
Базовый путь: /v1/management/various/records
Получить записи
GET /v1/management/various/records/{model}Query-параметры (формат Sequelize):
?where[status]=1&where[name][like]=%test%Получить запись по ID
GET /v1/management/various/records/{model}/{id}Создать/обновить запись
POST /v1/management/various/records/{model}
Content-Type: application/json{
"product_id": "SKU-001",
"name": "Товар 1",
"price": 999.90,
"category": "electronics",
"active": true
}Обновить конкретную запись
POST /v1/management/various/records/{model}/{id}
Content-Type: application/jsonУдалить запись
DELETE /v1/management/various/records/{model}/{id}DataTables (серверная пагинация)
POST /v1/management/various/datatables/{model}
Content-Type: application/json{
"draw": 1,
"start": 0,
"length": 25,
"search": {"value": "поисковый запрос"},
"columns": [
{"data": "name", "searchable": true},
{"data": "status", "searchable": false}
]
}Ответ:
{
"draw": 1,
"recordsTotal": 15000,
"recordsFiltered": 342,
"data": [...]
}API: Работа с записями (Internal)
Аутентификация: Basic Auth Базовый путь: /v1/internal/various
Получить список моделей
GET /v1/internal/various/
Authorization: Basic <credentials>Получить записи с фильтрацией
GET /v1/internal/various/records/{model}
Authorization: Basic <credentials>Query-параметры:
| Параметр | Описание | Пример |
|---|---|---|
query | JSON-фильтр | {"status": 1} |
fields | Выбор колонок | name,price,status |
limit | Кол-во записей (макс. 200) | 50 |
offset | Страница | 0 |
sort_by | Сортировка | price.asc,name.desc |
group_by | Группировка | category |
count | Агрегация | uid,total |
Специальные заголовки:
| Заголовок | Описание | Значение |
|---|---|---|
loyalty-use-cache | Включить кеширование | true / false |
loyalty-cache-lifetime | Время жизни кеша (минуты) | 60 (по умолчанию) |
Операторы фильтрации
Сравнение
{"age": {"gt": 18}} // age > 18
{"age": {"gte": 18}} // age >= 18
{"age": {"lt": 65}} // age < 65
{"age": {"lte": 65}} // age <= 65
{"status": {"ne": 0}} // status != 0
{"status": {"eq": 1}} // status = 1Строковые
{"email": {"like": "%@company.com"}} // LIKE
{"name": {"notLike": "%test%"}} // NOT LIKE
{"code": {"regexp": "^[A-Z]{3}\\d+"}} // regex ~Диапазоны
{"price": {"between": [100, 1000]}} // BETWEEN 100 AND 1000
{"age": {"notBetween": [0, 17]}} // NOT BETWEEN 0 AND 17Списки
{"status": {"in": [1, 2, 3]}} // IN (1, 2, 3)
{"category": {"notIn": ["test", "demo"]}} // NOT INЛогические
{"and": [{"status": 1}, {"active": true}]}
{"or": [{"role": "admin"}, {"role": "moderator"}]}Комбинированный пример
GET /v1/internal/various/records/products?query={"and":[{"price":{"gte":100,"lte":5000}},{"active":true},{"category":{"in":["electronics","gaming"]}}]}&sort_by=price.asc&fields=product_id,name,price&limit=50Создать или обновить запись
POST /v1/internal/various/records/{model}
Authorization: Basic <credentials>
Content-Type: application/jsonБез query — создание новой записи:
{
"product_id": "SKU-001",
"name": "Товар 1",
"price": 999.90
}С query — обновление существующей записи:
POST /v1/internal/various/records/{model}?query={"product_id":"SKU-001"}{
"price": 899.90,
"active": false
}Если запись по фильтру не найдена — будет создана новая.
Удалить записи
DELETE /v1/internal/various/records/{model}
Authorization: Basic <credentials>С query — удаление по фильтру:
DELETE /v1/internal/various/records/{model}?query={"status":0}Без query — TRUNCATE всей таблицы.
Batch-загрузка
Массовая вставка/обновление записей с буферизацией через Redis.
POST /v1/internal/various/records/{model}/batch?key={conflict_column}
Authorization: Basic <credentials>
Content-Type: application/jsonПараметры:
key(обязательный) — имя колонки с уникальным индексом для разрешения конфликтов (upsert)
Тело запроса — одна запись:
{
"product_id": "SKU-001",
"name": "Товар 1",
"price": 999.90,
"active": true
}Ответ: {"status": "received"}
Как работает batch
- Запись проходит валидацию (типы, обязательные поля, наличие conflict key)
- Добавляется в Redis-очередь
- При достижении порога (по умолчанию 20 записей) или по крону (каждые 20 секунд) — записи сбрасываются в БД
- Используется upsert: если запись с таким
keyсуществует — обновляется, иначе — создаётся - При ошибке записи попадают в очередь ошибок для ручного разбора
Валидация перед постановкой в очередь
- Наличие conflict key в каждой записи
- Соответствие типов данных описанию модели
- Наличие обязательных полей (
allowNull: falseбезdefaultValue)
Кеширование
Кеширование доступно только для Internal API и управляется заголовками запроса.
Включение
GET /v1/internal/various/records/{model}?query={...}
loyalty-use-cache: true
loyalty-cache-lifetime: 30Поведение
- Ключ кеша: полный URL запроса включая query-параметры
- Хранилище: Redis
- TTL: значение
loyalty-cache-lifetimeв минутах (по умолчанию 60) - Cache miss: запрос к БД, результат сохраняется в кеш
- Cache hit: возврат из Redis без обращения к БД
Когда использовать
- Справочные данные, которые редко меняются
- Высоконагруженные эндпоинты с одинаковыми запросами
- Данные, для которых допустима задержка обновления
Когда не использовать
- Часто обновляемые данные
- Запросы, требующие актуальных значений в реальном времени
Формат ошибок
{
"error": {
"message": "Описание ошибки",
"details": "Детали из БД (если применимо)"
}
}HTTP-коды ответов
| Код | Описание |
|---|---|
200 | Успешная операция |
400 | Ошибка валидации |
401 | Не авторизован |
404 | Модель или запись не найдена |
409 | Конфликт (запись уже существует) |
500 | Внутренняя ошибка сервера |
Примеры: типичные сценарии
Сценарий 1: Каталог товаров
Создание модели:
POST /v1/management/various/
{
"modelName": "product_catalog",
"title": "Каталог товаров",
"attributes": [
{"name": "sku", "type": "string", "allowNull": false, "unique": true},
{"name": "name", "type": "string", "allowNull": false, "maxLength": 200},
{"name": "price", "type": "float", "min": 0},
{"name": "category", "type": "string"},
{"name": "in_stock", "type": "boolean", "defaultValue": true}
],
"indexes": ["sku", "category"]
}Массовая загрузка товаров:
POST /v1/internal/various/records/product_catalog/batch?key=sku
{"sku": "A001", "name": "Наушники", "price": 2990, "category": "audio"}Поиск товаров:
GET /v1/internal/various/records/product_catalog?query={"category":"audio","price":{"lte":5000},"in_stock":true}&sort_by=price.asc
loyalty-use-cache: trueСценарий 2: Результаты рассылки
Создание модели:
POST /v1/management/various/
{
"modelName": "push_results",
"title": "Результаты push-рассылок",
"attributes": [
{"name": "client_id", "type": "string", "allowNull": false},
{"name": "message_id", "type": "string", "allowNull": false, "unique": true},
{"name": "channel", "type": "string"},
{"name": "delivery_status", "type": "string"},
{"name": "delivery_details", "type": "string"},
{"name": "sent_at", "type": "datetime"}
],
"indexes": ["client_id", "message_id", "delivery_status"]
}Запрос статусов по клиенту:
GET /v1/internal/various/records/push_results?query={"client_id":"ABC-123","delivery_status":"DELIVERED"}&sort_by=sent_at.desc&limit=20Сценарий 3: Участники промо-акции
Создание модели:
POST /v1/management/various/
{
"modelName": "promo_summer_2026",
"title": "Летняя акция 2026",
"attributes": [
{"name": "phone", "type": "string", "allowNull": false, "unique": true},
{"name": "bonus_amount", "type": "integer", "defaultValue": 0, "min": 0},
{"name": "registered_at", "type": "datetime"},
{"name": "status", "type": "string", "enum": ["new", "active", "completed", "cancelled"]}
],
"indexes": ["phone", "status"]
}Upsert участника:
POST /v1/internal/various/records/promo_summer_2026?query={"phone":"+79991234567"}
{"phone": "+79991234567", "bonus_amount": 500, "status": "active"}Особенности и ограничения
| Ограничение | Значение |
|---|---|
Длина modelName | 2-50 символов |
Максимум записей в ответе (limit) | 200 |
| Batch: размер буфера до flush | 20 записей (настраивается) |
| Batch: интервал flush | 20 секунд (настраивается) |
| JSON-колонки | нельзя индексировать |
| Удаление модели | soft delete (переименование таблицы) |
| Системные колонки | uid, createdAt, updatedAt — неизменяемы |
| Кеширование | только Internal API, управляется заголовками |