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",
       "framework": "luminus",
       "language": "Clojure",
       "language": "Clojure",
       "orm": "Raw",
       "orm": "Raw",
-      "platform": "http-kit",
+      "platform": "Immutant",
       "webserver": "None",
       "webserver": "None",
       "os": "Linux",
       "os": "Linux",
       "database_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:
 To start a web server for the application, run:
 
 
-    lein ring server
+    lein run
 
 
 ## License
 ## 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"]
                  [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"
   :min-lein-version "2.0.0"
   :uberjar-name "hello.jar"
   :uberjar-name "hello.jar"
   :jvm-opts ["-server"]
   :jvm-opts ["-server"]
+  :resource-paths ["resources"]
 
 
   :main hello.core
   :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
   :profiles
   {:uberjar {:omit-source true
   {:uberjar {:omit-source true
              :env {:production 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
 --name: get-all-fortunes
 -- query all fortune records
 -- query all fortune records
 SELECT id, message FROM fortune
 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
 (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))
   (: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]
 (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
 (ns hello.db.core
   (:require
   (: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
 (defn get-world-random
   "Query a random World record between 1 and 10,000 from the database"
   "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]
 (defn get-query-count [queries]
   "Parse provided string value of query count, clamping values to between 1 and 500."
   "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."
   message text, and then return the results."
   (sort-by
   (sort-by
    :message
    :message
-   (conj (get-all-fortunes {} {:connection @db})
+   (conj (get-all-fortunes {})
          {:id 0 :message "Additional fortune added at request time."})))
          {:id 0 :message "Additional fortune added at request time."})))
 
 
 (defn update-and-persist
 (defn update-and-persist
@@ -63,7 +80,6 @@
   [queries]
   [queries]
   (for [world (-> queries run-queries)]
   (for [world (-> queries run-queries)]
     (let [updated-world (assoc world :randomNumber (inc (rand-int 9999)))]
     (let [updated-world (assoc world :randomNumber (inc (rand-int 9999)))]
-      (update-world<! updated-world {:connection @db})
+      (update-world<! updated-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
 (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.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]
             [compojure.route :as route]
-            [taoensso.timbre :as timbre]
-            [taoensso.timbre.appenders.rotor :as rotor]
-            [selmer.parser :as parser]
             [environ.core :refer [env]]
             [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
 (defn init
   "init will be called once when
   "init will be called once when
@@ -22,36 +15,27 @@
    an app server such as Tomcat
    an app server such as Tomcat
    put any initialization code here"
    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
 (defn destroy
   "destroy will be called when your application
   "destroy will be called when your application
    shuts down, put any clean up code here"
    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]
   (:require [selmer.parser :as parser]
             [selmer.filters :as filters]
             [selmer.filters :as filters]
             [markdown.core :refer [md-to-html-string]]
             [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.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)))
 (parser/add-tag! :csrf-field (fn [_ _] (anti-forgery-field)))
 (filters/add-filter! :markdown (fn [content] [:safe (md-to-html-string content)]))
 (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
 (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]]
             [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.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]]
             [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]
   (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
       (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
 (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
 (deftest test-app
   (testing "main route"
   (testing "main route"