Merge remote-tracking branch 'origin/main' into feat/guest-upload-dng-raw

# Conflicts:
#	backend/src/services/uploadSettings.js
#	backend/src/utils/fileSecurityUtils.js
#	frontend/src/utils/fileTypes.ts
This commit is contained in:
Paul Nothaft
2026-07-18 20:52:08 +02:00
23 changed files with 197 additions and 21 deletions
@@ -0,0 +1,38 @@
import { describe, it, expect } from 'vitest';
import { extensionsToMimeTypes, extensionsToAcceptString, extensionsToLabel } from '../fileTypes';
describe('fileTypes', () => {
describe('extensionsToMimeTypes', () => {
it('maps known extensions to MIME types', () => {
expect(extensionsToMimeTypes('jpg,png,mov')).toEqual(['image/jpeg', 'image/png', 'video/quicktime']);
});
it('supports HEIC/HEIF (#821)', () => {
expect(extensionsToMimeTypes('heic,heif')).toEqual(['image/heic', 'image/heif']);
});
it('supports DNG (#821)', () => {
expect(extensionsToMimeTypes('dng')).toEqual(['image/x-adobe-dng']);
});
it('drops unknown extensions and falls back to default when nothing maps', () => {
expect(extensionsToMimeTypes('abc,xyz')).toEqual(['image/jpeg', 'image/png', 'image/webp']);
});
});
describe('extensionsToLabel', () => {
it('renders a de-duplicated, upper-cased list of the configured formats', () => {
expect(extensionsToLabel('jpg,jpeg,png,webp,mov')).toBe('JPG, JPEG, PNG, WEBP, MOV');
});
it('only lists supported extensions (drops unknowns like xyz)', () => {
expect(extensionsToLabel('jpg,png,xyz')).toBe('JPG, PNG');
});
it('falls back to the default set when empty', () => {
expect(extensionsToLabel('')).toBe('JPG, JPEG, PNG, WEBP');
expect(extensionsToLabel(null)).toBe('JPG, JPEG, PNG, WEBP');
});
});
describe('extensionsToAcceptString', () => {
it('joins MIME types for the input accept attribute', () => {
expect(extensionsToAcceptString('jpg,heic')).toBe('image/jpeg,image/heic');
});
});
});
+24
View File
@@ -12,6 +12,9 @@ const EXTENSION_TO_MIME: Record<string, string> = {
webm: 'video/webm',
mov: 'video/quicktime',
avi: 'video/x-msvideo',
// HEIC/HEIF (iPhone) — kept in sync with the backend EXTENSION_TO_MIME.
heic: 'image/heic',
heif: 'image/heif',
// Camera RAW / Apple ProRAW — backend extracts the embedded JPEG preview.
dng: 'image/x-adobe-dng',
};
@@ -48,3 +51,24 @@ export function extensionsToMimeTypes(extString?: string | null): string[] {
export function extensionsToAcceptString(extString?: string | null): string {
return extensionsToMimeTypes(extString).join(',');
}
/**
* Human-readable, de-duplicated list of the configured extensions for the
* upload requirements hint, e.g. "JPG, PNG, WEBP, MOV". Only extensions the
* app actually supports (present in EXTENSION_TO_MIME) are shown, so the hint
* never advertises a format the backend would reject.
*/
export function extensionsToLabel(extString?: string | null): string {
const input = extString?.trim() || DEFAULT_ALLOWED;
const seen = new Set<string>();
const labels: string[] = [];
input.split(',').forEach(ext => {
const cleaned = ext.trim().toLowerCase().replace(/^\./, '');
if (cleaned && EXTENSION_TO_MIME[cleaned] && !seen.has(cleaned)) {
seen.add(cleaned);
labels.push(cleaned.toUpperCase());
}
});
if (labels.length === 0) return extensionsToLabel(DEFAULT_ALLOWED);
return labels.join(', ');
}