diff --git a/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentController.java b/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentController.java index beb98256..5aa58ca7 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentController.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentController.java @@ -168,8 +168,8 @@ public class DocumentController { @DeleteMapping("/{id}") @RequirePermission(Permission.WRITE_ALL) - public ResponseEntity deleteDocument(@PathVariable UUID id) { - documentService.deleteDocument(id); + public ResponseEntity deleteDocument(@PathVariable UUID id, Authentication authentication) { + documentService.deleteDocument(id, requireUserId(authentication)); return ResponseEntity.noContent().build(); } diff --git a/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentService.java b/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentService.java index dce06511..207798ed 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentService.java @@ -1098,13 +1098,13 @@ public class DocumentService { } @Transactional - public void deleteDocument(UUID id) { + public void deleteDocument(UUID id, UUID actorId) { if (!documentRepository.existsById(id)) { throw DomainException.notFound(ErrorCode.DOCUMENT_NOT_FOUND, "Document not found: " + id); } eventPublisher.publishEvent(new DocumentDeletingEvent(id)); documentRepository.deleteById(id); - auditService.logAfterCommit(AuditKind.DOCUMENT_DELETED, null, id, null); + auditService.logAfterCommit(AuditKind.DOCUMENT_DELETED, actorId, id, null); } @Transactional diff --git a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentControllerTest.java b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentControllerTest.java index b655e0bf..15934986 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentControllerTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentControllerTest.java @@ -402,6 +402,7 @@ class DocumentControllerTest { @WithMockUser(authorities = "WRITE_ALL") void deleteDocument_returns204_whenHasWritePermission() throws Exception { UUID id = UUID.randomUUID(); + when(userService.findByEmail(any())).thenReturn(AppUser.builder().id(UUID.randomUUID()).build()); mockMvc.perform(org.springframework.test.web.servlet.request.MockMvcRequestBuilders .delete("/api/documents/" + id).with(csrf())) .andExpect(status().isNoContent()); diff --git a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentServiceTest.java b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentServiceTest.java index 65fbb21f..eadad6ac 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentServiceTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentServiceTest.java @@ -30,6 +30,7 @@ import org.raddatz.familienarchiv.document.DocumentRepository; import org.raddatz.familienarchiv.filestorage.FileService; import org.raddatz.familienarchiv.tag.TagService; import org.raddatz.familienarchiv.person.PersonService; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; @@ -75,6 +76,7 @@ class DocumentServiceTest { @Mock AuditLogQueryService auditLogQueryService; @Mock TranscriptionBlockQueryService transcriptionBlockQueryService; @Mock ThumbnailAsyncRunner thumbnailAsyncRunner; + @Mock ApplicationEventPublisher eventPublisher; // Real factory (pure, dependency-free) so save-time title-regeneration tests exercise the // shared composition rather than a stub — the #726 single source of truth. @Spy DocumentTitleFactory documentTitleFactory = new DocumentTitleFactory(); @@ -87,7 +89,7 @@ class DocumentServiceTest { UUID id = UUID.randomUUID(); when(documentRepository.existsById(id)).thenReturn(true); - documentService.deleteDocument(id); + documentService.deleteDocument(id, UUID.randomUUID()); verify(documentRepository).deleteById(id); } @@ -97,7 +99,7 @@ class DocumentServiceTest { UUID id = UUID.randomUUID(); when(documentRepository.existsById(id)).thenReturn(false); - assertThatThrownBy(() -> documentService.deleteDocument(id)) + assertThatThrownBy(() -> documentService.deleteDocument(id, UUID.randomUUID())) .isInstanceOf(DomainException.class) .hasMessageContaining(id.toString()); verify(documentRepository, never()).deleteById(any()); diff --git a/backend/src/test/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyItemDocumentDeleteTest.java b/backend/src/test/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyItemDocumentDeleteTest.java index 4a042b03..b23ef447 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyItemDocumentDeleteTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyItemDocumentDeleteTest.java @@ -109,7 +109,7 @@ class JourneyItemDocumentDeleteTest { JourneyItem.builder().geschichte(journey).position(10).document(doc).build()); em.clear(); - documentService.deleteDocument(doc.getId()); + documentService.deleteDocument(doc.getId(), writer.getId()); assertThat(journeyItemRepository.findById(item.getId())).isEmpty(); assertThat(docRepo.findById(doc.getId())).isEmpty(); @@ -123,7 +123,7 @@ class JourneyItemDocumentDeleteTest { JourneyItem.builder().geschichte(journey).position(10).document(doc).note("curator context").build()); em.clear(); - documentService.deleteDocument(doc.getId()); + documentService.deleteDocument(doc.getId(), writer.getId()); em.clear(); JourneyItem surviving = journeyItemRepository.findById(item.getId()).orElseThrow(); @@ -139,7 +139,7 @@ class JourneyItemDocumentDeleteTest { JourneyItem.builder().geschichte(journey).position(10).note("Einleitung").build()); em.clear(); - documentService.deleteDocument(doc.getId()); + documentService.deleteDocument(doc.getId(), writer.getId()); em.clear(); JourneyItem reloaded = journeyItemRepository.findById(noteOnly.getId()).orElseThrow(); @@ -163,7 +163,7 @@ class JourneyItemDocumentDeleteTest { JourneyItem.builder().geschichte(journey2).position(10).document(doc).note("Begleittext").build()); em.clear(); - documentService.deleteDocument(doc.getId()); + documentService.deleteDocument(doc.getId(), writer.getId()); em.clear(); assertThat(journeyItemRepository.findById(noteLess.getId())).isEmpty(); @@ -183,7 +183,7 @@ class JourneyItemDocumentDeleteTest { doThrow(new RuntimeException("simulated failure")) .when(documentRepository).deleteById(any()); - assertThatThrownBy(() -> documentService.deleteDocument(doc.getId())) + assertThatThrownBy(() -> documentService.deleteDocument(doc.getId(), writer.getId())) .isInstanceOf(RuntimeException.class); em.clear(); @@ -213,7 +213,7 @@ class JourneyItemDocumentDeleteTest { whitespaceNoteItemId, journey2.getId(), 20, doc.getId(), " "); em.clear(); - documentService.deleteDocument(doc.getId()); + documentService.deleteDocument(doc.getId(), writer.getId()); em.clear(); assertThat(journeyItemRepository.findById(emptyNoteItemId)).isEmpty(); @@ -235,7 +235,7 @@ class JourneyItemDocumentDeleteTest { JourneyItem.builder().geschichte(journey).position(10).note("unrelated note").build()); em.clear(); - documentService.deleteDocument(unlinked.getId()); + documentService.deleteDocument(unlinked.getId(), writer.getId()); em.clear(); assertThat(docRepo.findById(unlinked.getId())).isEmpty(); @@ -251,9 +251,9 @@ class JourneyItemDocumentDeleteTest { JourneyItem.builder().geschichte(journey).position(10).document(doc).build()); em.clear(); - documentService.deleteDocument(doc.getId()); + documentService.deleteDocument(doc.getId(), writer.getId()); - verify(auditService).logAfterCommit(eq(AuditKind.DOCUMENT_DELETED), any(), eq(doc.getId()), any()); + verify(auditService).logAfterCommit(eq(AuditKind.DOCUMENT_DELETED), eq(writer.getId()), eq(doc.getId()), any()); verify(auditService, never()).logAfterCommit(eq(AuditKind.JOURNEY_ITEM_REMOVED), any(), any(), any()); } } diff --git a/backend/src/test/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyItemIntegrationTest.java b/backend/src/test/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyItemIntegrationTest.java index 75ce305a..59a2bf83 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyItemIntegrationTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyItemIntegrationTest.java @@ -220,7 +220,7 @@ class JourneyItemIntegrationTest { // Route through service so the DocumentDeletingEvent fires and the listener // removes note-less items before ON DELETE SET NULL acts on note-carrying rows. - documentService.deleteDocument(doc.getId()); + documentService.deleteDocument(doc.getId(), writer.getId()); em.flush(); em.clear(); @@ -359,7 +359,7 @@ class JourneyItemIntegrationTest { em.clear(); // Route through service so the DocumentDeletingEvent fires (V72 cascade fix). - documentService.deleteDocument(doc.getId()); + documentService.deleteDocument(doc.getId(), writer.getId()); em.flush(); em.clear();