Browse Source

updated to latest Luminus

Yogthos 9 years ago
parent
commit
3292ad077c
25 changed files with 457 additions and 244 deletions
  1. 1 1
      frameworks/Clojure/luminus/benchmark_config.json
  2. 2 2
      frameworks/Clojure/luminus/hello/README.md
  3. 11 0
      frameworks/Clojure/luminus/hello/env/dev/clj/hello/config.clj
  4. 10 0
      frameworks/Clojure/luminus/hello/env/dev/clj/hello/dev_middleware.clj
  5. 0 34
      frameworks/Clojure/luminus/hello/env/dev/clj/hello/repl.clj
  6. 8 0
      frameworks/Clojure/luminus/hello/env/prod/clj/hello/config.clj
  7. 2 0
      frameworks/Clojure/luminus/hello/profiles.clj
  8. 54 36
      frameworks/Clojure/luminus/hello/project.clj
  9. 34 0
      frameworks/Clojure/luminus/hello/resources/docs/docs.md
  10. 25 0
      frameworks/Clojure/luminus/hello/resources/log4j.properties
  11. 1 0
      frameworks/Clojure/luminus/hello/resources/migrations/20160114154818-add-users-table.down.sql
  12. 9 0
      frameworks/Clojure/luminus/hello/resources/migrations/20160114154818-add-users-table.up.sql
  13. 0 0
      frameworks/Clojure/luminus/hello/resources/public/favicon.ico
  14. 0 2
      frameworks/Clojure/luminus/hello/resources/sql/queries.sql
  15. 4 0
      frameworks/Clojure/luminus/hello/resources/templates/about.html
  16. 56 0
      frameworks/Clojure/luminus/hello/resources/templates/error.html
  17. 35 24
      frameworks/Clojure/luminus/hello/src/hello/core.clj
  18. 44 28
      frameworks/Clojure/luminus/hello/src/hello/db/core.clj
  19. 21 0
      frameworks/Clojure/luminus/hello/src/hello/db/migrations.clj
  20. 24 40
      frameworks/Clojure/luminus/hello/src/hello/handler.clj
  21. 27 25
      frameworks/Clojure/luminus/hello/src/hello/layout.clj
  22. 52 28
      frameworks/Clojure/luminus/hello/src/hello/middleware.clj
  23. 0 21
      frameworks/Clojure/luminus/hello/src/hello/session.clj
  24. 34 0
      frameworks/Clojure/luminus/hello/test/hello/test/db/core.clj
  25. 3 3
      frameworks/Clojure/luminus/hello/test/hello/test/handler.clj

+ 1 - 1
frameworks/Clojure/luminus/benchmark_config.json

@@ -16,7 +16,7 @@
       "framework": "luminus",
       "language": "Clojure",
       "orm": "Raw",
-      "platform": "http-kit",
+      "platform": "Immutant",
       "webserver": "None",
       "os": "Linux",
       "database_os": "Linux",

+ 2 - 2
frameworks/Clojure/luminus/hello/README.md

@@ -12,8 +12,8 @@ You will need [Leiningen][1] 2.0 or above installed.
 
 To start a web server for the application, run:
 
-    lein ring server
+    lein run
 
 ## License
 
-Copyright © 2015 FIXME
+Copyright © 2016 FIXME

+ 11 - 0
frameworks/Clojure/luminus/hello/env/dev/clj/hello/config.clj

@@ -0,0 +1,11 @@
+(ns hello.config
+  (:require [selmer.parser :as parser]
+            [clojure.tools.logging :as log]
+            [hello.dev-middleware :refer [wrap-dev]]))
+
+(def defaults
+  {:init
+   (fn []
+     (parser/cache-off!)
+     (log/info "\n-=[hello started successfully using the development profile]=-"))
+   :middleware wrap-dev})

+ 10 - 0
frameworks/Clojure/luminus/hello/env/dev/clj/hello/dev_middleware.clj

@@ -0,0 +1,10 @@
+(ns hello.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))

+ 0 - 34
frameworks/Clojure/luminus/hello/env/dev/clj/hello/repl.clj

