feat(transcription): DTOs accept mentionedPersons sidecar with @Valid cascade

CreateTranscriptionBlockDTO and UpdateTranscriptionBlockDTO gain a
List<PersonMention> mentionedPersons field. @Valid is on the field itself,
not just on the controller method, so JSR-303 recurses into the list
elements when the controller boundary calls @Valid on the @RequestBody. The
collection defaults to an empty ArrayList via @Builder.Default; existing
constructor call sites in TranscriptionServiceTest are extended with
List.of() to match the new @AllArgsConstructor signature.

The controller-side @Valid wiring lands in the next commit alongside the
length-201 validation test.

Refs #362

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-28 20:11:01 +02:00
parent 7805da52e6
commit 80ddfb47ac
3 changed files with 30 additions and 8 deletions

View File

@@ -1,14 +1,21 @@
package org.raddatz.familienarchiv.dto;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Positive;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.raddatz.familienarchiv.model.PersonMention;
import java.util.ArrayList;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class CreateTranscriptionBlockDTO {
@Min(0)
private int pageNumber;
@@ -22,4 +29,8 @@ public class CreateTranscriptionBlockDTO {
private double height;
private String text;
private String label;
@Valid
@Builder.Default
private List<PersonMention> mentionedPersons = new ArrayList<>();
}

View File

@@ -1,13 +1,24 @@
package org.raddatz.familienarchiv.dto;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.raddatz.familienarchiv.model.PersonMention;
import java.util.ArrayList;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UpdateTranscriptionBlockDTO {
private String text;
private String label;
@Valid
@Builder.Default
private List<PersonMention> mentionedPersons = new ArrayList<>();
}

View File

@@ -98,7 +98,7 @@ class TranscriptionServiceTest {
return b;
});
CreateTranscriptionBlockDTO dto = new CreateTranscriptionBlockDTO(1, 0.1, 0.2, 0.3, 0.4, "hello", null);
CreateTranscriptionBlockDTO dto = new CreateTranscriptionBlockDTO(1, 0.1, 0.2, 0.3, 0.4, "hello", null, java.util.List.of());
TranscriptionBlock result = transcriptionService.createBlock(docId, dto, userId);
@@ -168,7 +168,7 @@ class TranscriptionServiceTest {
when(documentService.getDocumentById(any())).thenReturn(
Document.builder().scriptType(ScriptType.TYPEWRITER).build());
UpdateTranscriptionBlockDTO dto = new UpdateTranscriptionBlockDTO("new text", null);
UpdateTranscriptionBlockDTO dto = new UpdateTranscriptionBlockDTO("new text", null, java.util.List.of());
TranscriptionBlock result = transcriptionService.updateBlock(docId, blockId, dto, userId);
@@ -189,7 +189,7 @@ class TranscriptionServiceTest {
when(documentService.getDocumentById(any())).thenReturn(
Document.builder().scriptType(ScriptType.TYPEWRITER).build());
UpdateTranscriptionBlockDTO dto = new UpdateTranscriptionBlockDTO("text", "Anrede");
UpdateTranscriptionBlockDTO dto = new UpdateTranscriptionBlockDTO("text", "Anrede", java.util.List.of());
TranscriptionBlock result = transcriptionService.updateBlock(docId, blockId, dto, UUID.randomUUID());
@@ -208,7 +208,7 @@ class TranscriptionServiceTest {
Document.builder().scriptType(ScriptType.TYPEWRITER).build());
TranscriptionBlock result = transcriptionService.updateBlock(
docId, blockId, new UpdateTranscriptionBlockDTO("new", null), UUID.randomUUID());
docId, blockId, new UpdateTranscriptionBlockDTO("new", null, java.util.List.of()), UUID.randomUUID());
assertThat(result.getSource()).isEqualTo(BlockSource.MANUAL);
}
@@ -226,7 +226,7 @@ class TranscriptionServiceTest {
when(documentService.getDocumentById(any())).thenReturn(
Document.builder().scriptType(ScriptType.HANDWRITING_KURRENT).sender(sender).build());
transcriptionService.updateBlock(docId, blockId, new UpdateTranscriptionBlockDTO("text", null), UUID.randomUUID());
transcriptionService.updateBlock(docId, blockId, new UpdateTranscriptionBlockDTO("text", null, java.util.List.of()), UUID.randomUUID());
verify(senderModelService).checkAndTriggerTraining(senderId);
}
@@ -242,7 +242,7 @@ class TranscriptionServiceTest {
when(documentService.getDocumentById(any())).thenReturn(
Document.builder().scriptType(ScriptType.HANDWRITING_KURRENT).build());
transcriptionService.updateBlock(docId, blockId, new UpdateTranscriptionBlockDTO("text", null), UUID.randomUUID());
transcriptionService.updateBlock(docId, blockId, new UpdateTranscriptionBlockDTO("text", null, java.util.List.of()), UUID.randomUUID());
verify(senderModelService, never()).checkAndTriggerTraining(any());
}
@@ -477,7 +477,7 @@ class TranscriptionServiceTest {
Document.builder().scriptType(ScriptType.TYPEWRITER).build());
when(annotationRepository.findById(annotId)).thenReturn(Optional.of(annotation));
transcriptionService.updateBlock(docId, blockId, new UpdateTranscriptionBlockDTO("new text", null), userId);
transcriptionService.updateBlock(docId, blockId, new UpdateTranscriptionBlockDTO("new text", null, java.util.List.of()), userId);
@SuppressWarnings("unchecked")
ArgumentCaptor<Map<String, Object>> payloadCaptor = ArgumentCaptor.forClass(Map.class);
@@ -502,7 +502,7 @@ class TranscriptionServiceTest {
when(documentService.getDocumentById(any())).thenReturn(
Document.builder().scriptType(ScriptType.TYPEWRITER).build());
transcriptionService.updateBlock(docId, blockId, new UpdateTranscriptionBlockDTO("same text", null), userId);
transcriptionService.updateBlock(docId, blockId, new UpdateTranscriptionBlockDTO("same text", null, java.util.List.of()), userId);
verify(auditService, never()).logAfterCommit(any(), any(), any(), any());
}