commit de1f27fc537f7e84eb6716c81022b9637b42a92d Author: gered Date: Fri Jan 7 20:49:41 2022 -0500 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e91d482 --- /dev/null +++ b/.gitignore @@ -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 \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..238ed98 --- /dev/null +++ b/LICENSE @@ -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. \ No newline at end of file diff --git a/project.clj b/project.clj new file mode 100644 index 0000000..c3a3af5 --- /dev/null +++ b/project.clj @@ -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"]] + + ) diff --git a/resources/leiningen/new/simple_web_site/config.edn b/resources/leiningen/new/simple_web_site/config.edn new file mode 100644 index 0000000..076f152 --- /dev/null +++ b/resources/leiningen/new/simple_web_site/config.edn @@ -0,0 +1,3 @@ +{:nrepl {:port 7000 :bind "127.0.0.1"} + :http {:port 8080 :bind "0.0.0.0"} + :dev? true} diff --git a/resources/leiningen/new/simple_web_site/gitignore b/resources/leiningen/new/simple_web_site/gitignore new file mode 100644 index 0000000..278bb6d --- /dev/null +++ b/resources/leiningen/new/simple_web_site/gitignore @@ -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 diff --git a/resources/leiningen/new/simple_web_site/project.clj b/resources/leiningen/new/simple_web_site/project.clj new file mode 100644 index 0000000..2d5a510 --- /dev/null +++ b/resources/leiningen/new/simple_web_site/project.clj @@ -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]} + + ) diff --git a/resources/leiningen/new/simple_web_site/resources/logback.xml b/resources/leiningen/new/simple_web_site/resources/logback.xml new file mode 100644 index 0000000..0e58a40 --- /dev/null +++ b/resources/leiningen/new/simple_web_site/resources/logback.xml @@ -0,0 +1,12 @@ + + + + + %d{ISO8601} %-5p [%c] - %m%n + + + + + + + diff --git a/resources/leiningen/new/simple_web_site/resources/public/css/app.css b/resources/leiningen/new/simple_web_site/resources/public/css/app.css new file mode 100644 index 0000000..e69de29 diff --git a/resources/leiningen/new/simple_web_site/src/root_ns/core.clj b/resources/leiningen/new/simple_web_site/src/root_ns/core.clj new file mode 100644 index 0000000..6a6f52a --- /dev/null +++ b/resources/leiningen/new/simple_web_site/src/root_ns/core.clj @@ -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!")) diff --git a/resources/leiningen/new/simple_web_site/test/root_ns/core_test.clj b/resources/leiningen/new/simple_web_site/test/root_ns/core_test.clj new file mode 100644 index 0000000..1f829a5 --- /dev/null +++ b/resources/leiningen/new/simple_web_site/test/root_ns/core_test.clj @@ -0,0 +1,4 @@ +(ns {{root-ns}}.core-test + (:require + [clojure.test :refer :all] + [{{root-ns}}.core :refer :all])) diff --git a/src/leiningen/new/simple_web_site.clj b/src/leiningen/new/simple_web_site.clj new file mode 100644 index 0000000..38978f9 --- /dev/null +++ b/src/leiningen/new/simple_web_site.clj @@ -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)])))