penpot/backend/src/app/http/assets.clj

107 lines
3.4 KiB
Clojure

;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.http.assets
"Assets related handlers."
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.uri :as u]
[app.db :as db]
[app.storage :as sto]
[app.util.time :as dt]
[integrant.core :as ig]
[yetti.response :as-alias yres]))
(def ^:private cache-max-age
(dt/duration {:hours 24}))
(def ^:private signature-max-age
(dt/duration {:hours 24 :minutes 15}))
(defn get-id
[{:keys [path-params]}]
(or (some-> path-params :id d/parse-uuid)
(ex/raise :type :not-found
:hunt "object not found")))
(defn- get-file-media-object
[pool id]
(db/get pool :file-media-object {:id id}))
(defn- serve-object-from-s3
[{:keys [::sto/storage] :as cfg} obj]
(let [{:keys [host port] :as url} (sto/get-object-url storage obj {:max-age signature-max-age})]
{::yres/status 307
::yres/headers {"location" (str url)
"x-host" (cond-> host port (str ":" port))
"x-mtype" (-> obj meta :content-type)
"cache-control" (str "max-age=" (inst-ms cache-max-age))}}))
(defn- serve-object-from-fs
[{:keys [::path]} obj]
(let [purl (u/join (u/uri path)
(sto/object->relative-path obj))
mdata (meta obj)
headers {"x-accel-redirect" (:path purl)
"content-type" (:content-type mdata)
"cache-control" (str "max-age=" (inst-ms cache-max-age))}]
{::yres/status 204
::yres/headers headers}))
(defn- serve-object
"Helper function that returns the appropriate response depending on
the storage object backend type."
[cfg {:keys [backend] :as obj}]
(case backend
(:s3 :assets-s3) (serve-object-from-s3 cfg obj)
(:fs :assets-fs) (serve-object-from-fs cfg obj)))
(defn objects-handler
"Handler that servers storage objects by id."
[{:keys [::sto/storage] :as cfg} request]
(let [id (get-id request)
obj (sto/get-object storage id)]
(if obj
(serve-object cfg obj)
{::yres/status 404})))
(defn- generic-handler
"A generic handler helper/common code for file-media based handlers."
[{:keys [::sto/storage] :as cfg} request kf]
(let [pool (::db/pool storage)
id (get-id request)
mobj (get-file-media-object pool id)
sobj (sto/get-object storage (kf mobj))]
(if sobj
(serve-object cfg sobj)
{::yres/status 404})))
(defn file-objects-handler
"Handler that serves storage objects by file media id."
[cfg request]
(generic-handler cfg request :media-id))
(defn file-thumbnails-handler
"Handler that serves storage objects by thumbnail-id and quick
fallback to file-media-id if no thumbnail is available."
[cfg request]
(generic-handler cfg request #(or (:thumbnail-id %) (:media-id %))))
;; --- Initialization
(defmethod ig/assert-key ::routes
[_ params]
(assert (sto/valid-storage? (::sto/storage params)) "expected valid storage instance")
(assert (string? (::path params))))
(defmethod ig/init-key ::routes
[_ cfg]
["/assets"
["/by-id/:id" {:handler (partial objects-handler cfg)}]
["/by-file-media-id/:id" {:handler (partial file-objects-handler cfg)}]
["/by-file-media-id/:id/thumbnail" {:handler (partial file-thumbnails-handler cfg)}]])