diff --git a/backend/src/main/java/org/raddatz/familienarchiv/controller/DocumentController.java b/backend/src/main/java/org/raddatz/familienarchiv/controller/DocumentController.java index 0c6f6c4d..0a2d00b4 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/controller/DocumentController.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/controller/DocumentController.java @@ -22,6 +22,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -78,6 +79,18 @@ public class DocumentController { .orElseThrow(() -> DomainException.notFound(ErrorCode.DOCUMENT_NOT_FOUND, "Document not found: " + id)); } + @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @RequirePermission(Permission.WRITE_ALL) + public Document createDocument( + @ModelAttribute DocumentUpdateDTO dto, + @RequestPart(value = "file", required = false) MultipartFile file) { + try { + return documentService.createDocument(dto, file); + } catch (IOException e) { + throw DomainException.internal(ErrorCode.FILE_UPLOAD_FAILED, "Failed to upload file: " + e.getMessage()); + } + } + @PutMapping(value = "/{id}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @RequirePermission(Permission.WRITE_ALL) public Document updateDocument( diff --git a/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentService.java b/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentService.java index 14c05d38..c007aa61 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentService.java @@ -77,6 +77,57 @@ public class DocumentService { return documentRepository.save(document); } + @Transactional + public Document createDocument(DocumentUpdateDTO dto, MultipartFile file) throws IOException { + String filename = (file != null && !file.isEmpty()) + ? file.getOriginalFilename() + : (dto.getTitle() != null ? dto.getTitle() : "Unbenanntes Dokument"); + + Document doc = Document.builder() + .originalFilename(filename) + .title(dto.getTitle()) + .documentDate(dto.getDocumentDate()) + .location(dto.getLocation()) + .documentLocation(dto.getDocumentLocation()) + .transcription(dto.getTranscription()) + .summary(dto.getSummary()) + .status(DocumentStatus.PLACEHOLDER) + .build(); + + doc = documentRepository.save(doc); + + // Tags + List tags = new ArrayList<>(); + if (dto.getTags() != null && !dto.getTags().isBlank()) { + tags = Arrays.stream(dto.getTags().split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .toList(); + } + updateDocumentTags(doc.getId(), tags); + doc = documentRepository.findById(doc.getId()).orElseThrow(); + + // Sender + if (dto.getSenderId() != null) { + doc.setSender(personRepository.findById(dto.getSenderId()).orElse(null)); + } + + // Empfänger + if (dto.getReceiverIds() != null && !dto.getReceiverIds().isEmpty()) { + doc.setReceivers(new HashSet<>(personRepository.findAllById(dto.getReceiverIds()))); + } + + // Datei + if (file != null && !file.isEmpty()) { + String s3Key = fileService.uploadFile(file, file.getOriginalFilename()); + doc.setFilePath(s3Key); + doc.setContentType(file.getContentType()); + doc.setStatus(DocumentStatus.UPLOADED); + } + + return documentRepository.save(doc); + } + @Transactional public Document updateDocument(UUID id, DocumentUpdateDTO dto, MultipartFile newFile) throws IOException { Document doc = documentRepository.findById(id) diff --git a/frontend/src/lib/generated/api.ts b/frontend/src/lib/generated/api.ts index 37cd968f..e0445130 100644 --- a/frontend/src/lib/generated/api.ts +++ b/frontend/src/lib/generated/api.ts @@ -100,6 +100,22 @@ export interface paths { patch?: never; trace?: never; }; + "/api/documents": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["createDocument"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/admin/trigger-import": { parameters: { query?: never; @@ -635,6 +651,30 @@ export interface operations { }; }; }; + createDocument: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "multipart/form-data": components["schemas"]["DocumentUpdateDTO"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["Document"]; + }; + }; + }; + }; triggerMassImport: { parameters: { query?: never; diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index 8464b5fa..ceae8171 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -1,89 +1,86 @@ -
+
-
+
-
+
-
+
-
+
-

Schlagworte

+

+ Schlagworte +

-
+
@@ -212,42 +211,55 @@ {/if}
+ + + -
+
{#if data.error} -
+
{data.error}
{:else if data.documents && data.documents.length > 0}