feat(annotations): add updateAnnotation service method with partial-update DTO
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,29 @@
|
|||||||
|
package org.raddatz.familienarchiv.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.DecimalMax;
|
||||||
|
import jakarta.validation.constraints.DecimalMin;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Partial update payload for annotation position and size.
|
||||||
|
* All fields are optional — only non-null values are applied.
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class UpdateAnnotationDTO {
|
||||||
|
|
||||||
|
@DecimalMin("0.0") @DecimalMax("1.0")
|
||||||
|
private Double x;
|
||||||
|
|
||||||
|
@DecimalMin("0.0") @DecimalMax("1.0")
|
||||||
|
private Double y;
|
||||||
|
|
||||||
|
@DecimalMin("0.01") @DecimalMax("1.0")
|
||||||
|
private Double width;
|
||||||
|
|
||||||
|
@DecimalMin("0.01") @DecimalMax("1.0")
|
||||||
|
private Double height;
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package org.raddatz.familienarchiv.service;
|
|||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.raddatz.familienarchiv.dto.CreateAnnotationDTO;
|
import org.raddatz.familienarchiv.dto.CreateAnnotationDTO;
|
||||||
|
import org.raddatz.familienarchiv.dto.UpdateAnnotationDTO;
|
||||||
import org.raddatz.familienarchiv.exception.DomainException;
|
import org.raddatz.familienarchiv.exception.DomainException;
|
||||||
import org.raddatz.familienarchiv.exception.ErrorCode;
|
import org.raddatz.familienarchiv.exception.ErrorCode;
|
||||||
import org.raddatz.familienarchiv.model.DocumentAnnotation;
|
import org.raddatz.familienarchiv.model.DocumentAnnotation;
|
||||||
@@ -61,6 +62,21 @@ public class AnnotationService {
|
|||||||
return annotationRepository.save(annotation);
|
return annotationRepository.save(annotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public DocumentAnnotation updateAnnotation(UUID documentId, UUID annotationId, UpdateAnnotationDTO dto) {
|
||||||
|
DocumentAnnotation annotation = annotationRepository
|
||||||
|
.findByIdAndDocumentId(annotationId, documentId)
|
||||||
|
.orElseThrow(() -> DomainException.notFound(
|
||||||
|
ErrorCode.ANNOTATION_NOT_FOUND, "Annotation not found: " + annotationId));
|
||||||
|
|
||||||
|
if (dto.getX() != null) annotation.setX(dto.getX());
|
||||||
|
if (dto.getY() != null) annotation.setY(dto.getY());
|
||||||
|
if (dto.getWidth() != null) annotation.setWidth(dto.getWidth());
|
||||||
|
if (dto.getHeight() != null) annotation.setHeight(dto.getHeight());
|
||||||
|
|
||||||
|
return annotationRepository.save(annotation);
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void deleteAnnotation(UUID documentId, UUID annotationId, UUID userId) {
|
public void deleteAnnotation(UUID documentId, UUID annotationId, UUID userId) {
|
||||||
DocumentAnnotation annotation = annotationRepository
|
DocumentAnnotation annotation = annotationRepository
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import org.mockito.InjectMocks;
|
|||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import org.raddatz.familienarchiv.dto.CreateAnnotationDTO;
|
import org.raddatz.familienarchiv.dto.CreateAnnotationDTO;
|
||||||
|
import org.raddatz.familienarchiv.dto.UpdateAnnotationDTO;
|
||||||
import org.raddatz.familienarchiv.exception.DomainException;
|
import org.raddatz.familienarchiv.exception.DomainException;
|
||||||
import org.raddatz.familienarchiv.model.DocumentAnnotation;
|
import org.raddatz.familienarchiv.model.DocumentAnnotation;
|
||||||
import org.raddatz.familienarchiv.repository.AnnotationRepository;
|
import org.raddatz.familienarchiv.repository.AnnotationRepository;
|
||||||
@@ -203,6 +204,42 @@ class AnnotationServiceTest {
|
|||||||
.satisfies(e -> assertThat(((DomainException) e).getStatus()).isEqualTo(FORBIDDEN));
|
.satisfies(e -> assertThat(((DomainException) e).getStatus()).isEqualTo(FORBIDDEN));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── updateAnnotation ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateAnnotation_throwsNotFound_whenAnnotationNotInDocument() {
|
||||||
|
UUID docId = UUID.randomUUID();
|
||||||
|
UUID annotId = UUID.randomUUID();
|
||||||
|
when(annotationRepository.findByIdAndDocumentId(annotId, docId)).thenReturn(Optional.empty());
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> annotationService.updateAnnotation(docId, annotId, new UpdateAnnotationDTO()))
|
||||||
|
.isInstanceOf(DomainException.class)
|
||||||
|
.satisfies(e -> assertThat(((DomainException) e).getStatus()).isEqualTo(NOT_FOUND));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateAnnotation_updatesOnlyPresentFields() {
|
||||||
|
UUID docId = UUID.randomUUID();
|
||||||
|
UUID annotId = UUID.randomUUID();
|
||||||
|
DocumentAnnotation annotation = DocumentAnnotation.builder()
|
||||||
|
.id(annotId).documentId(docId)
|
||||||
|
.x(0.1).y(0.2).width(0.3).height(0.4).build();
|
||||||
|
when(annotationRepository.findByIdAndDocumentId(annotId, docId))
|
||||||
|
.thenReturn(Optional.of(annotation));
|
||||||
|
when(annotationRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
||||||
|
|
||||||
|
UpdateAnnotationDTO dto = new UpdateAnnotationDTO();
|
||||||
|
dto.setX(0.5);
|
||||||
|
dto.setY(0.6);
|
||||||
|
|
||||||
|
DocumentAnnotation result = annotationService.updateAnnotation(docId, annotId, dto);
|
||||||
|
|
||||||
|
assertThat(result.getX()).isEqualTo(0.5);
|
||||||
|
assertThat(result.getY()).isEqualTo(0.6);
|
||||||
|
assertThat(result.getWidth()).isEqualTo(0.3); // unchanged
|
||||||
|
assertThat(result.getHeight()).isEqualTo(0.4); // unchanged
|
||||||
|
}
|
||||||
|
|
||||||
// ─── listAnnotations ──────────────────────────────────────────────────────
|
// ─── listAnnotations ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user