@@ -1,34 +0,0 @@
-(ns hello.repl
-  (:use hello.handler
-    ring.server.standalone
-    [ring.middleware file-info file]))
-
-(defonce server (atom nil))
-
-(defn get-handler []
-  ;; #'app expands to (var app) so that when we reload our code,
-  ;; the server is forced to re-resolve the symbol in the var
-  ;; rather than having its own copy. When the root binding
-  ;; changes, the server picks it up without having to restart.
-  (-> #'app
-      ; Makes static assets in $PROJECT_DIR/resources/public/ available.
-      (wrap-file "resources")
-      ; Content-Type, Content-Length, and Last Modified headers for files in body
-      (wrap-file-info)))
-
-(defn start-server
-  "used for starting the server in development mode from REPL"
-  [& [port]]
-  (let [port (if port (Integer/parseInt port) 3000)]
-    (reset! server
-            (serve (get-handler)
-                   {:port port
-                    :init init
-                    :auto-reload? true
-                    :destroy destroy
-                    :join? false}))
-    (println (str "You can view the site at http://localhost:" port))))
-
-(defn stop-server []
-  (.stop @server)
-  (reset! server nil))

+ 8 - 0
frameworks/Clojure/luminus/hello/env/prod/clj/hello/config.clj

@@ -0,0 +1,8 @@
+(ns hello.config
+  (:require [clojure.tools.logging :as log]))
+
+(def defaults
+  {:init
+   (fn []
+     (log/info "\n-=[hello started successfully]=-"))
+   :middleware identity})

+ 2 - 0
frameworks/Clojure/luminus/hello/profiles.clj

@@ -0,0 +1,2 @@
+{:profiles/dev  {:env {:database-url "jdbc:mysql://localhost:3306/hello_dev?user=db_user_name_here&password=db_user_password_here"}}
+ :profiles/test {:env {:database-url "jdbc:mysql://localhost:3306/hello_test?user=db_user_name_here&password=db_user_password_here"}}}

+ 54 - 36
frameworks/Clojure/luminus/hello/project.clj

@@ -1,49 +1,67 @@
-(defproject hello "luminus"
+(defproject hello "0.1.0-SNAPSHOT"
 
-  :description "Luminus framework benchmarks"
-  :url "https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Clojure/luminus"
+  :description "FIXME: write description"
+  :url "http://example.com/FIXME"
 
-  :dependencies [[org.clojure/clojure "1.6.0"]
-                 [ring-server "0.4.0"]
-                 [selmer "0.8.2"]
-                 [com.taoensso/timbre "3.4.0"]
+  :dependencies [[org.clojure/clojure "1.7.0"]
+                 [selmer "1.0.0"]
+                 [markdown-clj "0.9.85"]
+                 [environ "1.0.1"]
+                 [ring-middleware-format "0.7.0"]
+                 [metosin/ring-http-response "0.6.5"]
+                 [bouncer "1.0.0"]
+                 [org.webjars/bootstrap "3.3.6"]
+                 [org.webjars/jquery "2.2.0"]
+                 [org.clojure/tools.logging "0.3.1"]
+                 [org.slf4j/slf4j-log4j12 "1.7.13"]
+                 [org.apache.logging.log4j/log4j-core "2.5"]
                  [com.taoensso/tower "3.0.2"]
-                 [markdown-clj "0.9.65"]
-                 [environ "1.0.0"]
-                 [im.chit/cronj "1.4.3"]
-                 [compojure "1.3.3"]
-                 [ring/ring-defaults "0.1.4"]
-                 [ring/ring-session-timeout "0.1.0"]
-                 [ring-middleware-format "0.5.0"]
-                 [noir-exception "0.2.3"]
-                 [bouncer "0.3.2"]
-                 [prone "0.8.1"]
-                 [org.clojure/tools.nrepl "0.2.8"]
-                 [yesql "0.5.0-rc2"]
-                 [mysql/mysql-connector-java "5.1.6"]
-                 [c3p0/c3p0 "0.9.1.2"]
-                 [http-kit "2.1.19"]
-                 [org.clojure/tools.cli "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.8"]
+                 [luminus-nrepl "0.1.2"]
+                 [migratus "0.8.8"]
+                 [conman "0.2.9"]
+                 [mysql/mysql-connector-java "5.1.34"]
+                 [org.webjars/webjars-locator-jboss-vfs "0.1.0"]
+                 [luminus-immutant "0.1.0"]]
 
   :min-lein-version "2.0.0"
   :uberjar-name "hello.jar"
   :jvm-opts ["-server"]
+  :resource-paths ["resources"]
 
   :main hello.core
+  :migratus {:store :database}
 
-  :plugins [[lein-ring "0.9.1"]
-            [lein-environ "1.0.0"]]
-
+  :plugins [[lein-environ "1.0.1"]
+            [migratus-lein "0.2.0"]]
   :profiles
   {:uberjar {:omit-source true
              :env {:production true}
-             :aot :all}
-   :dev {:dependencies [[ring-mock "0.1.5"]
-                        [ring/ring-devel "1.3.2"]
-                        [pjstadig/humane-test-output "0.7.0"]]
-         :source-paths ["env/dev/clj"]
-
-         :repl-options {:init-ns hello.repl}
-         :injections [(require 'pjstadig.humane-test-output)
-                      (pjstadig.humane-test-output/activate!)]
-         :env {:dev true}}})
+             :aot :all
+             :source-paths ["env/prod/clj"]}
+   :dev           [:project/dev :profiles/dev]
+   :test          [:project/test :profiles/test]
+   :project/dev  {:dependencies [[prone "1.0.0"]
+                                 [ring/ring-mock "0.3.0"]
+                                 [ring/ring-devel "1.4.0"]
+                                 [pjstadig/humane-test-output "0.7.1"]
+                                 [mvxcvi/puget "1.0.0"]]
+                  
+                  
+                  :source-paths ["env/dev/clj"]
+                  :repl-options {:init-ns hello.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}}
+   :project/test {:env {:test       true
+                        :port       3001
+                        :nrepl-port 7001}}
+   :profiles/dev {}
+   :profiles/test {}})

