import { Body, Controller, Header, Post, Res, StreamableFile } from "@nestjs/common" import { ApiBadGatewayResponse, ApiBadRequestResponse, ApiNotFoundResponse, ApiOkResponse, ApiOperation, ApiResponse, ApiTags, } from "@nestjs/swagger" import type { Response } from "express" import { EnsureImageVariantRequestDto } from "./ensure-image-variant.dto" import { InternalImagesService } from "./internal-images.service" @ApiTags("internal-images") @Controller("internal/images") export class InternalImagesController { constructor(private readonly internalImages: InternalImagesService) {} @Post("ensure") @ApiOperation({ summary: "подготовить variant изображения для Gateway", description: "Внутренний endpoint для Gateway. На L1 cache miss Backend проверяет PostgreSQL и S3, создаёт variant при необходимости, публикует RabbitMQ job, ждёт генерацию worker и возвращает готовые image bytes.", }) @ApiOkResponse({ description: "Variant уже был готов в S3 или был успешно сгенерирован worker.", content: { "image/*": { schema: { format: "binary", type: "string", }, }, }, }) @ApiBadRequestResponse({ description: "Некорректный assetId, version, preset, width, quality или format." }) @ApiNotFoundResponse({ description: "Asset или указанная версия source image не найдены." }) @ApiBadGatewayResponse({ description: "Worker/imgproxy/S3 не смогли подготовить или вернуть variant." }) @ApiResponse({ status: 504, description: "Variant не успел сгенерироваться до истечения IMAGE_ENSURE_WAIT_MS." }) @Header("Cache-Control", "public, max-age=31536000, immutable") async ensureImageVariant( @Body() request: EnsureImageVariantRequestDto, @Res({ passthrough: true }) response: Response, ): Promise { const result = await this.internalImages.ensureImageVariant(request) response.setHeader("Cache-Control", result.cacheControl) response.setHeader("Content-Length", result.contentLength.toString()) response.setHeader("Content-Type", result.contentType) if (result.etag) { response.setHeader("ETag", result.etag) } if (result.vary) { response.setHeader("Vary", result.vary) } return new StreamableFile(result.body) } }