Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,19 @@ public class DocumentController {
private final IncidentService incidentService;
private final MinioService minioService;

private AppUser getUser(CustomUserDetails userDetails) {
if (userDetails == null) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
}
return userDetails.getUser();
}

@GetMapping("/{fileKey}")
public ResponseEntity<Resource> getFile(@PathVariable String fileKey,
@AuthenticationPrincipal CustomUserDetails userDetails) {

log.info("GET /documents/{} - Fetching file", fileKey);
AppUser user = userDetails.getUser();
AppUser user = getUser(userDetails);

Document document = documentService.getByFileKey(fileKey)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
Expand All @@ -66,7 +73,7 @@ public ResponseEntity<List<DocumentDTO>> uploadFile(@PathVariable Long incidentI
@RequestParam("files") List<MultipartFile> files, @AuthenticationPrincipal CustomUserDetails userDetails) {

log.info("POST /documents/upload/{} - Uploading {} files", incidentId, files.size());
AppUser user = userDetails.getUser();
AppUser user = getUser(userDetails);
Incident incident = incidentService.getById(incidentId, user);
List<DocumentDTO> uploadedDocs = new ArrayList<>();

Expand All @@ -93,7 +100,7 @@ public ResponseEntity<Void> deleteFile(@PathVariable Long documentId,
@AuthenticationPrincipal CustomUserDetails userDetails) {

log.info("DELETE /documents/{} - Deleting file", documentId);
AppUser user = userDetails.getUser();
AppUser user = getUser(userDetails);

Document document = documentService.getById(documentId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package org.example.team6backend.document.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import org.example.team6backend.incident.entity.Incident;

@Entity
@Getter
@Setter
public class Document {

@Id
Expand All @@ -22,52 +26,4 @@ public class Document {
@ManyToOne
@JoinColumn(name = "incident_id")
private Incident incident;

public Long getId() {
return id;
}

public String getFileName() {
return fileName;
}

public String getContentType() {
return contentType;
}

public String getFileKey() {
return fileKey;
}

public Long getFileSize() {
return fileSize;
}

public Incident getIncident() {
return incident;
}

public void setId(Long id) {
this.id = id;
}

public void setFileName(String fileName) {
this.fileName = fileName;
}

public void setContentType(String contentType) {
this.contentType = contentType;
}

public void setFileKey(String fileKey) {
this.fileKey = fileKey;
}

public void setFileSize(Long fileSize) {
this.fileSize = fileSize;
}

public void setIncident(Incident incident) {
this.incident = incident;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public Document uploadFile(MultipartFile file, Incident incident, AppUser user)
log.warn("Failed to cleanup Minio file: {}", fileKey, cleanupEx);
}
}
throw new RuntimeException("File upload failed", e);
throw new FileUploadException("File upload failed", e);
}
}

Expand Down Expand Up @@ -130,4 +130,9 @@ public Optional<Document> getById(Long documentId) {
public List<Document> getDocumentsByIncidentId(Long incidentId) {
return documentRepository.findByIncidentId(incidentId);
}
public static class FileUploadException extends RuntimeException {
public FileUploadException(String message, Throwable cause) {
super(message, cause);
}
}
Comment thread
SandraNelj marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
import io.minio.errors.ErrorResponseException;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.server.ResponseStatusException;

import java.io.InputStream;

@Slf4j
@Service
@RequiredArgsConstructor
public class MinioService {
Expand All @@ -30,7 +30,7 @@ public void init() {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
} catch (Exception e) {
throw new RuntimeException("Could not initialize minio service", e);
throw new MinioOperationException("Could not initialize minio service", e);
}
}

Expand All @@ -40,7 +40,7 @@ public void uploadFile(String fileKey, MultipartFile file) {
minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(fileKey)
.stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build());
} catch (Exception e) {
throw new RuntimeException("Failed to upload file " + fileKey, e);
throw new MinioOperationException("Failed to upload file " + fileKey, e);
}
}

Expand All @@ -52,9 +52,9 @@ public InputStream downloadFile(String fileKey) {
if ("NoSuchKey".equals(e.errorResponse().code())) {
throw new FileMissingException("File not found: " + fileKey, e);
}
throw new RuntimeException("Failed to download file: " + fileKey, e);
throw new MinioOperationException("Failed to download file: " + fileKey, e);
} catch (Exception e) {
throw new RuntimeException("Failed to download file: " + fileKey, e);
throw new MinioOperationException("Failed to download file: " + fileKey, e);
}
}

Expand All @@ -63,19 +63,18 @@ public void deleteFile(String fileKey) {
try {
minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(fileKey).build());
} catch (Exception e) {
System.err.println("MinIO delete failed for " + fileKey + ": " + e.getMessage());
log.warn("MinIO delete failed for {}: {}", fileKey, e.getMessage());
}
}