+ 34 - 0
frameworks/Clojure/luminus/hello/resources/docs/docs.md

@@ -0,0 +1,34 @@
+<div class="bs-callout bs-callout-danger">
+
+### Database Configuration is Required
+
+If you haven't already, then please follow the steps below to configure your database connection and run the necessary migrations.
+
+* Create the database for your application.
+* Update the connection URL in the `profiles.clj` file with your database name and login.
+* Run `lein run migrate` in the root of the project to create the tables.
+* Restart the application.
+
+</div>
+
+
+### Managing Your Middleware
+
+Request middleware functions are located under the `hello.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 `hello.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. [Setting response types](http://www.luminusweb.net/docs/responses.md)
+4. [Defining routes](http://www.luminusweb.net/docs/routes.md)
+5. [Adding middleware](http://www.luminusweb.net/docs/middleware.md)
+6. [Sessions and cookies](http://www.luminusweb.net/docs/sessions_cookies.md)
+7. [Security](http://www.luminusweb.net/docs/security.md)
+8. [Deploying the application](http://www.luminusweb.net/docs/deployment.md)

+ 25 - 0
frameworks/Clojure/luminus/hello/resources/log4j.properties

@@ -0,0 +1,25 @@
+### stdout appender
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.Target=System.out
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=[%d][%p][%c] %m%n
+
+### rolling file appender
+log4j.appender.R=org.apache.log4j.RollingFileAppender
+log4j.appender.R.File=./log/hello.log
+
+log4j.appender.R.MaxFileSize=100KB
+log4j.appender.R.MaxBackupIndex=20
+
+log4j.appender.R.layout=org.apache.log4j.PatternLayout
+log4j.appender.R.layout.ConversionPattern=[%d][%p][%c] %m%n
+
+### suppress 3rd party debug logs
+log4j.logger.org.xnio.nio=INFO
+log4j.logger.com.zaxxer.hikari=INFO
+
+
+
+### root logger sets the minimum logging level
+### and aggregates the appenders
+log4j.rootLogger=DEBUG, stdout, R

+ 1 - 0
frameworks/Clojure/luminus/hello/resources/migrations/20160114154818-add-users-table.down.sql

@@ -0,0 +1 @@
+DROP TABLE users;

+ 9 - 0
frameworks/Clojure/luminus/hello/resources/migrations/20160114154818-add-users-table.up.sql

@@ -0,0 +1,9 @@
+CREATE TABLE users
+(id VARCHAR(20) PRIMARY KEY,
+ first_name VARCHAR(30),
+ last_name VARCHAR(30),
+ email VARCHAR(30),
+ admin BOOLEAN,
+ last_login TIME,
+ is_active BOOLEAN,
+ pass VARCHAR(300));

+ 0 - 0
frameworks/Clojure/luminus/hello/resources/public/favicon.ico


+ 0 - 2
frameworks/Clojure/luminus/hello/resources/sql/queries.sql

@@ -16,5 +16,3 @@ WHERE id = :id
 --name: get-all-fortunes
 -- query all fortune records
 SELECT id, message FROM fortune
-
-

+ 4 - 0
frameworks/Clojure/luminus/hello/resources/templates/about.html

@@ -0,0 +1,4 @@
+{% extends "base.html" %}
+{% block content %}
+  <p>this is the story of hello... work in progress</p>
+{% endblock %}

+ 56 - 0
frameworks/Clojure/luminus/hello/resources/templates/error.html

@@ -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>

+ 35 - 24
frameworks/Clojure/luminus/hello/src/hello/core.clj

@@ -1,30 +1,41 @@
 (ns hello.core
-  (:require [hello.handler :refer [app init]]
-            [clojure.tools.cli :refer [cli]]
-            [org.httpkit.server :refer [run-server]])
+  (:require [hello.handler :refer [app init destroy]]
+            [luminus.repl-server :as repl]
+            [luminus.http-server :as http]
+            [hello.db.migrations :as migrations]
+            [environ.core :refer [env]])
   (:gen-class))
 
-(defn parse-port [s]
-  "Convert stringy port number int. Defaults to 8080."
-  (cond
-    (string? s) (Integer/parseInt s)
-    (instance? Integer s) s
-    (instance? Long s) (.intValue ^Long s)
-    :else 8080))
+(defn parse-port [port]
+  (when port
+    (cond
+      (string? port) (Integer/parseInt port)
+      (number? port) port
+      :else          (throw (Exception. (str "invalid port value: " port))))))
+
+(defn http-port [port]
+  (parse-port (or port (env :port) 3000)))
 
-(defn start-server [{:keys [port]}]
-  (let [cpu (.availableProcessors (Runtime/getRuntime))]
-    ;; double worker threads should increase database access performance
-    (init)
-    (run-server app {:port port :thread (* 2 cpu)})
-    (println (str "http-kit server listens at :" port))))
+(defn stop-app []
+  (repl/stop)
+  (http/stop destroy)
+  (shutdown-agents))
+
+(defn start-app
+  "e.g. lein run 3000"
+  [[port]]
+  (let [port (http-port port)]
+    (.addShutdownHook (Runtime/getRuntime) (Thread. stop-app))
+    (when-let [repl-port (env :nrepl-port)]
+      (repl/start {:port (parse-port repl-port)}))
+    (http/start {:handler app
+                 :init    init
+                 :port    port})))
 
 (defn -main [& args]
-  (let [[options _ banner]
-        (cli args
-             ["-p" "--port" "Port to listen" :default 8080 :parse-fn parse-port]
-             ["--[no-]help" "Print this help"])]
-    (when (:help options)
-          (println banner)
-          (System/exit 0))
-    (start-server options)))
+  (cond
+    (some #{"migrate" "rollback"} args)
+    (do (migrations/migrate args) (System/exit 0))
+    :else
+    (start-app args)))
+  

+ 44 - 28
frameworks/Clojure/luminus/hello/src/hello/db/core.clj

@@ -1,39 +1,56 @@
 (ns hello.db.core
   (:require
-    [yesql.core :refer [defqueries]])
-  (:import com.mchange.v2.c3p0.ComboPooledDataSource))
+    [clojure.java.jdbc :as jdbc]
+    [conman.core :as conman]
+    [environ.core :refer [env]]
+    [mount.core :refer [defstate]])
+  (:import [java.sql
+            BatchUpdateException
+            PreparedStatement]))
 
-(def db-spec
-  {:classname "com.mysql.jdbc.Driver"
-   :subprotocol "mysql"
-   :subname "//localhost:3306/hello_world?jdbcCompliantTruncation=false&elideSetAutoCommits=true&useLocalSessionState=true&cachePrepStmts=true&cacheCallableStmts=true&alwaysSendSetIsolation=false&prepStmtCacheSize=4096&cacheServerConfiguration=true&prepStmtCacheSqlLimit=2048&zeroDateTimeBehavior=convertToNull&traceProtocol=false&useUnbufferedInput=false&useReadAheadInput=false&maintainTimeStats=false&useServerPrepStmts&cacheRSMetadata=true"
-   :user "benchmarkdbuser"
-   :password "benchmarkdbpass"})
+(def pool-spec
+  {:jdbc-uri   "jdbc:mysql://localhost:3306/hello_world?jdbcCompliantTruncation=false&elideSetAutoCommits=true&useLocalSessionState=true&cachePrepStmts=true&cacheCallableStmts=true&alwaysSendSetIsolation=false&prepStmtCacheSize=4096&cacheServerConfiguration=true&prepStmtCacheSqlLimit=2048&zeroDateTimeBehavior=convertToNull&traceProtocol=false&useUnbufferedInput=false&useReadAheadInput=false&maintainTimeStats=false&useServerPrepStmts&cacheRSMetadata=true"
+   :datasource-classname "com.mysql.jdbc.jdbc2.optional.MysqlDataSource"
+   :username   "benchmarkdbuser"
+   :password   "benchmarkdbpass"
+   :init-size  1
+   :min-idle   1
+   :max-idle   4
+   :max-active 32})
 
-(defn pool
-  [spec]
-  {:datasource
-   (doto (ComboPooledDataSource.)
-     (.setDriverClass (:classname spec))
-     (.setJdbcUrl (str "jdbc:" (:subprotocol spec) ":" (:subname spec)))
-     (.setUser (:user spec))
-     (.setPassword (:password spec))
-     ;; expire excess connections after 30 minutes of inactivity:
-     (.setMaxIdleTimeExcessConnections (* 30 60))
-     ;; expire connections after 3 hours of inactivity:
-     (.setMaxIdleTime (* 3 60 60)))})
+(defn connect! []
+  (let [conn (atom nil)]
+    (conman/connect! conn pool-spec)
+    conn))
 
-(defonce db (atom nil))
+(defn disconnect! [conn]
+  (conman/disconnect! conn))
 
-(defn connect! []
-  (reset! db (pool db-spec)))
+(defstate ^:dynamic *db*
+          :start (connect!)
+          :stop (disconnect! *db*))
+
+(conman/bind-connection *db* "sql/queries.sql")
+
+(defn to-date [sql-date]
+  (-> sql-date (.getTime) (java.util.Date.)))
 
-(defqueries "sql/queries.sql")
+(extend-protocol jdbc/IResultSetReadColumn
+  java.sql.Date
+  (result-set-read-column [v _ _] (to-date v))
+
+  java.sql.Timestamp
+  (result-set-read-column [v _ _] (to-date v)))
+
+(extend-type java.util.Date
+  jdbc/ISQLParameter
+  (set-parameter [v ^PreparedStatement stmt idx]
+    (.setTimestamp stmt idx (java.sql.Timestamp. (.getTime v)))))
 
 (defn get-world-random
   "Query a random World record between 1 and 10,000 from the database"
   []
-  (get-world {:id (inc (rand-int 9999))} {:connection @db}))
+  (get-world {:id (inc (rand-int 9999))}))
 
 (defn get-query-count [queries]
   "Parse provided string value of query count, clamping values to between 1 and 500."
@@ -54,7 +71,7 @@
   message text, and then return the results."
   (sort-by
    :message
-   (conj (get-all-fortunes {} {:connection @db})
+   (conj (get-all-fortunes {})
          {:id 0 :message "Additional fortune added at request time."})))
 
 (defn update-and-persist
@@ -63,7 +80,6 @@
   [queries]
   (for [world (-> queries run-queries)]
     (let [updated-world (assoc world :randomNumber (inc (rand-int 9999)))]
-      (update-world<! updated-world {:connection @db})
+      (update-world<! updated-world)
       updated-world)))
 
-

+ 21 - 0
frameworks/Clojure/luminus/hello/src/hello/db/migrations.clj

@@ -0,0 +1,21 @@
+(ns hello.db.migrations
+  (:require
+    [migratus.core :as migratus]
+    [environ.core :refer [env]]
+    [to-jdbc-uri.core :refer [to-jdbc-uri]]))
+
+(defn parse-ids [args]
+  (map #(Long/parseLong %) (rest args)))
+
+(defn migrate [args]
+  (let [config {:store :database
+                :db {:connection-uri (to-jdbc-uri (:database-url env))}}]
+    (case (first args)
+      "migrate"
+      (if (> (count args) 1)
+        (apply migratus/up config (parse-ids args))
+        (migratus/migrate config))
+      "rollback"
+      (if (> (count args) 1)
+        (apply migratus/down config (parse-ids args))
+        (migratus/rollback config)))))

+ 24 - 40
frameworks/Clojure/luminus/hello/src/hello/handler.clj

@@ -1,20 +1,13 @@
 (ns hello.handler
-  (:require [compojure.core :refer [defroutes routes]]
+  (:require [compojure.core :refer [defroutes routes wrap-routes]]
+            [hello.layout :refer [error-page]]
             [hello.routes.home :refer [home-routes]]
-            [hello.db.core :as db]
-            [hello.middleware
-             :refer [development-middleware production-middleware]]
-            [hello.session :as session]
+            [hello.middleware :as middleware]
+            [clojure.tools.logging :as log]
             [compojure.route :as route]
-            [taoensso.timbre :as timbre]
-            [taoensso.timbre.appenders.rotor :as rotor]
-            [selmer.parser :as parser]
             [environ.core :refer [env]]
-            [cronj.core :as cronj]))
-
-(defroutes base-routes
-           (route/resources "/")
-           (route/not-found "Not Found"))
+            [hello.config :refer [defaults]]
+            [mount.core :as mount]))
 
 (defn init
   "init will be called once when
@@ -22,36 +15,27 @@
    an app server such as Tomcat
    put any initialization code here"
   []
-  (timbre/set-config!
-    [:appenders :rotor]
-    {:min-level             :info
-     :enabled?              true
-     :async?                false ; should be always false for rotor
-     :max-message-per-msecs nil
-     :fn                    rotor/appender-fn})
-
-  (timbre/set-config!
-    [:shared-appender-config :rotor]
-    {:path "hello.log" :max-size (* 512 1024) :backlog 10})
-
-  (if (env :dev) (parser/cache-off!))
-  (db/connect!)
-  ;;start the expired session cleanup job
-  (cronj/start! session/cleanup-job)
-  (timbre/info "\n-=[ hello started successfully"
-               (when (env :dev) "using the development profile") "]=-"))
+  (when-let [config (:log-config env)]
+    (org.apache.log4j.PropertyConfigurator/configure config))
+  (doseq [component (:started (mount/start))]
+    (log/info component "started"))
+  ((:init defaults)))
 
 (defn destroy
   "destroy will be called when your application
    shuts down, put any clean up code here"
   []
-  (timbre/info "hello is shutting down...")
-  (cronj/shutdown! session/cleanup-job)
-  (timbre/info "shutdown complete!"))
+  (log/info "hello is shutting down...")
+  (doseq [component (:stopped (mount/stop))]
+    (log/info component "stopped"))
+  (log/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
-  (-> (routes
-        home-routes
-        base-routes)
-      development-middleware
-      production-middleware))
+(def app (middleware/wrap-base #'app-routes))

+ 27 - 25
frameworks/Clojure/luminus/hello/src/hello/layout.clj

@@ -2,36 +2,38 @@
   (:require [selmer.parser :as parser]
             [selmer.filters :as filters]
             [markdown.core :refer [md-to-html-string]]
-            [ring.util.response :refer [content-type response]]
-            [compojure.response :refer [Renderable]]
+            [ring.util.http-response :refer [content-type ok]]
             [ring.util.anti-forgery :refer [anti-forgery-field]]
-            [ring.middleware.anti-forgery :refer [*anti-forgery-token*]]
-            [environ.core :refer [env]]))
+            [ring.middleware.anti-forgery :refer [*anti-forgery-token*]]))
 
-(parser/set-resource-path!  (clojure.java.io/resource "templates"))
 
+(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)]))
 
