feat(backend): add POST /api/documents/{id}/file endpoint to attach file to existing document

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-18 13:46:25 +02:00
committed by marcel
parent 40db46945f
commit 96f8bfd822
3 changed files with 61 additions and 0 deletions

View File

@@ -129,6 +129,20 @@ public class DocumentController {
return ResponseEntity.noContent().build();
}
// --- ATTACH FILE ---
@PostMapping(value = "/{id}/file", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@RequirePermission(Permission.WRITE_ALL)
public Document attachFile(
@PathVariable UUID id,
@RequestPart("file") MultipartFile file) {
try {
return documentService.attachFile(id, file);
} catch (IOException e) {
throw DomainException.internal(ErrorCode.FILE_UPLOAD_FAILED, "Failed to upload file: " + e.getMessage());
}
}
// --- QUICK UPLOAD ---
private static final Set<String> ALLOWED_CONTENT_TYPES = Set.of(

View File

@@ -286,6 +286,23 @@ public class DocumentService {
return documentRepository.save(doc);
}
@Transactional
public Document attachFile(UUID id, MultipartFile file) throws IOException {
Document doc = documentRepository.findById(id)
.orElseThrow(() -> DomainException.notFound(ErrorCode.DOCUMENT_NOT_FOUND, "Document not found: " + id));
FileService.UploadResult upload = fileService.uploadFile(file, file.getOriginalFilename());
doc.setFilePath(upload.s3Key());
doc.setFileHash(upload.fileHash());
doc.setOriginalFilename(file.getOriginalFilename());
doc.setContentType(file.getContentType());
if (doc.getStatus() == DocumentStatus.PLACEHOLDER) {
doc.setStatus(DocumentStatus.UPLOADED);
}
Document saved = documentRepository.save(doc);
documentVersionService.recordVersion(saved);
return saved;
}
// 0. Zuletzt aktive Dokumente (sortiert nach updatedAt DESC)
public List<Document> getRecentActivity(int size) {
return documentRepository.findAll(

View File

@@ -563,6 +563,36 @@ class DocumentControllerTest {
.andExpect(status().isBadRequest());
}
// ─── POST /api/documents/{id}/file ───────────────────────────────────────
@Test
@WithMockUser
void attachFile_returns403_whenMissingWritePermission() throws Exception {
org.springframework.mock.web.MockMultipartFile file =
new org.springframework.mock.web.MockMultipartFile("file", "brief.pdf", "application/pdf", new byte[]{1});
mockMvc.perform(multipart("/api/documents/" + UUID.randomUUID() + "/file").file(file))
.andExpect(status().isForbidden());
}
@Test
@WithMockUser(authorities = "WRITE_ALL")
void attachFile_returns200_withUpdatedDocument_whenHasWritePermission() throws Exception {
UUID id = UUID.randomUUID();
Document doc = Document.builder()
.id(id).title("Brief").originalFilename("brief.pdf")
.filePath("docs/brief.pdf").status(DocumentStatus.UPLOADED).build();
when(documentService.attachFile(eq(id), any())).thenReturn(doc);
org.springframework.mock.web.MockMultipartFile file =
new org.springframework.mock.web.MockMultipartFile("file", "brief.pdf", "application/pdf", new byte[]{1});
mockMvc.perform(multipart("/api/documents/" + id + "/file").file(file))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(id.toString()))
.andExpect(jsonPath("$.status").value("UPLOADED"));
}
// ─── GET /api/documents/{id}/versions/{versionId} ────────────────────────
@Test