Files
edr-platform/apps/edr-freight-api/src/modules/support-content/support-content.controller.ts
2026-08-08 09:37:28 +00:00

136 lines
4.0 KiB
TypeScript

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:<key>`, 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);
}
}