diff --git a/api.planx.uk/modules/file/service/uploadFile.ts b/api.planx.uk/modules/file/service/uploadFile.ts index a6ee74227b..d272018c70 100644 --- a/api.planx.uk/modules/file/service/uploadFile.ts +++ b/api.planx.uk/modules/file/service/uploadFile.ts @@ -7,6 +7,7 @@ import mime from "mime"; import { customAlphabet } from "nanoid"; import { isLiveEnv } from "../../../helpers.js"; import { s3Factory } from "./utils.js"; +import { Readable } from "stream"; const nanoid = customAlphabet("1234567890abcdefghijklmnopqrstuvwxyz", 8); export const uploadPublicFile = async ( @@ -28,10 +29,14 @@ export const uploadPublicFile = async ( }; export const uploadPrivateFile = async ( - file: Express.Multer.File, + file: Express.Multer.File | Buffer, filename: string, filekey?: string, ) => { + if (file instanceof Buffer) { + file = convertToMulterFile(file); + } + const s3 = s3Factory(); const { params, key, fileType } = generateFileParams(file, filename, filekey); @@ -63,6 +68,19 @@ const buildFileUrl = async (key: string, path: "public" | "private") => { return `${process.env.API_URL_EXT}/file/${path}${s3Pathname}`; }; +const convertToMulterFile = (buffer: Buffer): Express.Multer.File => ({ + buffer: buffer, + originalname: "${data.id}.json", + mimetype: "application/json", + size: buffer.length, + fieldname: "file", + encoding: "7bit", + stream: Readable.from(buffer), + destination: "", + filename: "", + path: "", +}); + export function generateFileParams( file: Express.Multer.File, filename: string, @@ -79,9 +97,9 @@ export function generateFileParams( ACL: "public-read", Bucket: process.env.AWS_S3_BUCKET, Key: key, - Body: file.buffer || JSON.stringify(file), + Body: file.buffer, ContentDisposition: `inline;filename="${filename}"`, - ContentType: file.mimetype || "application/json", + ContentType: file.mimetype, }; return { diff --git a/api.planx.uk/modules/send/s3/index.ts b/api.planx.uk/modules/send/s3/index.ts index 6f7186ae8b..8bf46acf5e 100644 --- a/api.planx.uk/modules/send/s3/index.ts +++ b/api.planx.uk/modules/send/s3/index.ts @@ -9,6 +9,10 @@ import { markSessionAsSubmitted } from "../../saveAndReturn/service/utils.js"; import { isApplicationTypeSupported } from "../utils/helpers.js"; import type { SendIntegrationController } from "../types.js"; +// TS fails to validate this complex type when awaited +// Represent as a simple object to allow enough typechecking to kick in +type DigitalPlanningPayload = Record; + interface CreateS3Application { insertS3Application: { id: string; @@ -45,15 +49,13 @@ const sendToS3: SendIntegrationController = async (_req, res, next) => { // Generate the ODP Schema JSON, skipping validation if not a supported application type const doValidation = isApplicationTypeSupported(passport); - const exportData = doValidation - ? await $api.export.digitalPlanningDataPayload(sessionId) - : await $api.export.digitalPlanningDataPayload(sessionId, true); + const exportData: DigitalPlanningPayload = + await $api.export.digitalPlanningDataPayload(sessionId, doValidation); + + const buffer = Buffer.from(JSON.stringify(exportData)); // Create and upload the data as an S3 file - const { fileUrl } = await uploadPrivateFile( - exportData, - `${sessionId}.json`, - ); + const { fileUrl } = await uploadPrivateFile(buffer, `${sessionId}.json`); // Send a notification with the file URL to the Power Automate webhook const webhookRequest: AxiosRequestConfig = {