import { CurrentUser } from "@edr/api-common"; import { SUPPORT_MEDIA_MAX_BYTES } from "@edr/types"; import { Body, Controller, Get, Param, ParseIntPipe, Patch, Post, Query, UploadedFile, UseInterceptors, } from "@nestjs/common"; import { FileInterceptor } from "@nestjs/platform-express"; import { ApiBearerAuth, ApiConsumes, ApiOperation, ApiQuery, ApiTags, } from "@nestjs/swagger"; import type { TCurrentUser } from "@tria-plc/api-common/modules/auth/types/current-user.type"; import { BookingStaff } from "../../common/booking-guards"; import { FREIGHT_PERMS } from "../../seed/freight-permissions.registry"; import { UpdateSupportDocumentDto } from "./dto/support-content.dto"; import { SupportContentService } from "./support-content.service"; const READ = [ FREIGHT_PERMS.settings.supportContent.view, FREIGHT_PERMS.settings.supportContent.manage, FREIGHT_PERMS.admin, ]; const WRITE = [ FREIGHT_PERMS.settings.supportContent.manage, FREIGHT_PERMS.admin, ]; @ApiTags("support-content") @ApiBearerAuth() @Controller("support-content") export class SupportContentController { constructor(private readonly service: SupportContentService) {} /** * Stores a help-page image or video and returns its object key. The editor * saves the key, not the returned URL — see `SupportContentService.uploadMedia`. * * The Multer limit duplicates the service-side type check on purpose: it * stops reading the socket once the part is oversized instead of buffering * the whole thing into memory first. */ @Post("media") @BookingStaff(WRITE) @UseInterceptors( FileInterceptor("file", { limits: { fileSize: SUPPORT_MEDIA_MAX_BYTES } }), ) @ApiConsumes("multipart/form-data") @ApiOperation({ summary: "Upload an image or video for a help section" }) uploadMedia(@UploadedFile() file: Express.Multer.File) { return this.service.uploadMedia(file); } /** * Signs one stored key so the editor can preview an already-saved image. * The editor stores `minio:`, never a signed URL, so it needs somewhere * to resolve those refs for display — this is it. */ @Get("media-url") @BookingStaff(READ) @ApiQuery({ name: "key", description: "MinIO object key" }) @ApiOperation({ summary: "Presigned URL for one stored media key" }) mediaUrl(@Query("key") key: string) { return this.service.mediaUrl(key); } @Get("documents") @BookingStaff(READ) @ApiOperation({ summary: "List the five portal content documents (no payloads)" }) list() { return this.service.list(); } @Get("documents/:slug") @BookingStaff(READ) @ApiOperation({ summary: "Get one document with its payload" }) getBySlug(@Param("slug") slug: string) { return this.service.getBySlug(slug); } @Patch("documents/:slug") @BookingStaff(WRITE) @ApiOperation({ summary: "Replace a document's payload, recording a new version", }) update( @Param("slug") slug: string, @Body() dto: UpdateSupportDocumentDto, @CurrentUser() user: TCurrentUser, ) { return this.service.update(slug, dto, user?.id ?? null); } @Get("documents/:slug/versions") @BookingStaff(READ) @ApiOperation({ summary: "Version history, newest first (no payloads)" }) listVersions(@Param("slug") slug: string) { return this.service.listVersions(slug); } @Get("documents/:slug/versions/:version") @BookingStaff(READ) @ApiOperation({ summary: "One historical version, with its payload" }) getVersion( @Param("slug") slug: string, @Param("version", ParseIntPipe) version: number, ) { return this.service.getVersion(slug, version); } @Post("documents/:slug/versions/:version/restore") @BookingStaff(WRITE) @ApiOperation({ summary: "Restore a version — re-saves it as a new version, never destructive", }) restore( @Param("slug") slug: string, @Param("version", ParseIntPipe) version: number, @CurrentUser() user: TCurrentUser, ) { return this.service.restore(slug, version, user?.id ?? null); } }