Initial project structure
This commit is contained in:
parent
dd2040e0b1
commit
c3e12f32a3
19 changed files with 538 additions and 0 deletions
1
.lein-env
Normal file
1
.lein-env
Normal file
|
@ -0,0 +1 @@
|
|||
{:dev true, :port 3000, :nrepl-port 7000, :log-level :trace}
|
1
Procfile
Normal file
1
Procfile
Normal file
|
@ -0,0 +1 @@
|
|||
web: java $JVM_OPTS -cp target/mailhead.jar clojure.main -m mailhead.core
|
11
env/dev/clj/mailhead/config.clj
vendored
Normal file
11
env/dev/clj/mailhead/config.clj
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
(ns mailhead.config
|
||||
(:require [selmer.parser :as parser]
|
||||
[taoensso.timbre :as timbre]
|
||||
[mailhead.dev-middleware :refer [wrap-dev]]))
|
||||
|
||||
(def defaults
|
||||
{:init
|
||||
(fn []
|
||||
(parser/cache-off!)
|
||||
(timbre/info "\n-=[mailhead started successfully using the development profile]=-"))
|
||||
:middleware wrap-dev})
|
10
env/dev/clj/mailhead/dev_middleware.clj
vendored
Normal file
10
env/dev/clj/mailhead/dev_middleware.clj
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
(ns mailhead.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))
|
8
env/prod/clj/mailhead/config.clj
vendored
Normal file
8
env/prod/clj/mailhead/config.clj
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
(ns mailhead.config
|
||||
(:require [taoensso.timbre :as timbre]))
|
||||
|
||||
(def defaults
|
||||
{:init
|
||||
(fn []
|
||||
(timbre/info "\n-=[mailhead started successfully]=-"))
|
||||
:middleware identity})
|
2
profiles.clj
Normal file
2
profiles.clj
Normal file
|
@ -0,0 +1,2 @@
|
|||
{:profiles/dev {:env {}}
|
||||
:profiles/test {:env {}}}
|
60
project.clj
Normal file
60
project.clj
Normal file
|
@ -0,0 +1,60 @@
|
|||
(defproject mailhead "0.1.0-SNAPSHOT"
|
||||
|
||||
:description "FIXME: write description"
|
||||
:url "http://example.com/FIXME"
|
||||
|
||||
:dependencies [[org.clojure/clojure "1.7.0"]
|
||||
[selmer "0.9.5"]
|
||||
[markdown-clj "0.9.82"]
|
||||
[environ "1.0.1"]
|
||||
[metosin/ring-middleware-format "0.6.0"]
|
||||
[metosin/ring-http-response "0.6.5"]
|
||||
[bouncer "0.3.3"]
|
||||
[org.clojure/tools.nrepl "0.2.12"]
|
||||
[org.webjars/bootstrap "3.3.6"]
|
||||
[org.webjars/jquery "2.1.4"]
|
||||
[com.taoensso/tower "3.0.2"]
|
||||
[com.taoensso/timbre "4.1.4"]
|
||||
[com.fzakaria/slf4j-timbre "0.2.1"]
|
||||
[compojure "1.4.0"]
|
||||
[ring-webjars "0.1.1"]
|
||||
[ring/ring-defaults "0.1.5"]
|
||||
[ring "1.4.0" :exclusions [ring/ring-jetty-adapter]]
|
||||
[mount "0.1.5"]
|
||||
[org.immutant/web "2.1.1" :exclusions [ch.qos.logback/logback-classic]]]
|
||||
|
||||
:min-lein-version "2.0.0"
|
||||
:uberjar-name "mailhead.jar"
|
||||
:jvm-opts ["-server"]
|
||||
|
||||
:main mailhead.core
|
||||
|
||||
:plugins [[lein-environ "1.0.1"]]
|
||||
:profiles
|
||||
{:uberjar {:omit-source true
|
||||
:env {:production true}
|
||||
:aot :all
|
||||
:source-paths ["env/prod/clj"]}
|
||||
:dev [:project/dev :profiles/dev]
|
||||
:test [:project/test :profiles/test]
|
||||
:project/dev {:dependencies [[prone "0.8.2"]
|
||||
[ring/ring-mock "0.3.0"]
|
||||
[ring/ring-devel "1.4.0"]
|
||||
[pjstadig/humane-test-output "0.7.1"]]
|
||||
|
||||
|
||||
:source-paths ["env/dev/clj"]
|
||||
:repl-options {:init-ns mailhead.core}
|
||||
:injections [(require 'pjstadig.humane-test-output)
|
||||
(pjstadig.humane-test-output/activate!)]
|
||||
;;when :nrepl-port is set the application starts the nREPL server on load
|
||||
:env {:dev true
|
||||
:port 3000
|
||||
:nrepl-port 7000
|
||||
:log-level :trace}}
|
||||
:project/test {:env {:test true
|
||||
:port 3001
|
||||
:nrepl-port 7001
|
||||
:log-level :trace}}
|
||||
:profiles/dev {}
|
||||
:profiles/test {}})
|
23
resources/docs/docs.md
Normal file
23
resources/docs/docs.md
Normal file
|
@ -0,0 +1,23 @@
|
|||
|
||||
|
||||
### Managing Your Middleware
|
||||
|
||||
Request middleware functions are located under the `mailhead.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 `mailhead.dev-middleware` namespace found in
|
||||
the `env/dev/clj/` source path.
|
||||
|
||||
### Here are some links to get started
|
||||
|
||||
1. [HTML templating](http://www.luminusweb.net/docs/html_templating.md)
|
||||
2. [Accessing the database](http://www.luminusweb.net/docs/database.md)
|
||||
3. [Serving static resources](http://www.luminusweb.net/docs/static_resources.md)
|
||||
4. [Setting response types](http://www.luminusweb.net/docs/responses.md)
|
||||
5. [Defining routes](http://www.luminusweb.net/docs/routes.md)
|
||||
6. [Adding middleware](http://www.luminusweb.net/docs/middleware.md)
|
||||
7. [Sessions and cookies](http://www.luminusweb.net/docs/sessions_cookies.md)
|
||||
8. [Security](http://www.luminusweb.net/docs/security.md)
|
||||
9. [Deploying the application](http://www.luminusweb.net/docs/deployment.md)
|
58
resources/public/css/screen.css
Normal file
58
resources/public/css/screen.css
Normal file
|
@ -0,0 +1,58 @@
|
|||
html,
|
||||
body {
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
height: 100%;
|
||||
padding-top: 40px;
|
||||
}
|
||||
{% if cljs %}
|
||||
@-moz-keyframes three-quarters-loader {
|
||||
0% {
|
||||
-moz-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-moz-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes three-quarters-loader {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes three-quarters-loader {
|
||||
0% {
|
||||
-moz-transform: rotate(0deg);
|
||||
-ms-transform: rotate(0deg);
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-moz-transform: rotate(360deg);
|
||||
-ms-transform: rotate(360deg);
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
/* :not(:required) hides this rule from IE9 and below */
|
||||
.three-quarters-loader:not(:required) {
|
||||
-moz-animation: three-quarters-loader 1250ms infinite linear;
|
||||
-webkit-animation: three-quarters-loader 1250ms infinite linear;
|
||||
animation: three-quarters-loader 1250ms infinite linear;
|
||||
border: 8px solid #38e;
|
||||
border-right-color: transparent;
|
||||
border-radius: 16px;
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
text-indent: -9999px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
{% endif %}
|
0
resources/public/favicon.ico
Normal file
0
resources/public/favicon.ico
Normal file
4
resources/templates/about.html
Normal file
4
resources/templates/about.html
Normal file
|
@ -0,0 +1,4 @@
|
|||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<p>this is the story of mailhead... work in progress</p>
|
||||
{% endblock %}
|
61
resources/templates/base.html
Normal file
61
resources/templates/base.html
Normal file
|
@ -0,0 +1,61 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<META name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Welcome to mailhead</title>
|
||||
|
||||
<!-- styles -->
|
||||
{% style "/assets/bootstrap/css/bootstrap.min.css" %}
|
||||
{% style "/assets/bootstrap/css/bootstrap-theme.min.css" %}
|
||||
{% style "/css/screen.css" %}
|
||||
</head>
|
||||
<body>
|
||||
<!-- navbar -->
|
||||
<div id="navbar">
|
||||
<nav class="navbar navbar-inverse navbar-fixed-top">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<button class="navbar-toggle"
|
||||
data-target="#app-navbar"
|
||||
data-toggle="collapse"
|
||||
aria-expanded="false"
|
||||
aria-controls="navbar">
|
||||
<span class="sr-only">Toggle Navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a href="{{servlet-context}}/" class="navbar-brand">mailhead</a>
|
||||
</div>
|
||||
<div id="app-navbar" class="navbar-collapse collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li {% ifequal page "home.html" %} class="active"{%endifequal%}>
|
||||
<a href="{{servlet-context}}/">Home</a>
|
||||
</li>
|
||||
<li {% ifequal page "about.html" %} class="active"{%endifequal%}>
|
||||
<a href="{{servlet-context}}/about">About</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
||||
<!-- scripts -->
|
||||
{% script "/assets/jquery/jquery.min.js" %}
|
||||
{% script "/assets/bootstrap/js/bootstrap.min.js" %}
|
||||
{% script "/assets/bootstrap/js/collapse.js" %}
|
||||
|
||||
<script type="text/javascript">
|
||||
var context = "{{servlet-context}}";
|
||||
</script>
|
||||
{% block page-scripts %}
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
56
resources/templates/error.html
Normal file
56
resources/templates/error.html
Normal file
|
@ -0,0 +1,56 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Something bad happened</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
{% style "/assets/bootstrap/css/bootstrap.min.css" %}
|
||||
{% style "/assets/bootstrap/css/bootstrap-theme.min.css" %}
|
||||
<style type="text/css">
|
||||
html {
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
min-width: 100%;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
html body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
html .container-fluid {
|
||||
display: table;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
html .row-fluid {
|
||||
display: table-cell;
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
<div class="col-lg-12">
|
||||
<div class="centering text-center">
|
||||
<div class="text-center">
|
||||
<h1><span class="text-danger">Error: {{status}}</span></h1>
|
||||
<hr>
|
||||
{% if title %}
|
||||
<h2 class="without-margin">{{title}}</h2>
|
||||
{% endif %}
|
||||
{% if message %}
|
||||
<h4 class="text-danger">{{message}}</h4>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
14
resources/templates/home.html
Normal file
14
resources/templates/home.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<div class="jumbotron">
|
||||
<h1>Welcome to mailhead</h1>
|
||||
<p>Time to start building your site!</p>
|
||||
<p><a class="btn btn-primary btn-lg" href="http://luminusweb.net">Learn more »</a></p>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="span12">
|
||||
{{docs|markdown}}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
64
src/mailhead/core.clj
Normal file
64
src/mailhead/core.clj
Normal file
|
@ -0,0 +1,64 @@
|
|||
(ns mailhead.core
|
||||
(:require [mailhead.handler :refer [app init destroy]]
|
||||
[immutant.web :as immutant]
|
||||
[clojure.tools.nrepl.server :as nrepl]
|
||||
[taoensso.timbre :as timbre]
|
||||
[environ.core :refer [env]])
|
||||
(:gen-class))
|
||||
|
||||
(defonce nrepl-server (atom nil))
|
||||
|
||||
(defn parse-port [port]
|
||||
(when port
|
||||
(cond
|
||||
(string? port) (Integer/parseInt port)
|
||||
(number? port) port
|
||||
:else (throw (Exception. (str "invalid port value: " port))))))
|
||||
|
||||
(defn stop-nrepl []
|
||||
(when-let [server @nrepl-server]
|
||||
(nrepl/stop-server server)))
|
||||
|
||||
(defn start-nrepl
|
||||
"Start a network repl for debugging when the :nrepl-port is set in the environment."
|
||||
[]
|
||||
(if @nrepl-server
|
||||
(timbre/error "nREPL is already running!")
|
||||
(when-let [port (env :nrepl-port)]
|
||||
(try
|
||||
(->> port
|
||||
(parse-port)
|
||||
(nrepl/start-server :port)
|
||||
(reset! nrepl-server))
|
||||
(timbre/info "nREPL server started on port" port)
|
||||
(catch Throwable t
|
||||
(timbre/error t "failed to start nREPL"))))))
|
||||
|
||||
(defn http-port [port]
|
||||
(parse-port (or port (env :port) 3000)))
|
||||
|
||||
(defonce http-server (atom nil))
|
||||
|
||||
(defn start-http-server [port]
|
||||
(init)
|
||||
(reset! http-server (immutant/run app :host "0.0.0.0" :port port)))
|
||||
|
||||
(defn stop-http-server []
|
||||
(when @http-server
|
||||
(destroy)
|
||||
(immutant/stop @http-server)
|
||||
(reset! http-server nil)))
|
||||
|
||||
(defn stop-app []
|
||||
(stop-nrepl)
|
||||
(stop-http-server)
|
||||
(shutdown-agents))
|
||||
|
||||
(defn start-app [[port]]
|
||||
(.addShutdownHook (Runtime/getRuntime) (Thread. stop-app))
|
||||
(start-nrepl)
|
||||
(start-http-server (http-port port))
|
||||
(timbre/info "server started on port:" (:port @http-server)))
|
||||
|
||||
(defn -main [& args]
|
||||
(start-app args))
|
48
src/mailhead/handler.clj
Normal file
48
src/mailhead/handler.clj
Normal file
|
@ -0,0 +1,48 @@
|
|||
(ns mailhead.handler
|
||||
(:require [compojure.core :refer [defroutes routes wrap-routes]]
|
||||
[mailhead.layout :refer [error-page]]
|
||||
[mailhead.routes.home :refer [home-routes]]
|
||||
[mailhead.middleware :as middleware]
|
||||
[compojure.route :as route]
|
||||
[taoensso.timbre :as timbre]
|
||||
[taoensso.timbre.appenders.3rd-party.rotor :as rotor]
|
||||
[selmer.parser :as parser]
|
||||
[environ.core :refer [env]]
|
||||
[mailhead.config :refer [defaults]]
|
||||
[mount.core :as mount]))
|
||||
|
||||
(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"
|
||||
[]
|
||||
|
||||
(timbre/merge-config!
|
||||
{:level ((fnil keyword :info) (env :log-level))
|
||||
:appenders {:rotor (rotor/rotor-appender
|
||||
{:path (or (env :log-path) "mailhead.log")
|
||||
:max-size (* 512 1024)
|
||||
:backlog 10})}})
|
||||
(doseq [component (:started (mount/start))]
|
||||
(timbre/info component "started"))
|
||||
((:init defaults)))
|
||||
|
||||
(defn destroy
|
||||
"destroy will be called when your application
|
||||
shuts down, put any clean up code here"
|
||||
[]
|
||||
(timbre/info "mailhead is shutting down...")
|
||||
(doseq [component (:stopped (mount/stop))]
|
||||
(timbre/info component "stopped"))
|
||||
(timbre/info "shutdown complete!"))
|
||||
|
||||
(def app-routes
|
||||
(routes
|
||||
(wrap-routes #'home-routes middleware/wrap-csrf)
|
||||
(route/not-found
|
||||
(:body
|
||||
(error-page {:status 404
|
||||
:title "page not found"})))))
|
||||
|
||||
(def app (middleware/wrap-base #'app-routes))
|
39
src/mailhead/layout.clj
Normal file
39
src/mailhead/layout.clj
Normal file
|
@ -0,0 +1,39 @@
|
|||
(ns mailhead.layout
|
||||
(:require [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*]]))
|
||||
|
||||
|
||||
(declare ^:dynamic *app-context*)
|
||||
(parser/set-resource-path! (clojure.java.io/resource "templates"))
|
||||
(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/templates"
|
||||
[template & [params]]
|
||||
(content-type
|
||||
(ok
|
||||
(parser/render-file
|
||||
template
|
||||
(assoc params
|
||||
:page template
|
||||
:csrf-token *anti-forgery-token*
|
||||
:servlet-context *app-context*)))
|
||||
"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)})
|
61
src/mailhead/middleware.clj
Normal file
61
src/mailhead/middleware.clj
Normal file
|
@ -0,0 +1,61 @@
|
|||
(ns mailhead.middleware
|
||||
(:require [mailhead.layout :refer [*app-context* error-page]]
|
||||
[taoensso.timbre :as timbre]
|
||||
[environ.core :refer [env]]
|
||||
[ring.middleware.flash :refer [wrap-flash]]
|
||||
[immutant.web.middleware :refer [wrap-session]]
|
||||
[ring.middleware.webjars :refer [wrap-webjars]]
|
||||
[ring.middleware.defaults :refer [site-defaults wrap-defaults]]
|
||||
[ring.middleware.anti-forgery :refer [wrap-anti-forgery]]
|
||||
[ring.middleware.format :refer [wrap-restful-format]]
|
||||
[mailhead.config :refer [defaults]])
|
||||
(:import [javax.servlet ServletContext]))
|
||||
|
||||
(defn wrap-context [handler]
|
||||
(fn [request]
|
||||
(binding [*app-context*
|
||||
(if-let [context (:servlet-context request)]
|
||||
;; If we're not inside a servlet environment
|
||||
;; (for example when using mock requests), then
|
||||
;; .getContextPath might not exist
|
||||
(try (.getContextPath ^ServletContext context)
|
||||
(catch IllegalArgumentException _ context))
|
||||
;; if the context is not specified in the request
|
||||
;; we check if one has been specified in the environment
|
||||
;; instead
|
||||
(:app-context env))]
|
||||
(handler request))))
|
||||
|
||||
(defn wrap-internal-error [handler]
|
||||
(fn [req]
|
||||
(try
|
||||
(handler req)
|
||||
(catch Throwable t
|
||||
(timbre/error 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]
|
||||
(wrap-restful-format handler {:formats [:json-kw :transit-json :transit-msgpack]}))
|
||||
|
||||
(defn wrap-base [handler]
|
||||
(-> ((:middleware defaults) handler)
|
||||
wrap-formats
|
||||
wrap-webjars
|
||||
wrap-flash
|
||||
(wrap-session {:cookie-attrs {:http-only true}})
|
||||
(wrap-defaults
|
||||
(-> site-defaults
|
||||
(assoc-in [:security :anti-forgery] false)
|
||||
(dissoc :session)))
|
||||
wrap-context
|
||||
wrap-internal-error))
|
17
src/mailhead/routes/home.clj
Normal file
17
src/mailhead/routes/home.clj
Normal file
|
@ -0,0 +1,17 @@
|
|||
(ns mailhead.routes.home
|
||||
(:require [mailhead.layout :as layout]
|
||||
[compojure.core :refer [defroutes GET]]
|
||||
[ring.util.http-response :refer [ok]]
|
||||
[clojure.java.io :as io]))
|
||||
|
||||
(defn home-page []
|
||||
(layout/render
|
||||
"home.html" {:docs (-> "docs/docs.md" io/resource slurp)}))
|
||||
|
||||
(defn about-page []
|
||||
(layout/render "about.html"))
|
||||
|
||||
(defroutes home-routes
|
||||
(GET "/" [] (home-page))
|
||||
(GET "/about" [] (about-page)))
|
||||
|
Loading…
Reference in a new issue