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

184 lines
5.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Data Model
Текущая PostgreSQL модель описана в `packages/database/src/schema.ts` и миграциях Drizzle в `packages/database/drizzle`.
## allowed_image_hosts
```text
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
```text
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
```text
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
```text
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
```text
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
```text
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)
```
Индексы:
```text
image_asset_versions(source_hash)
image_variants(status)
```
## S3 layout
```text
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 всё равно пишется фактический формат:
```text
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` обязателен:
```text
/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`.
Пример:
```text
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
```