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 и документация
This commit is contained in:
93
apps/backend/src/assets/assets.controller.ts
Normal file
93
apps/backend/src/assets/assets.controller.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { Body, Controller, Get, Param, Post, Query } from "@nestjs/common"
|
||||
import {
|
||||
ApiBadRequestResponse,
|
||||
ApiConflictResponse,
|
||||
ApiCreatedResponse,
|
||||
ApiNotFoundResponse,
|
||||
ApiOkResponse,
|
||||
ApiOperation,
|
||||
ApiParam,
|
||||
ApiQuery,
|
||||
ApiTags,
|
||||
} from "@nestjs/swagger"
|
||||
|
||||
import { AssetsService } from "./assets.service"
|
||||
import { AssetResponseDto, AssetVariantsResponseDto, AssetsListResponseDto } from "./asset-response.dto"
|
||||
import { CreateAssetVariantsRequestDto, CreateAssetVariantsResponseDto } from "./create-asset-variants.dto"
|
||||
import { CreateAssetRequestDto, CreateAssetResponseDto } from "./create-asset.dto"
|
||||
|
||||
@ApiTags("assets")
|
||||
@Controller("assets")
|
||||
export class AssetsController {
|
||||
constructor(private readonly assets: AssetsService) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({
|
||||
summary: "получить список assets",
|
||||
description: "Возвращает последние зарегистрированные assets вместе с source URL текущей версии.",
|
||||
})
|
||||
@ApiQuery({ description: "Максимальное количество assets в ответе.", example: 50, name: "limit", required: false })
|
||||
@ApiQuery({ description: "Смещение для простого paging.", example: 0, name: "offset", required: false })
|
||||
@ApiOkResponse({ description: "Список assets возвращён.", type: AssetsListResponseDto })
|
||||
listAssets(@Query("limit") limit?: string, @Query("offset") offset?: string): Promise<AssetsListResponseDto> {
|
||||
return this.assets.listAssets({ limit, offset })
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({
|
||||
summary: "зарегистрировать исходное изображение",
|
||||
description:
|
||||
"Создаёт asset и первую версию source image. Source URL сохраняется в PostgreSQL, а публичный image URL строится через Gateway без раскрытия исходной ссылки клиенту.",
|
||||
})
|
||||
@ApiCreatedResponse({ description: "Asset создан, версия source image зарегистрирована.", type: CreateAssetResponseDto })
|
||||
@ApiBadRequestResponse({ description: "Некорректный sourceUrl, publicId или source host запрещён настройками." })
|
||||
@ApiConflictResponse({ description: "Asset с таким publicId уже существует." })
|
||||
createAsset(@Body() request: CreateAssetRequestDto): Promise<CreateAssetResponseDto> {
|
||||
return this.assets.createAsset(request)
|
||||
}
|
||||
|
||||
@Get(":publicId")
|
||||
@ApiOperation({
|
||||
summary: "получить asset по publicId",
|
||||
description: "Возвращает metadata asset и source URL текущей версии.",
|
||||
})
|
||||
@ApiParam({ description: "Публичный идентификатор asset.", example: "asset_demo", name: "publicId" })
|
||||
@ApiOkResponse({ description: "Asset найден.", type: AssetResponseDto })
|
||||
@ApiNotFoundResponse({ description: "Asset не найден." })
|
||||
getAsset(@Param("publicId") publicId: string): Promise<AssetResponseDto> {
|
||||
return this.assets.getAsset(publicId)
|
||||
}
|
||||
|
||||
@Get(":publicId/variants")
|
||||
@ApiOperation({
|
||||
summary: "получить variants asset",
|
||||
description: "Возвращает variants asset: preset/custom параметры, status, S3 key, public URL и ошибку генерации, если она была.",
|
||||
})
|
||||
@ApiParam({ description: "Публичный идентификатор asset.", example: "asset_demo", name: "publicId" })
|
||||
@ApiQuery({ description: "Версия source image. Если не передана, возвращаются variants всех версий.", example: 1, name: "version", required: false })
|
||||
@ApiOkResponse({ description: "Variants возвращены.", type: AssetVariantsResponseDto })
|
||||
@ApiNotFoundResponse({ description: "Asset не найден." })
|
||||
listAssetVariants(
|
||||
@Param("publicId") publicId: string,
|
||||
@Query("version") version?: string,
|
||||
): Promise<AssetVariantsResponseDto> {
|
||||
return this.assets.listAssetVariants(publicId, version)
|
||||
}
|
||||
|
||||
@Post(":publicId/variants")
|
||||
@ApiOperation({
|
||||
summary: "поставить generation jobs для variants",
|
||||
description:
|
||||
"Business endpoint для явной подготовки variants. В режиме `single` создаёт один variant, в режиме `family` создаёт набор variants preset по всем разрешённым widths/formats. Endpoint не ждёт bytes, а возвращает созданные/переиспользованные rows и public URLs.",
|
||||
})
|
||||
@ApiParam({ description: "Публичный идентификатор asset.", example: "asset_demo", name: "publicId" })
|
||||
@ApiCreatedResponse({ description: "Variants созданы или переиспользованы, jobs поставлены при необходимости.", type: CreateAssetVariantsResponseDto })
|
||||
@ApiBadRequestResponse({ description: "Некорректный preset/custom transform config." })
|
||||
@ApiNotFoundResponse({ description: "Asset или version не найдены." })
|
||||
createAssetVariants(
|
||||
@Param("publicId") publicId: string,
|
||||
@Body() request: CreateAssetVariantsRequestDto,
|
||||
): Promise<CreateAssetVariantsResponseDto> {
|
||||
return this.assets.createAssetVariants(publicId, request)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user