import { mkdir, writeFile } from 'node:fs/promises'; import { extname, resolve } from 'node:path'; import { requestLimits } from '../../config/limits.mjs'; import { paths } from '../../config/paths.mjs'; import { httpError } from '../../shared/http/httpError.mjs'; import { readJsonBody } from '../../shared/http/readJsonBody.mjs'; import { sendJson } from '../../shared/http/sendJson.mjs'; import { assetExtensions, imageExtensions } from './assetTypes.mjs'; import { getUniqueFileName } from './getUniqueFileName.mjs'; import { parseUploadPayload } from './parseUploadPayload.mjs'; import { sanitizeFileName } from './sanitizeFileName.mjs'; export async function handleUploadAsset(request, response) { const payload = await readJsonBody(request); const fileName = sanitizeFileName(readRequiredString(payload, 'fileName')); const extension = extname(fileName).toLowerCase(); validateExtension(extension); const fileBuffer = parseUploadPayload(payload); validateUploadSize(fileBuffer.length); await mkdir(paths.uploadRoot, { recursive: true }); const uniqueFileName = await getUniqueFileName(paths.uploadRoot, fileName); await writeFile(resolve(paths.uploadRoot, uniqueFileName), fileBuffer); return sendJson(response, { asset: createAssetResponse(uniqueFileName, extension, fileBuffer.length) }, 201); } function createAssetResponse(fileName, extension, size) { return { fileName, isImage: imageExtensions.has(extension), path: `/assets/uploads/${fileName}`, size, }; } function readRequiredString(input, key) { const value = input[key]; if (typeof value !== 'string' || !value.trim()) { throw httpError(400, `"${key}" is required`); } return value.trim(); } function validateExtension(extension) { if (!assetExtensions.has(extension)) { throw httpError(400, `Files with "${extension || 'no'}" extension are not allowed`); } } function validateUploadSize(size) { if (size === 0) { throw httpError(400, 'Uploaded file is empty'); } if (size > requestLimits.maxUploadSize) { throw httpError(413, `Uploaded file must be ${Math.round(requestLimits.maxUploadSize / 1024 / 1024)} MB or smaller`); } }