diff --git a/.changeset/hot-maps-unite.md b/.changeset/hot-maps-unite.md index d7b1057375..af37915d1f 100644 --- a/.changeset/hot-maps-unite.md +++ b/.changeset/hot-maps-unite.md @@ -20,7 +20,7 @@ export const upload = (context) => { }; ``` -Available options are: +Available options for the new `fileUpload` property in the `createServer` function are: - `enabled`: (boolean) Enable/disable file upload functionality. Default: `true`. If you set it to `false`, the middleware will not initialize file upload functionality and the `req.files` object will be empty. That can be useful if you do not want to handle file uploads in your app and want to avoid unnecessary processing. - `maxFileSize`: (number) Maximum file size in bytes. Default: `5242880` (5MB) diff --git a/packages/middleware/__tests__/integration/bootstrap/serverWithUpload.ts b/packages/middleware/__tests__/integration/bootstrap/serverWithUpload.ts index 8d96d65b0e..4619134fee 100644 --- a/packages/middleware/__tests__/integration/bootstrap/serverWithUpload.ts +++ b/packages/middleware/__tests__/integration/bootstrap/serverWithUpload.ts @@ -1,14 +1,7 @@ import { apiClientFactory } from "../../../src/apiClientFactory"; import * as api from "./api"; -import { AlokaiContainer, getLogger } from "../../../src"; - -const onCreate = async ( - config: Record = {}, - alokai: AlokaiContainer -) => { - const logger = getLogger(alokai); - logger.info("oncreate"); +const onCreate = async (config: Record = {}) => { return { config, client: null, diff --git a/packages/middleware/__tests__/integration/upload.spec.ts b/packages/middleware/__tests__/integration/upload.spec.ts index 7a5198757f..5419ae1753 100644 --- a/packages/middleware/__tests__/integration/upload.spec.ts +++ b/packages/middleware/__tests__/integration/upload.spec.ts @@ -58,4 +58,16 @@ describe("[Integration] Create server", () => { expect(body).toBeDefined(); expect(body.files).toBeUndefined(); }); + + it("should allow sending non multipart/form-data requests when fileUpload is enabled", async () => { + app = await getServer(); + + const { body } = await request(app) + .post("/test_integration/upload") + .expect(200); + + expect(body).toBeDefined(); + expect(body.files).toBeUndefined(); + expect(body.message).toBe("ok"); + }); }); diff --git a/packages/middleware/__tests__/unit/services/fileUpload.spec.ts b/packages/middleware/__tests__/unit/services/fileUpload.spec.ts index 266e6f48ed..4ad6e76da5 100644 --- a/packages/middleware/__tests__/unit/services/fileUpload.spec.ts +++ b/packages/middleware/__tests__/unit/services/fileUpload.spec.ts @@ -1,6 +1,6 @@ import express from "express"; import multer from "multer"; -import { configureFileUpload } from "../../../src/services/fileUpload"; +import { createMulterMiddleware } from "../../../src/services/fileUpload"; // Mock multer jest.mock("fileUpload", () => { @@ -22,13 +22,13 @@ describe("configureFileUpload", () => { }); it("should not configure upload when enabled is false", () => { - configureFileUpload(app, { enabled: false }); + createMulterMiddleware(app, { enabled: false }); expect(app.use).not.toHaveBeenCalled(); expect(multer).not.toHaveBeenCalled(); }); it("should configure upload with default options when enabled", () => { - configureFileUpload(app, { enabled: true }); + createMulterMiddleware(app, { enabled: true }); expect(multer).toHaveBeenCalledWith( expect.objectContaining({ @@ -49,7 +49,7 @@ describe("configureFileUpload", () => { allowedMimeTypes: ["image/jpeg"], }; - configureFileUpload(app, customOptions); + createMulterMiddleware(app, customOptions); expect(multer).toHaveBeenCalledWith( expect.objectContaining({ @@ -62,7 +62,7 @@ describe("configureFileUpload", () => { }); it("should use fields() when fieldNames are provided", () => { - configureFileUpload(app, { + createMulterMiddleware(app, { enabled: true, fieldNames: ["avatar", "document"], }); @@ -78,7 +78,7 @@ describe("configureFileUpload", () => { }); it("should use any() when no fieldNames are provided", () => { - configureFileUpload(app, { enabled: true }); + createMulterMiddleware(app, { enabled: true }); const multerInstance = (multer as unknown as jest.Mock).mock.results[0] .value; @@ -89,7 +89,7 @@ describe("configureFileUpload", () => { let fileFilter: (req: any, file: any, cb: any) => void; beforeEach(() => { - configureFileUpload(app, { + createMulterMiddleware(app, { enabled: true, allowedMimeTypes: ["image/*", "application/pdf"], }); @@ -98,7 +98,7 @@ describe("configureFileUpload", () => { it("should accept files when no mime types are specified", () => { const cb = jest.fn(); - configureFileUpload(app, { enabled: true, allowedMimeTypes: [] }); + createMulterMiddleware(app, { enabled: true, allowedMimeTypes: [] }); fileFilter = (multer as unknown as jest.Mock).mock.calls[1][0].fileFilter; fileFilter(null, { mimetype: "anything" }, cb); diff --git a/packages/middleware/src/createServer.ts b/packages/middleware/src/createServer.ts index 0b2c4825f0..965d5641d0 100644 --- a/packages/middleware/src/createServer.ts +++ b/packages/middleware/src/createServer.ts @@ -25,7 +25,7 @@ import { } from "./handlers"; import { createTerminusOptions } from "./terminus"; import { prepareLogger } from "./handlers/prepareLogger"; -import { configureFileUpload } from "./services/fileUpload"; +import { createMulterMiddleware } from "./services/fileUpload"; const defaultCorsOptions: CreateServerOptions["cors"] = { credentials: true, @@ -51,8 +51,7 @@ async function createServer< const app = express(); - configureFileUpload(app, options.fileUpload); - + app.use(createMulterMiddleware(options.fileUpload)); app.use(express.json(options.bodyParser)); app.use( options.cookieParser diff --git a/packages/middleware/src/services/fileUpload.ts b/packages/middleware/src/services/fileUpload.ts index 0ac5977cb1..657ae2cbcd 100644 --- a/packages/middleware/src/services/fileUpload.ts +++ b/packages/middleware/src/services/fileUpload.ts @@ -1,19 +1,21 @@ import multer from "multer"; -import type { Express } from "express"; +import { RequestHandler } from "express"; import type { FileUploadOptions } from "../types"; const DEFAULT_OPTIONS: FileUploadOptions = { - enabled: false, + enabled: true, maxFileSize: 5 * 1024 * 1024, // 5MB maxFiles: 5, allowedMimeTypes: ["image/*", "application/pdf"], }; -export function configureFileUpload(app: Express, options?: FileUploadOptions) { +export function createMulterMiddleware( + options?: FileUploadOptions +): RequestHandler | undefined { const config = { ...DEFAULT_OPTIONS, ...options }; if (!config.enabled) { - return; + return undefined; } const storage = multer.memoryStorage(); @@ -41,11 +43,9 @@ export function configureFileUpload(app: Express, options?: FileUploadOptions) { }, }); - // If specific field names are provided, use fields() if (config.fieldNames?.length) { const fields = config.fieldNames.map((name) => ({ name, maxCount: 1 })); - app.use(upload.fields(fields)); - } else { - app.use(upload.any()); + return upload.fields(fields); } + return upload.any(); } diff --git a/packages/middleware/src/types/fileUpload.ts b/packages/middleware/src/types/fileUpload.ts index 6cf8ae7e14..1248bcca84 100644 --- a/packages/middleware/src/types/fileUpload.ts +++ b/packages/middleware/src/types/fileUpload.ts @@ -1,7 +1,30 @@ +/** + * Options for file upload middleware + */ export interface FileUploadOptions { + /** + * Whether file upload is enabled + * @default true + */ enabled?: boolean; - maxFileSize?: number; // in bytes + /** + * Maximum file size in bytes + * @default 5MB + */ + maxFileSize?: number; + /** + * Maximum number of files + * @default 5 + */ maxFiles?: number; + /** + * Allowed MIME types + * @default ["image/*", "application/pdf"] + */ allowedMimeTypes?: string[]; - fieldNames?: string[]; // specific field names to accept + /** + * Specific field names to accept + * @default [] + */ + fieldNames?: string[]; } diff --git a/packages/middleware/tsconfig.json b/packages/middleware/tsconfig.json index 409b0459c6..b966bf605d 100644 --- a/packages/middleware/tsconfig.json +++ b/packages/middleware/tsconfig.json @@ -5,8 +5,7 @@ "outDir": "./lib", "declarationDir": "./lib", "declaration": true, - "rootDir": "./src", - "typeRoots": ["./src/types"] + "rootDir": "./src" }, "exclude": ["node_modules"], "include": ["src/**/*.ts"]