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 0a2d00b4..a4b30d81 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/controller/DocumentController.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/controller/DocumentController.java @@ -9,16 +9,15 @@ import org.raddatz.familienarchiv.dto.DocumentUpdateDTO; import org.raddatz.familienarchiv.exception.DomainException; import org.raddatz.familienarchiv.exception.ErrorCode; import org.raddatz.familienarchiv.model.Document; -import org.raddatz.familienarchiv.repository.DocumentRepository; import org.raddatz.familienarchiv.security.Permission; import org.raddatz.familienarchiv.security.RequirePermission; import org.raddatz.familienarchiv.service.DocumentService; import org.raddatz.familienarchiv.service.FileService; -import org.springframework.core.io.InputStreamResource; import org.springframework.data.domain.Sort; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.core.io.InputStreamResource; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; @@ -39,26 +38,21 @@ import lombok.extern.slf4j.Slf4j; @Slf4j public class DocumentController { - private final DocumentRepository documentRepository; private final DocumentService documentService; private final FileService fileService; // --- DOWNLOAD --- @GetMapping("/{id}/file") public ResponseEntity getDocumentFile(@PathVariable UUID id) { - // 1. Look up path in DB - Document doc = documentRepository.findById(id) - .orElseThrow(() -> DomainException.notFound(ErrorCode.DOCUMENT_NOT_FOUND, "Document not found: " + id)); + Document doc = documentService.getDocumentById(id); if (doc.getFilePath() == null) { throw DomainException.notFound(ErrorCode.DOCUMENT_NO_FILE, "Document has no file attached: " + id); } - // 2. Delegate Retrieval to FileService try { FileService.S3FileDownload download = fileService.downloadFile(doc.getFilePath()); - // Prefer the content type stored at upload time; fall back to whatever S3 reports String contentType = (doc.getContentType() != null && !doc.getContentType().isBlank()) ? doc.getContentType() : download.contentType(); @@ -75,8 +69,7 @@ public class DocumentController { // --- METADATA --- @GetMapping("/{id}") public Document getDocument(@PathVariable UUID id) { - return documentRepository.findById(id) - .orElseThrow(() -> DomainException.notFound(ErrorCode.DOCUMENT_NOT_FOUND, "Document not found: " + id)); + return documentService.getDocumentById(id); } @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @@ -95,7 +88,7 @@ public class DocumentController { @RequirePermission(Permission.WRITE_ALL) public Document updateDocument( @PathVariable UUID id, - @ModelAttribute DocumentUpdateDTO dto, // Bindet Form-Felder automatisch + @ModelAttribute DocumentUpdateDTO dto, @RequestPart(value = "file", required = false) MultipartFile file) { try { return documentService.updateDocument(id, dto, file); @@ -121,18 +114,8 @@ public class DocumentController { @RequestParam UUID receiverId, @RequestParam(required = false) LocalDate from, @RequestParam(required = false) LocalDate to, - @RequestParam(defaultValue = "DESC") String dir // ASC oder DESC - ) { - // 1. Standard-Datumswerte setzen - LocalDate dateFrom = (from != null) ? from : LocalDate.parse("0000-01-01"); - LocalDate dateTo = (to != null) ? to : LocalDate.now(); - - // 2. Sortierung - Sort.Direction direction = Sort.Direction.fromString(dir.toUpperCase()); - Sort sort = Sort.by(direction, "documentDate"); - - // 3. Abfrage - return documentRepository.findConversation( - senderId, receiverId, dateFrom, dateTo, sort); + @RequestParam(defaultValue = "DESC") String dir) { + Sort sort = Sort.by(Sort.Direction.fromString(dir.toUpperCase()), "documentDate"); + return documentService.getConversationFiltered(senderId, receiverId, from, to, sort); } } diff --git a/backend/src/main/java/org/raddatz/familienarchiv/controller/GroupController.java b/backend/src/main/java/org/raddatz/familienarchiv/controller/GroupController.java index 926d79e0..0b495e02 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/controller/GroupController.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/controller/GroupController.java @@ -5,12 +5,9 @@ import java.util.UUID; import org.raddatz.familienarchiv.dto.GroupDTO; import org.raddatz.familienarchiv.model.UserGroup; -import org.raddatz.familienarchiv.repository.UserGroupRepository; import org.raddatz.familienarchiv.security.Permission; import org.raddatz.familienarchiv.security.RequirePermission; import org.raddatz.familienarchiv.service.UserService; -import org.raddatz.familienarchiv.exception.DomainException; -import org.raddatz.familienarchiv.exception.ErrorCode; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -28,33 +25,22 @@ import lombok.RequiredArgsConstructor; @RequirePermission(Permission.ADMIN_PERMISSION) @RequiredArgsConstructor public class GroupController { - private final UserGroupRepository groupRepository; + private final UserService userService; @PostMapping("") public ResponseEntity createGroup(@RequestBody GroupDTO dto) { - UserGroup group = new UserGroup(); - group.setName(dto.getName()); - group.setPermissions(dto.getPermissions()); // Assuming entity has Set or Set - return ResponseEntity.ok(groupRepository.save(group)); + return ResponseEntity.ok(userService.createGroup(dto)); } @PatchMapping("/{id}") public ResponseEntity updateGroup(@PathVariable UUID id, @RequestBody GroupDTO dto) { - UserGroup group = groupRepository.findById(id) - .orElseThrow(() -> DomainException.notFound(ErrorCode.INTERNAL_ERROR, "Group not found: " + id)); - - if (dto.getName() != null) - group.setName(dto.getName()); - if (dto.getPermissions() != null) - group.setPermissions(dto.getPermissions()); - - return ResponseEntity.ok(groupRepository.save(group)); + return ResponseEntity.ok(userService.updateGroup(id, dto)); } @DeleteMapping("/{id}") public ResponseEntity deleteGroup(@PathVariable UUID id) { - groupRepository.deleteById(id); + userService.deleteGroup(id); return ResponseEntity.ok().build(); } @@ -62,5 +48,4 @@ public class GroupController { public ResponseEntity> getAllGroups() { return ResponseEntity.ok(userService.getAllGroups()); } - } diff --git a/backend/src/main/java/org/raddatz/familienarchiv/controller/PersonController.java b/backend/src/main/java/org/raddatz/familienarchiv/controller/PersonController.java index 260b0110..87be2361 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/controller/PersonController.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/controller/PersonController.java @@ -1,47 +1,41 @@ package org.raddatz.familienarchiv.controller; -import lombok.RequiredArgsConstructor; +import java.util.List; +import java.util.Map; +import java.util.UUID; import org.raddatz.familienarchiv.model.Document; import org.raddatz.familienarchiv.model.Person; -import org.raddatz.familienarchiv.repository.DocumentRepository; -import org.raddatz.familienarchiv.repository.PersonRepository; +import org.raddatz.familienarchiv.service.DocumentService; import org.raddatz.familienarchiv.service.PersonService; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.server.ResponseStatusException; -import java.util.List; -import java.util.Map; -import java.util.UUID; +import lombok.RequiredArgsConstructor; @RestController @RequestMapping("/api/persons") @RequiredArgsConstructor public class PersonController { - private final PersonRepository personRepository; - private final DocumentRepository documentRepository; private final PersonService personService; + private final DocumentService documentService; @GetMapping public ResponseEntity> getPersons(@RequestParam(required = false) String q) { - if (q != null && !q.isBlank()) { - return ResponseEntity.ok(personRepository.searchByName(q)); - } - return ResponseEntity.ok(personRepository.findAllByOrderByLastNameAscFirstNameAsc()); + return ResponseEntity.ok(personService.findAll(q)); } @GetMapping("/{id}") public Person getPerson(@PathVariable UUID id) { - return personRepository.findById(id) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Person nicht gefunden")); + return personService.getById(id); } @GetMapping("/{id}/documents") public List getPersonDocuments(@PathVariable UUID id) { - return documentRepository.findBySenderId(id); + return documentService.getDocumentsBySender(id); } @PostMapping diff --git a/backend/src/main/java/org/raddatz/familienarchiv/controller/TagController.java b/backend/src/main/java/org/raddatz/familienarchiv/controller/TagController.java index 0b3a6423..c3d99299 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/controller/TagController.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/controller/TagController.java @@ -4,14 +4,12 @@ import java.util.List; import java.util.Map; import java.util.UUID; -import org.raddatz.familienarchiv.model.Document; import org.raddatz.familienarchiv.model.Tag; -import org.raddatz.familienarchiv.repository.DocumentRepository; -import org.raddatz.familienarchiv.repository.TagRepository; import org.raddatz.familienarchiv.security.Permission; import org.raddatz.familienarchiv.security.RequirePermission; +import org.raddatz.familienarchiv.service.DocumentService; +import org.raddatz.familienarchiv.service.TagService; import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -21,46 +19,31 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; @RestController @RequestMapping("/api/tags") @RequiredArgsConstructor public class TagController { - private final TagRepository tagRepository; - private final DocumentRepository documentRepository; - // Rename Tag + private final TagService tagService; + private final DocumentService documentService; + @PutMapping("/{id}") @RequirePermission(Permission.ADMIN_TAG) public ResponseEntity updateTag(@PathVariable UUID id, @RequestBody Map payload) { - Tag tag = tagRepository.findById(id).orElseThrow(); - tag.setName(payload.get("name")); - return ResponseEntity.ok(tagRepository.save(tag)); + return ResponseEntity.ok(tagService.update(id, payload.get("name"))); } - // Delete Tag @DeleteMapping("/{id}") @RequirePermission(Permission.ADMIN_TAG) - @Transactional public ResponseEntity deleteTag(@PathVariable UUID id) { - Tag tag = tagRepository.findById(id).orElseThrow(); - - // Remove tag from all documents first to prevent FK constraint errors - List documents = documentRepository.findByTags_Id(id); - for (Document doc : documents) { - doc.getTags().remove(tag); - documentRepository.save(doc); - } - - tagRepository.delete(tag); + documentService.deleteTagCascading(id); return ResponseEntity.ok().build(); } @GetMapping public List searchTags(@RequestParam(defaultValue = "") String query) { - return tagRepository.findByNameContainingIgnoreCase(query); + return tagService.search(query); } - -} \ No newline at end of file +} 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 c007aa61..ebf6dbea 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentService.java @@ -9,8 +9,6 @@ import org.raddatz.familienarchiv.model.DocumentStatus; import org.raddatz.familienarchiv.model.Person; import org.raddatz.familienarchiv.model.Tag; import org.raddatz.familienarchiv.repository.DocumentRepository; -import org.raddatz.familienarchiv.repository.PersonRepository; -import org.raddatz.familienarchiv.repository.TagRepository; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.Specification; import org.raddatz.familienarchiv.exception.DomainException; @@ -36,9 +34,9 @@ import static org.raddatz.familienarchiv.repository.DocumentSpecifications.*; public class DocumentService { private final DocumentRepository documentRepository; - private final PersonRepository personRepository; + private final PersonService personService; private final FileService fileService; - private final TagRepository tagRepository; + private final TagService tagService; /** * Lädt eine Datei hoch. @@ -109,12 +107,12 @@ public class DocumentService { // Sender if (dto.getSenderId() != null) { - doc.setSender(personRepository.findById(dto.getSenderId()).orElse(null)); + doc.setSender(personService.getById(dto.getSenderId())); } // Empfänger if (dto.getReceiverIds() != null && !dto.getReceiverIds().isEmpty()) { - doc.setReceivers(new HashSet<>(personRepository.findAllById(dto.getReceiverIds()))); + doc.setReceivers(new HashSet<>(personService.getAllById(dto.getReceiverIds()))); } // Datei @@ -153,17 +151,14 @@ public class DocumentService { // 2. Sender verknüpfen if (dto.getSenderId() != null) { - Person sender = personRepository.findById(dto.getSenderId()).orElse(null); - doc.setSender(sender); + doc.setSender(personService.getById(dto.getSenderId())); } else { doc.setSender(null); } // 3. Empfänger verknüpfen if (dto.getReceiverIds() != null && !dto.getReceiverIds().isEmpty()) { - List receivers = personRepository.findAllById(dto.getReceiverIds()); - - doc.setReceivers(new HashSet<>(receivers)); + doc.setReceivers(new HashSet<>(personService.getAllById(dto.getReceiverIds()))); } else { doc.getReceivers().clear(); // Alle entfernen } @@ -195,11 +190,7 @@ public class DocumentService { if (cleanName.isEmpty()) continue; - // Find existing or Create new - Tag tag = tagRepository.findByNameIgnoreCase(cleanName) - .orElseGet(() -> tagRepository.save(Tag.builder().name(cleanName).build())); - - newTags.add(tag); + newTags.add(tagService.findOrCreate(cleanName)); } doc.setTags(newTags); @@ -253,4 +244,28 @@ public class DocumentService { return documentRepository.findAll(conversation, Sort.by(Sort.Direction.ASC, "documentDate")); } + + public Document getDocumentById(UUID id) { + return documentRepository.findById(id) + .orElseThrow(() -> DomainException.notFound(ErrorCode.DOCUMENT_NOT_FOUND, "Document not found: " + id)); + } + + public List getDocumentsBySender(UUID senderId) { + return documentRepository.findBySenderId(senderId); + } + + public List getConversationFiltered(UUID senderId, UUID receiverId, LocalDate from, LocalDate to, Sort sort) { + LocalDate dateFrom = (from != null) ? from : LocalDate.parse("0000-01-01"); + LocalDate dateTo = (to != null) ? to : LocalDate.now(); + return documentRepository.findConversation(senderId, receiverId, dateFrom, dateTo, sort); + } + + @Transactional + public void deleteTagCascading(UUID tagId) { + documentRepository.findByTags_Id(tagId).forEach(doc -> { + doc.getTags().removeIf(t -> t.getId().equals(tagId)); + documentRepository.save(doc); + }); + tagService.delete(tagId); + } } diff --git a/backend/src/main/java/org/raddatz/familienarchiv/service/MassImportService.java b/backend/src/main/java/org/raddatz/familienarchiv/service/MassImportService.java index c076078b..522bcfd7 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/service/MassImportService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/service/MassImportService.java @@ -10,8 +10,6 @@ import org.raddatz.familienarchiv.model.DocumentStatus; import org.raddatz.familienarchiv.model.Person; import org.raddatz.familienarchiv.model.Tag; import org.raddatz.familienarchiv.repository.DocumentRepository; -import org.raddatz.familienarchiv.repository.PersonRepository; -import org.raddatz.familienarchiv.repository.TagRepository; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @@ -57,8 +55,8 @@ public class MassImportService { } private final DocumentRepository documentRepository; - private final PersonRepository personRepository; - private final TagRepository tagRepository; + private final PersonService personService; + private final TagService tagService; private final S3Client s3Client; @Value("${app.s3.bucket}") @@ -307,8 +305,7 @@ public class MassImportService { Tag tag = null; if (!tagRaw.isBlank()) { - tag = tagRepository.findByNameIgnoreCase(tagRaw) - .orElseGet(() -> tagRepository.save(Tag.builder().name(tagRaw).build())); + tag = tagService.findOrCreate(tagRaw); } Document doc = existing.orElse(Document.builder() @@ -362,15 +359,7 @@ public class MassImportService { } private Person findOrCreatePerson(String rawName) { - String alias = rawName.trim(); - return personRepository.findByAliasIgnoreCase(alias).orElseGet(() -> { - PersonNameParser.SplitName split = PersonNameParser.split(alias); - return personRepository.save(Person.builder() - .alias(alias) - .firstName(split.firstName()) - .lastName(split.lastName()) - .build()); - }); + return personService.findOrCreateByAlias(rawName); } private Optional findFileRecursive(String filename) { diff --git a/backend/src/main/java/org/raddatz/familienarchiv/service/PersonService.java b/backend/src/main/java/org/raddatz/familienarchiv/service/PersonService.java index 84f3b072..8e7a2bc9 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/service/PersonService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/service/PersonService.java @@ -1,6 +1,8 @@ package org.raddatz.familienarchiv.service; -import lombok.RequiredArgsConstructor; +import java.util.List; +import java.util.UUID; + import org.raddatz.familienarchiv.model.Person; import org.raddatz.familienarchiv.repository.PersonRepository; import org.springframework.http.HttpStatus; @@ -8,7 +10,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.server.ResponseStatusException; -import java.util.UUID; +import lombok.RequiredArgsConstructor; @Service @RequiredArgsConstructor @@ -16,6 +18,35 @@ public class PersonService { private final PersonRepository personRepository; + public List findAll(String q) { + if (q != null && !q.isBlank()) { + return personRepository.searchByName(q); + } + return personRepository.findAllByOrderByLastNameAscFirstNameAsc(); + } + + public Person getById(UUID id) { + return personRepository.findById(id) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Person nicht gefunden")); + } + + public List getAllById(List ids) { + return personRepository.findAllById(ids); + } + + @Transactional + public Person findOrCreateByAlias(String rawName) { + String alias = rawName.trim(); + return personRepository.findByAliasIgnoreCase(alias).orElseGet(() -> { + PersonNameParser.SplitName split = PersonNameParser.split(alias); + return personRepository.save(Person.builder() + .alias(alias) + .firstName(split.firstName()) + .lastName(split.lastName()) + .build()); + }); + } + @Transactional public Person createPerson(String firstName, String lastName, String alias) { Person person = Person.builder() diff --git a/backend/src/main/java/org/raddatz/familienarchiv/service/TagService.java b/backend/src/main/java/org/raddatz/familienarchiv/service/TagService.java new file mode 100644 index 00000000..06b8b862 --- /dev/null +++ b/backend/src/main/java/org/raddatz/familienarchiv/service/TagService.java @@ -0,0 +1,47 @@ +package org.raddatz.familienarchiv.service; + +import java.util.List; +import java.util.UUID; + +import org.raddatz.familienarchiv.model.Tag; +import org.raddatz.familienarchiv.repository.TagRepository; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.server.ResponseStatusException; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class TagService { + + private final TagRepository tagRepository; + + public List search(String query) { + return tagRepository.findByNameContainingIgnoreCase(query); + } + + public Tag getById(UUID id) { + return tagRepository.findById(id) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Tag nicht gefunden")); + } + + public Tag findOrCreate(String name) { + String cleanName = name.trim(); + return tagRepository.findByNameIgnoreCase(cleanName) + .orElseGet(() -> tagRepository.save(Tag.builder().name(cleanName).build())); + } + + @Transactional + public Tag update(UUID id, String newName) { + Tag tag = getById(id); + tag.setName(newName); + return tagRepository.save(tag); + } + + @Transactional + public void delete(UUID id) { + tagRepository.delete(getById(id)); + } +} diff --git a/backend/src/main/java/org/raddatz/familienarchiv/service/UserService.java b/backend/src/main/java/org/raddatz/familienarchiv/service/UserService.java index 4e6b1326..897f19a7 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/service/UserService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/service/UserService.java @@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.raddatz.familienarchiv.dto.CreateUserRequest; +import org.raddatz.familienarchiv.dto.GroupDTO; import org.raddatz.familienarchiv.exception.DomainException; import org.raddatz.familienarchiv.exception.ErrorCode; import org.raddatz.familienarchiv.model.AppUser; @@ -81,4 +82,30 @@ public AppUser createUserOrUpdate(CreateUserRequest request) { public List getAllGroups() { return groupRepository.findAll(); } + + public UserGroup getGroupById(UUID id) { + return groupRepository.findById(id) + .orElseThrow(() -> DomainException.notFound(ErrorCode.INTERNAL_ERROR, "Group not found: " + id)); + } + + @Transactional + public UserGroup createGroup(GroupDTO dto) { + UserGroup group = new UserGroup(); + group.setName(dto.getName()); + group.setPermissions(dto.getPermissions()); + return groupRepository.save(group); + } + + @Transactional + public UserGroup updateGroup(UUID id, GroupDTO dto) { + UserGroup group = getGroupById(id); + if (dto.getName() != null) group.setName(dto.getName()); + if (dto.getPermissions() != null) group.setPermissions(dto.getPermissions()); + return groupRepository.save(group); + } + + @Transactional + public void deleteGroup(UUID id) { + groupRepository.deleteById(id); + } }