Remove old unused code
This commit is contained in:
parent
9966a27800
commit
18b31747ee
14
Dockerfile
14
Dockerfile
@ -108,15 +108,6 @@ RUN cd a2png && npm install
|
||||
COPY a2png /app/a2png
|
||||
RUN cd a2png && lein cljsbuild once main && lein cljsbuild once page
|
||||
|
||||
# build uberjar
|
||||
|
||||
COPY project.clj /app/
|
||||
RUN lein deps
|
||||
|
||||
COPY src /app/src
|
||||
COPY resources /app/resources
|
||||
RUN lein uberjar
|
||||
|
||||
# service URLs
|
||||
|
||||
ENV DATABASE_URL "postgresql://postgres@postgres/postgres"
|
||||
@ -195,16 +186,11 @@ COPY docker/supervisor/asciinema.conf /etc/supervisor/conf.d/asciinema.conf
|
||||
# add start script for Clojure app
|
||||
|
||||
ENV A2PNG_BIN_PATH "/app/a2png/a2png.sh"
|
||||
COPY docker/start.sh /app/start.sh
|
||||
RUN chmod a+x /app/start.sh
|
||||
|
||||
VOLUME ["/app/log", "/app/uploads", "/cache"]
|
||||
|
||||
CMD ["/usr/bin/supervisord"]
|
||||
# bundle exec rake db:setup
|
||||
# bundle exec sidekiq
|
||||
|
||||
EXPOSE 80
|
||||
EXPOSE 3000
|
||||
EXPOSE 4000
|
||||
EXPOSE 5000
|
||||
|
@ -2,5 +2,4 @@ RAILS_ENV=development
|
||||
MIX_ENV=dev
|
||||
BASE_URL=http://localhost:3000
|
||||
SECRET_KEY_BASE=19c70247f4034dd5ce4f3d6bd3b2b592624b63439d518367de9add564fdee9e6b8513f6cec24c2a933a84ea639136786813bb70d3dc4e84a365205a52e5bf1fa
|
||||
LEIN_ROOT=yes
|
||||
BUNDLE_PATH=/bundle
|
@ -32,8 +32,6 @@ services:
|
||||
ports:
|
||||
- 3000:3000
|
||||
- 4000:4000
|
||||
- 5000:5000
|
||||
- 44444:44444
|
||||
volumes:
|
||||
- ../uploads:/app/uploads:cached
|
||||
- ../deps:/app/deps:cached
|
||||
|
@ -1,6 +0,0 @@
|
||||
{:components
|
||||
{:mem-expiring-set #var asciinema.component.mem-expiring-set/mem-expiring-set}
|
||||
:config
|
||||
{:a2png {:bin-path "a2png/a2png.sh"}}
|
||||
:dependencies
|
||||
{:asciicasts {:exp-set :mem-expiring-set}}}
|
@ -1,33 +0,0 @@
|
||||
(ns dev
|
||||
(:refer-clojure :exclude [test])
|
||||
(:require [clojure.repl :refer :all]
|
||||
[clojure.pprint :refer [pprint]]
|
||||
[clojure.tools.namespace.repl :refer [refresh]]
|
||||
[clojure.java.io :as io]
|
||||
[com.stuartsierra.component :as component]
|
||||
[duct.generate :as gen]
|
||||
[duct.util.repl :refer [setup test cljs-repl migrate rollback]]
|
||||
[duct.util.system :refer [load-system]]
|
||||
[environ.core :refer [env]]
|
||||
[reloaded.repl :refer [system init start stop go reset]]
|
||||
[asciinema.boundary.file-store :as file-store]
|
||||
[asciinema.boundary.asciicast-database :as asciicast-database]
|
||||
[asciinema.component.local-file-store :refer [->LocalFileStore]]
|
||||
[asciinema.component.s3-file-store :refer [->S3FileStore]]))
|
||||
|
||||
(def default-db-uri "jdbc:postgresql://localhost/asciinema_development?user=asciinema")
|
||||
|
||||
(defn new-system []
|
||||
(let [bindings {'http-port (Integer/parseInt (:port env "4000"))
|
||||
'db-uri (:database-url env default-db-uri)
|
||||
's3-bucket (:s3-bucket env)
|
||||
's3-access-key (:s3-access-key env)
|
||||
's3-secret-key (:s3-secret-key env)}]
|
||||
(load-system (keep io/resource ["asciinema/system.edn" "dev.edn" "local.edn"]) bindings)))
|
||||
|
||||
(when (io/resource "local.clj")
|
||||
(load "local"))
|
||||
|
||||
(gen/set-ns-prefix 'asciinema)
|
||||
|
||||
(reloaded.repl/set-init! new-system)
|
@ -1,8 +0,0 @@
|
||||
(ns user)
|
||||
|
||||
(defn dev
|
||||
"Load and switch to the 'dev' namespace."
|
||||
[]
|
||||
(require 'dev)
|
||||
(in-ns 'dev)
|
||||
:loaded)
|
@ -57,7 +57,6 @@ services:
|
||||
env_file: .env.production
|
||||
ports:
|
||||
- "3000:80"
|
||||
- "5000:5000"
|
||||
volumes:
|
||||
- ./uploads:/app/uploads
|
||||
- ./log:/app/log
|
||||
|
@ -6,10 +6,6 @@ upstream phoenix-server {
|
||||
server 127.0.0.1:4000 fail_timeout=0;
|
||||
}
|
||||
|
||||
upstream clj-server {
|
||||
server 127.0.0.1:5000 fail_timeout=0;
|
||||
}
|
||||
|
||||
proxy_cache_path /cache levels=1:2 keys_zone=png_cache:10m max_size=10g
|
||||
inactive=14d use_temp_path=off;
|
||||
|
||||
@ -77,13 +73,4 @@ server {
|
||||
proxy_pass http://phoenix-server;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
location @clj {
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_pass http://clj-server;
|
||||
proxy_redirect off;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
export S3_ACCESS_KEY=${AWS_ACCESS_KEY_ID}
|
||||
export S3_SECRET_KEY=${AWS_SECRET_ACCESS_KEY}
|
||||
|
||||
exec java -server -jar /app/target/uberjar/asciinema-0.1.0-SNAPSHOT-standalone.jar
|
@ -24,12 +24,3 @@ stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
|
||||
[program:clj]
|
||||
command=/app/start.sh
|
||||
directory=/app
|
||||
environment=PORT=5000
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
48
project.clj
48
project.clj
@ -1,48 +0,0 @@
|
||||
(defproject asciinema "0.1.0-SNAPSHOT"
|
||||
:description "FIXME: write description"
|
||||
:url "http://example.com/FIXME"
|
||||
:min-lein-version "2.0.0"
|
||||
:dependencies [[org.clojure/clojure "1.8.0"]
|
||||
[com.stuartsierra/component "0.3.1"]
|
||||
[clj-time "0.13.0"]
|
||||
[duct "0.8.2"]
|
||||
[yada "1.2.0"]
|
||||
[aleph "0.4.1"]
|
||||
[bidi "2.0.16"]
|
||||
[prismatic/schema "1.1.3"]
|
||||
[environ "1.1.0"]
|
||||
[ring "1.5.0"]
|
||||
[clj-bugsnag "0.2.9"]
|
||||
[clj-aws-s3 "0.3.10" :exclusions [joda-time]]
|
||||
[cheshire "5.7.0"]
|
||||
[pandect "0.6.1"]
|
||||
[com.taoensso/timbre "4.8.0"]
|
||||
[com.taoensso/carmine "2.15.1"]
|
||||
[org.slf4j/slf4j-nop "1.7.21"]
|
||||
[duct/hikaricp-component "0.1.0"]
|
||||
[org.postgresql/postgresql "9.4.1211"]
|
||||
[duct/ragtime-component "0.1.4"]
|
||||
[me.raynes/conch "0.8.0"]]
|
||||
:plugins [[lein-environ "1.0.3"]]
|
||||
:main ^:skip-aot asciinema.main
|
||||
:target-path "target/%s/"
|
||||
:aliases {"setup" ["run" "-m" "duct.util.repl/setup"]}
|
||||
:profiles
|
||||
{:dev [:project/dev :profiles/dev]
|
||||
:test [:project/test :profiles/test]
|
||||
:uberjar {:aot :all}
|
||||
:repl {:repl-options {:host "0.0.0.0"
|
||||
:port 44444}}
|
||||
:profiles/dev {}
|
||||
:profiles/test {}
|
||||
:project/dev {:dependencies [[duct/generate "0.8.2"]
|
||||
[reloaded.repl "0.2.3"]
|
||||
[org.clojure/tools.namespace "0.2.11"]
|
||||
[org.clojure/tools.nrepl "0.2.12"]
|
||||
[eftest "0.1.1"]
|
||||
[com.gearswithingears/shrubbery "0.4.1"]
|
||||
[kerodon "0.8.0"]]
|
||||
:source-paths ["dev/src"]
|
||||
:resource-paths ["dev/resources"]
|
||||
:repl-options {:init-ns user}}
|
||||
:project/test {}})
|
@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="example">
|
||||
<head>
|
||||
<title>Example Endpoint</title>
|
||||
<link rel="stylesheet" href="/assets/normalize.css/normalize.css">
|
||||
<link rel="stylesheet" href="/css/site.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>This is an example endpoint</h1>
|
||||
</body>
|
||||
</html>
|
@ -1,12 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="error-page">
|
||||
<head>
|
||||
<title>Server Error</title>
|
||||
<link rel="stylesheet" href="/assets/normalize.css/normalize.css">
|
||||
<link rel="stylesheet" href="/css/site.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Resource Not Found</h1>
|
||||
<h2>The requested page does not exist.</h2>
|
||||
</body>
|
||||
</html>
|
@ -1,12 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="error-page">
|
||||
<head>
|
||||
<title>Server Error</title>
|
||||
<link rel="stylesheet" href="/assets/normalize.css/normalize.css">
|
||||
<link rel="stylesheet" href="/css/site.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Internal Server Error</h1>
|
||||
<h2>Sorry, something went wrong.</h2>
|
||||
</body>
|
||||
</html>
|
@ -1,103 +0,0 @@
|
||||
.error-page body {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
.error-page h1 {
|
||||
margin: 15% 0 0 0;
|
||||
text-align: center;
|
||||
font-size: 42px;
|
||||
color: #900;
|
||||
}
|
||||
|
||||
.error-page h2 {
|
||||
text-align: center;
|
||||
font-size: 32px;
|
||||
font-weight: normal;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.welcome body {
|
||||
background: #eee;
|
||||
color: #333;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
max-width: 700px;
|
||||
padding: 15px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.welcome p {
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
.welcome code {
|
||||
font-family: Menlo, DejaVu Sans Mono, Lucida Console, monospace;
|
||||
font-size: 12px;
|
||||
background: #ddd;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
.welcome h1 {
|
||||
text-align: center;
|
||||
font-size: 36px;
|
||||
font-weight: lighter;
|
||||
margin: 40px 0 30px 0;
|
||||
}
|
||||
|
||||
.welcome h1 .outer {
|
||||
border: solid 4px #555;
|
||||
padding: 3px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.welcome h1 .inner {
|
||||
border: solid 2px #555;
|
||||
padding: 0 3px;
|
||||
display: inline-block;
|
||||
font-weight: normal;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.welcome .project-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.welcome .profiles {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.welcome .profiles code {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.welcome .profiles h2 {
|
||||
font-weight: normal;
|
||||
font-size: 23px;
|
||||
margin-bottom: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.welcome .profiles dl {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.welcome .profiles dt {
|
||||
font-weight: normal;
|
||||
font-size: 19px;
|
||||
margin: 18px 0 5px 0;
|
||||
}
|
||||
|
||||
.welcome .profiles dd {
|
||||
font-size: 14px;
|
||||
margin: 8px 0 8px 0;
|
||||
}
|
||||
|
||||
.example body {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
.example h1 {
|
||||
margin: 15% 0 0 0;
|
||||
text-align: center;
|
||||
font-size: 36px;
|
||||
font-weight: normal;
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 1.1 KiB |
@ -1,36 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="welcome">
|
||||
<head>
|
||||
<title>Welcome to Duct</title>
|
||||
<link rel="stylesheet" href="/assets/normalize.css/normalize.css">
|
||||
<link rel="stylesheet" href="/css/site.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Welcome to <span class="outer"><span class="inner">Duct</span></span></h1>
|
||||
<div class="intro">
|
||||
<p>Congratulations! Your project <span class="project-name">asciinema</span> is
|
||||
ready and running.</p>
|
||||
<p>This is a static welcome page located at <code>resources/asciinema/public/index.html</code>
|
||||
in the project directory. Remove or replace it when you start developing.
|
||||
If you remove the index page entirely, be sure to change the
|
||||
<code>:route-aliases</code> map in <code>resources/asciinema/system.edn</code>.
|
||||
</div>
|
||||
<div class="profiles">
|
||||
<h2>Template profiles used:</h2>
|
||||
<dl>
|
||||
<dt>+example</dt>
|
||||
<dd>Adds an example endpoint at <a href="/example">/example</a>.</dd>
|
||||
<dt>+postgres</dt>
|
||||
<dd>Adds a PostgreSQL dependency and database component. The database used for
|
||||
development defaults to <code>postgres</code> on <code>localhost</code>.</dd>
|
||||
<dt>+ragtime</dt>
|
||||
<dd>Adds Ragtime migrations. Use <code>(migrate)</code> and <code>(rollback)</code>
|
||||
in the REPL. Migrations are stored in <code>resources/asciinema/migrations</code>.
|
||||
</dd>
|
||||
<dt>+site</dt>
|
||||
<dd>Adds middleware and configuration suited for a user-facing website.</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,2 +0,0 @@
|
||||
User-agent: *
|
||||
Disallow:
|
@ -1,37 +0,0 @@
|
||||
{:components
|
||||
{:http #var asciinema.component.yada-listener/yada-listener
|
||||
:db #var asciinema.component.db/hikaricp
|
||||
:ragtime #var duct.component.ragtime/ragtime
|
||||
:auto-file-store #var asciinema.component.auto-file-store/auto-file-store
|
||||
:redis-client #var asciinema.component.redis-client/redis-client
|
||||
:a2png #var asciinema.component.a2png/a2png
|
||||
:fixed-thread-executor #var asciinema.component.fixed-thread-executor/fixed-thread-executor}
|
||||
:endpoints
|
||||
{:asciicasts #var asciinema.endpoint.asciicasts/asciicasts-endpoint}
|
||||
:dependencies
|
||||
{:http {:app :asciicasts}
|
||||
:ragtime [:db]
|
||||
:asciicasts {:db :db
|
||||
:file-store :auto-file-store
|
||||
:exp-set :redis-client
|
||||
:executor :fixed-thread-executor
|
||||
:png-gen :a2png}}
|
||||
:config
|
||||
{:http
|
||||
{:port http-port}
|
||||
:db
|
||||
{:uri db-uri}
|
||||
:ragtime
|
||||
{:resource-path "asciinema/migrations"}
|
||||
:auto-file-store
|
||||
{:path "uploads/"
|
||||
:s3-bucket s3-bucket
|
||||
:s3-cred {:access-key s3-access-key
|
||||
:secret-key s3-secret-key}}
|
||||
:redis-client
|
||||
{:uri redis-url}
|
||||
:a2png
|
||||
{:bin-path a2png-bin-path}
|
||||
:fixed-thread-executor
|
||||
{:threads 2
|
||||
:queue-length 16}}}
|
@ -1,5 +0,0 @@
|
||||
(ns asciinema.boundary.asciicast-database)
|
||||
|
||||
(defprotocol AsciicastDatabase
|
||||
(get-asciicast-by-id [this id])
|
||||
(get-asciicast-by-token [this token]))
|
@ -1,4 +0,0 @@
|
||||
(ns asciinema.boundary.executor)
|
||||
|
||||
(defprotocol Executor
|
||||
(execute [this f]))
|
@ -1,6 +0,0 @@
|
||||
(ns asciinema.boundary.expiring-set
|
||||
(:refer-clojure :exclude [conj! contains?]))
|
||||
|
||||
(defprotocol ExpiringSet
|
||||
(conj! [this value expires-at])
|
||||
(contains? [this value]))
|
@ -1,8 +0,0 @@
|
||||
(ns asciinema.boundary.file-store)
|
||||
|
||||
(defprotocol FileStore
|
||||
(put-file [this file path] [this file path size])
|
||||
(input-stream [this path])
|
||||
(move-file [this old-path new-path])
|
||||
(delete-file [this path])
|
||||
(serve-file [this ctx path opts]))
|
@ -1,4 +0,0 @@
|
||||
(ns asciinema.boundary.png-generator)
|
||||
|
||||
(defprotocol PngGenerator
|
||||
(generate [this json-is png-params]))
|
@ -1,4 +0,0 @@
|
||||
(ns asciinema.boundary.user-database)
|
||||
|
||||
(defprotocol UserDatabase
|
||||
(get-user-by-id [this id]))
|
@ -1,26 +0,0 @@
|
||||
(ns asciinema.component.a2png
|
||||
(:require [asciinema.boundary.png-generator :as png-generator]
|
||||
[asciinema.util.io :refer [cleanup-input-stream create-tmp-dir]]
|
||||
[clojure.java.io :as io]
|
||||
[clojure.java
|
||||
[io :as io]
|
||||
[shell :as shell]]
|
||||
[me.raynes.conch :as conch]))
|
||||
|
||||
(defn- exec-a2png [bin-path in-url out-path {:keys [snapshot-at theme scale]}]
|
||||
(conch/let-programs [a2png bin-path]
|
||||
(a2png in-url out-path (str snapshot-at) theme (str scale) {:timeout 30000})))
|
||||
|
||||
(defrecord A2png [bin-path]
|
||||
png-generator/PngGenerator
|
||||
(generate [this json-is png-params]
|
||||
(let [dir (create-tmp-dir "a2png-")
|
||||
cleanup #(shell/sh "rm" "-rf" (.getPath dir))
|
||||
json-local-path (str dir "/asciicast.json")
|
||||
png-local-path (str dir "/asciicast.png")]
|
||||
(io/copy json-is (io/file json-local-path))
|
||||
(exec-a2png bin-path json-local-path png-local-path png-params)
|
||||
(cleanup-input-stream (io/input-stream png-local-path) cleanup))))
|
||||
|
||||
(defn a2png [{:keys [bin-path]}]
|
||||
(->A2png bin-path))
|
@ -1,8 +0,0 @@
|
||||
(ns asciinema.component.auto-file-store
|
||||
(:require [asciinema.component.local-file-store :refer [local-file-store]]
|
||||
[asciinema.component.s3-file-store :refer [s3-file-store]]))
|
||||
|
||||
(defn auto-file-store [config]
|
||||
(if (:s3-bucket config)
|
||||
(s3-file-store config)
|
||||
(local-file-store config)))
|
@ -1,62 +0,0 @@
|
||||
(ns asciinema.component.db
|
||||
(:require [asciinema.boundary.asciicast-database :refer :all]
|
||||
[asciinema.boundary.user-database :refer :all]
|
||||
[clojure.java.jdbc :as jdbc]
|
||||
[clj-time.coerce :as timec]
|
||||
[duct.component.hikaricp :as hikaricp]
|
||||
[clojure.string :as str]))
|
||||
|
||||
(extend-protocol jdbc/ISQLValue
|
||||
org.joda.time.DateTime
|
||||
(sql-value [val]
|
||||
(timec/to-sql-time val)))
|
||||
|
||||
(extend-protocol jdbc/IResultSetReadColumn
|
||||
java.sql.Timestamp
|
||||
(result-set-read-column [x _ _]
|
||||
(timec/from-sql-time x)))
|
||||
|
||||
;; AsciicastDatabase
|
||||
|
||||
(def q-get-asciicast-by-id "SELECT * FROM asciicasts WHERE id=?")
|
||||
(def q-get-asciicast-by-secret-token "SELECT * FROM asciicasts WHERE secret_token=?")
|
||||
(def q-get-public-asciicast-by-id "SELECT * FROM asciicasts WHERE id=? AND private=FALSE")
|
||||
|
||||
(extend-protocol AsciicastDatabase
|
||||
duct.component.hikaricp.HikariCP
|
||||
|
||||
(get-asciicast-by-id [{db :spec} id]
|
||||
(first (jdbc/query db [q-get-asciicast-by-id id])))
|
||||
|
||||
(get-asciicast-by-token [{db :spec} token]
|
||||
(when-let [query (cond
|
||||
(re-matches #"\d+" token)
|
||||
[q-get-public-asciicast-by-id (Long/parseLong token)]
|
||||
(= (count token) 25)
|
||||
[q-get-asciicast-by-secret-token token])]
|
||||
(first (jdbc/query db query)))))
|
||||
|
||||
;; UserDatabase
|
||||
|
||||
(def q-get-user-by-id "SELECT * FROM users WHERE id=?")
|
||||
|
||||
(extend-protocol UserDatabase
|
||||
duct.component.hikaricp.HikariCP
|
||||
|
||||
(get-user-by-id [{db :spec} id]
|
||||
(first (jdbc/query db [q-get-user-by-id id]))))
|
||||
|
||||
;; constructor
|
||||
|
||||
(defn- fix-uri [uri]
|
||||
(when uri
|
||||
(let [[_ user _ pass] (re-find #"://([^:@]+)(:([^@]+))?@" uri)]
|
||||
(cond-> uri
|
||||
(not (str/starts-with? uri "jdbc:")) (->> (str "jdbc:"))
|
||||
(str/includes? uri "@") (str/replace #"://[^@]+@" "://")
|
||||
user (str "?user=" user)
|
||||
pass (str "&password=" pass)))))
|
||||
|
||||
(defn hikaricp [opts]
|
||||
(let [opts (update opts :uri fix-uri)]
|
||||
(hikaricp/hikaricp opts)))
|
@ -1,39 +0,0 @@
|
||||
(ns asciinema.component.fixed-thread-executor
|
||||
(:require [aleph.flow :as flow]
|
||||
[asciinema.boundary.executor :as executor]
|
||||
[com.stuartsierra.component :as component]
|
||||
[manifold.deferred :as d])
|
||||
(:import [java.util.concurrent
|
||||
ExecutorService
|
||||
RejectedExecutionException
|
||||
TimeUnit]))
|
||||
|
||||
(defrecord FixedThreadExecutor [threads queue-length]
|
||||
executor/Executor
|
||||
(execute [{:keys [^ExecutorService executor]} f]
|
||||
(try
|
||||
(let [result (d/deferred)
|
||||
f (fn []
|
||||
(try
|
||||
(d/success! result (f))
|
||||
(catch Exception e
|
||||
(d/error! result e))))]
|
||||
(.execute executor f)
|
||||
result)
|
||||
(catch RejectedExecutionException _
|
||||
nil)))
|
||||
|
||||
component/Lifecycle
|
||||
(start [{:keys [threads queue-length] :as component}]
|
||||
(let [executor (flow/fixed-thread-executor threads {:onto? false
|
||||
:initial-thread-count threads
|
||||
:queue-length queue-length})]
|
||||
(assoc component :executor executor)))
|
||||
(stop [{:keys [^ExecutorService executor] :as component}]
|
||||
(.shutdown executor)
|
||||
(when-not (.awaitTermination executor 1000 TimeUnit/MILLISECONDS)
|
||||
(.shutdownNow executor))
|
||||
(assoc component :executor nil)))
|
||||
|
||||
(defn fixed-thread-executor [{:keys [threads queue-length]}]
|
||||
(->FixedThreadExecutor threads queue-length))
|
@ -1,38 +0,0 @@
|
||||
(ns asciinema.component.local-file-store
|
||||
(:require [asciinema.boundary.file-store :as file-store]
|
||||
[clojure.java.io :as io]
|
||||
[ring.util.http-response :as response]))
|
||||
|
||||
(defrecord LocalFileStore [base-path]
|
||||
file-store/FileStore
|
||||
|
||||
(put-file [this file path]
|
||||
(let [path (str base-path path)]
|
||||
(io/make-parents path)
|
||||
(io/copy file (io/file path))))
|
||||
|
||||
(put-file [this file path size]
|
||||
(file-store/put-file this file path))
|
||||
|
||||
(input-stream [this path]
|
||||
(let [path (str base-path path)]
|
||||
(io/input-stream path)))
|
||||
|
||||
(move-file [this old-path new-path]
|
||||
(let [old-path (str base-path old-path)
|
||||
new-path (str base-path new-path)]
|
||||
(.renameTo (io/file old-path) (io/file new-path))))
|
||||
|
||||
(delete-file [this path]
|
||||
(let [path (str base-path path)]
|
||||
(io/delete-file path)))
|
||||
|
||||
(serve-file [this ctx path {:keys [filename]}]
|
||||
(let [path (str base-path path)
|
||||
response (assoc (:response ctx) :body (io/file path))]
|
||||
(if filename
|
||||
(update response :headers assoc "content-disposition" (str "attachment; filename=" filename))
|
||||
response))))
|
||||
|
||||
(defn local-file-store [{:keys [path]}]
|
||||
(->LocalFileStore path))
|
@ -1,14 +0,0 @@
|
||||
(ns asciinema.component.mem-expiring-set
|
||||
(:require [asciinema.boundary.expiring-set :as exp-set]))
|
||||
|
||||
(defrecord MemExpiringSet [store]
|
||||
exp-set/ExpiringSet
|
||||
|
||||
(conj! [this value _expires-at]
|
||||
(swap! store conj value))
|
||||
|
||||
(contains? [this value]
|
||||
(contains? @store value)))
|
||||
|
||||
(defn mem-expiring-set [{:keys [store]}]
|
||||
(->MemExpiringSet (or store (atom #{}))))
|
@ -1,28 +0,0 @@
|
||||
(ns asciinema.component.redis-client
|
||||
(:require [asciinema.boundary.expiring-set :as exp-set]
|
||||
[clj-time.core :as t]
|
||||
[clj-time.local :as tl]
|
||||
[com.stuartsierra.component :as component]
|
||||
[taoensso.carmine :as car]))
|
||||
|
||||
(defrecord RedisClient [uri]
|
||||
component/Lifecycle
|
||||
(start [component]
|
||||
(if (:listener component)
|
||||
component
|
||||
(let [conn {:pool {} :spec {:uri uri}}]
|
||||
(assoc component :conn conn))))
|
||||
(stop [component]
|
||||
(if (:conn component)
|
||||
(dissoc component :conn)
|
||||
component))
|
||||
|
||||
exp-set/ExpiringSet
|
||||
(conj! [this value expires-at]
|
||||
(let [seconds (t/in-seconds (t/interval (tl/local-now) expires-at))]
|
||||
(car/wcar (:conn this) (car/setex value seconds true))))
|
||||
(contains? [this value]
|
||||
(car/as-bool (car/wcar (:conn this) (car/exists value)))))
|
||||
|
||||
(defn redis-client [{:keys [uri]}]
|
||||
(->RedisClient uri))
|
@ -1,66 +0,0 @@
|
||||
(ns asciinema.component.s3-file-store
|
||||
(:require [asciinema.boundary.file-store :as file-store]
|
||||
[aws.sdk.s3 :as s3]
|
||||
[clj-time
|
||||
[coerce :as timec]
|
||||
[core :as time]]
|
||||
[ring.util.http-response :as response]
|
||||
[ring.util.mime-type :as mime-type])
|
||||
(:import com.amazonaws.auth.BasicAWSCredentials
|
||||
com.amazonaws.services.s3.AmazonS3Client
|
||||
[com.amazonaws.services.s3.model GeneratePresignedUrlRequest ResponseHeaderOverrides]))
|
||||
|
||||
(defn- s3-client* [cred]
|
||||
(let [credentials (BasicAWSCredentials. (:access-key cred) (:secret-key cred))]
|
||||
(AmazonS3Client. credentials)))
|
||||
|
||||
(def ^:private s3-client (memoize s3-client*))
|
||||
|
||||
(defn- generate-presigned-url [cred bucket path {:keys [expires filename]
|
||||
:or {expires (-> 1 time/days time/from-now)}}]
|
||||
(let [client (s3-client cred)
|
||||
request (GeneratePresignedUrlRequest. bucket path)]
|
||||
(.setExpiration request (timec/to-date expires))
|
||||
(when filename
|
||||
(let [header-overrides (doto (ResponseHeaderOverrides.)
|
||||
(.setContentDisposition (str "attachment; filename=" filename)))]
|
||||
(.setResponseHeaders request header-overrides)))
|
||||
(.toString (.generatePresignedUrl client request))))
|
||||
|
||||
(defrecord S3FileStore [cred bucket path-prefix]
|
||||
file-store/FileStore
|
||||
|
||||
(put-file [this file path]
|
||||
(file-store/put-file this file path nil))
|
||||
|
||||
(put-file [this file path size]
|
||||
(let [path (str path-prefix path)
|
||||
content-type (mime-type/ext-mime-type path)]
|
||||
(s3/put-object cred bucket path file {:content-length size
|
||||
:content-type content-type})))
|
||||
|
||||
(input-stream [this path]
|
||||
(let [path (str path-prefix path)]
|
||||
(:content (s3/get-object cred bucket path))))
|
||||
|
||||
(move-file [this old-path new-path]
|
||||
(let [old-path (str path-prefix old-path)
|
||||
new-path (str path-prefix new-path)]
|
||||
(s3/copy-object cred bucket old-path new-path)
|
||||
(s3/delete-object cred bucket old-path)))
|
||||
|
||||
(delete-file [this path]
|
||||
(let [path (str path-prefix path)]
|
||||
(s3/delete-object cred bucket path)))
|
||||
|
||||
(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 [s3-cred s3-bucket path]}]
|
||||
{:pre [(some? s3-cred) (some? s3-bucket) (some? path)]}
|
||||
(->S3FileStore s3-cred s3-bucket path))
|
@ -1,22 +0,0 @@
|
||||
(ns asciinema.component.yada-listener
|
||||
(:require [bidi.vhosts :refer [vhosts-model]]
|
||||
[com.stuartsierra.component :as component]
|
||||
[yada.yada :as yada]))
|
||||
|
||||
(defrecord YadaListener [port server app]
|
||||
component/Lifecycle
|
||||
(start [component]
|
||||
(if server
|
||||
component
|
||||
(let [handler (vhosts-model [:* (:routes app)]) ; wrap in * vhost to make path-for work
|
||||
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}))
|
@ -1,90 +0,0 @@
|
||||
(ns asciinema.endpoint.asciicasts
|
||||
(:require [asciinema.boundary
|
||||
[asciicast-database :as adb]
|
||||
[executor :as executor]
|
||||
[expiring-set :as exp-set]
|
||||
[file-store :as fstore]
|
||||
[png-generator :as png]
|
||||
[user-database :as udb]]
|
||||
[asciinema.model.asciicast :as asciicast]
|
||||
[asciinema.yada :refer [not-found-model resource]]
|
||||
[clj-time.core :as t]
|
||||
[schema.core :as s]
|
||||
[yada.yada :as yada]))
|
||||
|
||||
(def Theme (apply s/enum asciicast/themes))
|
||||
|
||||
(defn- service-unavailable-response [ctx]
|
||||
(-> (:response ctx)
|
||||
(assoc :status 503)
|
||||
(update :headers assoc "retry-after" "5")))
|
||||
|
||||
(defn- async-response [ctx executor f]
|
||||
(or (executor/execute executor f)
|
||||
(service-unavailable-response ctx)))
|
||||
|
||||
(defn asciicast-file-resource [db file-store]
|
||||
(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))]
|
||||
{::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}))))}))
|
||||
|
||||
(def png-ttl-days 7)
|
||||
|
||||
(defn asciicast-image-resource [db file-store exp-set executor png-gen]
|
||||
(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)))]
|
||||
{: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)
|
||||
expires (-> png-ttl-days t/days t/from-now)]
|
||||
(if (exp-set/contains? exp-set png-store-path)
|
||||
(fstore/serve-file file-store ctx png-store-path {})
|
||||
(async-response ctx
|
||||
executor
|
||||
(fn []
|
||||
(let [json-store-path (asciicast/json-store-path asciicast)]
|
||||
(with-open [json-is (fstore/input-stream file-store json-store-path)
|
||||
png-is (png/generate png-gen json-is png-params)]
|
||||
(fstore/put-file file-store png-is png-store-path)))
|
||||
(exp-set/conj! exp-set png-store-path expires)
|
||||
(fstore/serve-file file-store ctx png-store-path {}))))))}))
|
||||
|
||||
(defn asciicasts-endpoint [{:keys [db file-store exp-set executor png-gen]}]
|
||||
["" [["/a/" [[[:token ".json"] (asciicast-file-resource db file-store)]
|
||||
[[:token ".png"] (asciicast-image-resource db file-store exp-set executor png-gen)]]]
|
||||
[true (yada/resource not-found-model)]]])
|
@ -1,41 +0,0 @@
|
||||
(ns asciinema.main
|
||||
(:gen-class)
|
||||
(:require [asciinema.yada :as y]
|
||||
[clj-bugsnag.core :as bugsnag]
|
||||
[com.stuartsierra.component :as component]
|
||||
[duct.util.runtime :refer [add-shutdown-hook]]
|
||||
[duct.util.system :refer [load-system]]
|
||||
[environ.core :refer [env]]
|
||||
[clojure.java.io :as io]))
|
||||
|
||||
(defn- request-context [req]
|
||||
(str (-> req (get :request-method :unknown) name .toUpperCase)
|
||||
" "
|
||||
(:uri req)))
|
||||
|
||||
(defn- create-exception-notifier []
|
||||
(when-let [key (:bugsnag-key env)]
|
||||
(let [environment (:env-name env "production")
|
||||
version (:git-sha env)]
|
||||
(fn [ex req]
|
||||
(bugsnag/notify ex {:api-key key
|
||||
:environment environment
|
||||
:project-ns "asciinema"
|
||||
:version version
|
||||
:context (request-context req)
|
||||
:meta {:request (dissoc req :body)}})))))
|
||||
|
||||
(defn -main [& args]
|
||||
(binding [y/*exception-notifier* (create-exception-notifier)]
|
||||
(let [bindings {'http-port (Integer/parseInt (:port env "4000"))
|
||||
'db-uri (:database-url env)
|
||||
's3-bucket (:s3-bucket env)
|
||||
's3-access-key (:s3-access-key env)
|
||||
's3-secret-key (:s3-secret-key env)
|
||||
'redis-url (:redis-url env "redis://localhost")
|
||||
'a2png-bin-path (:a2png-bin-path env "a2png/a2png.sh")}
|
||||
system (->> (load-system [(io/resource "asciinema/system.edn")] bindings)
|
||||
(component/start))]
|
||||
(add-shutdown-hook ::stop-system #(component/stop system))
|
||||
(println "Started HTTP server on port" (-> system :http :port))))
|
||||
@(promise))
|
@ -1,38 +0,0 @@
|
||||
(ns asciinema.model.asciicast
|
||||
(:require [pandect.algo.sha1 :as sha1]
|
||||
[clojure.string :as str]))
|
||||
|
||||
(defn json-store-path [{:keys [id file stdout_frames]}]
|
||||
(cond
|
||||
file (str "asciicast/file/" id "/" file)
|
||||
stdout_frames (str "asciicast/stdout_frames/" id "/" stdout_frames)))
|
||||
|
||||
(def themes #{"asciinema" "tango" "solarized-dark" "solarized-light" "monokai"})
|
||||
(def default-theme "asciinema")
|
||||
|
||||
(defn theme-name [asciicast user]
|
||||
(or (:theme_name asciicast)
|
||||
(:theme_name user)
|
||||
default-theme))
|
||||
|
||||
(defn snapshot-at [{:keys [snapshot_at duration]}]
|
||||
(or snapshot_at (/ duration 2.0)))
|
||||
|
||||
(def default-png-scale 2)
|
||||
|
||||
(defn png-params [asciicast user]
|
||||
{:snapshot-at (snapshot-at asciicast)
|
||||
:theme (theme-name asciicast user)
|
||||
:scale default-png-scale})
|
||||
|
||||
(defn png-version [asciicast params]
|
||||
(let [attrs (assoc params :id (:id asciicast))]
|
||||
(->> attrs
|
||||
(map (fn [[k v]] (str (name k) "=" v)))
|
||||
(str/join "/")
|
||||
(sha1/sha1))))
|
||||
|
||||
(defn png-store-path [asciicast params]
|
||||
(let [ver (png-version asciicast params)
|
||||
png-filename (str ver ".png")]
|
||||
(str "png/" (:id asciicast) "/" png-filename)))
|
@ -1,22 +0,0 @@
|
||||
(ns asciinema.util.io
|
||||
(:require [clojure.java.shell :as shell])
|
||||
(:import java.io.FilterInputStream
|
||||
java.nio.file.attribute.FileAttribute
|
||||
java.nio.file.Files))
|
||||
|
||||
(defn create-tmp-dir [prefix]
|
||||
(let [dir (Files/createTempDirectory prefix (into-array FileAttribute []))]
|
||||
(.toFile dir)))
|
||||
|
||||
(defmacro with-tmp-dir [[sym prefix] & body]
|
||||
`(let [~sym (create-tmp-dir ~prefix)]
|
||||
(try
|
||||
~@body
|
||||
(finally
|
||||
(shell/sh "rm" "-rf" (.getPath ~sym))))))
|
||||
|
||||
(defn cleanup-input-stream [is cleanup]
|
||||
(proxy [FilterInputStream] [is]
|
||||
(close []
|
||||
(proxy-super close)
|
||||
(cleanup))))
|
@ -1,46 +0,0 @@
|
||||
(ns asciinema.yada
|
||||
(:require [clojure.java.io :as io]
|
||||
[taoensso.timbre :as log]
|
||||
[yada.status :as status]
|
||||
[yada.yada :as yada]))
|
||||
|
||||
(def ^:dynamic *exception-notifier* nil)
|
||||
|
||||
(def not-found-model
|
||||
{:produces
|
||||
#{"text/html" "text/plain"}
|
||||
:response
|
||||
(fn [ctx]
|
||||
(assoc (:response ctx)
|
||||
:status 404
|
||||
:body (case (yada/content-type ctx)
|
||||
"text/html" (io/input-stream (io/resource "asciinema/errors/404.html"))
|
||||
"Not found")))})
|
||||
|
||||
(defn error-response [ctx]
|
||||
(let [status (-> ctx :response :status)
|
||||
status-name (get-in status/status [status :name])]
|
||||
(case (yada/content-type ctx)
|
||||
"text/html" (str "<html><body><h1>" status-name "</h1></body></html>")
|
||||
status-name)))
|
||||
|
||||
(defn create-logger []
|
||||
(let [notifier *exception-notifier*]
|
||||
(fn [ctx]
|
||||
(when-let [error (:error ctx)]
|
||||
(let [status (-> ctx :response :status)]
|
||||
(when (not= status 404)
|
||||
(log/error error))
|
||||
(when (and (= status 500) notifier)
|
||||
(let [ex (or (-> error ex-data :error) error)]
|
||||
(notifier ex (:request ctx))))))
|
||||
ctx)))
|
||||
|
||||
(defn resource [model]
|
||||
(let [error-statuses (set (concat (range 400 404) (range 405 600) ))]
|
||||
(-> model
|
||||
(assoc :logger (create-logger))
|
||||
(update-in [:responses 404] #(or % not-found-model))
|
||||
(update-in [:responses error-statuses] #(or % {:produces #{"text/html" "text/plain"}
|
||||
:response error-response}))
|
||||
yada/resource)))
|
@ -1,7 +0,0 @@
|
||||
(ns asciinema.boundary.file-store-test
|
||||
(:require [clojure.test :refer :all]
|
||||
[asciinema.boundary.file-store :as file-store]))
|
||||
|
||||
(deftest a-test
|
||||
(testing "FIXME, I fail."
|
||||
(is (= 0 1))))
|
@ -1,54 +0,0 @@
|
||||
(ns asciinema.component.db-test
|
||||
(:require [clojure.test :refer :all]
|
||||
[clojure.java.jdbc :as jdbc]
|
||||
[clj-time.local :as timel]
|
||||
[com.stuartsierra.component :as component]
|
||||
[asciinema.component.db :as db]
|
||||
[asciinema.boundary.asciicast-database :as adb]))
|
||||
|
||||
(defmacro with-db-component [component-var & body]
|
||||
`(let [component# (-> (db/hikaricp {:uri "jdbc:postgresql://localhost:15432/asciinema_test?user=vagrant"})
|
||||
component/start)]
|
||||
(try
|
||||
(jdbc/with-db-transaction [db# (:spec component#)]
|
||||
(let [~component-var (assoc component# :spec db#)]
|
||||
(jdbc/db-set-rollback-only! db#)
|
||||
~@body))
|
||||
(finally
|
||||
(component/stop component#)))))
|
||||
|
||||
(defn insert-asciicast
|
||||
([db] (insert-asciicast db {}))
|
||||
([db attrs]
|
||||
(first (jdbc/insert! db :asciicasts (merge {:duration 10.0
|
||||
:terminal_columns 80
|
||||
:terminal_lines 24
|
||||
:created_at (timel/local-now)
|
||||
:updated_at (timel/local-now)
|
||||
:version 1
|
||||
:secret_token "abcdeabcdeabcdeabcdeabcde"}
|
||||
attrs)))))
|
||||
|
||||
(deftest get-asciicast-by-id-test
|
||||
(testing "for existing asciicast"
|
||||
(with-db-component db
|
||||
(let [asciicast (insert-asciicast (:spec db))]
|
||||
(is (map? (adb/get-asciicast-by-id db (:id asciicast)))))))
|
||||
(testing "for non-existing asciicast"
|
||||
(with-db-component db
|
||||
(is (nil? (adb/get-asciicast-by-id db 1))))))
|
||||
|
||||
(deftest get-asciicast-by-token-test
|
||||
(testing "for existing public asciicast"
|
||||
(with-db-component db
|
||||
(let [asciicast (insert-asciicast (:spec db) {:private false})]
|
||||
(is (map? (adb/get-asciicast-by-token db (:secret_token asciicast))))
|
||||
(is (map? (adb/get-asciicast-by-token db (-> asciicast :id str)))))))
|
||||
(testing "for existing private asciicast"
|
||||
(with-db-component db
|
||||
(let [asciicast (insert-asciicast (:spec db) {:private true})]
|
||||
(is (map? (adb/get-asciicast-by-token db (:secret_token asciicast))))
|
||||
(is (nil? (adb/get-asciicast-by-token db (-> asciicast :id str)))))))
|
||||
(testing "for non-existing asciicast"
|
||||
(with-db-component db
|
||||
(is (nil? (adb/get-asciicast-by-token db "1"))))))
|
Loading…
Reference in New Issue
Block a user