- добавлен новый Nest backend для auth, projects и project access tokens - добавлена control-plane схема БД и миграция Drizzle - перенесён старый backend в old-backend - добавлен React/Vite cabinet с auth-only входом и Mantine layout - обновлены workspace scripts и lockfile
64 lines
2.5 KiB
TypeScript
64 lines
2.5 KiB
TypeScript
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<StreamableFile> {
|
||
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)
|
||
}
|
||
}
|