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