Added Luminus app skeleton; added ADL.
This commit is contained in:
parent
8a923c1b33
commit
f3c0e728a4
52 changed files with 1761 additions and 0 deletions
13
src/clj/pastoralist/config.clj
Normal file
13
src/clj/pastoralist/config.clj
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
(ns pastoralist.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)]))
|
||||
86
src/clj/pastoralist/db/core.clj
Normal file
86
src/clj/pastoralist/db/core.clj
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
(ns pastoralist.db.core
|
||||
(:require
|
||||
[cheshire.core :refer [generate-string parse-string]]
|
||||
[clojure.java.jdbc :as jdbc]
|
||||
[clojure.tools.logging :as log]
|
||||
[conman.core :as conman]
|
||||
[java-time :as jt]
|
||||
[java-time.pre-java8]
|
||||
[pastoralist.config :refer [env]]
|
||||
[mount.core :refer [defstate]])
|
||||
(:import org.postgresql.util.PGobject
|
||||
java.sql.Array
|
||||
clojure.lang.IPersistentMap
|
||||
clojure.lang.IPersistentVector
|
||||
[java.sql
|
||||
BatchUpdateException
|
||||
PreparedStatement]))
|
||||
(defstate ^:dynamic *db*
|
||||
:start (if-let [jdbc-url (env :database-url)]
|
||||
(conman/connect! {:jdbc-url jdbc-url})
|
||||
(do
|
||||
(log/warn "database connection URL was not found, please set :database-url in your config, e.g: dev-config.edn")
|
||||
*db*))
|
||||
:stop (conman/disconnect! *db*))
|
||||
|
||||
(conman/bind-connection *db* "sql/queries.sql")
|
||||
|
||||
|
||||
(extend-protocol jdbc/IResultSetReadColumn
|
||||
java.sql.Timestamp
|
||||
(result-set-read-column [v _2 _3]
|
||||
(.toLocalDateTime v))
|
||||
java.sql.Date
|
||||
(result-set-read-column [v _2 _3]
|
||||
(.toLocalDate v))
|
||||
java.sql.Time
|
||||
(result-set-read-column [v _2 _3]
|
||||
(.toLocalTime v))
|
||||
Array
|
||||
(result-set-read-column [v _ _] (vec (.getArray v)))
|
||||
PGobject
|
||||
(result-set-read-column [pgobj _metadata _index]
|
||||
(let [type (.getType pgobj)
|
||||
value (.getValue pgobj)]
|
||||
(case type
|
||||
"json" (parse-string value true)
|
||||
"jsonb" (parse-string value true)
|
||||
"citext" (str value)
|
||||
value))))
|
||||
|
||||
(defn to-pg-json [value]
|
||||
(doto (PGobject.)
|
||||
(.setType "jsonb")
|
||||
(.setValue (generate-string value))))
|
||||
|
||||
(extend-type clojure.lang.IPersistentVector
|
||||
jdbc/ISQLParameter
|
||||
(set-parameter [v ^java.sql.PreparedStatement stmt ^long idx]
|
||||
(let [conn (.getConnection stmt)
|
||||
meta (.getParameterMetaData stmt)
|
||||
type-name (.getParameterTypeName meta idx)]
|
||||
(if-let [elem-type (when (= (first type-name) \_) (apply str (rest type-name)))]
|
||||
(.setObject stmt idx (.createArrayOf conn elem-type (to-array v)))
|
||||
(.setObject stmt idx (to-pg-json v))))))
|
||||
|
||||
(extend-protocol jdbc/ISQLValue
|
||||
java.util.Date
|
||||
(sql-value [v]
|
||||
(java.sql.Timestamp. (.getTime v)))
|
||||
java.time.LocalTime
|
||||
(sql-value [v]
|
||||
(jt/sql-time v))
|
||||
java.time.LocalDate
|
||||
(sql-value [v]
|
||||
(jt/sql-date v))
|
||||
java.time.LocalDateTime
|
||||
(sql-value [v]
|
||||
(jt/sql-timestamp v))
|
||||
java.time.ZonedDateTime
|
||||
(sql-value [v]
|
||||
(jt/sql-timestamp v))
|
||||
IPersistentMap
|
||||
(sql-value [value] (to-pg-json value))
|
||||
IPersistentVector
|
||||
(sql-value [value] (to-pg-json value)))
|
||||
|
||||
64
src/clj/pastoralist/handler.clj
Normal file
64
src/clj/pastoralist/handler.clj
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
(ns pastoralist.handler
|
||||
(:require
|
||||
[pastoralist.middleware :as middleware]
|
||||
[pastoralist.layout :refer [error-page]]
|
||||
[pastoralist.routes.home :refer [home-routes]]
|
||||
[pastoralist.routes.services :refer [service-routes]]
|
||||
[pastoralist.routes.oauth :refer [oauth-routes]]
|
||||
[reitit.swagger-ui :as swagger-ui]
|
||||
[reitit.ring :as ring]
|
||||
[ring.middleware.content-type :refer [wrap-content-type]]
|
||||
[ring.middleware.webjars :refer [wrap-webjars]]
|
||||
[pastoralist.env :refer [defaults]]
|
||||
[mount.core :as mount]
|
||||
[clojure.tools.logging :as log]
|
||||
[pastoralist.config :refer [env]]))
|
||||
|
||||
(mount/defstate init-app
|
||||
:start ((or (:init defaults) (fn [])))
|
||||
:stop ((or (:stop defaults) (fn []))))
|
||||
|
||||
(defn init
|
||||
"init will be called once when
|
||||
app is deployed as a servlet on
|
||||
an app server such as Tomcat
|
||||
put any initialization code here"
|
||||
[]
|
||||
(doseq [component (:started (mount/start))]
|
||||
(log/info component "started")))
|
||||
|
||||
(defn destroy
|
||||
"destroy will be called when your application
|
||||
shuts down, put any clean up code here"
|
||||
[]
|
||||
(doseq [component (:stopped (mount/stop))]
|
||||
(log/info component "stopped"))
|
||||
(shutdown-agents)
|
||||
(log/info "pastoralist has shut down!"))
|
||||
|
||||
(mount/defstate app-routes
|
||||
:start
|
||||
(ring/ring-handler
|
||||
(ring/router
|
||||
[(home-routes)
|
||||
(service-routes)
|
||||
(oauth-routes)])
|
||||
(ring/routes
|
||||
(swagger-ui/create-swagger-ui-handler
|
||||
{:path "/swagger-ui"
|
||||
:url "/api/swagger.json"
|
||||
:config {:validator-url nil}})
|
||||
(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))
|
||||
39
src/clj/pastoralist/layout.clj
Normal file
39
src/clj/pastoralist/layout.clj
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
(ns pastoralist.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)})
|
||||
49
src/clj/pastoralist/middleware.clj
Normal file
49
src/clj/pastoralist/middleware.clj
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
(ns pastoralist.middleware
|
||||
(:require
|
||||
[pastoralist.env :refer [defaults]]
|
||||
[cheshire.generate :as cheshire]
|
||||
[cognitect.transit :as transit]
|
||||
[clojure.tools.logging :as log]
|
||||
[pastoralist.layout :refer [error-page]]
|
||||
[ring.middleware.anti-forgery :refer [wrap-anti-forgery]]
|
||||
[pastoralist.middleware.formats :as formats]
|
||||
[muuntaja.middleware :refer [wrap-format wrap-params]]
|
||||
[pastoralist.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))
|
||||
24
src/clj/pastoralist/middleware/exception.clj
Normal file
24
src/clj/pastoralist/middleware/exception.clj
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
(ns pastoralist.middleware.exception
|
||||
(:require [clojure.tools.logging :as log]
|
||||
[expound.alpha :as expound]
|
||||
[reitit.coercion :as coercion]
|
||||
[reitit.ring.middleware.exception :as exception]))
|
||||
|
||||
(defn coercion-error-handler [status]
|
||||
(let [printer (expound/custom-printer {:print-specs? false})]
|
||||
(fn [exception request]
|
||||
{:status status
|
||||
:headers {"Content-Type" "text/html"}
|
||||
:body (with-out-str (printer (-> exception ex-data :problems)))})))
|
||||
|
||||
(def exception-middleware
|
||||
(exception/create-exception-middleware
|
||||
(merge
|
||||
exception/default-handlers
|
||||
{;; log stack-traces for all exceptions
|
||||
::exception/wrap (fn [handler e request]
|
||||
(log/error e (.getMessage e))
|
||||
(handler e request))
|
||||
;; human-optimized validation messages
|
||||
::coercion/request-coercion (coercion-error-handler 400)
|
||||
::coercion/response-coercion (coercion-error-handler 500)})))
|
||||
15
src/clj/pastoralist/middleware/formats.clj
Normal file
15
src/clj/pastoralist/middleware/formats.clj
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
(ns pastoralist.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)))))
|
||||
27
src/clj/pastoralist/nrepl.clj
Normal file
27
src/clj/pastoralist/nrepl.clj
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
(ns pastoralist.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"))
|
||||
36
src/clj/pastoralist/oauth.clj
Normal file
36
src/clj/pastoralist/oauth.clj
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
(ns pastoralist.oauth
|
||||
(:require
|
||||
[pastoralist.config :refer [env]]
|
||||
[oauth.client :as oauth]
|
||||
[mount.core :refer [defstate]]
|
||||
[clojure.tools.logging :as log]))
|
||||
|
||||
(defstate consumer
|
||||
:start (oauth/make-consumer
|
||||
(env :oauth-consumer-key)
|
||||
(env :oauth-consumer-secret)
|
||||
(env :request-token-uri)
|
||||
(env :access-token-uri)
|
||||
(env :authorize-uri)
|
||||
:hmac-sha1))
|
||||
|
||||
(defn oauth-callback-uri
|
||||
"Generates the oauth request callback URI"
|
||||
[{:keys [headers]}]
|
||||
(str (headers "x-forwarded-proto") "://" (headers "host") "/oauth/oauth-callback"))
|
||||
|
||||
(defn fetch-request-token
|
||||
"Fetches a request token."
|
||||
[request]
|
||||
(let [callback-uri (oauth-callback-uri request)]
|
||||
(log/info "Fetching request token using callback-uri" callback-uri)
|
||||
(oauth/request-token consumer (oauth-callback-uri request))))
|
||||
|
||||
(defn fetch-access-token
|
||||
[request_token]
|
||||
(oauth/access-token consumer request_token (:oauth_verifier request_token)))
|
||||
|
||||
(defn auth-redirect-uri
|
||||
"Gets the URI the user should be redirected to when authenticating."
|
||||
[request-token]
|
||||
(str (oauth/user-approval-uri consumer request-token)))
|
||||
21
src/clj/pastoralist/routes/home.clj
Normal file
21
src/clj/pastoralist/routes/home.clj
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
(ns pastoralist.routes.home
|
||||
(:require
|
||||
[pastoralist.layout :as layout]
|
||||
[pastoralist.db.core :as db]
|
||||
[clojure.java.io :as io]
|
||||
[pastoralist.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")))}]])
|
||||
|
||||
33
src/clj/pastoralist/routes/oauth.clj
Normal file
33
src/clj/pastoralist/routes/oauth.clj
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
(ns pastoralist.routes.oauth
|
||||
(:require
|
||||
[ring.util.http-response :refer [ok found]]
|
||||
[clojure.java.io :as io]
|
||||
[pastoralist.oauth :as oauth]
|
||||
[clojure.tools.logging :as log]))
|
||||
|
||||
(defn oauth-init
|
||||
"Initiates the Twitter OAuth"
|
||||
[request]
|
||||
(-> (oauth/fetch-request-token request)
|
||||
:oauth_token
|
||||
oauth/auth-redirect-uri
|
||||
found))
|
||||
|
||||
(defn oauth-callback
|
||||
"Handles the callback from Twitter."
|
||||
[{:keys [session params]}]
|
||||
; oauth request was denied by user
|
||||
(if (:denied params)
|
||||
(-> (found "/")
|
||||
(assoc :flash {:denied true}))
|
||||
; fetch the request token and do anything else you wanna do if not denied.
|
||||
(let [{:keys [user_id screen_name]} (oauth/fetch-access-token params)]
|
||||
(log/info "successfully authenticated as" user_id screen_name)
|
||||
(-> (found "/")
|
||||
(assoc :session
|
||||
(assoc session :user-id user_id :screen-name screen_name))))))
|
||||
|
||||
(defn oauth-routes []
|
||||
["/oauth"
|
||||
["/oauth-init" {:get oauth-init}]
|
||||
["/oauth-callback" {:get oauth-callback}]])
|
||||
91
src/clj/pastoralist/routes/services.clj
Normal file
91
src/clj/pastoralist/routes/services.clj
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
(ns pastoralist.routes.services
|
||||
(:require
|
||||
[reitit.swagger :as swagger]
|
||||
[reitit.swagger-ui :as swagger-ui]
|
||||
[reitit.ring.coercion :as coercion]
|
||||
[reitit.coercion.spec :as spec-coercion]
|
||||
[reitit.ring.middleware.muuntaja :as muuntaja]
|
||||
[reitit.ring.middleware.multipart :as multipart]
|
||||
[reitit.ring.middleware.parameters :as parameters]
|
||||
[pastoralist.middleware.formats :as formats]
|
||||
[pastoralist.middleware.exception :as exception]
|
||||
[ring.util.http-response :refer :all]
|
||||
[clojure.java.io :as io]))
|
||||
|
||||
(defn service-routes []
|
||||
["/api"
|
||||
{:coercion spec-coercion/coercion
|
||||
:muuntaja formats/instance
|
||||
:swagger {:id ::api}
|
||||
:middleware [;; query-params & form-params
|
||||
parameters/parameters-middleware
|
||||
;; content-negotiation
|
||||
muuntaja/format-negotiate-middleware
|
||||
;; encoding response body
|
||||
muuntaja/format-response-middleware
|
||||
;; exception handling
|
||||
exception/exception-middleware
|
||||
;; decoding request body
|
||||
muuntaja/format-request-middleware
|
||||
;; coercing response bodys
|
||||
coercion/coerce-response-middleware
|
||||
;; coercing request parameters
|
||||
coercion/coerce-request-middleware
|
||||
;; multipart
|
||||
multipart/multipart-middleware]}
|
||||
|
||||
;; swagger documentation
|
||||
["" {:no-doc true
|
||||
:swagger {:info {:title "my-api"
|
||||
:description "https://cljdoc.org/d/metosin/reitit"}}}
|
||||
|
||||
["/swagger.json"
|
||||
{:get (swagger/create-swagger-handler)}]
|
||||
|
||||
["/api-docs/*"
|
||||
{:get (swagger-ui/create-swagger-ui-handler
|
||||
{:url "/api/swagger.json"
|
||||
:config {:validator-url nil}})}]]
|
||||
|
||||
["/ping"
|
||||
{:get (constantly (ok {:message "pong"}))}]
|
||||
|
||||
|
||||
["/math"
|
||||
{:swagger {:tags ["math"]}}
|
||||
|
||||
["/plus"
|
||||
{:get {:summary "plus with spec query parameters"
|
||||
:parameters {:query {:x int?, :y int?}}
|
||||
:responses {200 {:body {:total pos-int?}}}
|
||||
:handler (fn [{{{:keys [x y]} :query} :parameters}]
|
||||
{:status 200
|
||||
:body {:total (+ x y)}})}
|
||||
:post {:summary "plus with spec body parameters"
|
||||
:parameters {:body {:x int?, :y int?}}
|
||||
:responses {200 {:body {:total pos-int?}}}
|
||||
:handler (fn [{{{:keys [x y]} :body} :parameters}]
|
||||
{:status 200
|
||||
:body {:total (+ x y)}})}}]]
|
||||
|
||||
["/files"
|
||||
{:swagger {:tags ["files"]}}
|
||||
|
||||
["/upload"
|
||||
{:post {:summary "upload a file"
|
||||
:parameters {:multipart {:file multipart/temp-file-part}}
|
||||
:responses {200 {:body {:name string?, :size int?}}}
|
||||
:handler (fn [{{{:keys [file]} :multipart} :parameters}]
|
||||
{:status 200
|
||||
:body {:name (:filename file)
|
||||
:size (:size file)}})}}]
|
||||
|
||||
["/download"
|
||||
{:get {:summary "downloads a file"
|
||||
:swagger {:produces ["image/png"]}
|
||||
:handler (fn [_]
|
||||
{:status 200
|
||||
:headers {"Content-Type" "image/png"}
|
||||
:body (-> "public/img/warning_clojure.png"
|
||||
(io/resource)
|
||||
(io/input-stream))})}}]]])
|
||||
2
src/cljc/pastoralist/validation.cljc
Normal file
2
src/cljc/pastoralist/validation.cljc
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
(ns pastoralist.validation
|
||||
(:require [struct.core :as st]))
|
||||
30
src/cljs/pastoralist/ajax.cljs
Normal file
30
src/cljs/pastoralist/ajax.cljs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
(ns pastoralist.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})))
|
||||
55
src/cljs/pastoralist/core.cljs
Normal file
55
src/cljs/pastoralist/core.cljs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
(ns pastoralist.core
|
||||
(:require
|
||||
[kee-frame.core :as kf]
|
||||
[re-frame.core :as rf]
|
||||
[ajax.core :as http]
|
||||
[pastoralist.ajax :as ajax]
|
||||
[pastoralist.routing :as routing]
|
||||
[pastoralist.view :as view]))
|
||||
|
||||
|
||||
(rf/reg-event-fx
|
||||
::load-about-page
|
||||
(constantly nil))
|
||||
|
||||
(kf/reg-controller
|
||||
::about-controller
|
||||
{:params (constantly true)
|
||||
:start [::load-about-page]})
|
||||
|
||||
(rf/reg-sub
|
||||
:docs
|
||||
(fn [db _]
|
||||
(:docs db)))
|
||||
|
||||
(kf/reg-chain
|
||||
::load-home-page
|
||||
(fn [_ _]
|
||||
{:http-xhrio {:method :get
|
||||
:uri "/docs"
|
||||
:response-format (http/raw-response-format)
|
||||
:on-failure [:common/set-error]}})
|
||||
(fn [{:keys [db]} [_ docs]]
|
||||
{:db (assoc db :docs docs)}))
|
||||
|
||||
|
||||
(kf/reg-controller
|
||||
::home-controller
|
||||
{:params (constantly true)
|
||||
:start [::load-home-page]})
|
||||
|
||||
;; -------------------------
|
||||
;; Initialize app
|
||||
(defn mount-components
|
||||
([] (mount-components true))
|
||||
([debug?]
|
||||
(rf/clear-subscription-cache!)
|
||||
(kf/start! {:debug? (boolean debug?)
|
||||
:routes routing/routes
|
||||
:hash-routing? true
|
||||
:initial-db {}
|
||||
:root-component [view/root-component]})))
|
||||
|
||||
(defn init! [debug?]
|
||||
(ajax/load-interceptors!)
|
||||
(mount-components debug?))
|
||||
24
src/cljs/pastoralist/routing.cljs
Normal file
24
src/cljs/pastoralist/routing.cljs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
(ns pastoralist.routing
|
||||
(:require
|
||||
[re-frame.core :as rf]))
|
||||
|
||||
(def routes
|
||||
[["/" :home]
|
||||
["/about" :about]])
|
||||
|
||||
(rf/reg-sub
|
||||
:nav/route
|
||||
:<- [:kee-frame/route]
|
||||
identity)
|
||||
|
||||
(rf/reg-event-fx
|
||||
:nav/route-name
|
||||
(fn [_ [_ route-name]]
|
||||
{:navigate-to [route-name]}))
|
||||
|
||||
|
||||
(rf/reg-sub
|
||||
:nav/page
|
||||
:<- [:nav/route]
|
||||
(fn [route _]
|
||||
(-> route :data :name)))
|
||||
45
src/cljs/pastoralist/view.cljs
Normal file
45
src/cljs/pastoralist/view.cljs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
(ns pastoralist.view
|
||||
(:require
|
||||
[kee-frame.core :as kf]
|
||||
[markdown.core :refer [md->html]]
|
||||
[reagent.core :as r]
|
||||
[re-frame.core :as rf]))
|
||||
|
||||
(defn nav-link [title page]
|
||||
[:a.navbar-item
|
||||
{:href (kf/path-for [page])
|
||||
:class (when (= page @(rf/subscribe [:nav/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}} "pastoralist"]
|
||||
[: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]]]]))
|
||||
|
||||
(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)}}])])
|
||||
|
||||
(defn root-component []
|
||||
[:div
|
||||
[navbar]
|
||||
[kf/switch-route (fn [route] (get-in route [:data :name]))
|
||||
:home home-page
|
||||
:about about-page
|
||||
nil [:div ""]]])
|
||||
Loading…
Add table
Add a link
Reference in a new issue