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.

ПолеТипОписание
idTEXT PKULID с префиксом ast_
bucketTEXTMinIO bucket
object_keyTEXTПуть в bucket
mimeTEXTMIME-тип (image/jpeg, image/webp и т.д.)
size_bytesBIGINTРазмер файла
widthINT NULLШирина в пикселях (если изображение)
heightINT NULLВысота в пикселях
created_atTIMESTAMPTZ

presets

Системные пресеты — стили для генерации. Управляются через admin API.

ПолеТипОписание
idTEXT PKПроизвольный ID (например prs_dark-bar-drama)
slugTEXT UKURL-safe идентификатор, уникальный
name_enTEXTНазвание на английском
name_ruTEXT NULLНазвание на русском
categoryTEXTLegacy: первичная категория (один элемент)
categoriesTEXT[]Полный список категорий (GIN-индекс)
reference_object_keyTEXTКлюч reference-изображения в MinIO
enabledBOOLEANВидимость в публичном каталоге
created_atTIMESTAMPTZ
updated_atTIMESTAMPTZ

Soft delete

Удаление пресета = enabled = false. Физического удаления нет — существующие jobs сохраняют ссылку на пресет.

user_presets

Личные пресеты сотрудников. Квота: 50 штук на user_id.

ПолеТипОписание
idTEXT PKULID upr_...
user_idTEXTID сотрудника из JWT sub
nameTEXTПользовательское название
reference_object_keyTEXTКлюч reference-изображения в MinIO
enabledBOOLEANФлаг активности
created_atTIMESTAMPTZ
updated_atTIMESTAMPTZАвтообновляется триггером

jobs

Основная таблица заданий. Каждая генерация = один Job.

ПолеТипОписание
idTEXT PKULID job_...
kindTEXTphoto-studio или enhance
statusTEXTpending, running, succeeded, failed, cancelled
user_idTEXTID автора (из JWT)
franchise_idTEXTTenant ID (из JWT)
preset_idTEXT NULL → FK presetsСистемный пресет (взаимоисключает с user_preset_id)
user_preset_idTEXT NULLЛичный пресет
extra_promptTEXT NULLДополнительная инструкция (для enhance — основной prompt)
input_asset_idTEXT NULL → FK assetsВходное изображение
output_asset_idTEXT NULL → FK assetsLegacy: первый результат (для backward compat.)
sizeTEXT NULLФормат вывода: delivery-1x1, story-9x16, banner-16x9, menu-4x3
job_countINTКоличество запрошенных вариантов (1–4), default 1
retry_ofTEXT NULL → FK jobsID оригинального задания при повторе
error_codeTEXT NULLМашиночитаемый код ошибки
error_messageTEXT NULLЧеловекочитаемое описание ошибки
created_atTIMESTAMPTZ
started_atTIMESTAMPTZ NULLНачало обработки воркером
finished_atTIMESTAMPTZ NULLКонец обработки

Индексы:

  • idx_jobs_user_created на (user_id, created_at DESC) — история пользователя
  • idx_jobs_status на (status) WHERE status IN ('pending','running') — очередь воркера

job_outputs

Связь Job → результирующие ассеты. При count > 1 будет несколько строк.

ПолеТипОписание
idTEXT PKULID
job_idTEXT FK → jobs ON DELETE CASCADE
asset_idTEXT FK → assetsСсылка на ассет результата
idxINTИндекс варианта (0-based)
created_atTIMESTAMPTZ

Индекс: idx_job_outputs_job_idx на (job_id, idx).

audit_log

Аудит всех значимых действий (создание/удаление/admin-операции).

ПолеТипОписание
idBIGSERIAL PK
user_idTEXT NULLАвтор действия
actionTEXTСтрока типа job.created, preset.updated, user_preset.deleted
entity_idTEXT NULLID затронутой сущности
payloadJSONB NULLДополнительный контекст
created_atTIMESTAMPTZ

Бизнес-правила

  1. Изоляция по franchise_id — все фильтры списков включают WHERE franchise_id = ? из JWT
  2. Квота личных пресетов — не более 50 user_presets на user_id. Проверяется перед INSERT
  3. Взаимоисключение preset_id / user_preset_id — для kind = photo-studio ровно один из них должен быть заполнен; для kind = enhance оба должны быть NULL
  4. Статусная машина — переходы: pending → running, running → succeeded/failed, pending/running → cancelled. Обратные переходы запрещены
  5. Soft delete пресетов — выключенный пресет (enabled = false) недоступен в публичном каталоге, но остаётся в БД (исторические jobs не ломаются)
  6. job_count vs job_outputsjob_count = запрошено, COUNT(job_outputs) = фактически создано. При ошибке AI может быть меньше

Redis-структуры

Key patternValueTTL
introspect:{token_hash}JSON: ответ /internal/auth/validate60 сек
job:stream:{job_id}Pub/Sub channel для SSE

Кэш introspection

Photo Studio Service кэширует ответы POST /internal/auth/validate по хэшу токена. TTL 60 сек — тот же, что и в Auth Service для permissions-кэша. Таким образом смена прав сотрудника отражается через ≤ 60 сек.

Ссылки