Use yada+bidi instead of compojure-api
This commit is contained in:
parent
d343d89dea
commit
3424505587
@ -2,11 +2,7 @@
|
||||
{:file-store #var asciinema.component.local-file-store/local-file-store
|
||||
:exp-set #var asciinema.component.mem-expiring-set/mem-expiring-set}
|
||||
:config
|
||||
{:app
|
||||
{:middleware
|
||||
{:functions {:stacktrace #var ring.middleware.stacktrace/wrap-stacktrace}
|
||||
:applied ^:replace [:not-found :webjars :ring-defaults :route-aliases :ring-logger :stacktrace]}}
|
||||
:http
|
||||
{:http
|
||||
{:port 4000}
|
||||
:db
|
||||
{:uri "jdbc:postgresql://localhost:15432/asciinema_development?user=vagrant"}
|
||||
|
12
project.clj
12
project.clj
@ -4,25 +4,19 @@
|
||||
:min-lein-version "2.0.0"
|
||||
:dependencies [[org.clojure/clojure "1.8.0"]
|
||||
[com.stuartsierra/component "0.3.1"]
|
||||
[metosin/ring-http-response "0.8.0"]
|
||||
[clj-time "0.13.0"]
|
||||
[duct "0.8.2"]
|
||||
[compojure "1.5.1"]
|
||||
[metosin/compojure-api "1.1.10"]
|
||||
[yada "1.2.0"]
|
||||
[aleph "0.4.1"]
|
||||
[bidi "2.0.12"]
|
||||
[prismatic/schema "1.1.3"]
|
||||
[environ "1.1.0"]
|
||||
[ring "1.5.0"]
|
||||
[ring/ring-defaults "0.2.1"]
|
||||
[ring-jetty-component "0.3.1"]
|
||||
[ring-webjars "0.1.1"]
|
||||
[ring-logger-timbre "0.7.5"]
|
||||
[clj-bugsnag "0.2.9"]
|
||||
[clj-aws-s3 "0.3.10" :exclusions [joda-time com.fasterxml.jackson.core/jackson-core com.fasterxml.jackson.core/jackson-annotations]]
|
||||
[aleph "0.4.1"]
|
||||
[pandect "0.6.1"]
|
||||
[com.taoensso/carmine "2.15.1"]
|
||||
[org.slf4j/slf4j-nop "1.7.21"]
|
||||
[org.webjars/normalize.css "3.0.2"]
|
||||
[duct/hikaricp-component "0.1.0"]
|
||||
[org.postgresql/postgresql "9.4.1211"]
|
||||
[duct/ragtime-component "0.1.4"]]
|
||||
|
@ -1,6 +1,5 @@
|
||||
{:components
|
||||
{:app #var duct.component.handler/handler-component
|
||||
:http #var asciinema.component.aleph/aleph-server
|
||||
{:http #var asciinema.component.yada-listener/yada-listener
|
||||
:db #var asciinema.component.db/hikaricp
|
||||
:ragtime #var duct.component.ragtime/ragtime
|
||||
:file-store #var asciinema.component.s3-file-store/s3-file-store
|
||||
@ -9,50 +8,11 @@
|
||||
:endpoints
|
||||
{:asciicasts #var asciinema.endpoint.asciicasts/asciicasts-endpoint}
|
||||
:dependencies
|
||||
{:http [:app]
|
||||
:app [:asciicasts]
|
||||
{:http {:app :asciicasts}
|
||||
:ragtime [:db]
|
||||
:asciicasts [:db :file-store :exp-set :executor]}
|
||||
:config
|
||||
{:app
|
||||
{:middleware
|
||||
{:functions
|
||||
{:hide-errors #var duct.middleware.errors/wrap-hide-errors
|
||||
:not-found #var duct.middleware.not-found/wrap-not-found
|
||||
:ring-defaults #var ring.middleware.defaults/wrap-defaults
|
||||
:route-aliases #var duct.middleware.route-aliases/wrap-route-aliases
|
||||
:ring-logger #var ring.logger.timbre/wrap-with-logger
|
||||
:bugsnag #var clj-bugsnag.ring/wrap-bugsnag
|
||||
:webjars #var ring.middleware.webjars/wrap-webjars}
|
||||
:applied
|
||||
[:not-found :webjars :ring-defaults :route-aliases :ring-logger :bugsnag :hide-errors]
|
||||
:arguments
|
||||
{:not-found #resource "asciinema/errors/404.html"
|
||||
:hide-errors #resource "asciinema/errors/500.html"
|
||||
:bugsnag
|
||||
{:api-key bugsnag-key
|
||||
:environment env-name
|
||||
:version git-sha
|
||||
:project-ns "asciinema"}
|
||||
:route-aliases {"/" "/index.html"}
|
||||
:ring-defaults
|
||||
{:params {:urlencoded true
|
||||
:keywordize true
|
||||
:multipart true
|
||||
:nested true}
|
||||
:cookies true
|
||||
:session {:flash true
|
||||
:cookie-attrs {:http-only true}}
|
||||
:security {:anti-forgery true
|
||||
:xss-protection {:enable? true, :mode :block}
|
||||
:frame-options :sameorigin
|
||||
:content-type-options :nosniff}
|
||||
:static {:resources "asciinema/public"}
|
||||
:responses {:not-modified-responses true
|
||||
:absolute-redirects true
|
||||
:content-types true
|
||||
:default-charset "utf-8"}}}}}
|
||||
:http
|
||||
{:http
|
||||
{:port http-port}
|
||||
:db
|
||||
{:uri db-uri}
|
||||
|
@ -5,4 +5,4 @@
|
||||
(input-stream [this path])
|
||||
(move-file [this old-path new-path])
|
||||
(delete-file [this path])
|
||||
(serve-file [this path opts]))
|
||||
(serve-file [this ctx path opts]))
|
||||
|
@ -1,17 +0,0 @@
|
||||
(ns asciinema.component.aleph
|
||||
(:require [com.stuartsierra.component :as component]
|
||||
[aleph.http :refer [start-server]]))
|
||||
|
||||
(defrecord WebServer [port server app]
|
||||
component/Lifecycle
|
||||
(start [component]
|
||||
(let [handler (:handler app)
|
||||
server (start-server handler {:port port :join? false})]
|
||||
(assoc component :server server)))
|
||||
(stop [component]
|
||||
(when server
|
||||
(.close server)
|
||||
component)))
|
||||
|
||||
(defn aleph-server [{:keys [port app]}]
|
||||
(map->WebServer {:port port :app app}))
|
@ -21,7 +21,7 @@
|
||||
(.execute executor f)
|
||||
result)
|
||||
(catch RejectedExecutionException _
|
||||
{:status 503 :headers {"Retry-After" "5"} :body "<h1>503</h1>"})))
|
||||
nil)))
|
||||
|
||||
component/Lifecycle
|
||||
(start [{:keys [threads queue-length] :as component}]
|
||||
|
@ -27,11 +27,12 @@
|
||||
(let [path (str base-path path)]
|
||||
(io/delete-file path)))
|
||||
|
||||
(serve-file [this path {:keys [filename]}]
|
||||
(let [resp (response/ok (file-store/input-stream this path))]
|
||||
(serve-file [this ctx path {:keys [filename]}]
|
||||
(let [path (str base-path path)
|
||||
response (assoc (:response ctx) :body (io/file path))]
|
||||
(if filename
|
||||
(response/header resp "Content-Disposition" (str "attachment; filename=" filename))
|
||||
resp))))
|
||||
(update response :headers assoc "content-disposition" (str "attachment; filename=" filename))
|
||||
response))))
|
||||
|
||||
(defn local-file-store [{:keys [path]}]
|
||||
(->LocalFileStore path))
|
||||
|
@ -50,9 +50,12 @@
|
||||
(let [path (str path-prefix path)]
|
||||
(s3/delete-object cred bucket path)))
|
||||
|
||||
(serve-file [this path opts]
|
||||
(let [path (str path-prefix path)]
|
||||
(response/found (generate-presigned-url cred bucket path opts)))))
|
||||
(serve-file [this ctx path opts]
|
||||
(let [path (str path-prefix path)
|
||||
url (generate-presigned-url cred bucket path opts)]
|
||||
(-> (:response ctx)
|
||||
(assoc :status 302)
|
||||
(update :headers assoc "location" url)))))
|
||||
|
||||
(defn s3-file-store [{:keys [cred bucket path-prefix]}]
|
||||
(->S3FileStore cred bucket path-prefix))
|
||||
|
21
src/asciinema/component/yada_listener.clj
Normal file
21
src/asciinema/component/yada_listener.clj
Normal file
@ -0,0 +1,21 @@
|
||||
(ns asciinema.component.yada-listener
|
||||
(:require [com.stuartsierra.component :as component]
|
||||
[yada.yada :as yada]))
|
||||
|
||||
(defrecord YadaListener [port server app]
|
||||
component/Lifecycle
|
||||
(start [component]
|
||||
(if server
|
||||
component
|
||||
(let [handler (:routes app)
|
||||
server (yada/listener handler {:port port})]
|
||||
(assoc component :server server))))
|
||||
(stop [component]
|
||||
(if server
|
||||
(do
|
||||
((:close server))
|
||||
(assoc component :server nil))
|
||||
component)))
|
||||
|
||||
(defn yada-listener [{:keys [port app]}]
|
||||
(map->YadaListener {:port port :app app}))
|
@ -11,16 +11,15 @@
|
||||
[clojure.java
|
||||
[io :as io]
|
||||
[shell :as shell]]
|
||||
[clojure.string :as str]
|
||||
[compojure.api.sweet :refer :all]
|
||||
[environ.core :refer [env]]
|
||||
[ring.util.http-response :as response]
|
||||
[schema.core :as s]))
|
||||
[schema.core :as s]
|
||||
[yada.yada :as yada]))
|
||||
|
||||
(defn exception-handler [^Exception e data request]
|
||||
(throw e))
|
||||
(def Theme (apply s/enum asciicast/themes))
|
||||
|
||||
(defn a2png [in-url out-path {:keys [snapshot-at theme scale]}]
|
||||
(def png-ttl-days 7)
|
||||
|
||||
(defn- a2png [in-url out-path {:keys [snapshot-at theme scale]}]
|
||||
(let [a2png-bin (:a2png-bin env "a2png/a2png.sh")
|
||||
{:keys [exit] :as result} (shell/sh a2png-bin
|
||||
"-t" theme
|
||||
@ -31,53 +30,74 @@
|
||||
(when-not (zero? exit)
|
||||
(throw (ex-info "a2png error" result)))))
|
||||
|
||||
(def Num (s/if #(str/includes? % ".")
|
||||
Double
|
||||
s/Int))
|
||||
(defn- generate-png [file-store exp-set asciicast png-params png-store-path]
|
||||
(with-tmp-dir [dir "asciinema-png-"]
|
||||
(let [json-store-path (asciicast/json-store-path asciicast)
|
||||
json-local-path (str dir "/asciicast.json")
|
||||
png-local-path (str dir "/asciicast.png")
|
||||
expires (-> png-ttl-days t/days t/from-now)]
|
||||
(with-open [in (fstore/input-stream file-store json-store-path)]
|
||||
(let [out (io/file json-local-path)]
|
||||
(io/copy in out)))
|
||||
(a2png json-local-path png-local-path png-params)
|
||||
(fstore/put-file file-store (io/file png-local-path) png-store-path)
|
||||
(exp-set/conj! exp-set png-store-path expires))))
|
||||
|
||||
(def Theme (apply s/enum asciicast/themes))
|
||||
(defn- service-unavailable-response [ctx]
|
||||
(-> (:response ctx)
|
||||
(assoc :status 503)
|
||||
(update :headers assoc "retry-after" "5")))
|
||||
|
||||
(def png-ttl-days 7)
|
||||
(defn- async-response [ctx executor f]
|
||||
(or (executor/execute executor f)
|
||||
(service-unavailable-response ctx)))
|
||||
|
||||
(defn asciicast-json-resource [db file-store]
|
||||
(yada/resource {:produces "application/json"
|
||||
:parameters {:path {:token String}
|
||||
:query {(s/optional-key :dl) s/Bool}}
|
||||
:properties (fn [ctx]
|
||||
(if-let [asciicast (adb/get-asciicast-by-token db (-> ctx :parameters :path :token))]
|
||||
{:exists? true
|
||||
::asciicast asciicast}
|
||||
{:exists? false}))
|
||||
:response (fn [ctx]
|
||||
(let [asciicast (-> ctx :properties ::asciicast)
|
||||
dl (-> ctx :parameters :query :dl)
|
||||
path (asciicast/json-store-path asciicast)
|
||||
filename (str "asciicast-" (:id asciicast) ".json")]
|
||||
(fstore/serve-file file-store ctx path (when dl {:filename filename}))))}))
|
||||
|
||||
(defn asciicast-png-resource [db file-store exp-set executor]
|
||||
(yada/resource {:produces "image/png"
|
||||
:parameters {:path {:token String}
|
||||
:query {(s/optional-key :time) s/Num
|
||||
(s/optional-key :theme) Theme
|
||||
(s/optional-key :scale) (s/enum "1" "2")}}
|
||||
:properties (fn [ctx]
|
||||
(if-let [asciicast (adb/get-asciicast-by-token db (-> ctx :parameters :path :token))]
|
||||
(let [user (udb/get-user-by-id db (:user_id asciicast))
|
||||
{:keys [time theme scale]} (-> ctx :parameters :query)
|
||||
png-params (cond-> (asciicast/png-params asciicast user)
|
||||
time (assoc :snapshot-at time)
|
||||
theme (assoc :theme theme)
|
||||
scale (assoc :scale (Integer/parseInt scale)))]
|
||||
{:exists? true
|
||||
:version (asciicast/png-version asciicast png-params)
|
||||
::asciicast asciicast
|
||||
::png-params png-params})
|
||||
{:exists? false}))
|
||||
:response (fn [ctx]
|
||||
(let [asciicast (-> ctx :properties ::asciicast)
|
||||
png-params (-> ctx :properties ::png-params)
|
||||
png-store-path (asciicast/png-store-path asciicast png-params)]
|
||||
(if (exp-set/contains? exp-set png-store-path)
|
||||
(fstore/serve-file file-store ctx png-store-path {})
|
||||
(async-response ctx executor (fn []
|
||||
(generate-png file-store exp-set asciicast png-params png-store-path)
|
||||
(fstore/serve-file file-store ctx png-store-path {}))))))}))
|
||||
|
||||
(defn asciicasts-endpoint [{:keys [db file-store exp-set executor]}]
|
||||
(api
|
||||
{:exceptions {:handlers {:compojure.api.exception/default exception-handler}}}
|
||||
(context
|
||||
"/a" []
|
||||
(GET "/:token.json" []
|
||||
:path-params [token :- String]
|
||||
:query-params [{dl :- s/Bool false}]
|
||||
(if-let [asciicast (adb/get-asciicast-by-token db token)]
|
||||
(let [path (asciicast/json-store-path asciicast)
|
||||
filename (str "asciicast-" (:id asciicast) ".json")]
|
||||
(fstore/serve-file file-store path (when dl {:filename filename})))
|
||||
(response/not-found)))
|
||||
|
||||
(GET "/:token.png" []
|
||||
:path-params [token :- String]
|
||||
:query-params [{time :- Num nil}
|
||||
{theme :- Theme nil}
|
||||
{scale :- (s/enum "1" "2") nil}]
|
||||
(if-let [asciicast (adb/get-asciicast-by-token db token)]
|
||||
(let [user (udb/get-user-by-id db (:user_id asciicast))
|
||||
png-params (cond-> (asciicast/png-params asciicast user)
|
||||
time (assoc :snapshot-at time)
|
||||
theme (assoc :theme theme)
|
||||
scale (assoc :scale (Integer/parseInt scale)))
|
||||
png-store-path (asciicast/png-store-path asciicast png-params)]
|
||||
(if (exp-set/contains? exp-set png-store-path)
|
||||
(fstore/serve-file file-store png-store-path {})
|
||||
(executor/execute executor (fn []
|
||||
(with-tmp-dir [dir "asciinema-png-"]
|
||||
(let [json-store-path (asciicast/json-store-path asciicast)
|
||||
json-local-path (str dir "/asciicast.json")
|
||||
png-local-path (str dir "/asciicast.png")
|
||||
expires (-> png-ttl-days t/days t/from-now)]
|
||||
(with-open [in (fstore/input-stream file-store json-store-path)]
|
||||
(let [out (io/file json-local-path)]
|
||||
(io/copy in out)))
|
||||
(a2png json-local-path png-local-path png-params)
|
||||
(fstore/put-file file-store (io/file png-local-path) png-store-path)
|
||||
(exp-set/conj! exp-set png-store-path expires)))
|
||||
(fstore/serve-file file-store png-store-path {})))))
|
||||
(response/not-found))))))
|
||||
["" [["/a/" [[[:token ".json"] (asciicast-json-resource db file-store)]
|
||||
[[:token ".png"] (asciicast-png-resource db file-store exp-set executor)]]]
|
||||
[true (yada/as-resource nil)]]])
|
||||
|
@ -25,7 +25,7 @@
|
||||
:theme (theme-name asciicast user)
|
||||
:scale default-png-scale})
|
||||
|
||||
(defn- png-version [asciicast params]
|
||||
(defn png-version [asciicast params]
|
||||
(let [attrs (assoc params :id (:id asciicast))]
|
||||
(->> attrs
|
||||
(map (fn [[k v]] (str (name k) "=" v)))
|
||||
|
Loading…
Reference in New Issue
Block a user