feat: add create document feature via web interface

- Backend: new POST /api/documents endpoint with DocumentService.createDocument()
  reusing DocumentUpdateDTO; handles file upload, tags, sender, receivers
- Frontend: new /documents/new route with same four-section form as edit page
  (Wer & Wann, Beschreibung, Transkription, Datei) but with empty fields
- Home page: subtle '+ Neues Dokument' link above the document list

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-03-16 16:04:56 +01:00
parent c4806474df
commit 6fcff3355d
6 changed files with 476 additions and 96 deletions

View File

@@ -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(

View File

@@ -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<String> 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)