From a736b7399a0fc7465b74d7d46d544277d8273ba4 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 26 Apr 2026 14:51:15 +0200 Subject: [PATCH] feat(audit): emit USER_DELETED when admin removes a user Adds actorId param to deleteUser(), captures email before deletion, emits logAfterCommit(USER_DELETED) with userId+email in payload. Updates UserController to resolve and pass actorId. Co-Authored-By: Claude Sonnet 4.6 --- .../controller/UserController.java | 6 +++-- .../familienarchiv/service/UserService.java | 5 +++- .../service/UserServiceTest.java | 26 +++++++++++++++++-- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/org/raddatz/familienarchiv/controller/UserController.java b/backend/src/main/java/org/raddatz/familienarchiv/controller/UserController.java index bcd8b9bd..19a78ea7 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/controller/UserController.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/controller/UserController.java @@ -95,8 +95,10 @@ public class UserController { @DeleteMapping("/users/{id}") @RequirePermission(Permission.ADMIN_USER) - public ResponseEntity deleteUser(@PathVariable UUID id) { - userService.deleteUser(id); + public ResponseEntity deleteUser(Authentication authentication, + @PathVariable UUID id) { + AppUser actor = userService.findByEmail(authentication.getName()); + userService.deleteUser(actor.getId(), id); return ResponseEntity.ok().build(); } diff --git a/backend/src/main/java/org/raddatz/familienarchiv/service/UserService.java b/backend/src/main/java/org/raddatz/familienarchiv/service/UserService.java index fe77cb87..3d99ce75 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/service/UserService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/service/UserService.java @@ -106,10 +106,13 @@ public class UserService { } @Transactional - public void deleteUser(UUID userId) { + public void deleteUser(UUID actorId, UUID userId) { AppUser user = userRepository.findById(userId) .orElseThrow(() -> DomainException.notFound(ErrorCode.USER_NOT_FOUND, "No user found for id: " + userId)); + String email = user.getEmail(); userRepository.delete(user); + auditService.logAfterCommit(AuditKind.USER_DELETED, actorId, null, + Map.of("userId", userId.toString(), "email", email)); } public AppUser getById(UUID id) { diff --git a/backend/src/test/java/org/raddatz/familienarchiv/service/UserServiceTest.java b/backend/src/test/java/org/raddatz/familienarchiv/service/UserServiceTest.java index 5062d2c1..b522da90 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/service/UserServiceTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/service/UserServiceTest.java @@ -65,7 +65,7 @@ class UserServiceTest { UUID id = UUID.randomUUID(); when(userRepository.findById(id)).thenReturn(Optional.empty()); - assertThatThrownBy(() -> userService.deleteUser(id)) + assertThatThrownBy(() -> userService.deleteUser(UUID.randomUUID(), id)) .isInstanceOf(DomainException.class); } @@ -75,7 +75,7 @@ class UserServiceTest { AppUser user = AppUser.builder().id(id).email("gast@example.com").build(); when(userRepository.findById(id)).thenReturn(Optional.of(user)); - userService.deleteUser(id); + userService.deleteUser(UUID.randomUUID(), id); verify(userRepository).delete(user); } @@ -703,6 +703,28 @@ class UserServiceTest { assertThat(result).containsExactly(g); } + // ─── audit: USER_DELETED ────────────────────────────────────────────────── + + @Test + void deleteUser_logsUserDeleted_withEmailInPayload() { + UUID actorId = UUID.randomUUID(); + UUID userId = UUID.randomUUID(); + AppUser user = AppUser.builder().id(userId).email("gone@example.com").build(); + when(userRepository.findById(userId)).thenReturn(Optional.of(user)); + + userService.deleteUser(actorId, userId); + + @SuppressWarnings("unchecked") + ArgumentCaptor> payloadCaptor = ArgumentCaptor.forClass(java.util.Map.class); + verify(auditService).logAfterCommit( + org.mockito.ArgumentMatchers.eq(AuditKind.USER_DELETED), + org.mockito.ArgumentMatchers.eq(actorId), + org.mockito.ArgumentMatchers.isNull(), + payloadCaptor.capture()); + assertThat(payloadCaptor.getValue()).containsEntry("email", "gone@example.com"); + assertThat(payloadCaptor.getValue()).containsKey("userId"); + } + // ─── audit: USER_CREATED ────────────────────────────────────────────────── @Test