TraceAttachmentController.java

package fr.avenirsesr.portfolio.file.application.adapter.controller;

import fr.avenirsesr.portfolio.file.application.adapter.dto.AttachmentUploadDTO;
import fr.avenirsesr.portfolio.file.application.adapter.mapper.AttachmentUploadDTOMapper;
import fr.avenirsesr.portfolio.file.domain.exception.FileStorageException;
import fr.avenirsesr.portfolio.file.domain.port.input.TraceAttachmentService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import jakarta.validation.Valid;
import java.io.IOException;
import java.security.Principal;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

@Slf4j
@AllArgsConstructor
@RestController
@RequestMapping("/me/storage/traces")
public class TraceAttachmentController {
  private final TraceAttachmentService service;
  private final AttachmentUploadDTOMapper attachmentUploadDTOMapper;

  @PostMapping(value = "/{traceId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
  public ResponseEntity<AttachmentUploadDTO> uploadAttachment(
      Principal principal,
      @Valid @PathVariable UUID traceId,
      @RequestParam("file") MultipartFile file) {
    log.debug(
        "Received attachment upload request from user [{}] for trace [{}]",
        principal.getName(),
        traceId);
    try {
      var attachment =
          service.uploadTraceAttachment(
              traceId,
              file.getOriginalFilename(),
              file.getContentType(),
              file.getSize(),
              file.getBytes());
      return ResponseEntity.status(HttpStatus.CREATED)
          .body(attachmentUploadDTOMapper.fromDomain(attachment));
    } catch (IOException e) {
      throw new FileStorageException("Failed to read uploaded file", e);
    }
  }

  @GetMapping("/attachments/{attachmentId}")
  @Operation(
      summary = "Download an attachment",
      description =
          "Retrieves the binary content of a specific attachment associated with a trace.",
      responses = {
        @ApiResponse(
            responseCode = "200",
            description = "File downloaded successfully",
            content =
                @Content(
                    mediaType = "application/octet-stream",
                    schema = @Schema(type = "string", format = "binary"))),
        @ApiResponse(responseCode = "404", description = "Attachment not found")
      })
  public ResponseEntity<byte[]> downloadAttachment(@Valid @PathVariable UUID attachmentId) {
    log.debug("Received attachment download request for attachment [{}]", attachmentId);
    var attachment = service.downloadTraceAttachment(attachmentId);
    return ResponseEntity.ok()
        .contentType(MediaType.APPLICATION_OCTET_STREAM)
        .header(
            HttpHeaders.CONTENT_DISPOSITION,
            "attachment; filename=\"" + attachment.fileName() + "\"")
        .body(attachment.content());
  }
}