-(deftype RenderableTemplate [template params]
-  Renderable
-  (render [this request]
-    (content-type
-      (->> (assoc params
-                  :page template
-                  :dev (env :dev)
-                  :csrf-token *anti-forgery-token*
-                  :servlet-context
-                  (if-let [context (:servlet-context request)]
-                    ;; If we're not inside a serlvet environment (for
-                    ;; example when using mock requests), then
-                    ;; .getContextPath might not exist
-                    (try (.getContextPath context)
-                         (catch IllegalArgumentException _ context))))
-        (parser/render-file (str template))
-        response)
-      "text/html; charset=utf-8")))
+(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 render [template & [params]]
-  (RenderableTemplate. template params))
+(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)})

+ 52 - 28
frameworks/Clojure/luminus/hello/src/hello/middleware.clj

@@ -1,37 +1,61 @@
 (ns hello.middleware
-  (:require [hello.session :as session]
-            [taoensso.timbre :as timbre]
+  (:require [hello.layout :refer [*app-context* error-page]]
+            [clojure.tools.logging :as log]
             [environ.core :refer [env]]
-            [selmer.middleware :refer [wrap-error-page]]
-            [prone.middleware :refer [wrap-exceptions]]
-            [ring.util.response :refer [redirect]]
+            [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.session-timeout :refer [wrap-idle-session-timeout]]
-            [noir-exception.core :refer [wrap-internal-error]]
-            [ring.middleware.session.memory :refer [memory-store]]
+            [ring.middleware.anti-forgery :refer [wrap-anti-forgery]]
             [ring.middleware.format :refer [wrap-restful-format]]
-            
-            ))
+            [hello.config :refer [defaults]])
+  (:import [javax.servlet ServletContext]))
 
