feat(audit): instrument DocumentService for METADATA_UPDATED, STATUS_CHANGED, FILE_UPLOADED
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m37s
CI / OCR Service Tests (push) Successful in 40s
CI / Backend Unit Tests (push) Failing after 2m53s
CI / Unit & Component Tests (pull_request) Failing after 2m32s
CI / OCR Service Tests (pull_request) Successful in 28s
CI / Backend Unit Tests (pull_request) Failing after 2m42s

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-19 13:40:49 +02:00
parent 9887968236
commit 2deaaf167e
2 changed files with 144 additions and 3 deletions

View File

@@ -3,6 +3,8 @@ package org.raddatz.familienarchiv.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.raddatz.familienarchiv.audit.AuditKind;
import org.raddatz.familienarchiv.audit.AuditService;
import org.raddatz.familienarchiv.dto.DocumentSearchResult;
import org.raddatz.familienarchiv.dto.DocumentSort;
import org.raddatz.familienarchiv.dto.DocumentUpdateDTO;
@@ -10,6 +12,7 @@ import org.raddatz.familienarchiv.dto.IncompleteDocumentDTO;
import org.raddatz.familienarchiv.dto.MatchOffset;
import org.raddatz.familienarchiv.dto.SearchMatchData;
import org.raddatz.familienarchiv.dto.TagOperator;
import org.raddatz.familienarchiv.model.AppUser;
import org.raddatz.familienarchiv.model.Document;
import org.raddatz.familienarchiv.model.DocumentStatus;
import org.raddatz.familienarchiv.model.ScriptType;
@@ -20,6 +23,10 @@ import org.raddatz.familienarchiv.repository.DocumentRepository;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.raddatz.familienarchiv.exception.DomainException;
import org.raddatz.familienarchiv.exception.ErrorCode;
import org.springframework.stereotype.Service;
@@ -56,6 +63,8 @@ public class DocumentService {
private final TagService tagService;
private final DocumentVersionService documentVersionService;
private final AnnotationService annotationService;
private final AuditService auditService;
private final UserService userService;
public record StoreResult(Document document, boolean isNew) {}
@@ -108,11 +117,17 @@ public class DocumentService {
document.setFilePath(upload.s3Key());
document.setFileHash(upload.fileHash());
document.setContentType(file.getContentType());
if (document.getStatus() == DocumentStatus.PLACEHOLDER) {
boolean wasPlaceholder = document.getStatus() == DocumentStatus.PLACEHOLDER;
if (wasPlaceholder) {
document.setStatus(DocumentStatus.UPLOADED);
}
return new StoreResult(documentRepository.save(document), isNew);
Document saved = documentRepository.save(document);
if (wasPlaceholder) {
UUID actorId = resolveCurrentUserId();
logAfterCommit(AuditKind.FILE_UPLOADED, actorId, saved.getId(), null);
}
return new StoreResult(saved, isNew);
}
@Transactional
@@ -192,6 +207,8 @@ public class DocumentService {
Document doc = documentRepository.findById(id)
.orElseThrow(() -> DomainException.notFound(ErrorCode.DOCUMENT_NOT_FOUND, "Document not found: " + id));
DocumentStatus statusBefore = doc.getStatus();
// 1. Einfache Felder Update
doc.setTitle(dto.getTitle());
doc.setDocumentDate(dto.getDocumentDate());
@@ -245,6 +262,15 @@ public class DocumentService {
Document saved = documentRepository.save(doc);
documentVersionService.recordVersion(saved);
UUID actorId = resolveCurrentUserId();
if (saved.getStatus() != statusBefore) {
logAfterCommit(AuditKind.STATUS_CHANGED, actorId, saved.getId(),
Map.of("oldStatus", statusBefore.name(), "newStatus", saved.getStatus().name()));
} else {
logAfterCommit(AuditKind.METADATA_UPDATED, actorId, saved.getId(), null);
}
return saved;
}
@@ -300,11 +326,16 @@ public class DocumentService {
doc.setFileHash(upload.fileHash());
doc.setOriginalFilename(file.getOriginalFilename());
doc.setContentType(file.getContentType());
if (doc.getStatus() == DocumentStatus.PLACEHOLDER) {
boolean wasPlaceholder = doc.getStatus() == DocumentStatus.PLACEHOLDER;
if (wasPlaceholder) {
doc.setStatus(DocumentStatus.UPLOADED);
}
Document saved = documentRepository.save(doc);
documentVersionService.recordVersion(saved);
if (wasPlaceholder) {
UUID actorId = resolveCurrentUserId();
logAfterCommit(AuditKind.FILE_UPLOADED, actorId, saved.getId(), null);
}
return saved;
}
@@ -725,4 +756,26 @@ public class DocumentService {
throw new IllegalStateException("SHA-256 not available", e);
}
}
private UUID resolveCurrentUserId() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth == null || !auth.isAuthenticated()) return null;
String email = auth.getName();
if (email == null) return null;
AppUser user = userService.findByEmail(email);
return user != null ? user.getId() : null;
}
private void logAfterCommit(AuditKind kind, UUID actorId, UUID documentId, Map<String, Object> payload) {
if (TransactionSynchronizationManager.isActualTransactionActive()) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
auditService.log(kind, actorId, documentId, payload);
}
});
} else {
auditService.log(kind, actorId, documentId, payload);
}
}
}