;; 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)}]])