diff --git a/src/main/java/backendlab/team4you/s3/S3Controller.java b/src/main/java/backendlab/team4you/s3/S3Controller.java index 73157fc4..e7bc4ca8 100644 --- a/src/main/java/backendlab/team4you/s3/S3Controller.java +++ b/src/main/java/backendlab/team4you/s3/S3Controller.java @@ -6,16 +6,19 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import backendlab.team4you.exceptions.FileStorageConfigurationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; -// Handles HTTP requests and returns data @RestController -// All endpoints start with /api/files @RequestMapping("/api/files") public class S3Controller { + private static final Logger log = LoggerFactory.getLogger(S3Controller.class); + private final S3Service s3Service; public S3Controller(S3Service s3Service) { @@ -30,20 +33,28 @@ public ResponseEntity uploadFile(@RequestParam("file") MultipartFile fil if (key == null || key.isBlank()) { return ResponseEntity.badRequest().body("Invalid filename"); } - s3Service.uploadFile(key, file.getBytes(), file.getContentType()); - return ResponseEntity.ok("File uploaded: " + key); + try { + s3Service.uploadFile(key, file.getBytes(), file.getContentType()); + return ResponseEntity.ok("File uploaded: " + key); + } catch (FileStorageConfigurationException e) { + log.error("S3 upload failed for key={}", key, e); + return ResponseEntity.internalServerError().body("Could not upload file: " + key); + } } // GET /api/files/download/{key} — download a file @PreAuthorize("hasRole('ADMIN')") @GetMapping("/download/{key}") - public ResponseEntity downloadFile(@PathVariable String key) throws IOException { + public ResponseEntity downloadFile(@PathVariable String key) throws IOException { try (InputStream stream = s3Service.downloadFile(key)) { byte[] bytes = stream.readAllBytes(); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + key + "\"") .contentType(MediaType.APPLICATION_OCTET_STREAM) .body(bytes); + } catch (FileStorageConfigurationException e) { + log.error("S3 download failed for key={}", key, e); + return ResponseEntity.internalServerError().body("Could not download file: " + key); } } @@ -51,7 +62,12 @@ public ResponseEntity downloadFile(@PathVariable String key) throws IOEx @PreAuthorize("hasRole('ADMIN')") @DeleteMapping("/delete/{key}") public ResponseEntity deleteFile(@PathVariable String key) { - s3Service.deleteFile(key); - return ResponseEntity.ok("File deleted: " + key); + try { + s3Service.deleteFile(key); + return ResponseEntity.ok("File deleted: " + key); + } catch (FileStorageConfigurationException e) { + log.error("S3 delete failed for key={}", key, e); + return ResponseEntity.internalServerError().body("Could not delete file: " + key); + } } -} +} \ No newline at end of file diff --git a/src/main/java/backendlab/team4you/s3/S3Service.java b/src/main/java/backendlab/team4you/s3/S3Service.java index 89bc2e74..a79db409 100644 --- a/src/main/java/backendlab/team4you/s3/S3Service.java +++ b/src/main/java/backendlab/team4you/s3/S3Service.java @@ -10,6 +10,8 @@ import software.amazon.awssdk.services.s3.model.PutObjectRequest; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import software.amazon.awssdk.services.s3.model.S3Exception; +import backendlab.team4you.exceptions.FileStorageConfigurationException; + import java.io.InputStream; @@ -29,34 +31,46 @@ public S3Service(S3Client s3Client) { // Upload a file to S3 public void uploadFile(String key, byte[] data, String contentType) { - s3Client.putObject( - PutObjectRequest.builder() - .bucket(bucketName) - .key(key) - .contentType(contentType) - .build(), - RequestBody.fromBytes(data) - ); + try { + s3Client.putObject( + PutObjectRequest.builder() + .bucket(bucketName) + .key(key) + .contentType(contentType) + .build(), + RequestBody.fromBytes(data) + ); + } catch (S3Exception e) { + throw new FileStorageConfigurationException("Kunde inte ladda upp filen: " + key, e); + } } // Download a file from S3 public InputStream downloadFile(String key) { - return s3Client.getObject( - GetObjectRequest.builder() - .bucket(bucketName) - .key(key) - .build() - ); + try { + return s3Client.getObject( + GetObjectRequest.builder() + .bucket(bucketName) + .key(key) + .build() + ); + } catch (S3Exception e) { + throw new FileStorageConfigurationException("Kunde inte ladda ner filen: " + key, e); + } } // Delete a file from S3 public void deleteFile(String key) { - s3Client.deleteObject( - DeleteObjectRequest.builder() - .bucket(bucketName) - .key(key) - .build() - ); + try { + s3Client.deleteObject( + DeleteObjectRequest.builder() + .bucket(bucketName) + .key(key) + .build() + ); + } catch (S3Exception e) { + throw new FileStorageConfigurationException("Kunde inte radera filen: " + key, e); + } } public void uploadFileIfAbsent(String key, byte[] data, String contentType) { diff --git a/src/test/java/backendlab/team4you/s3/S3ServiceTest.java b/src/test/java/backendlab/team4you/s3/S3ServiceTest.java index 1810474f..67d72ae6 100644 --- a/src/test/java/backendlab/team4you/s3/S3ServiceTest.java +++ b/src/test/java/backendlab/team4you/s3/S3ServiceTest.java @@ -11,6 +11,8 @@ import software.amazon.awssdk.services.s3.model.*; import org.junit.jupiter.api.BeforeEach; import org.springframework.test.util.ReflectionTestUtils; +import static org.junit.jupiter.api.Assertions.*; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @@ -54,6 +56,7 @@ void deleteFile_shouldCallDeleteObject() { // Assert — verify that deleteObject was called once verify(s3Client, times(1)).deleteObject(any(DeleteObjectRequest.class)); } + @Test void downloadFile_shouldCallGetObject() { // Arrange @@ -67,4 +70,37 @@ void downloadFile_shouldCallGetObject() { // Assert — verify that getObject was called once verify(s3Client, times(1)).getObject(any(GetObjectRequest.class)); } + + @Test + void uploadFile_shouldThrowFileStorageConfigurationException_whenS3Fails() { + // Arrange + when(s3Client.putObject(any(PutObjectRequest.class), any(RequestBody.class))) + .thenThrow(S3Exception.builder().message("S3 error").build()); + + // Act & Assert + assertThrows(backendlab.team4you.exceptions.FileStorageConfigurationException.class, + () -> s3Service.uploadFile("test.txt", "Hello".getBytes(), "text/plain")); + } + + @Test + void downloadFile_shouldThrowFileStorageConfigurationException_whenS3Fails() { + // Arrange + when(s3Client.getObject(any(GetObjectRequest.class))) + .thenThrow(S3Exception.builder().message("S3 error").build()); + + // Act & Assert + assertThrows(backendlab.team4you.exceptions.FileStorageConfigurationException.class, + () -> s3Service.downloadFile("test.txt")); + } + + @Test + void deleteFile_shouldThrowFileStorageConfigurationException_whenS3Fails() { + // Arrange + when(s3Client.deleteObject(any(DeleteObjectRequest.class))) + .thenThrow(S3Exception.builder().message("S3 error").build()); + + // Act & Assert + assertThrows(backendlab.team4you.exceptions.FileStorageConfigurationException.class, + () -> s3Service.deleteFile("test.txt")); + } } \ No newline at end of file