feat(upload): validate MIME type and size on file replace in DocumentEditLayout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
"error_file_not_found": "Die Datei konnte im Speicher nicht gefunden werden.",
|
||||
"error_file_upload_failed": "Die Datei konnte nicht hochgeladen werden.",
|
||||
"error_unsupported_file_type": "Dieses Dateiformat wird nicht unterstützt.",
|
||||
"error_file_too_large": "Die Datei ist zu groß (max. 50 MB).",
|
||||
"error_user_not_found": "Der Benutzer wurde nicht gefunden.",
|
||||
"error_import_already_running": "Ein Import läuft bereits. Bitte warten Sie, bis dieser abgeschlossen ist.",
|
||||
"error_unauthorized": "Sie sind nicht angemeldet.",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"error_file_not_found": "The file could not be found in storage.",
|
||||
"error_file_upload_failed": "The file could not be uploaded.",
|
||||
"error_unsupported_file_type": "This file format is not supported.",
|
||||
"error_file_too_large": "The file is too large (max. 50 MB).",
|
||||
"error_user_not_found": "User not found.",
|
||||
"error_import_already_running": "An import is already running. Please wait for it to finish.",
|
||||
"error_unauthorized": "You are not logged in.",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"error_file_not_found": "El archivo no pudo encontrarse en el almacenamiento.",
|
||||
"error_file_upload_failed": "No se pudo subir el archivo.",
|
||||
"error_unsupported_file_type": "Este formato de archivo no está admitido.",
|
||||
"error_file_too_large": "El archivo es demasiado grande (máx. 50 MB).",
|
||||
"error_user_not_found": "Usuario no encontrado.",
|
||||
"error_import_already_running": "Ya hay una importación en curso. Por favor, espere a que finalice.",
|
||||
"error_unauthorized": "No ha iniciado sesión.",
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { Snippet } from 'svelte';
|
||||
import { createFileLoader } from '$lib/hooks/useFileLoader.svelte';
|
||||
import { m } from '$lib/paraglide/messages.js';
|
||||
import { countRequiredFilled } from '$lib/utils/requiredFields';
|
||||
import { validateFile } from '$lib/utils/validateFile';
|
||||
import DocumentViewer from '$lib/components/DocumentViewer.svelte';
|
||||
import UploadZone from '$lib/components/document/UploadZone.svelte';
|
||||
import WhoWhenSection from '$lib/components/document/WhoWhenSection.svelte';
|
||||
@@ -113,6 +114,15 @@ function cancelUpload() {
|
||||
async function handleReplaceFile(e: Event) {
|
||||
const file = (e.currentTarget as HTMLInputElement).files?.[0];
|
||||
if (!file) return;
|
||||
const validationError = validateFile(file);
|
||||
if (validationError === 'type') {
|
||||
uploadError = m.error_unsupported_file_type();
|
||||
return;
|
||||
}
|
||||
if (validationError === 'size') {
|
||||
uploadError = m.error_file_too_large();
|
||||
return;
|
||||
}
|
||||
await handleFile(file);
|
||||
}
|
||||
</script>
|
||||
|
||||
36
frontend/src/lib/utils/validateFile.spec.ts
Normal file
36
frontend/src/lib/utils/validateFile.spec.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { validateFile, MAX_SIZE_BYTES } from './validateFile';
|
||||
|
||||
function makeFile(type: string, size: number): File {
|
||||
return new File(['x'.repeat(Math.min(size, 100))], 'test.file', { type });
|
||||
}
|
||||
|
||||
describe('validateFile', () => {
|
||||
it('returns null for a valid PDF under 50 MB', () => {
|
||||
const file = makeFile('application/pdf', 1024);
|
||||
expect(validateFile(file)).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null for a valid JPEG', () => {
|
||||
expect(validateFile(makeFile('image/jpeg', 1024))).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null for a valid PNG', () => {
|
||||
expect(validateFile(makeFile('image/png', 1024))).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null for a valid TIFF', () => {
|
||||
expect(validateFile(makeFile('image/tiff', 1024))).toBeNull();
|
||||
});
|
||||
|
||||
it('returns "type" for an unsupported MIME type', () => {
|
||||
const file = makeFile('text/plain', 100);
|
||||
expect(validateFile(file)).toBe('type');
|
||||
});
|
||||
|
||||
it('returns "size" for a file exceeding 50 MB', () => {
|
||||
const oversized = new File(['x'], 'big.pdf', { type: 'application/pdf' });
|
||||
Object.defineProperty(oversized, 'size', { value: MAX_SIZE_BYTES + 1 });
|
||||
expect(validateFile(oversized)).toBe('size');
|
||||
});
|
||||
});
|
||||
10
frontend/src/lib/utils/validateFile.ts
Normal file
10
frontend/src/lib/utils/validateFile.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export const ALLOWED_TYPES = new Set(['application/pdf', 'image/jpeg', 'image/png', 'image/tiff']);
|
||||
export const MAX_SIZE_BYTES = 50 * 1024 * 1024;
|
||||
|
||||
export type FileValidationError = 'type' | 'size';
|
||||
|
||||
export function validateFile(file: File): FileValidationError | null {
|
||||
if (!ALLOWED_TYPES.has(file.type)) return 'type';
|
||||
if (file.size > MAX_SIZE_BYTES) return 'size';
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user