Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 63 additions & 8 deletions apps/api/src/policies/policies.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@ import {
Query,
Req,
Res,
UploadedFile,
UseGuards,
UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import {
ApiBody,
ApiConsumes,
ApiHeader,
ApiOperation,
ApiParam,
Expand Down Expand Up @@ -513,20 +517,74 @@ export class PoliciesController {

@Post(':id/pdf')
@RequirePermission('policy', 'update')
@UseInterceptors(FileInterceptor('file'))
@ApiOperation({ summary: 'Upload a PDF to a policy or version' })
@ApiConsumes('multipart/form-data', 'application/json')
@ApiParam(POLICY_PARAMS.policyId)
@ApiBody({
schema: {
oneOf: [
{
description: 'Multipart file upload (recommended)',
type: 'object',
properties: {
file: { type: 'string', format: 'binary' },
versionId: { type: 'string', description: 'Target version ID (optional)' },
},
required: ['file'],
},
{
description: 'JSON with base64-encoded file data',
type: 'object',
properties: {
fileName: { type: 'string' },
fileType: { type: 'string' },
fileData: { type: 'string', description: 'Base64-encoded file content' },
versionId: { type: 'string' },
},
required: ['fileName', 'fileType', 'fileData'],
},
],
},
})
async uploadPolicyPdf(
@Param('id') id: string,
@UploadedFile() file: Express.Multer.File | undefined,
@Body()
body: {
versionId?: string;
fileName: string;
fileType: string;
fileData: string;
fileName?: string;
fileType?: string;
fileData?: string;
},
@OrganizationId() organizationId: string,
@AuthContext() authContext: AuthContextType,
) {
let fileBuffer: Buffer;
let sanitizedFileName: string;
let fileType: string;

if (file) {
fileBuffer = file.buffer;
sanitizedFileName = file.originalname.replace(/[^a-zA-Z0-9.-]/g, '_');
fileType = file.mimetype;
} else if (body.fileData && body.fileName && body.fileType) {
const stripped = body.fileData.replace(/\s/g, '');
if (!/^[A-Za-z0-9+/\-_]*={0,2}$/.test(stripped)) {
throw new BadRequestException('fileData must be valid base64-encoded content');
}
fileBuffer = Buffer.from(stripped, 'base64');
if (fileBuffer.length === 0) {
throw new BadRequestException('fileData must be valid base64-encoded content');
}
sanitizedFileName = body.fileName.replace(/[^a-zA-Z0-9.-]/g, '_');
fileType = body.fileType;
} else {
throw new BadRequestException(
'Upload a file via multipart/form-data or provide fileName, fileType, and fileData in JSON',
);
}

const { S3Client, PutObjectCommand, DeleteObjectCommand } =
await import('@aws-sdk/client-s3');
const bucketName = process.env.APP_AWS_BUCKET_NAME;
Expand All @@ -547,9 +605,6 @@ export class PoliciesController {
});
if (!policy) throw new NotFoundException('Policy not found');

const sanitizedFileName = body.fileName.replace(/[^a-zA-Z0-9.-]/g, '_');
const fileBuffer = Buffer.from(body.fileData, 'base64');

if (body.versionId) {
const version = await db.policyVersion.findFirst({
where: { id: body.versionId, policyId: id },
Expand All @@ -573,7 +628,7 @@ export class PoliciesController {
Bucket: bucketName,
Key: s3Key,
Body: fileBuffer,
ContentType: body.fileType,
ContentType: fileType,
}),
);
const oldPdfUrl = version.pdfUrl;
Expand Down Expand Up @@ -602,7 +657,7 @@ export class PoliciesController {
Bucket: bucketName,
Key: s3Key,
Body: fileBuffer,
ContentType: body.fileType,
ContentType: fileType,
}),
);
const oldPdfUrl = policy.pdfUrl;
Expand Down
Loading