diff --git a/backend/src/main/java/org/raddatz/familienarchiv/controller/DocumentController.java b/backend/src/main/java/org/raddatz/familienarchiv/controller/DocumentController.java index f23f465b..8cafba7a 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/controller/DocumentController.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/controller/DocumentController.java @@ -2,7 +2,9 @@ package org.raddatz.familienarchiv.controller; import java.io.IOException; import java.time.LocalDate; +import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.UUID; @@ -23,6 +25,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.core.io.InputStreamResource; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; @@ -103,6 +106,56 @@ public class DocumentController { } } + // --- DELETE --- + + @DeleteMapping("/{id}") + @RequirePermission(Permission.WRITE_ALL) + public ResponseEntity deleteDocument(@PathVariable UUID id) { + documentService.deleteDocument(id); + return ResponseEntity.noContent().build(); + } + + // --- QUICK UPLOAD --- + + private static final Set ALLOWED_CONTENT_TYPES = Set.of( + "application/pdf", "image/jpeg", "image/png", "image/tiff"); + + public record UploadError(String filename, String code) {} + public record QuickUploadResult(List created, List updated, List errors) {} + + @PostMapping(value = "/quick-upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @RequirePermission(Permission.WRITE_ALL) + public QuickUploadResult quickUpload( + @RequestPart(value = "files", required = false) List files) { + List created = new ArrayList<>(); + List updated = new ArrayList<>(); + List errors = new ArrayList<>(); + + if (files == null || files.isEmpty()) { + return new QuickUploadResult(created, updated, errors); + } + + for (MultipartFile file : files) { + if (!ALLOWED_CONTENT_TYPES.contains(file.getContentType())) { + errors.add(new UploadError(file.getOriginalFilename(), "UNSUPPORTED_FILE_TYPE")); + continue; + } + try { + DocumentService.StoreResult result = documentService.storeDocument(file); + if (result.isNew()) { + created.add(result.document()); + } else { + updated.add(result.document()); + } + } catch (Exception e) { + errors.add(new UploadError(file.getOriginalFilename(), "FILE_UPLOAD_FAILED")); + log.warn("Quick upload failed for file {}: {}", file.getOriginalFilename(), e.getMessage()); + } + } + + return new QuickUploadResult(created, updated, errors); + } + @GetMapping("/search") public ResponseEntity> search( @RequestParam(required = false) String q, diff --git a/backend/src/main/java/org/raddatz/familienarchiv/exception/ErrorCode.java b/backend/src/main/java/org/raddatz/familienarchiv/exception/ErrorCode.java index a26e5e5a..bcf72ef8 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/exception/ErrorCode.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/exception/ErrorCode.java @@ -17,6 +17,8 @@ public enum ErrorCode { FILE_NOT_FOUND, /** An error occurred while uploading a file to object storage. 500 */ FILE_UPLOAD_FAILED, + /** The uploaded file's content type is not supported (PDF/JPEG/PNG/TIFF only). 400 */ + UNSUPPORTED_FILE_TYPE, // --- Users --- /** A user with the given ID or username does not exist. 404 */ diff --git a/backend/src/main/java/org/raddatz/familienarchiv/repository/DocumentRepository.java b/backend/src/main/java/org/raddatz/familienarchiv/repository/DocumentRepository.java index 46969526..25da2dcd 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/repository/DocumentRepository.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/repository/DocumentRepository.java @@ -21,6 +21,9 @@ public interface DocumentRepository extends JpaRepository, JpaSp // Wichtig für den Abgleich beim Excel-Import & Datei-Upload Optional findByOriginalFilename(String originalFilename); + // Wie oben, gibt aber nur das erste Ergebnis zurück — sicher wenn doppelte Dateinamen existieren + Optional findFirstByOriginalFilename(String originalFilename); + // Findet alle Dokumente mit einem bestimmten Status // z.B. um alle offenen "PLACEHOLDER" zu finden List findByStatus(DocumentStatus status); diff --git a/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentService.java b/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentService.java index a79a8f22..cd878814 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentService.java @@ -42,18 +42,21 @@ public class DocumentService { private final DocumentVersionService documentVersionService; private final AnnotationService annotationService; + public record StoreResult(Document document, boolean isNew) {} + /** * Lädt eine Datei hoch. * - Prüft, ob ein Eintrag (aus Excel) schon existiert. - * - Wenn JA: Aktualisiert Status und verknüpft Datei. - * - Wenn NEIN: Erstellt neuen Eintrag (wartet auf Metadaten). + * - Wenn JA: Aktualisiert Status und verknüpft Datei — isNew = false. + * - Wenn NEIN: Erstellt neuen Eintrag — isNew = true. */ @Transactional - public Document storeDocument(MultipartFile file) throws IOException { + public StoreResult storeDocument(MultipartFile file) throws IOException { String originalFilename = file.getOriginalFilename(); - // 1. Check for existing record - Optional existingDoc = documentRepository.findByOriginalFilename(originalFilename); + // 1. Check for existing record (findFirst to survive duplicate filenames in the DB) + Optional existingDoc = documentRepository.findFirstByOriginalFilename(originalFilename); + boolean isNew = existingDoc.isEmpty(); Document document; if (existingDoc.isPresent()) { @@ -61,7 +64,7 @@ public class DocumentService { } else { document = Document.builder() .originalFilename(originalFilename) - .title(originalFilename) + .title(stripExtension(originalFilename)) .status(DocumentStatus.UPLOADED) .build(); } @@ -77,7 +80,7 @@ public class DocumentService { document.setStatus(DocumentStatus.UPLOADED); } - return documentRepository.save(document); + return new StoreResult(documentRepository.save(document), isNew); } @Transactional @@ -234,8 +237,8 @@ public class DocumentService { .and(hasReceiver(receiver)) .and(hasTags(tags)); - // Immer sortiert nach Datum - return documentRepository.findAll(spec, Sort.by(Sort.Direction.ASC, "documentDate")); + // Neueste zuerst (nach Erstellungsdatum) + return documentRepository.findAll(spec, Sort.by(Sort.Direction.DESC, "createdAt")); } // 2. SPEZIALITÄT: Der Schriftwechsel @@ -277,6 +280,14 @@ public class DocumentService { return documentRepository.findConversation(senderId, receiverId, dateFrom, dateTo, sort); } + @Transactional + public void deleteDocument(UUID id) { + if (!documentRepository.existsById(id)) { + throw DomainException.notFound(ErrorCode.DOCUMENT_NOT_FOUND, "Document not found: " + id); + } + documentRepository.deleteById(id); + } + @Transactional public void deleteTagCascading(UUID tagId) { documentRepository.findByTags_Id(tagId).forEach(doc -> { @@ -307,6 +318,12 @@ public class DocumentService { // ─── private helpers ────────────────────────────────────────────────────── + private static String stripExtension(String filename) { + if (filename == null) return null; + int dot = filename.lastIndexOf('.'); + return dot > 0 ? filename.substring(0, dot) : filename; + } + private static String sha256Hex(byte[] bytes) { try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); diff --git a/backend/src/main/resources/db/migration/V14__add_cascade_delete_to_document_join_tables.sql b/backend/src/main/resources/db/migration/V14__add_cascade_delete_to_document_join_tables.sql new file mode 100644 index 00000000..7056a7da --- /dev/null +++ b/backend/src/main/resources/db/migration/V14__add_cascade_delete_to_document_join_tables.sql @@ -0,0 +1,12 @@ +-- Add ON DELETE CASCADE to document_tags and document_receivers so that +-- deleting a document automatically removes its tag and receiver associations. + +ALTER TABLE public.document_tags + DROP CONSTRAINT fkc99c5qjulwx9gru07yrhicgd2, + ADD CONSTRAINT fkc99c5qjulwx9gru07yrhicgd2 + FOREIGN KEY (document_id) REFERENCES public.documents(id) ON DELETE CASCADE; + +ALTER TABLE public.document_receivers + DROP CONSTRAINT fks7t60twjgfmpeqcuc3g0fvjpm, + ADD CONSTRAINT fks7t60twjgfmpeqcuc3g0fvjpm + FOREIGN KEY (document_id) REFERENCES public.documents(id) ON DELETE CASCADE; diff --git a/backend/src/test/java/org/raddatz/familienarchiv/controller/DocumentControllerTest.java b/backend/src/test/java/org/raddatz/familienarchiv/controller/DocumentControllerTest.java index 545e98f3..f4f6c439 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/controller/DocumentControllerTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/controller/DocumentControllerTest.java @@ -121,6 +121,97 @@ class DocumentControllerTest { .andExpect(status().isOk()); } + // ─── DELETE /api/documents/{id} ────────────────────────────────────────── + + @Test + void deleteDocument_returns401_whenUnauthenticated() throws Exception { + mockMvc.perform(org.springframework.test.web.servlet.request.MockMvcRequestBuilders + .delete("/api/documents/" + UUID.randomUUID())) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser + void deleteDocument_returns403_whenMissingWritePermission() throws Exception { + mockMvc.perform(org.springframework.test.web.servlet.request.MockMvcRequestBuilders + .delete("/api/documents/" + UUID.randomUUID())) + .andExpect(status().isForbidden()); + } + + @Test + @WithMockUser(authorities = "WRITE_ALL") + void deleteDocument_returns204_whenHasWritePermission() throws Exception { + UUID id = UUID.randomUUID(); + mockMvc.perform(org.springframework.test.web.servlet.request.MockMvcRequestBuilders + .delete("/api/documents/" + id)) + .andExpect(status().isNoContent()); + } + + // ─── POST /api/documents/quick-upload ──────────────────────────────────── + + @Test + void quickUpload_returns401_whenUnauthenticated() throws Exception { + mockMvc.perform(multipart("/api/documents/quick-upload")) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser + void quickUpload_returns403_whenMissingWritePermission() throws Exception { + mockMvc.perform(multipart("/api/documents/quick-upload")) + .andExpect(status().isForbidden()); + } + + @Test + @WithMockUser(authorities = "WRITE_ALL") + void quickUpload_returns200_withValidPdfFile() throws Exception { + Document doc = Document.builder() + .id(UUID.randomUUID()).title("scan001").originalFilename("scan001.pdf").build(); + when(documentService.storeDocument(any())) + .thenReturn(new DocumentService.StoreResult(doc, true)); + + org.springframework.mock.web.MockMultipartFile file = + new org.springframework.mock.web.MockMultipartFile("files", "scan001.pdf", "application/pdf", new byte[]{1}); + + mockMvc.perform(multipart("/api/documents/quick-upload").file(file)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.created[0].title").value("scan001")) + .andExpect(jsonPath("$.updated").isEmpty()) + .andExpect(jsonPath("$.errors").isEmpty()); + } + + @Test + @WithMockUser(authorities = "WRITE_ALL") + void quickUpload_placesDocumentInUpdated_whenFilenameAlreadyExists() throws Exception { + Document existing = Document.builder() + .id(UUID.randomUUID()).title("Alter Brief").originalFilename("scan001.pdf").build(); + when(documentService.storeDocument(any())) + .thenReturn(new DocumentService.StoreResult(existing, false)); + + org.springframework.mock.web.MockMultipartFile file = + new org.springframework.mock.web.MockMultipartFile("files", "scan001.pdf", "application/pdf", new byte[]{1}); + + mockMvc.perform(multipart("/api/documents/quick-upload").file(file)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.created").isEmpty()) + .andExpect(jsonPath("$.updated[0].title").value("Alter Brief")) + .andExpect(jsonPath("$.errors").isEmpty()); + } + + @Test + @WithMockUser(authorities = "WRITE_ALL") + void quickUpload_skipsUnsupportedFileType_andReturnsError() throws Exception { + org.springframework.mock.web.MockMultipartFile file = + new org.springframework.mock.web.MockMultipartFile("files", "report.docx", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", new byte[]{1}); + + mockMvc.perform(multipart("/api/documents/quick-upload").file(file)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.created").isEmpty()) + .andExpect(jsonPath("$.errors[0].filename").value("report.docx")) + .andExpect(jsonPath("$.errors[0].code").value("UNSUPPORTED_FILE_TYPE")); + } + // ─── GET /api/documents/{id}/versions ──────────────────────────────────── @Test diff --git a/backend/src/test/java/org/raddatz/familienarchiv/service/DocumentServiceTest.java b/backend/src/test/java/org/raddatz/familienarchiv/service/DocumentServiceTest.java index b6fc3dea..984be862 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/service/DocumentServiceTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/service/DocumentServiceTest.java @@ -35,6 +35,29 @@ class DocumentServiceTest { @Mock AnnotationService annotationService; @InjectMocks DocumentService documentService; + // ─── deleteDocument ─────────────────────────────────────────────────────── + + @Test + void deleteDocument_deletesById_whenExists() { + UUID id = UUID.randomUUID(); + when(documentRepository.existsById(id)).thenReturn(true); + + documentService.deleteDocument(id); + + verify(documentRepository).deleteById(id); + } + + @Test + void deleteDocument_throwsNotFound_whenMissing() { + UUID id = UUID.randomUUID(); + when(documentRepository.existsById(id)).thenReturn(false); + + assertThatThrownBy(() -> documentService.deleteDocument(id)) + .isInstanceOf(DomainException.class) + .hasMessageContaining(id.toString()); + verify(documentRepository, never()).deleteById(any()); + } + // ─── getDocumentById ────────────────────────────────────────────────────── @Test @@ -212,6 +235,75 @@ class DocumentServiceTest { verify(documentVersionService).recordVersion(any(Document.class)); } + // ─── storeDocument ─────────────────────────────────────────────────────── + + @Test + void storeDocument_setsTitle_withoutFileExtension_forNewDocument() throws Exception { + org.springframework.mock.web.MockMultipartFile file = + new org.springframework.mock.web.MockMultipartFile("file", "scan001.pdf", "application/pdf", new byte[]{1}); + FileService.UploadResult uploadResult = new FileService.UploadResult("documents/uuid_scan001.pdf", "abc123"); + Document saved = Document.builder().id(UUID.randomUUID()).title("scan001").originalFilename("scan001.pdf").build(); + + when(documentRepository.findFirstByOriginalFilename("scan001.pdf")).thenReturn(Optional.empty()); + when(documentRepository.save(any())).thenReturn(saved); + when(fileService.uploadFile(any(), any())).thenReturn(uploadResult); + + org.mockito.ArgumentCaptor captor = org.mockito.ArgumentCaptor.forClass(Document.class); + documentService.storeDocument(file); + + verify(documentRepository).save(captor.capture()); + assertThat(captor.getValue().getTitle()).isEqualTo("scan001"); + } + + @Test + void storeDocument_preservesExistingTitle_whenPlaceholderAlreadyExists() throws Exception { + org.springframework.mock.web.MockMultipartFile file = + new org.springframework.mock.web.MockMultipartFile("file", "scan001.pdf", "application/pdf", new byte[]{1}); + FileService.UploadResult uploadResult = new FileService.UploadResult("documents/uuid_scan001.pdf", "abc123"); + Document placeholder = Document.builder() + .id(UUID.randomUUID()).title("Brief an Oma").originalFilename("scan001.pdf") + .status(DocumentStatus.PLACEHOLDER).build(); + + when(documentRepository.findFirstByOriginalFilename("scan001.pdf")).thenReturn(Optional.of(placeholder)); + when(documentRepository.save(any())).thenReturn(placeholder); + when(fileService.uploadFile(any(), any())).thenReturn(uploadResult); + + documentService.storeDocument(file); + + assertThat(placeholder.getTitle()).isEqualTo("Brief an Oma"); + } + + @Test + void storeDocument_marksResultAsNew_whenNoExistingDocument() throws Exception { + org.springframework.mock.web.MockMultipartFile file = + new org.springframework.mock.web.MockMultipartFile("file", "new.pdf", "application/pdf", new byte[]{1}); + Document saved = Document.builder().id(UUID.randomUUID()).originalFilename("new.pdf").build(); + + when(documentRepository.findFirstByOriginalFilename("new.pdf")).thenReturn(Optional.empty()); + when(documentRepository.save(any())).thenReturn(saved); + when(fileService.uploadFile(any(), any())).thenReturn(new FileService.UploadResult("documents/new.pdf", "hash")); + + DocumentService.StoreResult result = documentService.storeDocument(file); + + assertThat(result.isNew()).isTrue(); + } + + @Test + void storeDocument_marksResultAsNotNew_whenDocumentWithSameFilenameExists() throws Exception { + org.springframework.mock.web.MockMultipartFile file = + new org.springframework.mock.web.MockMultipartFile("file", "existing.pdf", "application/pdf", new byte[]{1}); + Document existing = Document.builder().id(UUID.randomUUID()).originalFilename("existing.pdf") + .status(DocumentStatus.UPLOADED).build(); + + when(documentRepository.findFirstByOriginalFilename("existing.pdf")).thenReturn(Optional.of(existing)); + when(documentRepository.save(any())).thenReturn(existing); + when(fileService.uploadFile(any(), any())).thenReturn(new FileService.UploadResult("documents/existing.pdf", "hash")); + + DocumentService.StoreResult result = documentService.storeDocument(file); + + assertThat(result.isNew()).isFalse(); + } + // ─── backfillFileHashes ─────────────────────────────────────────────────── @Test diff --git a/frontend/messages/de.json b/frontend/messages/de.json index 5d29f488..c033a147 100644 --- a/frontend/messages/de.json +++ b/frontend/messages/de.json @@ -7,6 +7,7 @@ "error_document_no_file": "Diesem Dokument ist noch keine Datei zugeordnet.", "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_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.", @@ -23,6 +24,7 @@ "btn_edit": "Bearbeiten", "btn_create": "Erstellen", "btn_delete": "Löschen", + "doc_delete_confirm": "Dokument wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.", "btn_back_to_overview": "Zurück zur Übersicht", "btn_back": "Zurück", "btn_back_to_document": "Zurück zum Dokument", @@ -265,5 +267,12 @@ "doc_panel_annotation_thread_title": "Annotation", "doc_panel_discussion_annotation_tab": "Annotation · Seite {page}", "pdf_annotations_show": "Annotierungen anzeigen", - "pdf_annotations_hide": "Annotierungen verbergen" + "pdf_annotations_hide": "Annotierungen verbergen", + "upload_drop_hint": "Dateien ablegen oder auswählen", + "upload_accepted_types": "PDF, JPEG, PNG, TIFF", + "upload_success": "{count} Dokument(e) erstellt", + "upload_duplicate": "{filename} existiert bereits —", + "upload_duplicate_link": "Zum Dokument", + "upload_invalid_type": "{filename}: Dateiformat nicht unterstützt", + "upload_error": "Fehler beim Hochladen von {filename}" } diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 94bf692f..f9d53545 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -7,6 +7,7 @@ "error_document_no_file": "No file is associated with this document.", "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_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.", @@ -23,6 +24,7 @@ "btn_edit": "Edit", "btn_create": "Create", "btn_delete": "Delete", + "doc_delete_confirm": "Really delete this document? This action cannot be undone.", "btn_back_to_overview": "Back to overview", "btn_back": "Back", "btn_back_to_document": "Back to document", @@ -265,5 +267,12 @@ "doc_panel_annotation_thread_title": "Annotation", "doc_panel_discussion_annotation_tab": "Annotation · Page {page}", "pdf_annotations_show": "Show annotations", - "pdf_annotations_hide": "Hide annotations" + "pdf_annotations_hide": "Hide annotations", + "upload_drop_hint": "Drop files or click to select", + "upload_accepted_types": "PDF, JPEG, PNG, TIFF", + "upload_success": "{count} document(s) created", + "upload_duplicate": "{filename} already exists —", + "upload_duplicate_link": "View document", + "upload_invalid_type": "{filename}: unsupported file format", + "upload_error": "Error uploading {filename}" } diff --git a/frontend/messages/es.json b/frontend/messages/es.json index e0a46eef..958fb02e 100644 --- a/frontend/messages/es.json +++ b/frontend/messages/es.json @@ -7,6 +7,7 @@ "error_document_no_file": "No hay ningún archivo asociado a este documento.", "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_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.", @@ -23,6 +24,7 @@ "btn_edit": "Editar", "btn_create": "Crear", "btn_delete": "Eliminar", + "doc_delete_confirm": "¿Realmente eliminar este documento? Esta acción no se puede deshacer.", "btn_back_to_overview": "Volver al resumen", "btn_back": "Volver", "btn_back_to_document": "Volver al documento", @@ -265,5 +267,12 @@ "doc_panel_annotation_thread_title": "Anotación", "doc_panel_discussion_annotation_tab": "Anotación · Página {page}", "pdf_annotations_show": "Mostrar anotaciones", - "pdf_annotations_hide": "Ocultar anotaciones" + "pdf_annotations_hide": "Ocultar anotaciones", + "upload_drop_hint": "Soltar archivos o hacer clic para seleccionar", + "upload_accepted_types": "PDF, JPEG, PNG, TIFF", + "upload_success": "{count} documento(s) creado(s)", + "upload_duplicate": "{filename} ya existe —", + "upload_duplicate_link": "Ver documento", + "upload_invalid_type": "{filename}: formato de archivo no admitido", + "upload_error": "Error al subir {filename}" } diff --git a/frontend/src/lib/errors.ts b/frontend/src/lib/errors.ts index 3e679fbc..3abe2b0c 100644 --- a/frontend/src/lib/errors.ts +++ b/frontend/src/lib/errors.ts @@ -9,6 +9,7 @@ export type ErrorCode = | 'DOCUMENT_NO_FILE' | 'FILE_NOT_FOUND' | 'FILE_UPLOAD_FAILED' + | 'UNSUPPORTED_FILE_TYPE' | 'USER_NOT_FOUND' | 'EMAIL_ALREADY_IN_USE' | 'WRONG_CURRENT_PASSWORD' @@ -54,6 +55,8 @@ export function getErrorMessage(code: ErrorCode | string | undefined): string { return m.error_file_not_found(); case 'FILE_UPLOAD_FAILED': return m.error_file_upload_failed(); + case 'UNSUPPORTED_FILE_TYPE': + return m.error_unsupported_file_type(); case 'USER_NOT_FOUND': return m.error_user_not_found(); case 'EMAIL_ALREADY_IN_USE': diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index cbe28932..71a5c93b 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -1,12 +1,13 @@