fix(upload): structured error codes for quick-upload, fix duplicate filename crash
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled

- Switch errors from plain strings to { filename, code } objects so the
  frontend can show translated messages instead of raw exception text
- Add UNSUPPORTED_FILE_TYPE error code end-to-end (Java enum → errors.ts
  → de/en/es messages)
- Fix IncorrectResultSizeDataAccessException when a filename exists more
  than once in the DB: use findFirstByOriginalFilename instead of
  findByOriginalFilename in storeDocument()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-03-26 10:38:30 +01:00
parent 6a663cefe6
commit 963807ff05
11 changed files with 28 additions and 11 deletions

View File

@@ -110,14 +110,15 @@ public class DocumentController {
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) {}
public record UploadError(String filename, String code) {}
public record QuickUploadResult(List<Document> created, List<UploadError> 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<>();
List<UploadError> errors = new ArrayList<>();
if (files == null || files.isEmpty()) {
return new QuickUploadResult(created, errors);
@@ -125,13 +126,13 @@ public class DocumentController {
for (MultipartFile file : files) {
if (!ALLOWED_CONTENT_TYPES.contains(file.getContentType())) {
errors.add(file.getOriginalFilename() + ": unsupported file type");
errors.add(new UploadError(file.getOriginalFilename(), "UNSUPPORTED_FILE_TYPE"));
continue;
}
try {
created.add(documentService.storeDocument(file));
} catch (Exception e) {
errors.add(file.getOriginalFilename() + ": " + e.getMessage());
errors.add(new UploadError(file.getOriginalFilename(), "FILE_UPLOAD_FAILED"));
log.warn("Quick upload failed for file {}: {}", file.getOriginalFilename(), e.getMessage());
}
}

View File

@@ -17,6 +17,8 @@ public enum ErrorCode {
FILE_NOT_FOUND,
/** An error occurred while uploading a file to object storage. 500 */
FILE_UPLOAD_FAILED,
/** The uploaded file's content type is not supported (PDF/JPEG/PNG/TIFF only). 400 */
UNSUPPORTED_FILE_TYPE,
// --- Users ---
/** A user with the given ID or username does not exist. 404 */

View File

@@ -21,6 +21,9 @@ public interface DocumentRepository extends JpaRepository<Document, UUID>, JpaSp
// Wichtig für den Abgleich beim Excel-Import & Datei-Upload
Optional<Document> findByOriginalFilename(String originalFilename);
// Wie oben, gibt aber nur das erste Ergebnis zurück — sicher wenn doppelte Dateinamen existieren
Optional<Document> findFirstByOriginalFilename(String originalFilename);
// Findet alle Dokumente mit einem bestimmten Status
// z.B. um alle offenen "PLACEHOLDER" zu finden
List<Document> findByStatus(DocumentStatus status);

View File

@@ -52,8 +52,8 @@ public class DocumentService {
public Document storeDocument(MultipartFile file) throws IOException {
String originalFilename = file.getOriginalFilename();
// 1. Check for existing record
Optional<Document> existingDoc = documentRepository.findByOriginalFilename(originalFilename);
// 1. Check for existing record (findFirst to survive duplicate filenames in the DB)
Optional<Document> existingDoc = documentRepository.findFirstByOriginalFilename(originalFilename);
Document document;
if (existingDoc.isPresent()) {