feat: wire document versioning into DocumentService and DocumentController
DocumentService now calls documentVersionService.recordVersion() after
createDocument and updateDocument. DocumentController exposes two new
read-only endpoints: GET /{id}/versions and GET /{id}/versions/{versionId}.
Refs #38
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,13 +5,18 @@ import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
|
||||
|
||||
import org.raddatz.familienarchiv.dto.DocumentUpdateDTO;
|
||||
import org.raddatz.familienarchiv.dto.DocumentVersionSummary;
|
||||
import org.raddatz.familienarchiv.exception.DomainException;
|
||||
import org.raddatz.familienarchiv.exception.ErrorCode;
|
||||
import org.raddatz.familienarchiv.model.Document;
|
||||
import org.raddatz.familienarchiv.model.DocumentVersion;
|
||||
import org.raddatz.familienarchiv.security.Permission;
|
||||
import org.raddatz.familienarchiv.security.RequirePermission;
|
||||
import org.raddatz.familienarchiv.service.DocumentService;
|
||||
import org.raddatz.familienarchiv.service.DocumentVersionService;
|
||||
import org.raddatz.familienarchiv.service.FileService;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
@@ -39,6 +44,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
public class DocumentController {
|
||||
|
||||
private final DocumentService documentService;
|
||||
private final DocumentVersionService documentVersionService;
|
||||
private final FileService fileService;
|
||||
|
||||
// --- DOWNLOAD ---
|
||||
@@ -108,6 +114,18 @@ public class DocumentController {
|
||||
return ResponseEntity.ok(documentService.searchDocuments(q, from, to, senderId, receiverId, tags));
|
||||
}
|
||||
|
||||
// --- VERSIONS ---
|
||||
|
||||
@GetMapping("/{id}/versions")
|
||||
public List<DocumentVersionSummary> getVersions(@PathVariable UUID id) {
|
||||
return documentVersionService.getSummaries(id);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}/versions/{versionId}")
|
||||
public DocumentVersion getVersion(@PathVariable UUID id, @PathVariable UUID versionId) {
|
||||
return documentVersionService.getVersion(id, versionId);
|
||||
}
|
||||
|
||||
@GetMapping("/conversation")
|
||||
public List<Document> getConversation(
|
||||
@RequestParam UUID senderId,
|
||||
|
||||
@@ -37,6 +37,7 @@ public class DocumentService {
|
||||
private final PersonService personService;
|
||||
private final FileService fileService;
|
||||
private final TagService tagService;
|
||||
private final DocumentVersionService documentVersionService;
|
||||
|
||||
/**
|
||||
* Lädt eine Datei hoch.
|
||||
@@ -125,7 +126,9 @@ public class DocumentService {
|
||||
doc.setStatus(DocumentStatus.UPLOADED);
|
||||
}
|
||||
|
||||
return documentRepository.save(doc);
|
||||
Document finalDoc = documentRepository.save(doc);
|
||||
documentVersionService.recordVersion(finalDoc);
|
||||
return finalDoc;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -178,7 +181,9 @@ public class DocumentService {
|
||||
doc.setStatus(DocumentStatus.UPLOADED);
|
||||
}
|
||||
|
||||
return documentRepository.save(doc);
|
||||
Document saved = documentRepository.save(doc);
|
||||
documentVersionService.recordVersion(saved);
|
||||
return saved;
|
||||
}
|
||||
|
||||
public Document updateDocumentTags(UUID docId, List<String> tagNames) {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package org.raddatz.familienarchiv.controller;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.raddatz.familienarchiv.dto.DocumentVersionSummary;
|
||||
import org.raddatz.familienarchiv.model.Document;
|
||||
import org.raddatz.familienarchiv.model.DocumentVersion;
|
||||
import org.raddatz.familienarchiv.security.PermissionAspect;
|
||||
import org.raddatz.familienarchiv.service.CustomUserDetailsService;
|
||||
import org.raddatz.familienarchiv.service.DocumentService;
|
||||
import org.raddatz.familienarchiv.service.DocumentVersionService;
|
||||
import org.raddatz.familienarchiv.service.FileService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
|
||||
@@ -15,13 +18,16 @@ import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@WebMvcTest(DocumentController.class)
|
||||
@@ -31,6 +37,7 @@ class DocumentControllerTest {
|
||||
@Autowired MockMvc mockMvc;
|
||||
|
||||
@MockitoBean DocumentService documentService;
|
||||
@MockitoBean DocumentVersionService documentVersionService;
|
||||
@MockitoBean FileService fileService;
|
||||
@MockitoBean CustomUserDetailsService customUserDetailsService;
|
||||
|
||||
@@ -113,4 +120,48 @@ class DocumentControllerTest {
|
||||
.with(req -> { req.setMethod("PUT"); return req; }))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
// ─── GET /api/documents/{id}/versions ────────────────────────────────────
|
||||
|
||||
@Test
|
||||
void getVersions_returns401_whenUnauthenticated() throws Exception {
|
||||
mockMvc.perform(get("/api/documents/" + UUID.randomUUID() + "/versions"))
|
||||
.andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
void getVersions_returns200_whenAuthenticated() throws Exception {
|
||||
UUID docId = UUID.randomUUID();
|
||||
DocumentVersionSummary summary = new DocumentVersionSummary(
|
||||
UUID.randomUUID(), LocalDateTime.now(), "Emma Müller", List.of("title"));
|
||||
when(documentVersionService.getSummaries(docId)).thenReturn(List.of(summary));
|
||||
|
||||
mockMvc.perform(get("/api/documents/" + docId + "/versions"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$[0].editorName").value("Emma Müller"));
|
||||
}
|
||||
|
||||
// ─── GET /api/documents/{id}/versions/{versionId} ────────────────────────
|
||||
|
||||
@Test
|
||||
void getVersion_returns401_whenUnauthenticated() throws Exception {
|
||||
mockMvc.perform(get("/api/documents/" + UUID.randomUUID() + "/versions/" + UUID.randomUUID()))
|
||||
.andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
void getVersion_returns200_whenAuthenticated() throws Exception {
|
||||
UUID docId = UUID.randomUUID();
|
||||
UUID versionId = UUID.randomUUID();
|
||||
DocumentVersion version = DocumentVersion.builder()
|
||||
.id(versionId).documentId(docId).savedAt(LocalDateTime.now())
|
||||
.editorName("Otto").snapshot("{\"title\":\"Brief\"}").changedFields("[]").build();
|
||||
when(documentVersionService.getVersion(docId, versionId)).thenReturn(version);
|
||||
|
||||
mockMvc.perform(get("/api/documents/" + docId + "/versions/" + versionId))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.editorName").value("Otto"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.raddatz.familienarchiv.dto.DocumentUpdateDTO;
|
||||
import org.raddatz.familienarchiv.exception.DomainException;
|
||||
import org.raddatz.familienarchiv.model.Document;
|
||||
import org.raddatz.familienarchiv.model.DocumentStatus;
|
||||
import org.raddatz.familienarchiv.model.Tag;
|
||||
import org.raddatz.familienarchiv.repository.DocumentRepository;
|
||||
|
||||
@@ -29,6 +30,7 @@ class DocumentServiceTest {
|
||||
@Mock PersonService personService;
|
||||
@Mock FileService fileService;
|
||||
@Mock TagService tagService;
|
||||
@Mock DocumentVersionService documentVersionService;
|
||||
@InjectMocks DocumentService documentService;
|
||||
|
||||
// ─── getDocumentById ──────────────────────────────────────────────────────
|
||||
@@ -132,4 +134,37 @@ class DocumentServiceTest {
|
||||
|
||||
assertThat(documentService.getDocumentsByReceiver(receiverId)).containsExactly(doc);
|
||||
}
|
||||
|
||||
// ─── versioning ───────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
void createDocument_recordsVersionAfterSave() throws Exception {
|
||||
DocumentUpdateDTO dto = new DocumentUpdateDTO();
|
||||
dto.setTitle("Neuer Brief");
|
||||
|
||||
Document saved = Document.builder()
|
||||
.id(UUID.randomUUID()).title("Neuer Brief")
|
||||
.originalFilename("Neuer Brief").status(DocumentStatus.PLACEHOLDER)
|
||||
.build();
|
||||
when(documentRepository.save(any())).thenReturn(saved);
|
||||
when(documentRepository.findById(any())).thenReturn(Optional.of(saved));
|
||||
|
||||
documentService.createDocument(dto, null);
|
||||
|
||||
verify(documentVersionService, atLeastOnce()).recordVersion(any(Document.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateDocument_recordsVersionAfterSave() throws Exception {
|
||||
UUID id = UUID.randomUUID();
|
||||
Document existing = Document.builder()
|
||||
.id(id).title("Alt").originalFilename("alt.pdf")
|
||||
.status(DocumentStatus.PLACEHOLDER).build();
|
||||
when(documentRepository.findById(id)).thenReturn(Optional.of(existing));
|
||||
when(documentRepository.save(any())).thenReturn(existing);
|
||||
|
||||
documentService.updateDocument(id, new DocumentUpdateDTO(), null);
|
||||
|
||||
verify(documentVersionService).recordVersion(any(Document.class));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user