feat(upload): add POST /api/documents/quick-upload endpoint for bulk file upload
Adds a new multipart endpoint that accepts multiple files and creates one document per file without requiring any form metadata. Each document gets title = filename-without-extension and status = UPLOADED. - Fix storeDocument() to strip the file extension from the document title - Validate content type (PDF/JPEG/PNG/TIFF) server-side; unsupported files are skipped and returned as per-file errors in QuickUploadResult - Tests cover 401/403 auth, success path, and unsupported file type Closes #66 (backend part) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,9 @@ package org.raddatz.familienarchiv.controller;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
|
||||
@@ -103,6 +105,40 @@ public class DocumentController {
|
||||
}
|
||||
}
|
||||
|
||||
// --- QUICK UPLOAD ---
|
||||
|
||||
private static final Set<String> ALLOWED_CONTENT_TYPES = Set.of(
|
||||
"application/pdf", "image/jpeg", "image/png", "image/tiff");
|
||||
|
||||
public record QuickUploadResult(List<Document> created, List<String> errors) {}
|
||||
|
||||
@PostMapping(value = "/quick-upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
@RequirePermission(Permission.WRITE_ALL)
|
||||
public QuickUploadResult quickUpload(
|
||||
@RequestPart(value = "files", required = false) List<MultipartFile> files) {
|
||||
List<Document> created = new ArrayList<>();
|
||||
List<String> errors = new ArrayList<>();
|
||||
|
||||
if (files == null || files.isEmpty()) {
|
||||
return new QuickUploadResult(created, errors);
|
||||
}
|
||||
|
||||
for (MultipartFile file : files) {
|
||||
if (!ALLOWED_CONTENT_TYPES.contains(file.getContentType())) {
|
||||
errors.add(file.getOriginalFilename() + ": unsupported file type");
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
created.add(documentService.storeDocument(file));
|
||||
} catch (Exception e) {
|
||||
errors.add(file.getOriginalFilename() + ": " + e.getMessage());
|
||||
log.warn("Quick upload failed for file {}: {}", file.getOriginalFilename(), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return new QuickUploadResult(created, errors);
|
||||
}
|
||||
|
||||
@GetMapping("/search")
|
||||
public ResponseEntity<List<Document>> search(
|
||||
@RequestParam(required = false) String q,
|
||||
|
||||
@@ -61,7 +61,7 @@ public class DocumentService {
|
||||
} else {
|
||||
document = Document.builder()
|
||||
.originalFilename(originalFilename)
|
||||
.title(originalFilename)
|
||||
.title(stripExtension(originalFilename))
|
||||
.status(DocumentStatus.UPLOADED)
|
||||
.build();
|
||||
}
|
||||
@@ -307,6 +307,12 @@ public class DocumentService {
|
||||
|
||||
// ─── private helpers ──────────────────────────────────────────────────────
|
||||
|
||||
private static String stripExtension(String filename) {
|
||||
if (filename == null) return null;
|
||||
int dot = filename.lastIndexOf('.');
|
||||
return dot > 0 ? filename.substring(0, dot) : filename;
|
||||
}
|
||||
|
||||
private static String sha256Hex(byte[] bytes) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
|
||||
Reference in New Issue
Block a user