Files
image-platform/docs/data-model.md
S.Gromov 1c0e8277a3 feat: добавить генерацию image variants
- добавлен shared config presets, custom transforms и allowlist hosts
- реализованы Backend endpoints для assets, presets и variants
- добавлена orchestration через PostgreSQL, RabbitMQ, S3 и worker
- обновлён Gateway read-through flow с L1 cache и корректным Vary: Accept
- добавлена миграция resize_mode для variants lookup
- обновлены dev scripts, env template, lockfile и документация
2026-05-05 13:25:28 +03:00

5.2 KiB
Raw Blame History

Data Model

Текущая PostgreSQL модель описана в packages/database/src/schema.ts и миграциях Drizzle в packages/database/drizzle.

allowed_image_hosts

id uuid pk default gen_random_uuid()
hostname text not null unique
enabled boolean not null default true
description text nullable
created_at timestamptz not null default now()
updated_at timestamptz not null default now()

Правила normalization:

  • lowercase;
  • без protocol;
  • без path;
  • без trailing slash;
  • без wildcard на первом этапе;
  • source URL должен быть http или https;
  • запрещены localhost, private IP, loopback, link-local.

image_assets

id uuid pk default gen_random_uuid()
public_id text not null unique
current_version integer not null default 1
status asset_status not null default active
created_at timestamptz not null default now()
updated_at timestamptz not null default now()

public_id - стабильный идентификатор в public URL. current_version указывает активную версию source image и используется для cache invalidation без purge.

image_asset_versions

id uuid pk default gen_random_uuid()
asset_id uuid not null references image_assets(id) on delete cascade
version integer not null
source_url text not null
source_host text not null
source_hash text not null
original_s3_key text nullable
width integer nullable
height integer nullable
content_type text nullable
size_bytes bigint nullable
created_at timestamptz not null default now()

Каждое изменение source image создаёт новую версию. Старые versioned URLs остаются immutable, новые клиенты получают URL с новым v{version}.

image_variants

id uuid pk default gen_random_uuid()
asset_id uuid not null references image_assets(id) on delete cascade
asset_version_id uuid not null references image_asset_versions(id) on delete cascade
asset_version integer not null
preset text not null
variant_hash text not null unique
requested_format requested_format not null default auto
format variant_format not null
width integer not null
height integer nullable
resize_mode resize_mode not null default fit
quality integer not null
s3_key text not null unique
content_type text nullable
etag text nullable
status variant_status not null default pending
size_bytes bigint nullable
error text nullable
attempt_count integer not null default 0
last_accessed_at timestamptz nullable
created_at timestamptz not null default now()
updated_at timestamptz not null default now()

requested_format хранит то, что запросил клиент (auto, avif, webp, jpg, png). format хранит фактический output format после negotiation по Accept.

Enums

asset_status: active | disabled | deleted
requested_format: auto | avif | webp | jpg | png
variant_format: avif | webp | jpg | png
resize_mode: fit | fill
variant_status: pending | processing | ready | failed

Unique constraints

allowed_image_hosts(hostname)
image_assets(public_id)
image_asset_versions(asset_id, version)
image_variants(asset_id, asset_version, preset, width, height, resize_mode, quality, format)
image_variants(s3_key)
image_variants(variant_hash)

Индексы:

image_asset_versions(source_hash)
image_variants(status)

S3 layout

originals/{assetId}/v{version}/source
variants/{assetId}/v{version}/{variantHash}.{format}

variantHash должен включать:

  • assetId;
  • assetVersion;
  • preset;
  • normalized width;
  • normalized height, где 0 означает auto height;
  • normalized resize mode;
  • normalized quality;
  • фактический output format;
  • параметры transform, влияющие на bytes.

Для f=auto в public URL в S3 всё равно пишется фактический формат:

variants/asset_123/v4/card_w640_q80_avif.avif
variants/asset_123/v4/card_w640_q80_webp.webp
variants/asset_123/v4/card_w640_q80_jpg.jpg

Public URL также versioned. Для fixed preset w и q можно не передавать, для responsive preset w обязателен:

/images/{assetId}/v{version}/{preset}?w={width}&q={quality}&f=auto
/images/{assetId}/v{version}/avatar?f=auto

Presets

Клиент не должен бесконтрольно создавать произвольные трансформации. Сейчас есть статический config в packages/image-config.

Режимы:

  • fixed - preset задаёт один размер, например avatar.
  • responsive - preset задаёт allowlist ширин, например card и hero.
  • custom - произвольный single image, только если включён IMAGE_ALLOW_CUSTOM_TRANSFORMS=true.

Пример:

avatar:
  mode: fixed
  width: 256
  height: 256
  formats: avif, webp, jpg
  quality: 80
  resize: fill

card:
  mode: responsive
  widths: 320, 640, 960
  formats: avif, webp, jpg
  qualities: 75, 80
  resize: fit

hero:
  mode: responsive
  widths: 1280, 1920
  formats: avif, webp, jpg
  qualities: 75, 80
  resize: fit