Skip to content

Commit 9b490d2

Browse files
Merge pull request #3408 from pierreb-devkit/feat/uploads-extensible-mime-types
feat(uploads): make MIME type map extensible via config.uploads.mimeTypes (#3407)
2 parents 6555ba1 + b2b55d7 commit 9b490d2

2 files changed

Lines changed: 49 additions & 1 deletion

File tree

modules/uploads/services/uploads.service.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ const createFromBuffer = async (buffer, contentType, kind, metadata = {}) => {
9494
throw new AppError(`Upload: buffer size ${buffer.length} exceeds limit ${kindConfig.limits.fileSize}`, { code: 'SERVICE_ERROR', status: 422 });
9595
}
9696

97-
const ext = MIME_TO_EXT[contentType] || 'bin';
97+
const rawExt = config.uploads?.mimeTypes?.[contentType] || MIME_TO_EXT[contentType];
98+
const ext = rawExt && /^[a-zA-Z0-9]+$/.test(rawExt) ? rawExt : 'bin';
9899
const filename = `${crypto.randomBytes(32).toString('hex')}.${ext}`;
99100

100101
const result = await gridfs.createFromBuffer(buffer, filename, contentType, { ...metadata, kind, contentType });

modules/uploads/tests/uploads.createFromBuffer.unit.tests.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,22 @@ describe('Uploads createFromBuffer unit tests:', () => {
159159
expect(mockGridfs.createFromBuffer).toHaveBeenCalledTimes(1);
160160
});
161161

162+
test('should use custom mimeType extension from config.uploads.mimeTypes', async () => {
163+
mockConfig.uploads.mimeTypes = { 'text/html': 'html' };
164+
mockConfig.uploads.report = {
165+
kind: 'report',
166+
formats: ['text/html'],
167+
limits: { fileSize: 1 * 1024 * 1024 },
168+
};
169+
mockGridfs.createFromBuffer.mockResolvedValue({ ...fakeFile, contentType: 'text/html' });
170+
171+
const buffer = Buffer.alloc(512);
172+
await UploadsService.createFromBuffer(buffer, 'text/html', 'report', { user: '507f1f77bcf86cd799439011' });
173+
174+
const [, filename] = mockGridfs.createFromBuffer.mock.calls[0];
175+
expect(filename).toMatch(/^[a-f0-9]{64}\.html$/);
176+
});
177+
162178
test('should throw error when kind has no formats configured', async () => {
163179
// Adding 'broken' kind at runtime — service reads config dynamically via module reference
164180
mockConfig.uploads.broken = { kind: 'broken', limits: { fileSize: 1024 } };
@@ -169,4 +185,35 @@ describe('Uploads createFromBuffer unit tests:', () => {
169185

170186
expect(mockGridfs.createFromBuffer).not.toHaveBeenCalled();
171187
});
188+
189+
test('should fallback to bin extension for unmapped MIME types', async () => {
190+
mockConfig.uploads.generic = {
191+
kind: 'generic',
192+
formats: ['application/octet-stream'],
193+
limits: { fileSize: 1 * 1024 * 1024 },
194+
};
195+
mockGridfs.createFromBuffer.mockResolvedValue({ ...fakeFile, contentType: 'application/octet-stream' });
196+
197+
const buffer = Buffer.alloc(512);
198+
await UploadsService.createFromBuffer(buffer, 'application/octet-stream', 'generic');
199+
200+
const [, filename] = mockGridfs.createFromBuffer.mock.calls[0];
201+
expect(filename).toMatch(/^[a-f0-9]{64}\.bin$/);
202+
});
203+
204+
test('should fallback to bin extension when config extension contains invalid characters', async () => {
205+
mockConfig.uploads.mimeTypes = { 'application/x-tar': 'tar.gz' };
206+
mockConfig.uploads.archive = {
207+
kind: 'archive',
208+
formats: ['application/x-tar'],
209+
limits: { fileSize: 10 * 1024 * 1024 },
210+
};
211+
mockGridfs.createFromBuffer.mockResolvedValue({ ...fakeFile, contentType: 'application/x-tar' });
212+
213+
const buffer = Buffer.alloc(512);
214+
await UploadsService.createFromBuffer(buffer, 'application/x-tar', 'archive');
215+
216+
const [, filename] = mockGridfs.createFromBuffer.mock.calls[0];
217+
expect(filename).toMatch(/^[a-f0-9]{64}\.bin$/);
218+
});
172219
});

0 commit comments

Comments
 (0)