From 572319d1a7fdaeba1b2b5616a207824eff9e6a74 Mon Sep 17 00:00:00 2001 From: Thorben <38816229+tklein1801@users.noreply.github.com> Date: Wed, 7 Feb 2024 19:41:10 +0100 Subject: [PATCH] feat: Provide endpoints in order to attach files to a transaction --- .../backend/transaction/Transaction.java | 1 - .../transaction/TransactionController.java | 116 +++++++-- .../transaction/file/TransactionFile.java | 14 +- .../TransactionControllerTests.java | 238 ++++++++++++++++++ 4 files changed, 349 insertions(+), 20 deletions(-) diff --git a/src/main/java/de/budgetbuddy/backend/transaction/Transaction.java b/src/main/java/de/budgetbuddy/backend/transaction/Transaction.java index 4a7ec22..cec26b2 100644 --- a/src/main/java/de/budgetbuddy/backend/transaction/Transaction.java +++ b/src/main/java/de/budgetbuddy/backend/transaction/Transaction.java @@ -108,7 +108,6 @@ public static class Update { private String receiver; private String description; private Double transferAmount; - private ArrayList attachedFiles = new ArrayList<>(); } @Data diff --git a/src/main/java/de/budgetbuddy/backend/transaction/TransactionController.java b/src/main/java/de/budgetbuddy/backend/transaction/TransactionController.java index 77fd81c..d1634fe 100644 --- a/src/main/java/de/budgetbuddy/backend/transaction/TransactionController.java +++ b/src/main/java/de/budgetbuddy/backend/transaction/TransactionController.java @@ -49,8 +49,9 @@ public TransactionController(UserRepository userRepository, } @PostMapping - public ResponseEntity>> createTransaction(@RequestBody List payload, - HttpSession session) throws JsonProcessingException { + public ResponseEntity>> createTransaction( + @RequestBody List payload, + HttpSession session) throws JsonProcessingException { Optional optSessionUser = AuthorizationInterceptor.getSessionUser(session); if (optSessionUser.isEmpty()) { return AuthorizationInterceptor.noValidSessionResponse(); @@ -190,21 +191,6 @@ public ResponseEntity> updateTransaction(@RequestBody T "Provided payment-method not found")); } - List transactionFiles = new ArrayList<>(); - if (!payload.getAttachedFiles().isEmpty()) { - for (TransactionFile.Create file : payload.getAttachedFiles()) { - transactionFiles.add(TransactionFile.builder() - .owner(transactionOwner) - .transaction(transaction) - .fileName(file.getFileName()) - .fileSize(file.getFileSize()) - .mimeType(file.getMimeType()) - .location(file.getFileUrl()) - .build()); - } - transactionFileRepository.saveAll(transactionFiles); - } - return ResponseEntity .status(HttpStatus.OK) .body(new ApiResponse<>(transactionRepository.save(Transaction.builder() @@ -216,12 +202,106 @@ public ResponseEntity> updateTransaction(@RequestBody T .receiver(payload.getReceiver()) .description(payload.getDescription()) .transferAmount(payload.getTransferAmount()) - .attachedFiles(transactionFiles) + .attachedFiles(transaction.getAttachedFiles()) .createdAt(transaction.getCreatedAt()) .build()))); } + @PostMapping("/file") + public ResponseEntity>> attachFiles( + @RequestBody List files, + HttpSession session) throws JsonProcessingException { + if (files.isEmpty()) { + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .body(new ApiResponse<>(HttpStatus.BAD_REQUEST.value(), "No files provided")); + } + + Optional sessionUser = AuthorizationInterceptor.getSessionUser(session); + if (sessionUser.isEmpty()) { + return AuthorizationInterceptor.noValidSessionResponse(); + } + + List transactionFiles = files.stream() +// .filter(file -> transactionRepository.findById(file.getTransactionId()).isPresent()) + .map(file -> { + Optional optTransaction = transactionRepository.findById(file.getTransactionId()); + if (optTransaction.isEmpty()) return null; + Transaction transaction = optTransaction.get(); + User transactionOwner = transaction.getOwner(); + + if (!transactionOwner.getUuid().equals(sessionUser.get().getUuid())) { + return null; + } + + return TransactionFile.builder() + .owner(transactionOwner) + .transaction(transaction) + .fileName(file.getFileName()) + .fileSize(file.getFileSize()) + .mimeType(file.getMimeType()) + .location(file.getFileUrl()) + .build(); + }) + .filter(Objects::nonNull) + .toList(); + + if (transactionFiles.isEmpty()) { + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .body(new ApiResponse<>(HttpStatus.BAD_REQUEST.value(), "No valid transactions and files we're provided")); + } + + return ResponseEntity + .status(HttpStatus.OK) + .body(new ApiResponse<>(HttpStatus.OK.value(), transactionFileRepository.saveAll(transactionFiles))); + } + + @DeleteMapping("/file") + public ResponseEntity>> detachFiles( + @RequestBody List files, + HttpSession session) throws JsonProcessingException { + if (files.isEmpty()) { + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .body(new ApiResponse<>(HttpStatus.BAD_REQUEST.value(), "No file id's provided")); + } + + Optional sessionUser = AuthorizationInterceptor.getSessionUser(session); + if (sessionUser.isEmpty()) { + return AuthorizationInterceptor.noValidSessionResponse(); + } + + List transactionFiles = files.stream() +// .filter(file -> transactionFileRepository.findById(file.getUuid()).isPresent()) + .map(file -> { + Optional optionalTransactionFile = transactionFileRepository.findById(file.getUuid()); + if (optionalTransactionFile.isEmpty()) return null; + TransactionFile transactionFile = optionalTransactionFile.get(); + User transactionFileOwner = transactionFile.getOwner(); + if (!transactionFileOwner.getUuid().equals(sessionUser.get().getUuid())) { + return null; + } + return transactionFile; + }) + .filter(Objects::nonNull) + .toList(); + + if (transactionFiles.isEmpty()) { + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .body(new ApiResponse<>(HttpStatus.BAD_REQUEST.value(), "No valid file id's we're provided")); + } + + transactionFileRepository.deleteAllById(transactionFiles.stream().map(TransactionFile::getUuid).toList()); + + return ResponseEntity + .status(HttpStatus.OK) + .body(new ApiResponse<>(HttpStatus.OK.value(), transactionFiles)); + } + @DeleteMapping + public ResponseEntity>>> deleteTransactions( @RequestBody List payloads, HttpSession session) throws JsonProcessingException { diff --git a/src/main/java/de/budgetbuddy/backend/transaction/file/TransactionFile.java b/src/main/java/de/budgetbuddy/backend/transaction/file/TransactionFile.java index 7a32206..8ff52e9 100644 --- a/src/main/java/de/budgetbuddy/backend/transaction/file/TransactionFile.java +++ b/src/main/java/de/budgetbuddy/backend/transaction/file/TransactionFile.java @@ -45,7 +45,7 @@ public class TransactionFile { @Column(name = "mimetype", length = 20) private String mimeType; - @Column(name = "location", length = 100) + @Column(name = "location") private String location; @Builder.Default @@ -54,11 +54,23 @@ public class TransactionFile { private Date createdAt = new Date(); @Data + @Builder + @AllArgsConstructor + @NoArgsConstructor public static class Create { + private Long transactionId; private String fileName; private int fileSize; private String mimeType; private String fileUrl; } + @Data + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class Delete { + private UUID uuid; + } + } diff --git a/src/test/java/de/budgetbuddy/backend/transaction/TransactionControllerTests.java b/src/test/java/de/budgetbuddy/backend/transaction/TransactionControllerTests.java index 71d5eca..4bf4153 100644 --- a/src/test/java/de/budgetbuddy/backend/transaction/TransactionControllerTests.java +++ b/src/test/java/de/budgetbuddy/backend/transaction/TransactionControllerTests.java @@ -8,6 +8,7 @@ import de.budgetbuddy.backend.paymentMethod.PaymentMethod; import de.budgetbuddy.backend.paymentMethod.PaymentMethodRepository; import de.budgetbuddy.backend.subscription.SubscriptionRepository; +import de.budgetbuddy.backend.transaction.file.TransactionFile; import de.budgetbuddy.backend.transaction.file.TransactionFileRepository; import de.budgetbuddy.backend.user.User; import de.budgetbuddy.backend.user.UserRepository; @@ -26,6 +27,7 @@ import java.time.LocalDate; import java.util.*; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; @@ -656,4 +658,240 @@ void testGetDailyTransactions_Success() throws JsonProcessingException { assertNull(Objects.requireNonNull(response.getBody()).getMessage()); assertEquals(0, Objects.requireNonNull(response.getBody()).getData().size()); } + + @Test + void attachFiles_NoFilesProvided() throws JsonProcessingException { + List files = new ArrayList<>(); + + ResponseEntity>> response = + transactionController.attachFiles(files, session); + + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + assertEquals("No files provided", Objects.requireNonNull(response.getBody()).getMessage()); + } + + @Test + void attachFiles_OnlyInvalidFilesProvided() throws JsonProcessingException { + User sessionUser = new User(UUID.randomUUID()); + session.setAttribute("user", objectMapper.writeValueAsString(sessionUser)); + + TransactionFile.Create file = TransactionFile.Create.builder() + .transactionId(1L) + .build(); + + List files = List.of(file); + + when(transactionRepository.findById(file.getTransactionId())) + .thenReturn(Optional.empty()); + + ResponseEntity>> response = + transactionController.attachFiles(files, session); + + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + assertEquals("No valid transactions and files we're provided", + Objects.requireNonNull(response.getBody()).getMessage()); + assertNull(Objects.requireNonNull(response.getBody()).getData()); + } + + @Test + void attachFiles_SomeInvalidFilesProvided() throws JsonProcessingException { + User sessionUser = new User(UUID.randomUUID()); + session.setAttribute("user", objectMapper.writeValueAsString(sessionUser)); + + Transaction t2 = Transaction.builder() + .id(2L) + .owner(new User(UUID.randomUUID())) + .build(); + + Transaction t3 = Transaction.builder() + .id(3L) + .owner(sessionUser) + .build(); + + List files = List.of( + TransactionFile.Create.builder() + .transactionId(1L) + .build(), + TransactionFile.Create.builder() + .transactionId(t2.getId()) + .build(), + TransactionFile.Create.builder() + .transactionId(t3.getId()) + .build() + ); + + List transactionFiles = List.of(TransactionFile.builder() + .transaction(t3) + .build()); + + when(transactionRepository.findById(1L)) + .thenReturn(Optional.empty()); + + when(transactionRepository.findById(t2.getId())) + .thenReturn(Optional.of(t2)); + + when(transactionRepository.findById(t3.getId())) + .thenReturn(Optional.of(t3)); + + when(transactionFileRepository.saveAll(any())) + .thenReturn(transactionFiles); + + ResponseEntity>> response = + transactionController.attachFiles(files, session); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNull(Objects.requireNonNull(response.getBody()).getMessage()); + assertEquals(transactionFiles, Objects.requireNonNull(response.getBody()).getData()); + } + + @Test + void attachFiles_Success() throws JsonProcessingException { + User sessionUser = new User(UUID.randomUUID()); + session.setAttribute("user", objectMapper.writeValueAsString(sessionUser)); + + Transaction transaction = Transaction.builder() + .id(1L) + .owner(sessionUser) + .build(); + + List files = List.of( + TransactionFile.Create.builder() + .transactionId(transaction.getId()) + .build() + ); + + List transactionFiles = List.of(TransactionFile.builder() + .transaction(transaction) + .owner(sessionUser) + .build()); + + when(transactionRepository.findById(transaction.getId())) + .thenReturn(Optional.of(transaction)); + + when(transactionFileRepository.saveAll(any())) + .thenReturn(transactionFiles); + + ResponseEntity>> response = + transactionController.attachFiles(files, session); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNull(Objects.requireNonNull(response.getBody()).getMessage()); + assertEquals(transactionFiles, Objects.requireNonNull(response.getBody()).getData()); + } + + @Test + void detachFiles_NoFilesProvided() throws JsonProcessingException { + ResponseEntity>> response = + transactionController.detachFiles(new ArrayList<>(), session); + + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + assertEquals("No file id's provided", Objects.requireNonNull(response.getBody()).getMessage()); + assertNull(Objects.requireNonNull(response.getBody()).getData()); + } + + @Test + void detachFiles_OnlyInvalidFilesProvided() throws JsonProcessingException { + User sessionUser = new User(UUID.randomUUID()); + session.setAttribute("user", objectMapper.writeValueAsString(sessionUser)); + + when(transactionFileRepository.findById(any(UUID.class))) + .thenReturn(Optional.empty()); + + List fileIds = List.of( + TransactionFile.Delete.builder().uuid(UUID.randomUUID()).build() + ); + + ResponseEntity>> response = + transactionController.detachFiles(fileIds, session); + + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + assertEquals("No valid file id's we're provided", Objects.requireNonNull(response.getBody()).getMessage()); + assertNull(Objects.requireNonNull(response.getBody()).getData()); + } + + @Test + void detachFiles_SomeInvalidFilesProvided() throws JsonProcessingException { + User sessionUser = new User(UUID.randomUUID()); + session.setAttribute("user", objectMapper.writeValueAsString(sessionUser)); + + Transaction transaction = Transaction.builder() + .id(1L) + .owner(sessionUser) + .build(); + + when(transactionRepository.findById(transaction.getId())) + .thenReturn(Optional.of(transaction)); + + TransactionFile invalidFile = TransactionFile.builder() + .uuid(UUID.randomUUID()) + .transaction(any(Transaction.class)) + .owner(new User(UUID.randomUUID())) + .build(); + + TransactionFile validFile = TransactionFile.builder() + .uuid(UUID.randomUUID()) + .transaction(transaction) + .owner(sessionUser) + .build(); + + List fileIds = Stream.of(invalidFile.getUuid(), validFile.getUuid()) + .map(uuid -> TransactionFile.Delete.builder().uuid(uuid).build()) + .toList(); + + when(transactionFileRepository.findById(invalidFile.getUuid())) + .thenReturn(Optional.of(invalidFile)); + + when(transactionFileRepository.findById(validFile.getUuid())) + .thenReturn(Optional.of(validFile)); + + ResponseEntity>> response = + transactionController.detachFiles(fileIds, session); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNull(Objects.requireNonNull(response.getBody()).getMessage()); + assertEquals(List.of(validFile), Objects.requireNonNull(response.getBody()).getData()); + } + + @Test + void detachFiles_Success() throws JsonProcessingException { + User sessionUser = new User(UUID.randomUUID()); + session.setAttribute("user", objectMapper.writeValueAsString(sessionUser)); + + Transaction transaction = Transaction.builder() + .id(1L) + .owner(sessionUser) + .build(); + + when(transactionRepository.findById(transaction.getId())) + .thenReturn(Optional.of(transaction)); + + TransactionFile validFile = TransactionFile.builder() + .uuid(UUID.randomUUID()) + .transaction(transaction) + .owner(sessionUser) + .build(); + + TransactionFile anotherValidFile = TransactionFile.builder() + .uuid(UUID.randomUUID()) + .transaction(transaction) + .owner(sessionUser) + .build(); + + List fileIds = Stream.of(validFile.getUuid(), anotherValidFile.getUuid()) + .map(uuid -> TransactionFile.Delete.builder().uuid(uuid).build()) + .toList(); + + when(transactionFileRepository.findById(validFile.getUuid())) + .thenReturn(Optional.of(validFile)); + + when(transactionFileRepository.findById(anotherValidFile.getUuid())) + .thenReturn(Optional.of(anotherValidFile)); + + ResponseEntity>> response = + transactionController.detachFiles(fileIds, session); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNull(Objects.requireNonNull(response.getBody()).getMessage()); + assertEquals(List.of(validFile, anotherValidFile), Objects.requireNonNull(response.getBody()).getData()); + } }