initial commit
This commit is contained in:
commit
de1f27fc53
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
.DS_Store
|
||||||
|
/target
|
||||||
|
/classes
|
||||||
|
/checkouts
|
||||||
|
pom.xml
|
||||||
|
pom.xml.asc
|
||||||
|
*.jar
|
||||||
|
*.class
|
||||||
|
/.lein-*
|
||||||
|
/.nrepl-port
|
||||||
|
.settings/
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2022 Gered King
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
21
project.clj
Normal file
21
project.clj
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
(defproject net.gered/lein-template.simple-web-site "0.1.0-SNAPSHOT"
|
||||||
|
:description "Simple Clojure web site project template."
|
||||||
|
:url "https://github.com/gered/simple-web-site-template"
|
||||||
|
:license {:name "MIT License"
|
||||||
|
:url "http://opensource.org/licenses/MIT"}
|
||||||
|
|
||||||
|
:eval-in-leiningen true
|
||||||
|
|
||||||
|
:deploy-repositories [["releases" :clojars]
|
||||||
|
["snapshots" :clojars]]
|
||||||
|
|
||||||
|
:release-tasks [["vcs" "assert-committed"]
|
||||||
|
["change" "version" "leiningen.release/bump-version" "release"]
|
||||||
|
["vcs" "commit"]
|
||||||
|
["vcs" "tag" "v" "--no-sign"]
|
||||||
|
["deploy"]
|
||||||
|
["change" "version" "leiningen.release/bump-version"]
|
||||||
|
["vcs" "commit" "bump to next snapshot version for future development"]
|
||||||
|
["vcs" "push"]]
|
||||||
|
|
||||||
|
)
|
3
resources/leiningen/new/simple_web_site/config.edn
Normal file
3
resources/leiningen/new/simple_web_site/config.edn
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{:nrepl {:port 7000 :bind "127.0.0.1"}
|
||||||
|
:http {:port 8080 :bind "0.0.0.0"}
|
||||||
|
:dev? true}
|
17
resources/leiningen/new/simple_web_site/gitignore
Normal file
17
resources/leiningen/new/simple_web_site/gitignore
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
.DS_Store
|
||||||
|
/target
|
||||||
|
/classes
|
||||||
|
/checkouts
|
||||||
|
pom.xml
|
||||||
|
pom.xml.asc
|
||||||
|
*.jar
|
||||||
|
*.class
|
||||||
|
/.lein-*
|
||||||
|
/.nrepl-port
|
||||||
|
.settings/
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
49
resources/leiningen/new/simple_web_site/project.clj
Normal file
49
resources/leiningen/new/simple_web_site/project.clj
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
(defproject {{name}} "0.1.0-SNAPSHOT"
|
||||||
|
|
||||||
|
:description "FIXME: write description"
|
||||||
|
:url "http://example.com/FIXME"
|
||||||
|
:license {:name "MIT License"
|
||||||
|
:url "http://opensource.org/licenses/MIT"}
|
||||||
|
|
||||||
|
:dependencies [[ch.qos.logback/logback-classic "1.2.7"]
|
||||||
|
[cprop "0.1.19"]
|
||||||
|
[hiccup "1.0.5"]
|
||||||
|
[http-kit "2.5.3"]
|
||||||
|
[javax.servlet/servlet-api "2.5"]
|
||||||
|
[metosin/reitit-core "0.5.15"]
|
||||||
|
[metosin/reitit-dev "0.5.15"]
|
||||||
|
[metosin/reitit-middleware "0.5.15"]
|
||||||
|
[metosin/reitit-schema "0.5.15"]
|
||||||
|
[metosin/reitit-ring "0.5.15"]
|
||||||
|
[metosin/ring-http-response "0.9.3"]
|
||||||
|
[mount "0.1.16"]
|
||||||
|
[net.gered/aging-session "0.2.0"]
|
||||||
|
[nrepl "0.9.0"]
|
||||||
|
[org.clojure/clojure "1.10.0"]
|
||||||
|
[org.clojure/tools.logging "1.2.1"]
|
||||||
|
[org.webjars.npm/mini.css "3.0.1"]
|
||||||
|
[org.webjars/webjars-locator "0.42"]
|
||||||
|
[ring/ring-anti-forgery "1.3.0"]
|
||||||
|
[ring/ring-devel "1.9.4"]
|
||||||
|
[ring-webjars "0.2.0"]]
|
||||||
|
|
||||||
|
:main {{root-ns}}.core
|
||||||
|
|
||||||
|
:repl-options {:init-ns {{root-ns}}.core}
|
||||||
|
:target-path "target/%s/"
|
||||||
|
|
||||||
|
:profiles {:dev {:source-paths ["env/dev/src"]
|
||||||
|
:resource-paths ["env/dev/resources"]
|
||||||
|
:dependencies [[pjstadig/humane-test-output "0.11.0"]]
|
||||||
|
:injections [(require 'pjstadig.humane-test-output)
|
||||||
|
(pjstadig.humane-test-output/activate!)]}
|
||||||
|
|
||||||
|
:release {:source-paths ["env/release/src"]
|
||||||
|
:resource-paths ["env/release/resources"]}
|
||||||
|
|
||||||
|
:release/uberjar {:omit-source true
|
||||||
|
:aot :all}
|
||||||
|
|
||||||
|
:uberjar [:release :release/uberjar]}
|
||||||
|
|
||||||
|
)
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration>
|
||||||
|
<appender name="C" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{ISO8601} %-5p [%c] - %m%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<root level="${ROOT_LEVEL:-INFO}">
|
||||||
|
<appender-ref ref="C"/>
|
||||||
|
</root>
|
||||||
|
</configuration>
|
212
resources/leiningen/new/simple_web_site/src/root_ns/core.clj
Normal file
212
resources/leiningen/new/simple_web_site/src/root_ns/core.clj
Normal file
|
@ -0,0 +1,212 @@
|
||||||
|
{{=<% %>=}}
|
||||||
|
(ns <%root-ns%>.core
|
||||||
|
(:gen-class)
|
||||||
|
(:require
|
||||||
|
[clojure.tools.logging :as log]
|
||||||
|
[aging-session.core :as aging-session]
|
||||||
|
[cprop.core :refer [load-config]]
|
||||||
|
[hiccup.page :refer [html5 include-css include-js]]
|
||||||
|
[org.httpkit.server :as http-kit]
|
||||||
|
[mount.core :as mount :refer [defstate]]
|
||||||
|
[muuntaja.core :as m]
|
||||||
|
[nrepl.server :as nrepl]
|
||||||
|
[reitit.coercion.schema]
|
||||||
|
[reitit.ring :as ring]
|
||||||
|
[reitit.ring.coercion :as coercion]
|
||||||
|
[reitit.ring.middleware.exception :as exception]
|
||||||
|
[reitit.ring.middleware.multipart :as multipart]
|
||||||
|
[reitit.ring.middleware.muuntaja :as muuntaja]
|
||||||
|
[reitit.ring.middleware.parameters :as parameters]
|
||||||
|
[ring.middleware.anti-forgery :refer [wrap-anti-forgery]]
|
||||||
|
[ring.middleware.cookies :refer [wrap-cookies]]
|
||||||
|
[ring.middleware.content-type :refer [wrap-content-type]]
|
||||||
|
[ring.middleware.flash :refer [wrap-flash]]
|
||||||
|
[ring.middleware.reload :refer [wrap-reload]]
|
||||||
|
[ring.middleware.session :refer [wrap-session]]
|
||||||
|
[ring.middleware.webjars :refer [wrap-webjars]]
|
||||||
|
[ring.util.anti-forgery :refer [anti-forgery-field]]
|
||||||
|
[ring.util.http-response :refer :all]
|
||||||
|
[schema.core :as s]))
|
||||||
|
|
||||||
|
(declare handler)
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; infrastructure components
|
||||||
|
;;
|
||||||
|
|
||||||
|
(defstate ^{:on-reload :noop} config
|
||||||
|
:start
|
||||||
|
(do
|
||||||
|
(log/info "Loading config.edn")
|
||||||
|
(load-config :file "config.edn")))
|
||||||
|
|
||||||
|
(defstate ^{:on-reload :noop} repl-server
|
||||||
|
:start
|
||||||
|
(let [{:keys [port bind]
|
||||||
|
:or {port 7000
|
||||||
|
bind "127.0.0.1"}} (:nrepl config)
|
||||||
|
server (nrepl/start-server :port port :bind bind)]
|
||||||
|
(log/info (format "Starting nREPL server listening on %s:%d" bind port))
|
||||||
|
server)
|
||||||
|
:stop
|
||||||
|
(when repl-server
|
||||||
|
(log/info "Stopping nREPL server")
|
||||||
|
(nrepl/stop-server repl-server)))
|
||||||
|
|
||||||
|
(defstate ^{:on-reload :noop} http-server
|
||||||
|
:start
|
||||||
|
(let [{:keys [port bind]
|
||||||
|
:or {port 8080
|
||||||
|
bind "0.0.0.0"}} (:http-server config)
|
||||||
|
server (http-kit/run-server
|
||||||
|
(as-> #'handler h
|
||||||
|
(if (:dev? config) (wrap-reload h) h))
|
||||||
|
{:port port
|
||||||
|
:ip bind
|
||||||
|
:server-header nil
|
||||||
|
:legacy-return-value? false})]
|
||||||
|
(log/info (format "Started HTTP server listening on %s:%d" bind port))
|
||||||
|
server)
|
||||||
|
:stop
|
||||||
|
(when http-server
|
||||||
|
(log/info "Stopping HTTP server")
|
||||||
|
(http-kit/server-stop! http-server)
|
||||||
|
nil))
|
||||||
|
|
||||||
|
(defstate ^{:on-reload :noop} session-store
|
||||||
|
:start
|
||||||
|
(let [session-timeout (get config :session-timeout (* 60 20))]
|
||||||
|
(aging-session/aging-memory-store session-timeout))
|
||||||
|
:stop
|
||||||
|
(aging-session/stop session-store))
|
||||||
|
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; main web handler and route stuff
|
||||||
|
;;
|
||||||
|
|
||||||
|
(defn render-page
|
||||||
|
[& content]
|
||||||
|
(html5
|
||||||
|
[:head
|
||||||
|
[:meta {:charset "utf-8"}]
|
||||||
|
[:meta {:name "viewport" :content "width=device-width, initial-scale=1"}]
|
||||||
|
(include-css "/assets/mini.css/dist/mini-default.css"
|
||||||
|
"/css/app.css")]
|
||||||
|
[:body
|
||||||
|
[:header.sticky
|
||||||
|
[:a.logo {:href "/"} "<%name%>"]
|
||||||
|
[:a.button {:href "/calculator"} "Calculator"]
|
||||||
|
[:a.button {:href "/about"} "About"]]
|
||||||
|
[:main content]
|
||||||
|
[:footer.sticky "© <%name%>"]]))
|
||||||
|
|
||||||
|
(defn render-error
|
||||||
|
([e]
|
||||||
|
(render-error
|
||||||
|
(.getName (.getClass e))
|
||||||
|
(.getMessage e)))
|
||||||
|
([title message]
|
||||||
|
(render-page
|
||||||
|
[:h2 "Error!"]
|
||||||
|
[:div.card.fluid.error
|
||||||
|
[:h3 title]
|
||||||
|
[:p message]])))
|
||||||
|
|
||||||
|
; example exception handler that logs all unhandled exceptions thrown by your routes
|
||||||
|
(def exception-middleware
|
||||||
|
(exception/create-exception-middleware
|
||||||
|
(merge
|
||||||
|
;; uncomment the below to enable default handlers. many are clearly more intended for use in webservices, so some
|
||||||
|
;; customization may be desired to get something presentable for use in a user-facing website ...
|
||||||
|
;exception/default-handlers
|
||||||
|
{::exception/default
|
||||||
|
(fn [e {:keys [uri request-method remote-addr] :as request}]
|
||||||
|
(log/error e (format "Unhandled exception during request - %s %s from %s"
|
||||||
|
request-method uri remote-addr))
|
||||||
|
(internal-server-error (render-error e)))})))
|
||||||
|
|
||||||
|
(defstate handler
|
||||||
|
:start
|
||||||
|
(ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
[["/"
|
||||||
|
{:get
|
||||||
|
(fn [request]
|
||||||
|
(ok (render-page
|
||||||
|
[:h1 "Hello!"]
|
||||||
|
[:p "Welcome to the <%name%> web site."])))}]
|
||||||
|
|
||||||
|
["/about"
|
||||||
|
{:get
|
||||||
|
(fn [request]
|
||||||
|
(ok (render-page
|
||||||
|
[:h1 "About"]
|
||||||
|
[:p "There is nothing much to say right now ..."])))}]
|
||||||
|
|
||||||
|
; example route
|
||||||
|
["/calculator"
|
||||||
|
{:parameters {:form {(s/optional-key :a) s/Num
|
||||||
|
(s/optional-key :b) s/Num
|
||||||
|
(s/optional-key :op) s/Str}}
|
||||||
|
:handler
|
||||||
|
(fn [{{{:keys [a b op]} :form} :parameters :as request}]
|
||||||
|
(let [operations {"+" + "-" - "*" * "/" /}]
|
||||||
|
(ok (render-page
|
||||||
|
[:h1 "Calculator"]
|
||||||
|
[:p "Do some number crunching!"]
|
||||||
|
(when (= :post (:request-method request))
|
||||||
|
[:div.card.warning
|
||||||
|
[:h3 "Calculation Result"]
|
||||||
|
[:p a " " op " " b " = "
|
||||||
|
(let [f (get operations op)]
|
||||||
|
(if f
|
||||||
|
(f a b)
|
||||||
|
(bad-request! "Invalid operation!")))]])
|
||||||
|
[:form {:method "post" :action "/calculator"}
|
||||||
|
(anti-forgery-field)
|
||||||
|
[:div [:label "A:" [:input {:type "text" :name "a" :value a}]]]
|
||||||
|
[:div [:label "B:" [:input {:type "text" :name "b" :value b}]]]
|
||||||
|
[:div
|
||||||
|
[:label "Operation:" [:select {:name "op"}
|
||||||
|
(map (fn [[k _]]
|
||||||
|
[:option {:selected (= op k)} k])
|
||||||
|
operations)
|
||||||
|
[:option "foo"]]]]
|
||||||
|
[:div [:button {:type "submit"} "Calculate"]]]))))}]
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
{:data {:coercion reitit.coercion.schema/coercion
|
||||||
|
:muuntaja m/instance
|
||||||
|
:middleware [parameters/parameters-middleware ; query-params & form-params
|
||||||
|
muuntaja/format-negotiate-middleware ; content-negotiation
|
||||||
|
muuntaja/format-response-middleware ; encoding response body
|
||||||
|
exception-middleware ; exception handling
|
||||||
|
muuntaja/format-request-middleware ; decoding request body
|
||||||
|
coercion/coerce-response-middleware ; coercing response body
|
||||||
|
coercion/coerce-request-middleware ; coercing request parameters
|
||||||
|
multipart/multipart-middleware ; multipart
|
||||||
|
wrap-cookies
|
||||||
|
[wrap-session {:store session-store}]
|
||||||
|
wrap-flash
|
||||||
|
wrap-anti-forgery]}})
|
||||||
|
|
||||||
|
(ring/routes
|
||||||
|
(ring/create-resource-handler
|
||||||
|
{:path "/"})
|
||||||
|
(wrap-content-type
|
||||||
|
(wrap-webjars (constantly nil)))
|
||||||
|
(ring/create-default-handler
|
||||||
|
{:not-found (constantly (not-found (html5 [:h2 "404 Not Found"])))
|
||||||
|
:method-not-allowed (constantly (method-not-allowed (html5 [:h2 "405 Method Not Allowed"])))
|
||||||
|
:not-acceptable (constantly (not-acceptable (html5 [:h2 "406 Not Acceptable"])))}))))
|
||||||
|
|
||||||
|
|
||||||
|
;;
|
||||||
|
|
||||||
|
(defn -main
|
||||||
|
[& args]
|
||||||
|
(log/info "<%name%> is starting up ...")
|
||||||
|
(mount/start-with-args args)
|
||||||
|
(log/info "Ready!"))
|
|
@ -0,0 +1,4 @@
|
||||||
|
(ns {{root-ns}}.core-test
|
||||||
|
(:require
|
||||||
|
[clojure.test :refer :all]
|
||||||
|
[{{root-ns}}.core :refer :all]))
|
27
src/leiningen/new/simple_web_site.clj
Normal file
27
src/leiningen/new/simple_web_site.clj
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
(ns leiningen.new.simple-web-site
|
||||||
|
(:require
|
||||||
|
[leiningen.new.templates :as t]
|
||||||
|
[leiningen.core.main :as main]))
|
||||||
|
|
||||||
|
(def render (t/renderer "simple_web_site"))
|
||||||
|
|
||||||
|
(defn simple-web-site
|
||||||
|
[name]
|
||||||
|
(let [data {:name name
|
||||||
|
:sanitized (t/sanitize name)
|
||||||
|
:root-ns (t/sanitize-ns name)
|
||||||
|
:root-ns-path (t/name-to-path name)}]
|
||||||
|
(main/info (str "Creating new project via net.gered/simple-web-site called \"" name "\" ..."))
|
||||||
|
(t/->files
|
||||||
|
data
|
||||||
|
"env/dev/resources"
|
||||||
|
"env/dev/src"
|
||||||
|
"env/release/resources"
|
||||||
|
"env/release/src"
|
||||||
|
["resources/logback.xml" (render "resources/logback.xml" data)]
|
||||||
|
["resources/public/css/app.css" (render "resources/public/css/app.css" data)]
|
||||||
|
["src/{{root-ns-path}}/core.clj" (render "src/root_ns/core.clj" data)]
|
||||||
|
["test/{{root-ns-path}}/core_test.clj" (render "test/root_ns/core_test.clj" data)]
|
||||||
|
[".gitignore" (render "gitignore" data)]
|
||||||
|
["config.edn" (render "config.edn" data)]
|
||||||
|
["project.clj" (render "project.clj" data)])))
|
Loading…
Reference in a new issue