- добавлен 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 и документация
184 lines
5.2 KiB
Markdown
184 lines
5.2 KiB
Markdown
# 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
|
||
```
|