DocumentUploadController.java

package com.tdmconsult.ete.api;

import com.tdmconsult.ete.configuration.CustomJwtAuthenticationToken.CustomUserPrincipal;
import com.tdmconsult.ete.documents.DocumentAuthorizationService;
import com.tdmconsult.ete.documents.DocumentCreationRequestDto;
import com.tdmconsult.ete.documents.DocumentDto;
import com.tdmconsult.ete.documents.DocumentIndexDto;
import com.tdmconsult.ete.documents.DocumentUpdateRequestDto;
import com.tdmconsult.ete.documents.UploadDocumentService;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import java.io.IOException;
import java.net.URI;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

@Tag(name = "NAme", description = "Description")
@RestController
@RequestMapping(value = "/api/documents", produces = MediaType.APPLICATION_JSON_VALUE)
@RequiredArgsConstructor
public class DocumentUploadController {

    private final UploadDocumentService uploadDocumentService;
    private final DocumentAuthorizationService documentAuthorizationService;

    @PreAuthorize("@documentAuthorizationService.isAllowedToCreateDocument(principal)")
    @PostMapping(consumes = "multipart/form-data")
    public ResponseEntity<Void> createUploadDocument(
            final @Valid DocumentCreationRequestDto requestDto,
            final @AuthenticationPrincipal CustomUserPrincipal userDetails) throws IOException {

        final var publicId = uploadDocumentService.createUploadDocument(requestDto, userDetails);
        return ResponseEntity.created(URI.create("/api/documents/" + publicId)).build();
    }

    @PreAuthorize("@documentAuthorizationService.isAllowedToListDocuments(principal)")
    @GetMapping
    public ResponseEntity<List<DocumentIndexDto>> getUploadDocumentIndex(
            final @AuthenticationPrincipal CustomUserPrincipal userDetails) throws IOException {

        final var index = uploadDocumentService.getUploadDocumentIndex(userDetails);
        return ResponseEntity.ok(index);
    }

    @PreAuthorize("@documentAuthorizationService.isAllowedToShowDocument(#publicId, principal)")
    @GetMapping("{publicId}")
    public ResponseEntity<DocumentDto> getUploadDocument(
            final @PathVariable String publicId,
            final @RequestParam(required = false) Boolean excludeContent) {

        final var document = uploadDocumentService.getUploadDocument(publicId, excludeContent);
        return ResponseEntity.ok(document);
    }

    @PreAuthorize("@documentAuthorizationService.isAllowedToEditDocument(#publicId, principal)")
    @PutMapping(value = "{publicId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public ResponseEntity<DocumentDto> updateUploadDocument(
            final @PathVariable String publicId,
            final @RequestPart @Valid DocumentUpdateRequestDto requestDto,
            final @RequestParam(required = false) Map<String, MultipartFile> attachmentsByPublicId,
            final @AuthenticationPrincipal CustomUserPrincipal userDetails) throws IOException {

        uploadDocumentService.updateUploadDocument(publicId, requestDto, attachmentsByPublicId, userDetails);
        return ResponseEntity.noContent().build();
    }

    @PreAuthorize("@documentAuthorizationService.isAllowedToEditDocument(#publicId, principal)")
    @DeleteMapping("{publicId}")
    public ResponseEntity<DocumentDto> deleteDocument(
            final @PathVariable String publicId) throws IOException {

        uploadDocumentService.deleteUploadDocument(publicId);
        return ResponseEntity.noContent().build();
    }

    @PreAuthorize("@documentAuthorizationService.isAllowedToShowDocument(#publicId, principal)")
    @GetMapping("{publicId}/attachments/{attachmentPublicId}")
    public ResponseEntity<Resource> downloadAttachment(
            final @PathVariable String publicId,
            final @PathVariable String attachmentPublicId) {

        final var attachment = uploadDocumentService.getAttachment(publicId, attachmentPublicId);
        final var resource = new ByteArrayResource(attachment.fileData());
        final var mediaType = MediaType.parseMediaType(attachment.fileContentType());

        return ResponseEntity.ok()
                // Signallisiert dem Browser, dass es ein Download ist und setzt den Dateinamen
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + attachment.fileName() + "\"")
                // Teilt dem Browser die genaue Dateigröße in Bytes mit
                .contentLength(attachment.fileSize())
                // Reines Binärformat (verhindert, dass der Browser versucht es als JSON zu interpretieren)
                .contentType(mediaType)
                .body(resource);
    }
}