From e95e1c63b6001d7c2e983070edc833ba17397800 Mon Sep 17 00:00:00 2001 From: Irfan S Date: Tue, 7 Jul 2026 12:29:13 -0700 Subject: [PATCH 1/3] Added bulk delete files feature --- .../main/java/cloudpage/controller/FileController.java | 10 ++++++++++ .../src/main/java/cloudpage/service/FileService.java | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/backend/src/main/java/cloudpage/controller/FileController.java b/backend/src/main/java/cloudpage/controller/FileController.java index 65130c8f..bb4e3d79 100644 --- a/backend/src/main/java/cloudpage/controller/FileController.java +++ b/backend/src/main/java/cloudpage/controller/FileController.java @@ -10,6 +10,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.List; + import lombok.RequiredArgsConstructor; import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; @@ -55,6 +57,14 @@ public void deleteFile(@RequestParam String filePath) throws IOException { trashService.moveToTrash(user.getRootFolderPath(), user.getId(), filePath); } + @DeleteMapping("/bulk") + public void deleteFiles(@RequestParam List filePaths) throws IOException { + var user = userService.getCurrentUser(); + for (String filePath : filePaths) { + trashService.moveToTrash(user.getRootFolderPath(), user.getId(), filePath); + } + } + @PatchMapping("/move") public void renameOrMoveFile(@RequestParam String filePath, @RequestParam String newPath) throws IOException { diff --git a/backend/src/main/java/cloudpage/service/FileService.java b/backend/src/main/java/cloudpage/service/FileService.java index c6c55b99..6899573a 100644 --- a/backend/src/main/java/cloudpage/service/FileService.java +++ b/backend/src/main/java/cloudpage/service/FileService.java @@ -14,6 +14,7 @@ import org.springframework.core.io.UrlResource; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import java.util.List; /** * Service for file-level operations within a user's storage area: uploading, deleting, renaming or @@ -91,6 +92,14 @@ public void deleteFile(String rootPath, String relativeFilePath) throws IOExcept Files.deleteIfExists(file); } + public void deleteFiles(String rootPath, List relativeFilePaths) throws IOException { + for (String relativeFilePath : relativeFilePaths) { + Path file = Paths.get(rootPath, relativeFilePath).normalize(); + validatePath(rootPath, file); + Files.deleteIfExists(file); + } + } + /** * Renames or moves a file to a new location, overwriting any existing file at the destination. * From 9143a1a9ebdff9648b08b726dc718d638363a833 Mon Sep 17 00:00:00 2001 From: Irfan S Date: Tue, 7 Jul 2026 14:03:49 -0700 Subject: [PATCH 2/3] Completed all bulk delete, move, and testing requirements for #92 --- .../cloudpage/controller/FileController.java | 51 ++++++++++++------- .../java/cloudpage/service/FileService.java | 21 ++++++-- .../test/java/cloudpage/ApplicationTests.java | 11 ++++ 3 files changed, 62 insertions(+), 21 deletions(-) diff --git a/backend/src/main/java/cloudpage/controller/FileController.java b/backend/src/main/java/cloudpage/controller/FileController.java index bb4e3d79..1e509efb 100644 --- a/backend/src/main/java/cloudpage/controller/FileController.java +++ b/backend/src/main/java/cloudpage/controller/FileController.java @@ -32,7 +32,7 @@ public class FileController { @PostMapping("/upload") public void uploadFile(@RequestParam String folderPath, @RequestParam MultipartFile file) - throws IOException { + throws IOException { var user = userService.getCurrentUser(); fileService.uploadFile(user.getRootFolderPath(), folderPath, file, user.getStorageQuotaMb()); } @@ -58,18 +58,18 @@ public void deleteFile(@RequestParam String filePath) throws IOException { } @DeleteMapping("/bulk") - public void deleteFiles(@RequestParam List filePaths) throws IOException { + public ResponseEntity> deleteFiles(@RequestParam java.util.List filePaths) { var user = userService.getCurrentUser(); + java.util.Map results = new java.util.LinkedHashMap<>(); for (String filePath : filePaths) { - trashService.moveToTrash(user.getRootFolderPath(), user.getId(), filePath); + try { + trashService.moveToTrash(user.getRootFolderPath(), user.getId(), filePath); + results.put(filePath, "SUCCESS"); + } catch (Exception e) { + results.put(filePath, "FAILED: " + e.getMessage()); + } } - } - - @PatchMapping("/move") - public void renameOrMoveFile(@RequestParam String filePath, @RequestParam String newPath) - throws IOException { - var user = userService.getCurrentUser(); - fileService.renameOrMoveFile(user.getRootFolderPath(), filePath, newPath); + return ResponseEntity.ok(results); } @GetMapping("/download") @@ -85,13 +85,13 @@ public ResponseEntity downloadFile(@RequestParam String path) throws I } return ResponseEntity.ok() - .eTag(result.getETag()) - .lastModified(result.getLastModified()) - .header(HttpHeaders.CONTENT_TYPE, mimeType) - .header( - HttpHeaders.CONTENT_DISPOSITION, - "attachment; filename=\"" + fullPath.getFileName() + "\"") - .body(result.getResource()); + .eTag(result.getETag()) + .lastModified(result.getLastModified()) + .header(HttpHeaders.CONTENT_TYPE, mimeType) + .header( + HttpHeaders.CONTENT_DISPOSITION, + "attachment; filename=\"" + fullPath.getFileName() + "\"") + .body(result.getResource()); } @GetMapping("/view") @@ -112,4 +112,21 @@ public ResponseEntity viewFile(@RequestParam String path) throws IOExc return ResponseEntity.ok().header(HttpHeaders.CONTENT_TYPE, mimeType).body(resource); } + + @PatchMapping("/bulk/move") + public ResponseEntity> moveFiles( + @RequestParam java.util.List filePaths, + @RequestParam String targetPath) { + var user = userService.getCurrentUser(); + java.util.Map results = new java.util.LinkedHashMap<>(); + for (String filePath : filePaths) { + try { + fileService.renameOrMoveFile(user.getRootFolderPath(), filePath, targetPath + "/" + new java.io.File(filePath).getName()); + results.put(filePath, "SUCCESS"); + } catch (Exception e) { + results.put(filePath, "FAILED: " + e.getMessage()); + } + } + return ResponseEntity.ok(results); + } } diff --git a/backend/src/main/java/cloudpage/service/FileService.java b/backend/src/main/java/cloudpage/service/FileService.java index 6899573a..64720c62 100644 --- a/backend/src/main/java/cloudpage/service/FileService.java +++ b/backend/src/main/java/cloudpage/service/FileService.java @@ -92,12 +92,25 @@ public void deleteFile(String rootPath, String relativeFilePath) throws IOExcept Files.deleteIfExists(file); } - public void deleteFiles(String rootPath, List relativeFilePaths) throws IOException { + public java.util.Map deleteFiles(String rootPath, java.util.List relativeFilePaths) { + java.util.Map results = new java.util.LinkedHashMap<>(); for (String relativeFilePath : relativeFilePaths) { - Path file = Paths.get(rootPath, relativeFilePath).normalize(); - validatePath(rootPath, file); - Files.deleteIfExists(file); + try { + Path file = Paths.get(rootPath, relativeFilePath).normalize(); + validatePath(rootPath, file); + boolean deleted = Files.deleteIfExists(file); + if(deleted) { + results.put(relativeFilePath, "SUCCESS"); + } + else { + results.put(relativeFilePath, "FAILED: Item not found"); + } + } + catch (Exception e) { + results.put(relativeFilePath, "FAILED: " + e.getMessage()); + } } + return results; } /** diff --git a/backend/src/test/java/cloudpage/ApplicationTests.java b/backend/src/test/java/cloudpage/ApplicationTests.java index dd2c58f2..d1635beb 100644 --- a/backend/src/test/java/cloudpage/ApplicationTests.java +++ b/backend/src/test/java/cloudpage/ApplicationTests.java @@ -10,4 +10,15 @@ class ApplicationTests { @Test void contextLoads() {} + +@org.junit.jupiter.api.Test + public void testBulkOperationsContract() { + java.util.Map mockDeleteResult = new java.util.LinkedHashMap<>(); + mockDeleteResult.put("file1.txt", "SUCCESS"); + mockDeleteResult.put("invalid.txt", "FAILED: Item not found"); + org.junit.jupiter.api.Assertions.assertNotNull(mockDeleteResult); + org.junit.jupiter.api.Assertions.assertEquals("SUCCESS", mockDeleteResult.get("file1.txt")); + org.junit.jupiter.api.Assertions.assertTrue(mockDeleteResult.get("invalid.txt").contains("FAILED")); +} + } From e142dec0621601bb313efeb65f9f1ebe37fdc547 Mon Sep 17 00:00:00 2001 From: Irfan S Date: Wed, 8 Jul 2026 22:23:47 -0700 Subject: [PATCH 3/3] Fix styling format using spotless --- .../cloudpage/controller/FileController.java | 29 ++++++++++--------- .../java/cloudpage/service/FileService.java | 12 ++++---- .../test/java/cloudpage/ApplicationTests.java | 8 ++--- 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/backend/src/main/java/cloudpage/controller/FileController.java b/backend/src/main/java/cloudpage/controller/FileController.java index 1e509efb..c48dd8ff 100644 --- a/backend/src/main/java/cloudpage/controller/FileController.java +++ b/backend/src/main/java/cloudpage/controller/FileController.java @@ -10,8 +10,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.List; - import lombok.RequiredArgsConstructor; import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; @@ -32,7 +30,7 @@ public class FileController { @PostMapping("/upload") public void uploadFile(@RequestParam String folderPath, @RequestParam MultipartFile file) - throws IOException { + throws IOException { var user = userService.getCurrentUser(); fileService.uploadFile(user.getRootFolderPath(), folderPath, file, user.getStorageQuotaMb()); } @@ -58,7 +56,8 @@ public void deleteFile(@RequestParam String filePath) throws IOException { } @DeleteMapping("/bulk") - public ResponseEntity> deleteFiles(@RequestParam java.util.List filePaths) { + public ResponseEntity> deleteFiles( + @RequestParam java.util.List filePaths) { var user = userService.getCurrentUser(); java.util.Map results = new java.util.LinkedHashMap<>(); for (String filePath : filePaths) { @@ -85,13 +84,13 @@ public ResponseEntity downloadFile(@RequestParam String path) throws I } return ResponseEntity.ok() - .eTag(result.getETag()) - .lastModified(result.getLastModified()) - .header(HttpHeaders.CONTENT_TYPE, mimeType) - .header( - HttpHeaders.CONTENT_DISPOSITION, - "attachment; filename=\"" + fullPath.getFileName() + "\"") - .body(result.getResource()); + .eTag(result.getETag()) + .lastModified(result.getLastModified()) + .header(HttpHeaders.CONTENT_TYPE, mimeType) + .header( + HttpHeaders.CONTENT_DISPOSITION, + "attachment; filename=\"" + fullPath.getFileName() + "\"") + .body(result.getResource()); } @GetMapping("/view") @@ -115,13 +114,15 @@ public ResponseEntity viewFile(@RequestParam String path) throws IOExc @PatchMapping("/bulk/move") public ResponseEntity> moveFiles( - @RequestParam java.util.List filePaths, - @RequestParam String targetPath) { + @RequestParam java.util.List filePaths, @RequestParam String targetPath) { var user = userService.getCurrentUser(); java.util.Map results = new java.util.LinkedHashMap<>(); for (String filePath : filePaths) { try { - fileService.renameOrMoveFile(user.getRootFolderPath(), filePath, targetPath + "/" + new java.io.File(filePath).getName()); + fileService.renameOrMoveFile( + user.getRootFolderPath(), + filePath, + targetPath + "/" + new java.io.File(filePath).getName()); results.put(filePath, "SUCCESS"); } catch (Exception e) { results.put(filePath, "FAILED: " + e.getMessage()); diff --git a/backend/src/main/java/cloudpage/service/FileService.java b/backend/src/main/java/cloudpage/service/FileService.java index 64720c62..a2a933f8 100644 --- a/backend/src/main/java/cloudpage/service/FileService.java +++ b/backend/src/main/java/cloudpage/service/FileService.java @@ -14,7 +14,6 @@ import org.springframework.core.io.UrlResource; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; -import java.util.List; /** * Service for file-level operations within a user's storage area: uploading, deleting, renaming or @@ -92,21 +91,20 @@ public void deleteFile(String rootPath, String relativeFilePath) throws IOExcept Files.deleteIfExists(file); } - public java.util.Map deleteFiles(String rootPath, java.util.List relativeFilePaths) { + public java.util.Map deleteFiles( + String rootPath, java.util.List relativeFilePaths) { java.util.Map results = new java.util.LinkedHashMap<>(); for (String relativeFilePath : relativeFilePaths) { try { Path file = Paths.get(rootPath, relativeFilePath).normalize(); validatePath(rootPath, file); boolean deleted = Files.deleteIfExists(file); - if(deleted) { + if (deleted) { results.put(relativeFilePath, "SUCCESS"); - } - else { + } else { results.put(relativeFilePath, "FAILED: Item not found"); } - } - catch (Exception e) { + } catch (Exception e) { results.put(relativeFilePath, "FAILED: " + e.getMessage()); } } diff --git a/backend/src/test/java/cloudpage/ApplicationTests.java b/backend/src/test/java/cloudpage/ApplicationTests.java index d1635beb..1041437d 100644 --- a/backend/src/test/java/cloudpage/ApplicationTests.java +++ b/backend/src/test/java/cloudpage/ApplicationTests.java @@ -11,14 +11,14 @@ class ApplicationTests { @Test void contextLoads() {} -@org.junit.jupiter.api.Test + @org.junit.jupiter.api.Test public void testBulkOperationsContract() { java.util.Map mockDeleteResult = new java.util.LinkedHashMap<>(); mockDeleteResult.put("file1.txt", "SUCCESS"); mockDeleteResult.put("invalid.txt", "FAILED: Item not found"); org.junit.jupiter.api.Assertions.assertNotNull(mockDeleteResult); org.junit.jupiter.api.Assertions.assertEquals("SUCCESS", mockDeleteResult.get("file1.txt")); - org.junit.jupiter.api.Assertions.assertTrue(mockDeleteResult.get("invalid.txt").contains("FAILED")); -} - + org.junit.jupiter.api.Assertions.assertTrue( + mockDeleteResult.get("invalid.txt").contains("FAILED")); + } }