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