public InputStream getFile(String fileKey) {
try {
return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileKey).build());
} catch (Exception e) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "File not found: " + fileKey);
public static class FileMissingException extends RuntimeException {
public FileMissingException(String message, Throwable cause) {
super(message, cause);
}
}
public class FileMissingException extends RuntimeException {
public FileMissingException(String message, Throwable cause) {

public static class MinioOperationException extends RuntimeException {
public MinioOperationException(String message, Throwable cause) {
super(message, cause);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.example.team6backend.exception;

import org.example.team6backend.document.service.DocumentService;
import org.example.team6backend.document.service.MinioService;
import org.springframework.security.core.AuthenticationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
Expand Down Expand Up @@ -43,7 +45,19 @@ public ResponseEntity<ErrorResponse> handleIllegalState(IllegalStateException ex
public ResponseEntity<ErrorResponse> handleAuthentication(AuthenticationException ex) {
return new ResponseEntity<>(new ErrorResponse(401, "Unauthorized!", Instant.now()), HttpStatus.UNAUTHORIZED);
}
@ExceptionHandler(DocumentService.FileUploadException.class)
public ResponseEntity<ErrorResponse> handleFileUpload(DocumentService.FileUploadException ex) {
ErrorResponse error = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), ex.getMessage(),
Instant.now());
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}

@ExceptionHandler(MinioService.MinioOperationException.class)
public ResponseEntity<ErrorResponse> handleMinioOperation(MinioService.MinioOperationException ex) {
ErrorResponse error = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), ex.getMessage(),
Instant.now());
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGeneral(Exception ex) {
ex.printStackTrace();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ void deleteFile_shouldIgnoreMinioFail() {
document.setFileKey("abc");

doThrow(new RuntimeException("MinIO Failure")).when(minioService).deleteFile(anyString());
System.out.println(minioService.getClass());

doNothing().when(auditLogService).log(anyString(), anyString(), any(AppUser.class), anyString(), anyString());

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
package org.example.team6backend.document.service;

import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.multipart.MultipartFile;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.verify;

@ExtendWith(MockitoExtension.class)
Expand All @@ -28,4 +35,20 @@ void deleteFile_shouldCallMinioClient() throws Exception {
verify(minioClient).removeObject(any(RemoveObjectArgs.class));
}

@Test
void deleteFile_shouldLogWarning_whenMinioFails() throws Exception {
ReflectionTestUtils.setField(minioService, "bucketName", "test");
doThrow(new RuntimeException("MinIO down")).when(minioClient).removeObject(any(RemoveObjectArgs.class));
assertDoesNotThrow(() -> minioService.deleteFile("abc"));
}

@Test
void uploadFile_shouldThrowMinioOperationException_whenMinioFails() throws Exception {
ReflectionTestUtils.setField(minioService, "bucketName", "test");
doThrow(new RuntimeException("MinIO down")).when(minioClient).putObject(any(PutObjectArgs.class));

MultipartFile file = new MockMultipartFile("file", "test.txt", "text/plain", "test".getBytes());

assertThrows(MinioService.MinioOperationException.class, () -> minioService.uploadFile("key", file));
}
}
Loading