View Javadoc
1   /*
2    * Copyright (C) 2024 B3Partners B.V.
3    *
4    * SPDX-License-Identifier: MIT
5    */
6   
7   package org.tailormap.api.controller;
8   
9   import static org.springframework.http.HttpStatus.BAD_REQUEST;
10  import static org.springframework.http.HttpStatus.NOT_FOUND;
11  import static org.springframework.http.HttpStatus.NOT_MODIFIED;
12  import static org.tailormap.api.service.UploadsService.createUploadResponseEntity;
13  
14  import jakarta.servlet.http.HttpServletRequest;
15  import java.util.UUID;
16  import org.springframework.http.CacheControl;
17  import org.springframework.http.ResponseEntity;
18  import org.springframework.web.bind.annotation.GetMapping;
19  import org.springframework.web.bind.annotation.PathVariable;
20  import org.springframework.web.bind.annotation.RestController;
21  import org.springframework.web.server.ResponseStatusException;
22  import org.tailormap.api.persistence.Upload;
23  import org.tailormap.api.persistence.UploadCategory;
24  import org.tailormap.api.repository.UploadRepository;
25  import org.tailormap.api.service.UploadsService;
26  
27  @RestController
28  public class UploadsController {
29    private final UploadRepository uploadRepository;
30    private final UploadsService uploadsService;
31  
32    public UploadsController(UploadRepository uploadRepository, UploadsService uploadsService) {
33      this.uploadRepository = uploadRepository;
34      this.uploadsService = uploadsService;
35    }
36  
37    @GetMapping(
38        path = {
39          // Can't use ${tailormap-api.base-path} because linkTo() used in UploadHelper#getUrlForImage() won't
40          // work
41          "/api/uploads/{category}/{id}",
42          "/api/uploads/{category}/{id}/{filename}"
43        })
44    public ResponseEntity<byte[]> getUpload(
45        HttpServletRequest request,
46        @PathVariable UploadCategory category,
47        @PathVariable(name = "id") UUID id,
48        @PathVariable(required = false) String filename) {
49  
50      if (category.isRestricted()) {
51        throw new ResponseStatusException(
52            BAD_REQUEST, "Uploads for category " + category + " are not accessible via this endpoint");
53      }
54  
55      long ifModifiedSince = request.getDateHeader("If-Modified-Since");
56  
57      if (!uploadsService.checkIfModifiedSince(id, ifModifiedSince)) {
58        return ResponseEntity.status(NOT_MODIFIED).build();
59      }
60  
61      Upload upload = uploadRepository
62          .findWithContentByIdAndCategory(id, category)
63          .orElseThrow(() -> new ResponseStatusException(NOT_FOUND));
64      return createUploadResponseEntity(upload);
65    }
66  
67    /**
68     * Gets the latest upload for a specific category, if any. This is most useful for
69     * {@code Upload.CATEGORY_DRAWING_STYLE} .
70     */
71    @GetMapping("/api/uploads/{category}/latest")
72    public ResponseEntity<byte[]> getLatestUpload(@PathVariable UploadCategory category) {
73      if (category.isRestricted()) {
74        throw new ResponseStatusException(
75            BAD_REQUEST, "Uploads for category " + category + " are not accessible via this endpoint");
76      }
77      return uploadRepository
78          .findFirstWithContentByCategoryOrderByLastModifiedDesc(category)
79          .map(upload -> ResponseEntity.ok()
80              .header("Content-Type", upload.getMimeType())
81              .header(UploadsService.DESCRIPTION_HEADER_NAME, upload.getDescription())
82              .lastModified(upload.getLastModified().toInstant())
83              .contentLength(upload.getContentLength())
84              .cacheControl(CacheControl.noCache().cachePublic())
85              .body(upload.getContent()))
86          .orElseThrow(() -> new ResponseStatusException(NOT_FOUND));
87    }
88  }