- добавлен базовый pnpm workspace для будущих приложений - добавлена dev-инфраструктура PostgreSQL и MinIO - добавлены env-пример и базовые правила репозитория - зафиксированы архитектура, data model и API-контракт - описан контракт с внешним imgproxy
125 lines
2.5 KiB
Markdown
125 lines
2.5 KiB
Markdown
# Черновик API Контракта
|
||
|
||
Это не реализация API, а фиксация будущего контракта для NestJS backend.
|
||
|
||
Backend отдаёт JSON, metadata, statuses и URLs. Он не должен проксировать image bytes на каждый обычный запрос.
|
||
|
||
## Allowed Hosts
|
||
|
||
```text
|
||
GET /allowed-hosts
|
||
POST /allowed-hosts
|
||
PATCH /allowed-hosts/:id
|
||
DELETE /allowed-hosts/:id
|
||
```
|
||
|
||
## Assets
|
||
|
||
```text
|
||
GET /assets
|
||
POST /assets
|
||
GET /assets/:id
|
||
DELETE /assets/:id
|
||
```
|
||
|
||
`POST /assets` request:
|
||
|
||
```json
|
||
{
|
||
"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
|
||
|
||
```text
|
||
GET /assets/:id/variants
|
||
POST /assets/:id/variants
|
||
POST /variants/:id/regenerate
|
||
DELETE /variants/:id
|
||
```
|
||
|
||
`POST /assets/:id/variants` request:
|
||
|
||
```json
|
||
{
|
||
"preset": "card",
|
||
"format": "webp",
|
||
"width": 640
|
||
}
|
||
```
|
||
|
||
Response if ready:
|
||
|
||
```json
|
||
{
|
||
"id": "variant_123",
|
||
"status": "ready",
|
||
"url": "http://localhost:8888/images/asset_123/w640_q80_card.webp"
|
||
}
|
||
```
|
||
|
||
Response if generation is async:
|
||
|
||
```json
|
||
{
|
||
"id": "variant_123",
|
||
"status": "pending",
|
||
"url": null
|
||
}
|
||
```
|
||
|
||
## Image URLs For UI
|
||
|
||
Для UI нужен endpoint, который возвращает готовый набор URLs для `<picture>`/`srcset`.
|
||
|
||
```text
|
||
GET /assets/:id/picture?preset=card
|
||
```
|
||
|
||
Example response:
|
||
|
||
```json
|
||
{
|
||
"assetId": "asset_123",
|
||
"preset": "card",
|
||
"sources": [
|
||
{
|
||
"type": "image/avif",
|
||
"srcset": "http://localhost:8888/images/asset_123/w320_card.avif 320w, http://localhost:8888/images/asset_123/w640_card.avif 640w"
|
||
},
|
||
{
|
||
"type": "image/webp",
|
||
"srcset": "http://localhost:8888/images/asset_123/w320_card.webp 320w, http://localhost:8888/images/asset_123/w640_card.webp 640w"
|
||
}
|
||
],
|
||
"fallback": {
|
||
"src": "http://localhost:8888/images/asset_123/w640_card.jpg",
|
||
"width": 640,
|
||
"height": null
|
||
}
|
||
}
|
||
```
|
||
|
||
## Worker Lifecycle
|
||
|
||
Первый MVP может генерировать sync на request. Если генерация тяжёлая, variant создаётся как `pending`, а worker обрабатывает job.
|
||
|
||
PostgreSQL может выступить первой очередью:
|
||
|
||
```text
|
||
SELECT * FROM image_variants
|
||
WHERE status = 'pending'
|
||
FOR UPDATE SKIP LOCKED
|
||
LIMIT 1
|
||
```
|
||
|
||
Позже можно добавить Redis/Valkey или отдельную queue, если PostgreSQL станет узким местом.
|