Files
image-platform/docs/backend-contract-draft.md
S.Gromov bcadb85a83 feat: добавить базовые сервисы image-platform
- добавлены backend, admin, gateway и worker skeleton
- добавлены Drizzle schema, database package и initial migration
- добавлены shared packages для RabbitMQ topology и S3 helpers
- обновлены dev-инфраструктура, env example, scripts и dependencies
- обновлена документация под versioned image URLs и read-through flow
2026-05-05 09:59:21 +03:00

6.0 KiB
Raw Blame History

Черновик Backend Контракта

Это черновик будущего бизнес-контракта для NestJS backend. Сейчас реализованы system endpoints, Swagger и placeholder для internal image ensure.

System

GET /api/health
GET /docs
GET /docs-json

NestJS backend отдаёт JSON, metadata, statuses и URLs. Public image origin находится в Fastify Gateway. Backend владеет PostgreSQL, S3 orchestration и RabbitMQ jobs.

Internal Image Ensure

Этот internal endpoint вызывается Gateway на L1 miss. Gateway не ходит в DB/S3 напрямую.

POST /api/internal/images/ensure

Текущий статус реализации - endpoint зарезервирован и возвращает 501 Not Implemented. Gateway /images/* пока тоже возвращает placeholder 501 Not Implemented.

Request body:

{
  "assetId": "asset_123",
  "version": 4,
  "preset": "card",
  "width": 640,
  "quality": 80,
  "format": "webp"
}

Query params:

Param Описание
w целевая ширина, должна быть разрешена preset или округлена до ближайшей разрешённой
q качество, должно быть из allowlist качества
f auto, avif, webp, jpg; для Next/image по умолчанию auto

Responsibilities:

  • проверить assetId и preset;
  • вычислить deterministic variantHash;
  • проверить PostgreSQL и S3;
  • если variant готов в S3, вернуть bytes или stream metadata для Gateway;
  • если variant отсутствует, создать/переиспользовать variant row;
  • поставить generate-variant job в RabbitMQ;
  • дождаться ready до timeout, чтобы первый next/image request мог получить картинку;
  • вернуть image response или metadata для Gateway.

Response headers:

Content-Type: image/avif | image/webp | image/jpeg
Cache-Control: public, max-age=31536000, immutable
Vary: Accept
ETag: "..."

Ошибки:

Status Когда
400 некорректные query params
404 asset или preset не найден
409 variant уже генерируется и sync ожидание отключено
422 source image нельзя обработать
502 external imgproxy недоступен

Public endpoint реализуется в Fastify Gateway, internal ensure endpoint - в Backend:

client -> CDN -> Gateway /images/* -> Backend ensure -> RabbitMQ -> Worker -> imgproxy -> S3

Важно: /images/* не должен жить в NestJS /api. Gateway остаётся public image origin, Backend остаётся управлением процессов. Public image URL versioned: /images/{assetId}/v{version}/{preset}?w={width}&q={quality}&f=auto.

Allowed Hosts

GET /allowed-hosts
POST /allowed-hosts
PATCH /allowed-hosts/:id
DELETE /allowed-hosts/:id

Assets

GET /assets
POST /assets
GET /assets/:id
DELETE /assets/:id

POST /assets request:

{
  "sourceUrl": "https://example.com/photo.jpg"
}

Responsibilities:

  • validate source URL;
  • check allowed_image_hosts;
  • create or reuse image_assets row;
  • optionally save original to S3 later.

Variants

GET /assets/:id/variants
POST /assets/:id/variants
POST /variants/:id/regenerate
DELETE /variants/:id

POST /assets/:id/variants request:

{
  "preset": "card",
  "format": "webp",
  "width": 640
}

Response if ready:

{
  "id": "variant_123",
  "status": "ready",
  "url": "http://localhost:8888/images/asset_123/v4/card?w=640&q=80&f=webp"
}

Response if generation is async:

{
  "id": "variant_123",
  "status": "pending",
  "url": null
}

Image URLs For UI

Для ручного UI можно добавить endpoint, который возвращает готовый набор URLs для <picture>/srcset. Для next/image основным контрактом остаётся custom loader из docs/next-image-provider.md.

GET /assets/:id/picture?preset=card

Example response:

{
  "assetId": "asset_123",
  "preset": "card",
  "sources": [
    {
      "type": "image/avif",
      "srcset": "http://localhost:8888/images/asset_123/v4/card?w=320&q=80&f=avif 320w, http://localhost:8888/images/asset_123/v4/card?w=640&q=80&f=avif 640w"
    },
    {
      "type": "image/webp",
      "srcset": "http://localhost:8888/images/asset_123/v4/card?w=320&q=80&f=webp 320w, http://localhost:8888/images/asset_123/v4/card?w=640&q=80&f=webp 640w"
    }
  ],
  "fallback": {
    "src": "http://localhost:8888/images/asset_123/v4/card?w=640&q=80&f=jpg",
    "width": 640,
    "height": null
  }
}

Worker Lifecycle

Worker выполняет задачи из RabbitMQ. Задачи создаёт Backend.

RabbitMQ topology:

exchange: image-platform.jobs
queue: image.generate-variant
dead-letter exchange: image-platform.jobs.dlx
dead-letter queue: image.generate-variant.dlq

Job payload должен быть минимальным:

{
  "jobId": "job_123",
  "variantId": "variant_123"
}

Worker читает детали variant из PostgreSQL, вызывает imgproxy, пишет результат в S3 и обновляет status в PostgreSQL.

Если генерация тяжёлая или не успела завершиться до timeout, Backend может вернуть Gateway 504, а job продолжит выполняться/retry по очереди.

PostgreSQL может выступить первой очередью:

SELECT * FROM image_variants
WHERE status = 'pending'
FOR UPDATE SKIP LOCKED
LIMIT 1

PostgreSQL-backed очередь не используется как основной механизм: для jobs выбран RabbitMQ.