Photo Studio Service — Data Model
База данных: gensvc_db
erDiagram assets { TEXT id PK TEXT bucket TEXT object_key TEXT mime BIGINT size_bytes INT width "nullable" INT height "nullable" TIMESTAMPTZ created_at } presets { TEXT id PK TEXT slug UK TEXT name_en TEXT name_ru "nullable" TEXT category "legacy primary" TEXT[] categories TEXT reference_object_key BOOLEAN enabled TIMESTAMPTZ created_at TIMESTAMPTZ updated_at } user_presets { TEXT id PK TEXT user_id TEXT name TEXT reference_object_key BOOLEAN enabled TIMESTAMPTZ created_at TIMESTAMPTZ updated_at } jobs { TEXT id PK TEXT kind "photo-studio | enhance" TEXT status "pending|running|succeeded|failed|cancelled" TEXT user_id TEXT franchise_id TEXT preset_id FK "nullable" TEXT user_preset_id "nullable" TEXT extra_prompt "nullable" TEXT input_asset_id FK "nullable" TEXT output_asset_id FK "nullable, legacy" TEXT size "nullable" INT job_count "default 1" TEXT retry_of FK "nullable, self-ref" TEXT error_code "nullable" TEXT error_message "nullable" TIMESTAMPTZ created_at TIMESTAMPTZ started_at "nullable" TIMESTAMPTZ finished_at "nullable" } job_outputs { TEXT id PK TEXT job_id FK TEXT asset_id FK INT idx TIMESTAMPTZ created_at } audit_log { BIGSERIAL id PK TEXT user_id "nullable" TEXT action TEXT entity_id "nullable" JSONB payload "nullable" TIMESTAMPTZ created_at } jobs ||--o{ job_outputs : "has outputs" job_outputs }o--|| assets : "links to" jobs }o--|| assets : "input_asset" jobs }o--|| assets : "output_asset (legacy)" jobs }o--|| presets : "uses preset" jobs }o--|| jobs : "retry_of"
Таблицы
assets
Бинарные файлы в MinIO. Хранит только метаданные — сам файл лежит в S3 по bucket + object_key.
| Поле | Тип | Описание |
|---|---|---|
id | TEXT PK | ULID с префиксом ast_ |
bucket | TEXT | MinIO bucket |
object_key | TEXT | Путь в bucket |
mime | TEXT | MIME-тип (image/jpeg, image/webp и т.д.) |
size_bytes | BIGINT | Размер файла |
width | INT NULL | Ширина в пикселях (если изображение) |
height | INT NULL | Высота в пикселях |
created_at | TIMESTAMPTZ |
presets
Системные пресеты — стили для генерации. Управляются через admin API.
| Поле | Тип | Описание |
|---|---|---|
id | TEXT PK | Произвольный ID (например prs_dark-bar-drama) |
slug | TEXT UK | URL-safe идентификатор, уникальный |
name_en | TEXT | Название на английском |
name_ru | TEXT NULL | Название на русском |
category | TEXT | Legacy: первичная категория (один элемент) |
categories | TEXT[] | Полный список категорий (GIN-индекс) |
reference_object_key | TEXT | Ключ reference-изображения в MinIO |
enabled | BOOLEAN | Видимость в публичном каталоге |
created_at | TIMESTAMPTZ | |
updated_at | TIMESTAMPTZ |
Soft delete
Удаление пресета =
enabled = false. Физического удаления нет — существующие jobs сохраняют ссылку на пресет.
user_presets
Личные пресеты сотрудников. Квота: 50 штук на user_id.
| Поле | Тип | Описание |
|---|---|---|
id | TEXT PK | ULID upr_... |
user_id | TEXT | ID сотрудника из JWT sub |
name | TEXT | Пользовательское название |
reference_object_key | TEXT | Ключ reference-изображения в MinIO |
enabled | BOOLEAN | Флаг активности |
created_at | TIMESTAMPTZ | |
updated_at | TIMESTAMPTZ | Автообновляется триггером |
jobs
Основная таблица заданий. Каждая генерация = один Job.
| Поле | Тип | Описание |
|---|---|---|
id | TEXT PK | ULID job_... |
kind | TEXT | photo-studio или enhance |
status | TEXT | pending, running, succeeded, failed, cancelled |
user_id | TEXT | ID автора (из JWT) |
franchise_id | TEXT | Tenant ID (из JWT) |
preset_id | TEXT NULL → FK presets | Системный пресет (взаимоисключает с user_preset_id) |
user_preset_id | TEXT NULL | Личный пресет |
extra_prompt | TEXT NULL | Дополнительная инструкция (для enhance — основной prompt) |
input_asset_id | TEXT NULL → FK assets | Входное изображение |
output_asset_id | TEXT NULL → FK assets | Legacy: первый результат (для backward compat.) |
size | TEXT NULL | Формат вывода: delivery-1x1, story-9x16, banner-16x9, menu-4x3 |
job_count | INT | Количество запрошенных вариантов (1–4), default 1 |
retry_of | TEXT NULL → FK jobs | ID оригинального задания при повторе |
error_code | TEXT NULL | Машиночитаемый код ошибки |
error_message | TEXT NULL | Человекочитаемое описание ошибки |
created_at | TIMESTAMPTZ | |
started_at | TIMESTAMPTZ NULL | Начало обработки воркером |
finished_at | TIMESTAMPTZ NULL | Конец обработки |
Индексы:
idx_jobs_user_createdна(user_id, created_at DESC)— история пользователяidx_jobs_statusна(status)WHEREstatus IN ('pending','running')— очередь воркера
job_outputs
Связь Job → результирующие ассеты. При count > 1 будет несколько строк.
| Поле | Тип | Описание |
|---|---|---|
id | TEXT PK | ULID |
job_id | TEXT FK → jobs ON DELETE CASCADE | |
asset_id | TEXT FK → assets | Ссылка на ассет результата |
idx | INT | Индекс варианта (0-based) |
created_at | TIMESTAMPTZ |
Индекс: idx_job_outputs_job_idx на (job_id, idx).
audit_log
Аудит всех значимых действий (создание/удаление/admin-операции).
| Поле | Тип | Описание |
|---|---|---|
id | BIGSERIAL PK | |
user_id | TEXT NULL | Автор действия |
action | TEXT | Строка типа job.created, preset.updated, user_preset.deleted |
entity_id | TEXT NULL | ID затронутой сущности |
payload | JSONB NULL | Дополнительный контекст |
created_at | TIMESTAMPTZ |
Бизнес-правила
- Изоляция по franchise_id — все фильтры списков включают
WHERE franchise_id = ?из JWT - Квота личных пресетов — не более 50
user_presetsнаuser_id. Проверяется перед INSERT - Взаимоисключение preset_id / user_preset_id — для
kind = photo-studioровно один из них должен быть заполнен; дляkind = enhanceоба должны быть NULL - Статусная машина — переходы:
pending → running,running → succeeded/failed,pending/running → cancelled. Обратные переходы запрещены - Soft delete пресетов — выключенный пресет (
enabled = false) недоступен в публичном каталоге, но остаётся в БД (исторические jobs не ломаются) - job_count vs job_outputs —
job_count= запрошено,COUNT(job_outputs)= фактически создано. При ошибке AI может быть меньше
Redis-структуры
| Key pattern | Value | TTL |
|---|---|---|
introspect:{token_hash} | JSON: ответ /internal/auth/validate | 60 сек |
job:stream:{job_id} | Pub/Sub channel для SSE | — |
Кэш introspection
Photo Studio Service кэширует ответы
POST /internal/auth/validateпо хэшу токена. TTL 60 сек — тот же, что и в Auth Service для permissions-кэша. Таким образом смена прав сотрудника отражается через ≤ 60 сек.