diff --git a/Capstanfile b/Capstanfile
new file mode 100644
index 0000000..cb59536
--- /dev/null
+++ b/Capstanfile
@@ -0,0 +1,28 @@
+
+#
+# Name of the base image. Capstan will download this automatically from
+# Cloudius S3 repository.
+#
+#base: cloudius/osv
+base: cloudius/osv-openjdk8
+
+#
+# The command line passed to OSv to start up the application.
+#
+cmdline: /java.so -jar /datamapper/app.jar
+
+#
+# The command to use to build the application.
+# You can use any build tool/command (make/rake/lein/boot) - this runs locally on your machine
+#
+# For Leiningen, you can use:
+#build: lein uberjar
+# For Boot, you can use:
+#build: boot build
+
+#
+# List of files that are included in the generated image.
+#
+files:
+ /datamapper/app.jar: ./target/uberjar/datamapper.jar
+
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..e63847f
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,7 @@
+FROM openjdk:8-alpine
+
+COPY target/uberjar/datamapper.jar /datamapper/app.jar
+
+EXPOSE 3000
+
+CMD ["java", "-jar", "/datamapper/app.jar"]
diff --git a/Procfile b/Procfile
new file mode 100644
index 0000000..eab424f
--- /dev/null
+++ b/Procfile
@@ -0,0 +1 @@
+web: java -cp target/uberjar/datamapper.jar clojure.main -m datamapper.core
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ad83120
--- /dev/null
+++ b/README.md
@@ -0,0 +1,21 @@
+# datamapper
+
+generated using Luminus version "3.56"
+
+FIXME
+
+## Prerequisites
+
+You will need [Leiningen][1] 2.0 or above installed.
+
+[1]: https://github.com/technomancy/leiningen
+
+## Running
+
+To start a web server for the application, run:
+
+ lein run
+
+## License
+
+Copyright © 2020 FIXME
diff --git a/dev-config.edn b/dev-config.edn
new file mode 100644
index 0000000..d27bc99
--- /dev/null
+++ b/dev-config.edn
@@ -0,0 +1,10 @@
+;; WARNING
+;; The dev-config.edn file is used for local environment variables, such as database credentials.
+;; This file is listed in .gitignore and will be excluded from version control by Git.
+
+{:dev true
+ :port 3000
+ ;; when :nrepl-port is set the application starts the nREPL server on load
+ :nrepl-port 7000
+
+ }
diff --git a/env/dev/clj/datamapper/dev_middleware.clj b/env/dev/clj/datamapper/dev_middleware.clj
new file mode 100644
index 0000000..ecdce47
--- /dev/null
+++ b/env/dev/clj/datamapper/dev_middleware.clj
@@ -0,0 +1,11 @@
+(ns datamapper.dev-middleware
+ (:require
+ [ring.middleware.reload :refer [wrap-reload]]
+ [selmer.middleware :refer [wrap-error-page]]
+ [prone.middleware :refer [wrap-exceptions]]))
+
+(defn wrap-dev [handler]
+ (-> handler
+ wrap-reload
+ wrap-error-page
+ (wrap-exceptions {:app-namespaces ['datamapper]})))
diff --git a/env/dev/clj/datamapper/env.clj b/env/dev/clj/datamapper/env.clj
new file mode 100644
index 0000000..be342f0
--- /dev/null
+++ b/env/dev/clj/datamapper/env.clj
@@ -0,0 +1,15 @@
+(ns datamapper.env
+ (:require
+ [selmer.parser :as parser]
+ [clojure.tools.logging :as log]
+ [datamapper.dev-middleware :refer [wrap-dev]]))
+
+(def defaults
+ {:init
+ (fn []
+ (parser/cache-off!)
+ (log/info "\n-=[datamapper started successfully using the development profile]=-"))
+ :stop
+ (fn []
+ (log/info "\n-=[datamapper has shut down successfully]=-"))
+ :middleware wrap-dev})
diff --git a/env/dev/clj/datamapper/figwheel.clj b/env/dev/clj/datamapper/figwheel.clj
new file mode 100644
index 0000000..75e8af8
--- /dev/null
+++ b/env/dev/clj/datamapper/figwheel.clj
@@ -0,0 +1,12 @@
+(ns datamapper.figwheel
+ (:require [figwheel-sidecar.repl-api :as ra]))
+
+(defn start-fw []
+ (ra/start-figwheel!))
+
+(defn stop-fw []
+ (ra/stop-figwheel!))
+
+(defn cljs []
+ (ra/cljs-repl))
+
diff --git a/env/dev/clj/user.clj b/env/dev/clj/user.clj
new file mode 100644
index 0000000..cf97646
--- /dev/null
+++ b/env/dev/clj/user.clj
@@ -0,0 +1,33 @@
+(ns user
+ "Userspace functions you can run by default in your local REPL."
+ (:require
+ [datamapper.config :refer [env]]
+ [clojure.pprint]
+ [clojure.spec.alpha :as s]
+ [expound.alpha :as expound]
+ [mount.core :as mount]
+ [datamapper.figwheel :refer [start-fw stop-fw cljs]]
+ [datamapper.core :refer [start-app]]))
+
+(alter-var-root #'s/*explain-out* (constantly expound/printer))
+
+(add-tap (bound-fn* clojure.pprint/pprint))
+
+(defn start
+ "Starts application.
+ You'll usually want to run this on startup."
+ []
+ (mount/start-without #'datamapper.core/repl-server))
+
+(defn stop
+ "Stops application."
+ []
+ (mount/stop-except #'datamapper.core/repl-server))
+
+(defn restart
+ "Restarts application."
+ []
+ (stop)
+ (start))
+
+
diff --git a/env/dev/cljs/datamapper/app.cljs b/env/dev/cljs/datamapper/app.cljs
new file mode 100644
index 0000000..159c058
--- /dev/null
+++ b/env/dev/cljs/datamapper/app.cljs
@@ -0,0 +1,19 @@
+(ns^:figwheel-no-load datamapper.app
+ (:require
+ [datamapper.core :as core]
+ [cljs.spec.alpha :as s]
+ [expound.alpha :as expound]
+ [devtools.core :as devtools]))
+
+(extend-protocol IPrintWithWriter
+ js/Symbol
+ (-pr-writer [sym writer _]
+ (-write writer (str "\"" (.toString sym) "\""))))
+
+(set! s/*explain-out* expound/printer)
+
+(enable-console-print!)
+
+(devtools/install!)
+
+(core/init!)
diff --git a/env/dev/resources/config.edn b/env/dev/resources/config.edn
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/env/dev/resources/config.edn
@@ -0,0 +1 @@
+{}
diff --git a/env/dev/resources/logback.xml b/env/dev/resources/logback.xml
new file mode 100644
index 0000000..a83e8a5
--- /dev/null
+++ b/env/dev/resources/logback.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+ UTF-8
+ %date{ISO8601} [%thread] %-5level %logger{36} - %msg %n
+
+
+
+ log/datamapper.log
+
+ log/datamapper.%d{yyyy-MM-dd}.%i.log
+
+ 100MB
+
+
+ 30
+
+
+ UTF-8
+ %date{ISO8601} [%thread] %-5level %logger{36} - %msg %n
+
+
+
+
+
+
+
+
+
+
diff --git a/env/prod/clj/datamapper/env.clj b/env/prod/clj/datamapper/env.clj
new file mode 100644
index 0000000..fd252fc
--- /dev/null
+++ b/env/prod/clj/datamapper/env.clj
@@ -0,0 +1,11 @@
+(ns datamapper.env
+ (:require [clojure.tools.logging :as log]))
+
+(def defaults
+ {:init
+ (fn []
+ (log/info "\n-=[datamapper started successfully]=-"))
+ :stop
+ (fn []
+ (log/info "\n-=[datamapper has shut down successfully]=-"))
+ :middleware identity})
diff --git a/env/prod/cljs/datamapper/app.cljs b/env/prod/cljs/datamapper/app.cljs
new file mode 100644
index 0000000..2f06b67
--- /dev/null
+++ b/env/prod/cljs/datamapper/app.cljs
@@ -0,0 +1,7 @@
+(ns datamapper.app
+ (:require [datamapper.core :as core]))
+
+;;ignore println statements in prod
+(set! *print-fn* (fn [& _]))
+
+(core/init!)
diff --git a/env/prod/resources/config.edn b/env/prod/resources/config.edn
new file mode 100644
index 0000000..e24ec21
--- /dev/null
+++ b/env/prod/resources/config.edn
@@ -0,0 +1,2 @@
+{:prod true
+ :port 3000}
diff --git a/env/prod/resources/logback.xml b/env/prod/resources/logback.xml
new file mode 100644
index 0000000..c170930
--- /dev/null
+++ b/env/prod/resources/logback.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ log/datamapper.log
+
+ log/datamapper.%d{yyyy-MM-dd}.%i.log
+
+ 100MB
+
+
+ 30
+
+
+ UTF-8
+ %date{ISO8601} [%thread] %-5level %logger{36} - %msg %n
+
+
+
+
+
+
+
+
+
diff --git a/env/test/resources/config.edn b/env/test/resources/config.edn
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/env/test/resources/config.edn
@@ -0,0 +1 @@
+{}
diff --git a/env/test/resources/logback.xml b/env/test/resources/logback.xml
new file mode 100644
index 0000000..a83e8a5
--- /dev/null
+++ b/env/test/resources/logback.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+ UTF-8
+ %date{ISO8601} [%thread] %-5level %logger{36} - %msg %n
+
+
+
+ log/datamapper.log
+
+ log/datamapper.%d{yyyy-MM-dd}.%i.log
+
+ 100MB
+
+
+ 30
+
+
+ UTF-8
+ %date{ISO8601} [%thread] %-5level %logger{36} - %msg %n
+
+
+
+
+
+
+
+
+
+
diff --git a/project.clj b/project.clj
new file mode 100644
index 0000000..5f23435
--- /dev/null
+++ b/project.clj
@@ -0,0 +1,132 @@
+(defproject datamapper "0.1.0-SNAPSHOT"
+
+ :description "FIXME: write description"
+ :url "http://example.com/FIXME"
+
+ :dependencies [[ch.qos.logback/logback-classic "1.2.3"]
+ [cheshire "5.9.0"]
+ [cljs-ajax "0.8.0"]
+ [clojure.java-time "0.3.2"]
+ [com.cognitect/transit-clj "0.8.319"]
+ [cprop "0.1.15"]
+ [day8.re-frame/http-fx "0.1.6"]
+ [expound "0.8.3"]
+ [funcool/struct "1.4.0"]
+ [luminus-jetty "0.1.7"]
+ [luminus-transit "0.1.2"]
+ [luminus/ring-ttl-session "0.3.3"]
+ [markdown-clj "1.10.1"]
+ [metosin/muuntaja "0.6.6"]
+ [metosin/reitit "0.3.10"]
+ [metosin/ring-http-response "0.9.1"]
+ [mount "0.1.16"]
+ [nrepl "0.6.0"]
+ [org.clojure/clojure "1.10.1"]
+ [org.clojure/clojurescript "1.10.597" :scope "provided"]
+ [org.clojure/tools.cli "0.4.2"]
+ [org.clojure/tools.logging "0.5.0"]
+ [org.webjars.npm/bulma "0.8.0"]
+ [org.webjars.npm/material-icons "0.3.1"]
+ [org.webjars/webjars-locator "0.38"]
+ [re-frame "0.10.9"]
+ [reagent "0.9.0-rc3"]
+ [ring-webjars "0.2.0"]
+ [ring/ring-core "1.8.0"]
+ [ring/ring-defaults "0.3.2"]
+ [selmer "1.12.18"]]
+
+ :min-lein-version "2.0.0"
+
+ :source-paths ["src/clj" "src/cljs" "src/cljc"]
+ :test-paths ["test/clj"]
+ :resource-paths ["resources" "target/cljsbuild"]
+ :target-path "target/%s/"
+ :main ^:skip-aot datamapper.core
+
+ :plugins [[lein-cljsbuild "1.1.7"]]
+ :clean-targets ^{:protect false}
+ [:target-path [:cljsbuild :builds :app :compiler :output-dir] [:cljsbuild :builds :app :compiler :output-to]]
+ :figwheel
+ {:http-server-root "public"
+ :server-logfile "log/figwheel-logfile.log"
+ :nrepl-port 7002
+ :css-dirs ["resources/public/css"]
+ :nrepl-middleware [cider.piggieback/wrap-cljs-repl]}
+
+
+ :profiles
+ {:uberjar {:omit-source true
+ :prep-tasks ["compile" ["cljsbuild" "once" "min"]]
+ :cljsbuild{:builds
+ {:min
+ {:source-paths ["src/cljc" "src/cljs" "env/prod/cljs"]
+ :compiler
+ {:output-dir "target/cljsbuild/public/js"
+ :output-to "target/cljsbuild/public/js/app.js"
+ :source-map "target/cljsbuild/public/js/app.js.map"
+ :optimizations :advanced
+ :pretty-print false
+ :infer-externs true
+ :closure-warnings
+ {:externs-validation :off :non-standard-jsdoc :off}
+ :externs ["react/externs/react.js"]}}}}
+
+ :aot :all
+ :uberjar-name "datamapper.jar"
+ :source-paths ["env/prod/clj"]
+ :resource-paths ["env/prod/resources"]}
+
+ :dev [:project/dev :profiles/dev]
+ :test [:project/dev :project/test :profiles/test]
+
+ :project/dev {:jvm-opts ["-Dconf=dev-config.edn"]
+ :dependencies [[binaryage/devtools "0.9.11"]
+ [cider/piggieback "0.4.2"]
+ [doo "0.1.11"]
+ [figwheel-sidecar "0.5.19"]
+ [pjstadig/humane-test-output "0.10.0"]
+ [prone "2019-07-08"]
+ [re-frisk "0.5.4.1"]
+ [ring/ring-devel "1.8.0"]
+ [ring/ring-mock "0.4.0"]]
+ :plugins [[com.jakemccrary/lein-test-refresh "0.24.1"]
+ [jonase/eastwood "0.3.5"]
+ [lein-doo "0.1.11"]
+ [lein-figwheel "0.5.19"]]
+ :cljsbuild{:builds
+ {:app
+ {:source-paths ["src/cljs" "src/cljc" "env/dev/cljs"]
+ :figwheel {:on-jsload "datamapper.core/mount-components"}
+ :compiler
+ {:output-dir "target/cljsbuild/public/js/out"
+ :closure-defines {"re_frame.trace.trace_enabled_QMARK_" true}
+ :optimizations :none
+ :preloads [re-frisk.preload]
+ :output-to "target/cljsbuild/public/js/app.js"
+ :asset-path "/js/out"
+ :source-map true
+ :main "datamapper.app"
+ :pretty-print true}}}}
+
+
+ :doo {:build "test"}
+ :source-paths ["env/dev/clj"]
+ :resource-paths ["env/dev/resources"]
+ :repl-options {:init-ns user}
+ :injections [(require 'pjstadig.humane-test-output)
+ (pjstadig.humane-test-output/activate!)]}
+ :project/test {:jvm-opts ["-Dconf=test-config.edn"]
+ :resource-paths ["env/test/resources"]
+ :cljsbuild
+ {:builds
+ {:test
+ {:source-paths ["src/cljc" "src/cljs" "test/cljs"]
+ :compiler
+ {:output-to "target/test.js"
+ :main "datamapper.doo-runner"
+ :optimizations :whitespace
+ :pretty-print true}}}}
+
+ }
+ :profiles/dev {}
+ :profiles/test {}})
diff --git a/resources/docs/docs.md b/resources/docs/docs.md
new file mode 100644
index 0000000..7f6c82d
--- /dev/null
+++ b/resources/docs/docs.md
@@ -0,0 +1,88 @@
+
Congratulations, your Luminus site is ready!
+
+This page will help guide you through the first steps of building your site.
+
+#### Why are you seeing this page?
+
+The `home-routes` handler in the `datamapper.routes.home` namespace
+defines the route that invokes the `home-page` function whenever an HTTP
+request is made to the `/` URI using the `GET` method.
+
+```
+(defn home-routes []
+ [""
+ {:middleware [middleware/wrap-csrf
+ middleware/wrap-formats]}
+ ["/" {:get home-page}]
+ ["/docs" {:get (fn [_]
+ (-> (response/ok (-> "docs/docs.md" io/resource slurp))
+ (response/header "Content-Type" "text/plain; charset=utf-8")))}]])
+```
+
+The `home-page` function will in turn call the `datamapper.layout/render` function
+to render the HTML content:
+
+```
+(defn home-page [_]
+ (layout/render "home.html"))
+```
+
+The page contains a link to the compiled ClojureScript found in the `target/cljsbuild/public` folder:
+
+```
+{% script "/js/app.js" %}
+```
+
+The rest of this page is rendered by ClojureScript found in the `src/cljs/datamapper/core.cljs` file.
+
+
+
+#### Organizing the routes
+
+The routes are aggregated and wrapped with middleware in the `datamapper.handler` namespace:
+
+```
+(mount/defstate app
+ :start
+ (middleware/wrap-base
+ (ring/ring-handler
+ (ring/router
+ [(home-routes)])
+ (ring/routes
+ (ring/create-resource-handler
+ {:path "/"})
+ (wrap-content-type
+ (wrap-webjars (constantly nil)))
+ (ring/create-default-handler
+ {:not-found
+ (constantly (error-page {:status 404, :title "404 - Page not found"}))
+ :method-not-allowed
+ (constantly (error-page {:status 405, :title "405 - Not allowed"}))
+ :not-acceptable
+ (constantly (error-page {:status 406, :title "406 - Not acceptable"}))})))))
+```
+
+The `app` definition groups all the routes in the application into a single handler.
+A default route group is added to handle the `404`, `405`, and `406` errors.
+
+learn more about routing »
+
+#### Managing your middleware
+
+Request middleware functions are located under the `datamapper.middleware` namespace.
+
+This namespace is reserved for any custom middleware for the application. Some default middleware is
+already defined here. The middleware is assembled in the `wrap-base` function.
+
+Middleware used for development is placed in the `datamapper.dev-middleware` namespace found in
+the `env/dev/clj/` source path.
+
+learn more about middleware »
+
+
+
+
+#### Need some help?
+
+Visit the [official documentation](http://www.luminusweb.net/docs) for examples
+on how to accomplish common tasks with Luminus. The `#luminus` channel on the [Clojurians Slack](http://clojurians.net/) and [Google Group](https://groups.google.com/forum/#!forum/luminusweb) are both great places to seek help and discuss projects with other users.
diff --git a/resources/html/error.html b/resources/html/error.html
new file mode 100644
index 0000000..fd31cc5
--- /dev/null
+++ b/resources/html/error.html
@@ -0,0 +1,49 @@
+
+
+
+ Something Bad Happened
+
+
+ {% style "/assets/bulma/css/bulma.min.css" %}
+
+
+
+
+
+
Error: {{status}}
+
+ {% if title %}
+ {{title}}
+ {% endif %}
+ {% if message %}
+ {{message}}
+ {% endif %}
+
+
+
+
diff --git a/resources/html/home.html b/resources/html/home.html
new file mode 100644
index 0000000..f4fc464
--- /dev/null
+++ b/resources/html/home.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+ Welcome to datamapper
+
+
+
+
+
+
+
+
Welcome to datamapper
+
If you're seeing this message, that means you haven't yet compiled your ClojureScript!
+
Please run lein figwheel
to start the ClojureScript compiler and reload the page.
+
For better ClojureScript development experience in Chrome follow these steps:
+
+ - Open DevTools
+
- Go to Settings ("three dots" icon in the upper right corner of DevTools > Menu > Settings F1 > General > Console)
+
- Check-in "Enable custom formatters"
+
- Close DevTools
+
- Open DevTools
+
+
See ClojureScript documentation for further details.
+
+
+
+
+
+
+ {% style "/assets/bulma/css/bulma.min.css" %}
+ {% style "/assets/material-icons/css/material-icons.min.css" %}
+ {% style "/css/screen.css" %}
+
+
+ {% script "/js/app.js" %}
+
+
diff --git a/resources/public/css/screen.css b/resources/public/css/screen.css
new file mode 100644
index 0000000..e90ddb8
--- /dev/null
+++ b/resources/public/css/screen.css
@@ -0,0 +1,35 @@
+html,
+body {
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
+}
+@font-face {
+ font-family: 'Material Icons';
+ font-style: normal;
+ font-weight: 400;
+ src: url(/assets/material-icons/iconfont/MaterialIcons-Regular.eot); /* For IE6-8 */
+ src: local('Material Icons'),
+ local('MaterialIcons-Regular'),
+ url(/assets/material-icons/iconfont/MaterialIcons-Regular.woff2) format('woff2'),
+ url(/assets/material-icons/iconfont/MaterialIcons-Regular.woff) format('woff'),
+ url(/assets/material-icons/iconfont/MaterialIcons-Regular.ttf) format('truetype');
+}
+.material-icons {
+ font-family: 'Material Icons';
+ font-weight: normal;
+ font-style: normal;
+ font-size: 24px; /* Preferred icon size */
+ display: inline-block;
+ line-height: 1;
+ text-transform: none;
+ letter-spacing: normal;
+ word-wrap: normal;
+ white-space: nowrap;
+ direction: ltr;
+ /* Support for all WebKit browsers. */
+ -webkit-font-smoothing: antialiased;
+ /* Support for Safari and Chrome. */
+ text-rendering: optimizeLegibility;
+ /* Support for Firefox. */
+ -moz-osx-font-smoothing: grayscale;
+}
+
diff --git a/resources/public/favicon.ico b/resources/public/favicon.ico
new file mode 100644
index 0000000..0e50cb2
Binary files /dev/null and b/resources/public/favicon.ico differ
diff --git a/resources/public/img/warning_clojure.png b/resources/public/img/warning_clojure.png
new file mode 100644
index 0000000..78d59e9
Binary files /dev/null and b/resources/public/img/warning_clojure.png differ
diff --git a/src/clj/datamapper/config.clj b/src/clj/datamapper/config.clj
new file mode 100644
index 0000000..1fb0c7a
--- /dev/null
+++ b/src/clj/datamapper/config.clj
@@ -0,0 +1,13 @@
+(ns datamapper.config
+ (:require
+ [cprop.core :refer [load-config]]
+ [cprop.source :as source]
+ [mount.core :refer [args defstate]]))
+
+(defstate env
+ :start
+ (load-config
+ :merge
+ [(args)
+ (source/from-system-props)
+ (source/from-env)]))
diff --git a/src/clj/datamapper/core.clj b/src/clj/datamapper/core.clj
new file mode 100644
index 0000000..d051c17
--- /dev/null
+++ b/src/clj/datamapper/core.clj
@@ -0,0 +1,58 @@
+(ns datamapper.core
+ (:require
+ [datamapper.handler :as handler]
+ [datamapper.nrepl :as nrepl]
+ [luminus.http-server :as http]
+ [datamapper.config :refer [env]]
+ [clojure.tools.cli :refer [parse-opts]]
+ [clojure.tools.logging :as log]
+ [mount.core :as mount])
+ (:gen-class))
+
+;; log uncaught exceptions in threads
+(Thread/setDefaultUncaughtExceptionHandler
+ (reify Thread$UncaughtExceptionHandler
+ (uncaughtException [_ thread ex]
+ (log/error {:what :uncaught-exception
+ :exception ex
+ :where (str "Uncaught exception on" (.getName thread))}))))
+
+(def cli-options
+ [["-p" "--port PORT" "Port number"
+ :parse-fn #(Integer/parseInt %)]])
+
+(mount/defstate ^{:on-reload :noop} http-server
+ :start
+ (http/start
+ (-> env
+ (assoc :handler (handler/app))
+ (update :io-threads #(or % (* 2 (.availableProcessors (Runtime/getRuntime)))))
+ (update :port #(or (-> env :options :port) %))))
+ :stop
+ (http/stop http-server))
+
+(mount/defstate ^{:on-reload :noop} repl-server
+ :start
+ (when (env :nrepl-port)
+ (nrepl/start {:bind (env :nrepl-bind)
+ :port (env :nrepl-port)}))
+ :stop
+ (when repl-server
+ (nrepl/stop repl-server)))
+
+
+(defn stop-app []
+ (doseq [component (:stopped (mount/stop))]
+ (log/info component "stopped"))
+ (shutdown-agents))
+
+(defn start-app [args]
+ (doseq [component (-> args
+ (parse-opts cli-options)
+ mount/start-with-args
+ :started)]
+ (log/info component "started"))
+ (.addShutdownHook (Runtime/getRuntime) (Thread. stop-app)))
+
+(defn -main [& args]
+ (start-app args))
diff --git a/src/clj/datamapper/handler.clj b/src/clj/datamapper/handler.clj
new file mode 100644
index 0000000..834fbc9
--- /dev/null
+++ b/src/clj/datamapper/handler.clj
@@ -0,0 +1,35 @@
+(ns datamapper.handler
+ (:require
+ [datamapper.middleware :as middleware]
+ [datamapper.layout :refer [error-page]]
+ [datamapper.routes.home :refer [home-routes]]
+ [reitit.ring :as ring]
+ [ring.middleware.content-type :refer [wrap-content-type]]
+ [ring.middleware.webjars :refer [wrap-webjars]]
+ [datamapper.env :refer [defaults]]
+ [mount.core :as mount]))
+
+(mount/defstate init-app
+ :start ((or (:init defaults) (fn [])))
+ :stop ((or (:stop defaults) (fn []))))
+
+(mount/defstate app-routes
+ :start
+ (ring/ring-handler
+ (ring/router
+ [(home-routes)])
+ (ring/routes
+ (ring/create-resource-handler
+ {:path "/"})
+ (wrap-content-type
+ (wrap-webjars (constantly nil)))
+ (ring/create-default-handler
+ {:not-found
+ (constantly (error-page {:status 404, :title "404 - Page not found"}))
+ :method-not-allowed
+ (constantly (error-page {:status 405, :title "405 - Not allowed"}))
+ :not-acceptable
+ (constantly (error-page {:status 406, :title "406 - Not acceptable"}))}))))
+
+(defn app []
+ (middleware/wrap-base #'app-routes))
diff --git a/src/clj/datamapper/layout.clj b/src/clj/datamapper/layout.clj
new file mode 100644
index 0000000..f74a0b9
--- /dev/null
+++ b/src/clj/datamapper/layout.clj
@@ -0,0 +1,39 @@
+(ns datamapper.layout
+ (:require
+ [clojure.java.io]
+ [selmer.parser :as parser]
+ [selmer.filters :as filters]
+ [markdown.core :refer [md-to-html-string]]
+ [ring.util.http-response :refer [content-type ok]]
+ [ring.util.anti-forgery :refer [anti-forgery-field]]
+ [ring.middleware.anti-forgery :refer [*anti-forgery-token*]]
+ [ring.util.response]))
+
+(parser/set-resource-path! (clojure.java.io/resource "html"))
+(parser/add-tag! :csrf-field (fn [_ _] (anti-forgery-field)))
+(filters/add-filter! :markdown (fn [content] [:safe (md-to-html-string content)]))
+
+(defn render
+ "renders the HTML template located relative to resources/html"
+ [request template & [params]]
+ (content-type
+ (ok
+ (parser/render-file
+ template
+ (assoc params
+ :page template
+ :csrf-token *anti-forgery-token*)))
+ "text/html; charset=utf-8"))
+
+(defn error-page
+ "error-details should be a map containing the following keys:
+ :status - error status
+ :title - error title (optional)
+ :message - detailed error message (optional)
+
+ returns a response map with the error page as the body
+ and the status specified by the status key"
+ [error-details]
+ {:status (:status error-details)
+ :headers {"Content-Type" "text/html; charset=utf-8"}
+ :body (parser/render-file "error.html" error-details)})
diff --git a/src/clj/datamapper/middleware.clj b/src/clj/datamapper/middleware.clj
new file mode 100644
index 0000000..5b1e5ca
--- /dev/null
+++ b/src/clj/datamapper/middleware.clj
@@ -0,0 +1,49 @@
+(ns datamapper.middleware
+ (:require
+ [datamapper.env :refer [defaults]]
+ [cheshire.generate :as cheshire]
+ [cognitect.transit :as transit]
+ [clojure.tools.logging :as log]
+ [datamapper.layout :refer [error-page]]
+ [ring.middleware.anti-forgery :refer [wrap-anti-forgery]]
+ [datamapper.middleware.formats :as formats]
+ [muuntaja.middleware :refer [wrap-format wrap-params]]
+ [datamapper.config :refer [env]]
+ [ring-ttl-session.core :refer [ttl-memory-store]]
+ [ring.middleware.defaults :refer [site-defaults wrap-defaults]])
+
+ )
+
+(defn wrap-internal-error [handler]
+ (fn [req]
+ (try
+ (handler req)
+ (catch Throwable t
+ (log/error t (.getMessage t))
+ (error-page {:status 500
+ :title "Something very bad has happened!"
+ :message "We've dispatched a team of highly trained gnomes to take care of the problem."})))))
+
+(defn wrap-csrf [handler]
+ (wrap-anti-forgery
+ handler
+ {:error-response
+ (error-page
+ {:status 403
+ :title "Invalid anti-forgery token"})}))
+
+
+(defn wrap-formats [handler]
+ (let [wrapped (-> handler wrap-params (wrap-format formats/instance))]
+ (fn [request]
+ ;; disable wrap-formats for websockets
+ ;; since they're not compatible with this middleware
+ ((if (:websocket? request) handler wrapped) request))))
+
+(defn wrap-base [handler]
+ (-> ((:middleware defaults) handler)
+ (wrap-defaults
+ (-> site-defaults
+ (assoc-in [:security :anti-forgery] false)
+ (assoc-in [:session :store] (ttl-memory-store (* 60 30)))))
+ wrap-internal-error))
diff --git a/src/clj/datamapper/middleware/formats.clj b/src/clj/datamapper/middleware/formats.clj
new file mode 100644
index 0000000..45176f4
--- /dev/null
+++ b/src/clj/datamapper/middleware/formats.clj
@@ -0,0 +1,15 @@
+(ns datamapper.middleware.formats
+ (:require
+ [cognitect.transit :as transit]
+ [luminus-transit.time :as time]
+ [muuntaja.core :as m]))
+
+(def instance
+ (m/create
+ (-> m/default-options
+ (update-in
+ [:formats "application/transit+json" :decoder-opts]
+ (partial merge time/time-deserialization-handlers))
+ (update-in
+ [:formats "application/transit+json" :encoder-opts]
+ (partial merge time/time-serialization-handlers)))))
diff --git a/src/clj/datamapper/nrepl.clj b/src/clj/datamapper/nrepl.clj
new file mode 100644
index 0000000..977df42
--- /dev/null
+++ b/src/clj/datamapper/nrepl.clj
@@ -0,0 +1,27 @@
+(ns datamapper.nrepl
+ (:require
+ [nrepl.server :as nrepl]
+ [clojure.tools.logging :as log]))
+
+(defn start
+ "Start a network repl for debugging on specified port followed by
+ an optional parameters map. The :bind, :transport-fn, :handler,
+ :ack-port and :greeting-fn will be forwarded to
+ clojure.tools.nrepl.server/start-server as they are."
+ [{:keys [port bind transport-fn handler ack-port greeting-fn]}]
+ (try
+ (log/info "starting nREPL server on port" port)
+ (nrepl/start-server :port port
+ :bind bind
+ :transport-fn transport-fn
+ :handler handler
+ :ack-port ack-port
+ :greeting-fn greeting-fn)
+
+ (catch Throwable t
+ (log/error t "failed to start nREPL")
+ (throw t))))
+
+(defn stop [server]
+ (nrepl/stop-server server)
+ (log/info "nREPL server stopped"))
diff --git a/src/clj/datamapper/routes/home.clj b/src/clj/datamapper/routes/home.clj
new file mode 100644
index 0000000..3ae9e75
--- /dev/null
+++ b/src/clj/datamapper/routes/home.clj
@@ -0,0 +1,20 @@
+(ns datamapper.routes.home
+ (:require
+ [datamapper.layout :as layout]
+ [clojure.java.io :as io]
+ [datamapper.middleware :as middleware]
+ [ring.util.response]
+ [ring.util.http-response :as response]))
+
+(defn home-page [request]
+ (layout/render request "home.html"))
+
+(defn home-routes []
+ [""
+ {:middleware [middleware/wrap-csrf
+ middleware/wrap-formats]}
+ ["/" {:get home-page}]
+ ["/docs" {:get (fn [_]
+ (-> (response/ok (-> "docs/docs.md" io/resource slurp))
+ (response/header "Content-Type" "text/plain; charset=utf-8")))}]])
+
diff --git a/src/cljc/datamapper/validation.cljc b/src/cljc/datamapper/validation.cljc
new file mode 100644
index 0000000..4cb22ef
--- /dev/null
+++ b/src/cljc/datamapper/validation.cljc
@@ -0,0 +1,2 @@
+(ns datamapper.validation
+ (:require [struct.core :as st]))
diff --git a/src/cljs/datamapper/ajax.cljs b/src/cljs/datamapper/ajax.cljs
new file mode 100644
index 0000000..e2f4d8e
--- /dev/null
+++ b/src/cljs/datamapper/ajax.cljs
@@ -0,0 +1,30 @@
+(ns datamapper.ajax
+ (:require
+ [ajax.core :as ajax]
+ [luminus-transit.time :as time]
+ [cognitect.transit :as transit]
+ [re-frame.core :as rf]))
+
+(defn local-uri? [{:keys [uri]}]
+ (not (re-find #"^\w+?://" uri)))
+
+(defn default-headers [request]
+ (if (local-uri? request)
+ (-> request
+ (update :headers #(merge {"x-csrf-token" js/csrfToken} %)))
+ request))
+
+;; injects transit serialization config into request options
+(defn as-transit [opts]
+ (merge {:raw false
+ :format :transit
+ :response-format :transit
+ :reader (transit/reader :json time/time-deserialization-handlers)
+ :writer (transit/writer :json time/time-serialization-handlers)}
+ opts))
+
+(defn load-interceptors! []
+ (swap! ajax/default-interceptors
+ conj
+ (ajax/to-interceptor {:name "default headers"
+ :request default-headers})))
diff --git a/src/cljs/datamapper/core.cljs b/src/cljs/datamapper/core.cljs
new file mode 100644
index 0000000..5191df8
--- /dev/null
+++ b/src/cljs/datamapper/core.cljs
@@ -0,0 +1,83 @@
+(ns datamapper.core
+ (:require
+ [day8.re-frame.http-fx]
+ [reagent.core :as r]
+ [re-frame.core :as rf]
+ [goog.events :as events]
+ [goog.history.EventType :as HistoryEventType]
+ [markdown.core :refer [md->html]]
+ [datamapper.ajax :as ajax]
+ [datamapper.events]
+ [reitit.core :as reitit]
+ [reitit.frontend.easy :as rfe]
+ [clojure.string :as string])
+ (:import goog.History))
+
+(defn nav-link [uri title page]
+ [:a.navbar-item
+ {:href uri
+ :class (when (= page @(rf/subscribe [:page])) :is-active)}
+ title])
+
+(defn navbar []
+ (r/with-let [expanded? (r/atom false)]
+ [:nav.navbar.is-info>div.container
+ [:div.navbar-brand
+ [:a.navbar-item {:href "/" :style {:font-weight :bold}} "datamapper"]
+ [:span.navbar-burger.burger
+ {:data-target :nav-menu
+ :on-click #(swap! expanded? not)
+ :class (when @expanded? :is-active)}
+ [:span][:span][:span]]]
+ [:div#nav-menu.navbar-menu
+ {:class (when @expanded? :is-active)}
+ [:div.navbar-start
+ [nav-link "#/" "Home" :home]
+ [nav-link "#/about" "About" :about]]]]))
+
+(defn about-page []
+ [:section.section>div.container>div.content
+ [:img {:src "/img/warning_clojure.png"}]])
+
+(defn home-page []
+ [:section.section>div.container>div.content
+ (when-let [docs @(rf/subscribe [:docs])]
+ [:div {:dangerouslySetInnerHTML {:__html (md->html docs)}}])])
+
+(def pages
+ {:home #'home-page
+ :about #'about-page})
+
+(defn page []
+ (if-let [page @(rf/subscribe [:page])]
+ [:div
+ [navbar]
+ [page]]))
+
+(defn navigate! [match _]
+ (rf/dispatch [:navigate match]))
+
+(def router
+ (reitit/router
+ [["/" {:name :home
+ :view #'home-page
+ :controllers [{:start (fn [_] (rf/dispatch [:page/init-home]))}]}]
+ ["/about" {:name :about
+ :view #'about-page}]]))
+
+(defn start-router! []
+ (rfe/start!
+ router
+ navigate!
+ {}))
+
+;; -------------------------
+;; Initialize app
+(defn mount-components []
+ (rf/clear-subscription-cache!)
+ (r/render [#'page] (.getElementById js/document "app")))
+
+(defn init! []
+ (start-router!)
+ (ajax/load-interceptors!)
+ (mount-components))
diff --git a/src/cljs/datamapper/events.cljs b/src/cljs/datamapper/events.cljs
new file mode 100644
index 0000000..f4c95ef
--- /dev/null
+++ b/src/cljs/datamapper/events.cljs
@@ -0,0 +1,78 @@
+(ns datamapper.events
+ (:require
+ [re-frame.core :as rf]
+ [ajax.core :as ajax]
+ [reitit.frontend.easy :as rfe]
+ [reitit.frontend.controllers :as rfc]))
+
+;;dispatchers
+
+(rf/reg-event-db
+ :navigate
+ (fn [db [_ match]]
+ (let [old-match (:common/route db)
+ new-match (assoc match :controllers
+ (rfc/apply-controllers (:controllers old-match) match))]
+ (assoc db :route new-match))))
+
+(rf/reg-fx
+ :navigate-fx!
+ (fn [[k & [params query]]]
+ (rfe/push-state k params query)))
+
+(rf/reg-event-fx
+ :navigate!
+ (fn [_ [_ url-key params query]]
+ {:navigate-fx! [url-key params query]}))
+
+(rf/reg-event-db
+ :set-docs
+ (fn [db [_ docs]]
+ (assoc db :docs docs)))
+
+(rf/reg-event-fx
+ :fetch-docs
+ (fn [_ _]
+ {:http-xhrio {:method :get
+ :uri "/docs"
+ :response-format (ajax/raw-response-format)
+ :on-success [:set-docs]}}))
+
+(rf/reg-event-db
+ :common/set-error
+ (fn [db [_ error]]
+ (assoc db :common/error error)))
+
+(rf/reg-event-fx
+ :page/init-home
+ (fn [_ _]
+ {:dispatch [:fetch-docs]}))
+
+;;subscriptions
+
+(rf/reg-sub
+ :route
+ (fn [db _]
+ (-> db :route)))
+
+(rf/reg-sub
+ :page-id
+ :<- [:route]
+ (fn [route _]
+ (-> route :data :name)))
+
+(rf/reg-sub
+ :page
+ :<- [:route]
+ (fn [route _]
+ (-> route :data :view)))
+
+(rf/reg-sub
+ :docs
+ (fn [db _]
+ (:docs db)))
+
+(rf/reg-sub
+ :common/error
+ (fn [db _]
+ (:common/error db)))
diff --git a/test-config.edn b/test-config.edn
new file mode 100644
index 0000000..42b7146
--- /dev/null
+++ b/test-config.edn
@@ -0,0 +1,6 @@
+;; WARNING
+;; The test-config.edn file is used for local environment variables, such as database credentials.
+;; This file is listed in .gitignore and will be excluded from version control by Git.
+
+{:port 3000
+ }
diff --git a/test/clj/datamapper/test/handler.clj b/test/clj/datamapper/test/handler.clj
new file mode 100644
index 0000000..b521fdb
--- /dev/null
+++ b/test/clj/datamapper/test/handler.clj
@@ -0,0 +1,27 @@
+(ns datamapper.test.handler
+ (:require
+ [clojure.test :refer :all]
+ [ring.mock.request :refer :all]
+ [datamapper.handler :refer :all]
+ [datamapper.middleware.formats :as formats]
+ [muuntaja.core :as m]
+ [mount.core :as mount]))
+
+(defn parse-json [body]
+ (m/decode formats/instance "application/json" body))
+
+(use-fixtures
+ :once
+ (fn [f]
+ (mount/start #'datamapper.config/env
+ #'datamapper.handler/app-routes)
+ (f)))
+
+(deftest test-app
+ (testing "main route"
+ (let [response ((app) (request :get "/"))]
+ (is (= 200 (:status response)))))
+
+ (testing "not-found route"
+ (let [response ((app) (request :get "/invalid"))]
+ (is (= 404 (:status response))))))
diff --git a/test/cljs/datamapper/core_test.cljs b/test/cljs/datamapper/core_test.cljs
new file mode 100644
index 0000000..cd61189
--- /dev/null
+++ b/test/cljs/datamapper/core_test.cljs
@@ -0,0 +1,9 @@
+(ns datamapper.core-test
+ (:require [cljs.test :refer-macros [is are deftest testing use-fixtures]]
+ [pjstadig.humane-test-output]
+ [reagent.core :as reagent :refer [atom]]
+ [datamapper.core :as rc]))
+
+(deftest test-home
+ (is (= true true)))
+
diff --git a/test/cljs/datamapper/doo_runner.cljs b/test/cljs/datamapper/doo_runner.cljs
new file mode 100644
index 0000000..9be1e55
--- /dev/null
+++ b/test/cljs/datamapper/doo_runner.cljs
@@ -0,0 +1,6 @@
+(ns datamapper.doo-runner
+ (:require [doo.runner :refer-macros [doo-tests]]
+ [datamapper.core-test]))
+
+(doo-tests 'datamapper.core-test)
+