-(defn log-request [handler]
+(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]
-    (timbre/debug req)
-    (handler req)))
+    (try
+      (handler req)
+      (catch Throwable t
+        (log/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 development-middleware [handler]
-  (if (env :dev)
-    (-> handler
-        wrap-error-page
-        wrap-exceptions)
-    handler))
+(defn wrap-formats [handler]
+  (wrap-restful-format handler {:formats [:json-kw :transit-json :transit-msgpack]}))
 
-(defn production-middleware [handler]
-  (-> handler
-      
-      (wrap-restful-format :formats [:json-kw :edn :transit-json :transit-msgpack])
-      (wrap-idle-session-timeout
-        {:timeout (* 60 30)
-         :timeout-response (redirect "/")})
+(defn wrap-base [handler]
+  (-> ((:middleware defaults) handler)
+      wrap-formats
+      wrap-webjars
+      wrap-flash
+      (wrap-session {:cookie-attrs {:http-only true}})
       (wrap-defaults
-        (assoc-in site-defaults [:session :store] (memory-store session/mem)))
-      (wrap-internal-error :log #(timbre/error %))))
+        (-> site-defaults
+            (assoc-in [:security :anti-forgery] false)
+            (dissoc :session)))
+      wrap-context
+      wrap-internal-error))

+ 0 - 21
frameworks/Clojure/luminus/hello/src/hello/session.clj

@@ -1,21 +0,0 @@
-(ns hello.session
-  (:require [cronj.core :refer [cronj]]))
-
-(defonce mem (atom {}))
-
-(defn- current-time []
-  (quot (System/currentTimeMillis) 1000))
-
-(defn- expired? [[id session]]
-  (pos? (- (:ring.middleware.session-timeout/idle-timeout session) (current-time))))
-
-(defn clear-expired-sessions []
-  (clojure.core/swap! mem #(->> % (filter expired?) (into {}))))
-
-(def cleanup-job
-  (cronj
-    :entries
-    [{:id "session-cleanup"
-      :handler (fn [_ _] (clear-expired-sessions))
-      :schedule "* /30 * * * * *"
-      :opts {}}]))

+ 34 - 0
frameworks/Clojure/luminus/hello/test/hello/test/db/core.clj

@@ -0,0 +1,34 @@
+(ns hello.test.db.core
+  (:require [hello.db.core :as db]
+            [hello.db.migrations :as migrations]
+            [clojure.test :refer :all]
+            [clojure.java.jdbc :as jdbc]
+            [conman.core :refer [with-transaction]]
+            [environ.core :refer [env]]
+            [mount.core :as mount]))
+
+(use-fixtures
+  :once
+  (fn [f]
+    (mount/start #'hello.db.core/*db*)
+    (migrations/migrate ["migrate"])
+    (f)))
+
+(deftest test-users
+  (with-transaction [t-conn db/*db*]
+    (jdbc/db-set-rollback-only! t-conn)
+    (is (= 1 (db/create-user!
+               {:id         "1"
+                :first_name "Sam"
+                :last_name  "Smith"
+                :email      "[email protected]"
+                :pass       "pass"})))
+    (is (= [{:id         "1"
+             :first_name "Sam"
+             :last_name  "Smith"
+             :email      "[email protected]"
+             :pass       "pass"
+             :admin      nil
+             :last_login nil
+             :is_active  nil}]
+           (db/get-user {:id "1"})))))

+ 3 - 3
frameworks/Clojure/luminus/hello/test/hello/test/handler.clj

@@ -1,7 +1,7 @@
 (ns hello.test.handler
-  (:use clojure.test
-        ring.mock.request
-        hello.handler))
+  (:require [clojure.test :refer :all]
+            [ring.mock.request :refer :all]
+            [hello.handler :refer :all]))
 
 (deftest test-app
   (testing "main route"