├── .gitignore ├── Procfile ├── README.md ├── env └── dev │ └── clj │ └── clojuredatascience │ └── repl.clj ├── project.clj ├── resources ├── data │ └── resources.edn ├── docs │ └── docs.md ├── md │ └── about.md ├── public │ └── css │ │ └── screen.css └── templates │ ├── about.html │ ├── base.html │ └── home.html ├── src └── clojure_datascience │ ├── config.clj │ ├── core.clj │ ├── handler.clj │ ├── layout.clj │ ├── middleware.clj │ ├── resources.clj │ ├── routes │ └── home.clj │ ├── session.clj │ └── views │ └── resources.clj └── test └── clojure_datascience └── test └── handler.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /classes 4 | /checkouts 5 | pom.xml 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.env 10 | *.log 11 | .nrepl-port 12 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: java $JVM_OPTS -cp target/clojure-datascience.jar clojure.main -m clojure-datascience.core 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clojure-datascience 2 | 3 | [![Join the chat at https://gitter.im/metasoarous/clojure-datascience](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/metasoarous/clojure-datascience?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | The source code for [clojure-datascience.herokuapp.com](http://clojure-datascience.herokuapp.com) -- a collection of resources for the budding intersection of data scientists and Clojurists. 6 | 7 | ## Contributing 8 | 9 | For now resource submissions and website modifications should be done via pull requests. 10 | The data for the application is in `resources/data/resources.edn`. 11 | Any reasonable contributions will be merged. 12 | 13 | ## Prerequisites 14 | 15 | You will need [Leiningen][1] 2.0 or above installed. 16 | 17 | [1]: https://github.com/technomancy/leiningen 18 | 19 | ## Running 20 | 21 | To start a web server for the application, run: 22 | 23 | lein ring server 24 | 25 | ## License 26 | 27 | Copyright © 2015 Christopher Small 28 | 29 | -------------------------------------------------------------------------------- /env/dev/clj/clojuredatascience/repl.clj: -------------------------------------------------------------------------------- 1 | (ns clojuredatascience.repl 2 | (:use clojuredatascience.handler 3 | ring.server.standalone 4 | [ring.middleware file-info file])) 5 | 6 | (defonce server (atom nil)) 7 | 8 | (defn get-handler [] 9 | ;; #'app expands to (var app) so that when we reload our code, 10 | ;; the server is forced to re-resolve the symbol in the var 11 | ;; rather than having its own copy. When the root binding 12 | ;; changes, the server picks it up without having to restart. 13 | (-> #'app 14 | ; Makes static assets in $PROJECT_DIR/resources/public/ available. 15 | (wrap-file "resources") 16 | ; Content-Type, Content-Length, and Last Modified headers for files in body 17 | (wrap-file-info))) 18 | 19 | (defn start-server 20 | "used for starting the server in development mode from REPL" 21 | [& [port]] 22 | (let [port (if port (Integer/parseInt port) 3000)] 23 | (reset! server 24 | (serve (get-handler) 25 | {:port port 26 | :init init 27 | :auto-reload? true 28 | :destroy destroy 29 | :join? false})) 30 | (println (str "You can view the site at http://localhost:" port)))) 31 | 32 | (defn stop-server [] 33 | (.stop @server) 34 | (reset! server nil)) 35 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject clojure-datascience "0.1.0-SNAPSHOT" 2 | 3 | :description "FIXME: write description" 4 | :url "http://example.com/FIXME" 5 | 6 | :dependencies [[org.clojure/clojure "1.6.0"] 7 | [ring-server "0.4.0"] 8 | [selmer "0.8.2"] 9 | [com.taoensso/timbre "3.4.0"] 10 | [com.taoensso/tower "3.0.2"] 11 | [markdown-clj "0.9.65"] 12 | [hiccup "1.0.5"] 13 | [environ "1.0.0"] 14 | [im.chit/cronj "1.4.3"] 15 | [compojure "1.3.3"] 16 | [ring/ring-defaults "0.1.4"] 17 | [ring/ring-session-timeout "0.1.0"] 18 | [ring-middleware-format "0.5.0"] 19 | [noir-exception "0.2.3"] 20 | [lib-noir "0.8.9"] 21 | [bouncer "0.3.2"] 22 | [com.stuartsierra/component "0.2.3"] 23 | [com.novemberain/monger "2.0.0"] 24 | [com.draines/postal "1.11.3"] 25 | [prone "0.8.1"] 26 | ] 27 | 28 | :min-lein-version "2.0.0" 29 | :uberjar-name "clojure-datascience.jar" 30 | :repl-options {:init-ns clojure-datascience.handler} 31 | :jvm-opts ["-server"] 32 | 33 | :main clojure-datascience.core 34 | 35 | :plugins [[lein-ring "0.9.1"] 36 | [lein-environ "1.0.0"] 37 | [lein-ancient "0.6.5"] 38 | ] 39 | 40 | 41 | 42 | 43 | :ring {:handler clojure-datascience.handler/app 44 | :init clojure-datascience.handler/init 45 | :destroy clojure-datascience.handler/destroy 46 | :uberwar-name "clojure-datascience.war"} 47 | 48 | 49 | 50 | :profiles 51 | {:uberjar {:omit-source true 52 | :env {:production true} 53 | 54 | :aot :all} 55 | :dev {:dependencies [[ring-mock "0.1.5"] 56 | [ring/ring-devel "1.3.2"] 57 | [pjstadig/humane-test-output "0.7.0"] 58 | ] 59 | :source-paths ["env/dev/clj"] 60 | 61 | 62 | 63 | :repl-options {:init-ns clojure-datascience.core} 64 | :injections [(require 'pjstadig.humane-test-output) 65 | (pjstadig.humane-test-output/activate!)] 66 | :env {:dev true}}}) 67 | -------------------------------------------------------------------------------- /resources/data/resources.edn: -------------------------------------------------------------------------------- 1 | [;; Books 2 | {:name "Mastering Clojure Data Analysis" 3 | :category "Books" 4 | :author "Eric Rochester" 5 | :url "https://www.packtpub.com/big-data-and-business-intelligence/mastering-clojure-data-analysis" 6 | :alt-resources [{:title "Announcement" :url "http://www.ericrochester.com/pages/announcements/clj-data-analysis/index.html"}]} 7 | {:name "Clojure Machine Learning" 8 | :category "Books" 9 | :tags ["ml"] 10 | :author "Akhil Wali" 11 | :url "https://www.packtpub.com/big-data-and-business-intelligence/clojure-machine-learning"} 12 | 13 | ;; Videos 14 | {:name "Clojure Data Science" 15 | :category "Video" 16 | :author "Edmund Jackson" 17 | :url "https://www.youtube.com/watch?v=RVmY2lQ4DHE"} 18 | {:name "Enter the Matrix" 19 | :category "Video" 20 | :tags ["matrices"] 21 | :author "Mike Anderson" 22 | :url "https://www.youtube.com/watch?v=_h9TLJtjSJo"} 23 | {:name "Machine Learning Live" 24 | :tags ["ml"] 25 | :category "Video" 26 | :author "Mike Anderson" 27 | :url "https://www.youtube.com/watch?v=QJ1qgCr09j8"} 28 | {:name "Loom and Graphs in Clojure" 29 | :tags ["graph"] 30 | :category "Video" 31 | :url "https://www.youtube.com/watch?v=wEEutxTYQQU" 32 | :author "Aysylu Greenberg"} 33 | {:name "Cascalog" 34 | :category "Video" 35 | :tags ["big-data"] 36 | :url "https://www.youtube.com/results?search_query=cascalog"} 37 | ;# Youtube videos 38 | ;- Jony Epsilon: http://gorilla-repl.org/ 39 | 40 | ;; Libraries 41 | {:name "Gorilla REPL" 42 | :category "Library" 43 | :tags ["visualization" "environment"] 44 | :url "http://gorilla-repl.org/" 45 | :author "Jony Hudson" 46 | :alt-resources [{:title "Source code" :url "https://github.com/JonyEpsilon/gorilla-repl"}] 47 | :description "Wonderful browser based notebook programming environment for Clojure, akin to IPython or Mathematica Notebook."} 48 | {:name "Semantic CSV" 49 | :category "Library" 50 | :tags ["processing"] 51 | :url "http://github.com/metasoarous/semantic-csv" 52 | :author "Christopher Small" 53 | :description "Higher level tools for processing CSV and other tabular data." 54 | :alt-resources [{:title "Documentation" :url "http://metasoarous.github.io/semantic-csv"}]} 55 | {:name "clj-vw" 56 | :category "Library" 57 | :description "A Clojure client and wrapper for Vowpal Wabbit" 58 | :tags ["nlp" "ml"] 59 | :url "https://github.com/engagor/clj-vw"} 60 | {:name "deeplearning4j.org" 61 | :category "Library" 62 | :tags ["ml"] 63 | :lein-name [org.deeplearning4j/dl4j-spark "0.0.3.3.2.alpha1"]} 64 | {:name "core.matrix" 65 | :category "Library" 66 | :tags ["matrices"] 67 | :description "Abstract matrix/array computation API, with both native, Clojure and Java implementations" 68 | :author "Mike Anderson" 69 | :url "https://github.com/mikera/core.matrix"} 70 | {:name "Raynes/Conch" 71 | :category "Library" 72 | :description "Shell out to the executable of your choice" 73 | :url "https://github.com/Raynes/conch"} 74 | {:name "Loom" 75 | :category "Library" 76 | :tags ["graph"] 77 | :description "Graph processing library" 78 | :url "https://github.com/aysylu/loom"} 79 | {:name "Mallet LDA" 80 | :category "Library" 81 | :url "https://github.com/marcliberatore/mallet-lda"} 82 | {:name "clj-ml" 83 | :category "Library" 84 | :author "2010: Antonio Garrote; 2010-2013: Ben Mabey; 2013: Joshua Eckroth" 85 | :url "https://github.com/joshuaeckroth/clj-ml" 86 | :tags ["ml"] 87 | :description "Clojure wrapper around Weka and other ML libraries"} 88 | {:name "Grafter" 89 | :description "Tranform tabular data or produce Linked Graph Data" 90 | :category "Library" 91 | :author "Swirrl" 92 | :url "http://grafter.org/" 93 | :tags ["datamunging"] 94 | :lein-name [grafter "0.4.0"]} 95 | 96 | ;; Featured Research Projects 97 | {:name "Climate-driven introduction of the Black Death and successive plague reintroductions into Europe" 98 | :description "This paper shows the Black Plague likely resulted from continued reintroduction of plague into Europe from Asian rodent reservoirs. 99 | Previously, it was assumed that once introduced from Asia, European reservoirs were established, from which subsequence epidemics spawned. 100 | However, climate data analyses support the alternative hypothesis. 101 | The analyses in this paper used a number of Clojure analysis tools." 102 | :category "Showcase" 103 | :url "http://www.pnas.org/content/early/2015/02/20/1412887112" 104 | :alt-resources [{:title "Source Code" :url "https://zenodo.org/record/14973#.VOxH5vnF9vo"}] 105 | :author "Boris Schmid" 106 | :tags ["bio" "visualization"] 107 | } 108 | ] 109 | -------------------------------------------------------------------------------- /resources/docs/docs.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Managing Your Middleware 4 | 5 | Request middleware functions are located under the `clojuredatascience.middleware` namespace. 6 | A request logging helper called `log-request` has already been defined for you there. 7 | 8 | This namespace also defines two vectors for organizing the middleware called `development-middleware` and `production-middleware`. 9 | Any middleware that you only wish to run in development mode, such as `log-request`, should be added to the first vector. 10 | 11 | ### Here are some links to get started 12 | 13 | 1. [HTML templating](http://www.luminusweb.net/docs/html_templating.md) 14 | 2. [Accessing the database](http://www.luminusweb.net/docs/database.md) 15 | 3. [Serving static resources](http://www.luminusweb.net/docs/static_resources.md) 16 | 4. [Setting response types](http://www.luminusweb.net/docs/responses.md) 17 | 5. [Defining routes](http://www.luminusweb.net/docs/routes.md) 18 | 6. [Adding middleware](http://www.luminusweb.net/docs/middleware.md) 19 | 7. [Sessions and cookies](http://www.luminusweb.net/docs/sessions_cookies.md) 20 | 8. [Security](http://www.luminusweb.net/docs/security.md) 21 | 9. [Deploying the application](http://www.luminusweb.net/docs/deployment.md) 22 | -------------------------------------------------------------------------------- /resources/md/about.md: -------------------------------------------------------------------------------- 1 | 2 | This site was born out of [a discussion](https://groups.google.com/d/topic/clojure/vsjUlAWm64g/discussion) on the [Clojure Google Group](https://groups.google.com/forum/#!forum/clojure) about the state of Clojure as a language for Data Science. 3 | One of the points of discussion led to the acknowledgement that it would be nice to have a place for finding and reviewing Data Science & Machine Learning resources, similar to the [Clojure Toolbox](http://clojure-toolbox.com). 4 | This is a humble manifestation of that recognition. 5 | 6 | -------------------------------------------------------------------------------- /resources/public/css/screen.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 4 | height: 100%; 5 | padding-top: 40px; 6 | } 7 | 8 | div.category { 9 | padding: 20px; 10 | padding-top: 20px; 11 | padding-bottom: 20px; 12 | } 13 | 14 | small.attribution-footer { 15 | padding-top: 20px; 16 | padding-bottom: 20px; 17 | text-align: center; 18 | } 19 | 20 | -------------------------------------------------------------------------------- /resources/templates/about.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |
4 | {{about|markdown}} 5 |
6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /resources/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Welcome to clojuredatascience 6 | 7 | 8 | 9 | 26 | 27 |
28 | {% block content %} 29 | {% endblock %} 30 |
31 | 32 |
33 | 34 | 35 |

Site maintained by Christopher Small.

36 |
37 | 38 | 39 | 40 | 41 | 42 | {% style "/css/screen.css" %} 43 | 44 | 45 | 46 | 47 | 50 | {% block page-scripts %} 51 | {% endblock %} 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /resources/templates/home.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |
4 |

Welcome to clojure-datascience!

5 |

6 | Resources for the budding Clojure Data Scientist. 7 |

8 |

9 | Contribute Resources! » 10 |

11 |
12 | 13 |
14 |
15 | {{resources|hiccup}} 16 |
17 |
18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /src/clojure_datascience/config.clj: -------------------------------------------------------------------------------- 1 | (ns clojure-datascience.config 2 | (:require [com.stuartsierra.component :as component] 3 | [environ.core :refer [env]] 4 | [taoensso.timbre :as timbre :refer [log info]])) 5 | 6 | (def config-defaults 7 | {:server-port 3000}) 8 | 9 | 10 | (def config-parsers 11 | [[:server-port #(Integer/parseInt %)] 12 | [:mongo-uri identity]]) 13 | 14 | 15 | (defrecord Config [overrides] 16 | component/Lifecycle 17 | (start [component] 18 | (info "Reading env variables for configuration") 19 | (reduce 20 | (fn [c [k parser]] 21 | (assoc c k (if-let [v (env k)] 22 | (parser v) 23 | (get (merge config-defaults overrides) k)))) 24 | component 25 | config-parsers)) 26 | 27 | (stop [component] 28 | (info "Closing config (no-op)") 29 | component)) 30 | 31 | 32 | (defn new-config 33 | [overrides] 34 | (Config. overrides)) 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/clojure_datascience/core.clj: -------------------------------------------------------------------------------- 1 | (ns clojure-datascience.core 2 | (:require [clojure-datascience.handler :refer [make-handler]] 3 | [clojure-datascience.config :refer [new-config]] 4 | [clojure-datascience.resources :refer [new-db]] 5 | [com.stuartsierra.component :as component] 6 | [taoensso.timbre :as timbre :refer [log info]] 7 | [ring.adapter.jetty :refer [run-jetty]]) 8 | (:gen-class)) 9 | 10 | 11 | (defrecord WebServer [config database jetty] 12 | component/Lifecycle 13 | (start [component] 14 | (info "Starting Jetty server") 15 | (assoc component :jetty 16 | (run-jetty (make-handler database) {:port (:server-port config) :join? false}))) 17 | 18 | (stop [component] 19 | (info "Stopping Jetty server") 20 | (.stop (:jetty component)) 21 | (assoc component :jetty nil))) 22 | 23 | (defn new-web-server [] 24 | (map->WebServer {})) 25 | 26 | (defn new-system 27 | ([overrides] 28 | (component/system-map 29 | :config (new-config overrides) 30 | :database (component/using (new-db) [:config]) 31 | :web-server (component/using (new-web-server) [:config :database]))) 32 | ([] (new-system {}))) 33 | 34 | (defonce system nil) 35 | 36 | (defn init-if-needed 37 | [] 38 | (when-not system 39 | (alter-var-root #'system new-system))) 40 | 41 | (defn reinit 42 | [] 43 | (alter-var-root #'system (constantly (new-system {})))) 44 | 45 | (:database system) 46 | (defn start 47 | ([options] 48 | (init-if-needed) 49 | (alter-var-root #'system #(assoc % :overrides options)) 50 | (start)) 51 | ([] 52 | (init-if-needed) 53 | (alter-var-root #'system component/start))) 54 | 55 | (defn stop 56 | [] 57 | (alter-var-root #'system (fn [s] (when s (component/stop s))))) 58 | 59 | (defn restart 60 | ([] (stop) (start)) 61 | ([options] (stop) (start options))) 62 | 63 | (defn -main [& [port]] 64 | (let [opts (if port 65 | {:server-port (Integer/parseInt port)} 66 | {})] 67 | (component/start (new-system opts)))) 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/clojure_datascience/handler.clj: -------------------------------------------------------------------------------- 1 | (ns clojure-datascience.handler 2 | (:require [compojure.core :refer [defroutes routes]] 3 | [clojure-datascience.routes.home :refer [home-routes]] 4 | [clojure-datascience.middleware 5 | :refer [development-middleware production-middleware]] 6 | [clojure-datascience.session :as session] 7 | [com.stuartsierra.component :as component] 8 | [compojure.route :as route] 9 | [taoensso.timbre :as timbre] 10 | [taoensso.timbre.appenders.rotor :as rotor] 11 | [selmer.parser :as parser] 12 | [environ.core :refer [env]] 13 | [cronj.core :as cronj])) 14 | 15 | (defroutes base-routes 16 | (route/resources "/") 17 | (route/not-found "Not Found")) 18 | 19 | (defn init 20 | "init will be called once when 21 | app is deployed as a servlet on 22 | an app server such as Tomcat 23 | put any initialization code here" 24 | [] 25 | (timbre/set-config! 26 | [:appenders :rotor] 27 | {:min-level :info 28 | :enabled? true 29 | :async? false ; should be always false for rotor 30 | :max-message-per-msecs nil 31 | :fn rotor/appender-fn}) 32 | 33 | (timbre/set-config! 34 | [:shared-appender-config :rotor] 35 | {:path "clojure-datascience.log" :max-size (* 512 1024) :backlog 10}) 36 | 37 | (if (env :dev) (parser/cache-off!)) 38 | ;;start the expired session cleanup job 39 | (cronj/start! session/cleanup-job) 40 | (timbre/info "\n-=[ clojure-datascience started successfully" 41 | (when (env :dev) "using the development profile") "]=-")) 42 | 43 | (defn destroy 44 | "destroy will be called when your application 45 | shuts down, put any clean up code here" 46 | [] 47 | (timbre/info "clojure-datascience is shutting down...") 48 | (cronj/shutdown! session/cleanup-job) 49 | (timbre/info "shutdown complete!")) 50 | 51 | 52 | ;; Compoent compatibility stuff (al. a Stuart Sierra's approach of injecting rout handler dependencies into 53 | ;; the request hash...); here we're modifying a bit to make it clear that it's the database that we're 54 | ;; associng in. Also note that we're using a more custom key name for this than ::web-app 55 | 56 | (defn wrap-app-component [f database] 57 | (fn [req] 58 | (f (assoc req :clojure-datascience.component/database database)))) 59 | 60 | (defn make-handler [database] 61 | (-> (routes 62 | home-routes 63 | base-routes) 64 | (wrap-app-component database) 65 | development-middleware 66 | production-middleware)) 67 | 68 | -------------------------------------------------------------------------------- /src/clojure_datascience/layout.clj: -------------------------------------------------------------------------------- 1 | (ns clojure-datascience.layout 2 | (:require [selmer.parser :as parser] 3 | [selmer.filters :as filters] 4 | [hiccup.core :as hc] 5 | [markdown.core :refer [md-to-html-string]] 6 | [ring.util.response :refer [content-type response]] 7 | [compojure.response :refer [Renderable]] 8 | [ring.util.anti-forgery :refer [anti-forgery-field]] 9 | [ring.middleware.anti-forgery :refer [*anti-forgery-token*]] 10 | [environ.core :refer [env]])) 11 | 12 | (parser/set-resource-path! (clojure.java.io/resource "templates")) 13 | 14 | (parser/add-tag! :csrf-field (fn [_ _] (anti-forgery-field))) 15 | (filters/add-filter! :markdown (fn [content] [:safe (md-to-html-string content)])) 16 | (filters/add-filter! :hiccup (fn [content] [:safe (hc/html content)])) 17 | 18 | (deftype RenderableTemplate [template params] 19 | Renderable 20 | (render [this request] 21 | (content-type 22 | (->> (assoc params 23 | :page template 24 | :dev (env :dev) 25 | :csrf-token *anti-forgery-token* 26 | :servlet-context 27 | (if-let [context (:servlet-context request)] 28 | ;; If we're not inside a serlvet environment (for 29 | ;; example when using mock requests), then 30 | ;; .getContextPath might not exist 31 | (try (.getContextPath context) 32 | (catch IllegalArgumentException _ context)))) 33 | (parser/render-file (str template)) 34 | response) 35 | "text/html; charset=utf-8"))) 36 | 37 | (defn render [template & [params]] 38 | (RenderableTemplate. template params)) 39 | 40 | -------------------------------------------------------------------------------- /src/clojure_datascience/middleware.clj: -------------------------------------------------------------------------------- 1 | (ns clojure-datascience.middleware 2 | (:require [clojure-datascience.session :as session] 3 | [taoensso.timbre :as timbre] 4 | [environ.core :refer [env]] 5 | [selmer.middleware :refer [wrap-error-page]] 6 | [prone.middleware :refer [wrap-exceptions]] 7 | [ring.util.response :refer [redirect]] 8 | [ring.middleware.defaults :refer [site-defaults wrap-defaults]] 9 | [ring.middleware.session-timeout :refer [wrap-idle-session-timeout]] 10 | [noir-exception.core :refer [wrap-internal-error]] 11 | [ring.middleware.session.memory :refer [memory-store]] 12 | [ring.middleware.format :refer [wrap-restful-format]] 13 | 14 | )) 15 | 16 | (defn log-request [handler] 17 | (fn [req] 18 | (timbre/debug req) 19 | (handler req))) 20 | 21 | (defn development-middleware [handler] 22 | (if (env :dev) 23 | (-> handler 24 | wrap-error-page 25 | wrap-exceptions) 26 | handler)) 27 | 28 | (defn production-middleware [handler] 29 | (-> handler 30 | 31 | (wrap-restful-format :formats [:json-kw :edn :transit-json :transit-msgpack]) 32 | (wrap-idle-session-timeout 33 | {:timeout (* 60 30) 34 | :timeout-response (redirect "/")}) 35 | (wrap-defaults 36 | (assoc-in site-defaults [:session :store] (memory-store session/mem))) 37 | (wrap-internal-error :log #(timbre/error %)))) 38 | -------------------------------------------------------------------------------- /src/clojure_datascience/resources.clj: -------------------------------------------------------------------------------- 1 | (ns clojure-datascience.resources 2 | (:require [monger.core :as mg] 3 | [monger.collection :as mc] 4 | [com.stuartsierra.component :as component] 5 | [taoensso.timbre :as timbre :refer [log info]])) 6 | 7 | 8 | (defrecord MongoDatabase [config] 9 | component/Lifecycle 10 | (start [component] 11 | (info "Opening mongo connection") 12 | (let [conn (if (:mongo-uri config) 13 | (mg/connect-via-uri (:mongo-uri config)) 14 | (mg/connect)) 15 | mongo-db (mg/get-db conn "clojure-datascience")] 16 | (assoc component 17 | :mongo-connection conn 18 | :mongo-db mongo-db))) 19 | 20 | (stop [component] 21 | (info "Closing mongo connection") 22 | (mg/disconnect (:mongo-connection component)) 23 | (assoc component 24 | :mongo-connection nil 25 | :mongo-db nil))) 26 | 27 | 28 | (defn new-db [] 29 | (map->MongoDatabase {})) 30 | 31 | 32 | (def attrs [:name :category :author :url :alt-resources :tags :description :posted-by-name :posted-by-email]) 33 | 34 | (defn filter-attrs 35 | [data] 36 | (let [attrs-set (set attrs)] 37 | (->> data 38 | (filter (fn [[k v]] (attrs-set k))) 39 | (into {})))) 40 | 41 | (defn post-attrs 42 | [database data] 43 | (mc/insert (:mongo-db database) "resources" (filter-attrs data))) 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/clojure_datascience/routes/home.clj: -------------------------------------------------------------------------------- 1 | (ns clojure-datascience.routes.home 2 | (:require [clojure-datascience.layout :as layout] 3 | [clojure-datascience.views.resources :as rcs] 4 | [compojure.core :as compojure :refer [defroutes GET POST]] 5 | [com.stuartsierra.component :as component] 6 | [hiccup.core :as hc] 7 | [noir.response :as res] 8 | [clojure.java.io :as io])) 9 | 10 | ;(defrecord HomeRoutes [database routes] 11 | ;component/Lifecycle 12 | ;(start [component] 13 | ;(assoc component 14 | ;:routes (compojure/routes 15 | ;(GET "/" [] (home-page database)) 16 | 17 | (defn home-page [req] 18 | (layout/render 19 | "home.html" 20 | {:resources (-> (io/resource "data/resources.edn") 21 | slurp 22 | read-string 23 | rcs/resources-view)})) 24 | 25 | (defn about-page [] 26 | (layout/render 27 | "about.html" 28 | {:about (-> "md/about.md" io/resource slurp)})) 29 | 30 | 31 | (defn handle-submission 32 | [{:keys [params database]}] 33 | (res/redirect "/")) 34 | 35 | 36 | (defroutes home-routes 37 | (GET "/" [req] (home-page req)) 38 | (GET "/about" [] (about-page)) 39 | (POST "/submit" [req] (handle-submission req))) 40 | 41 | -------------------------------------------------------------------------------- /src/clojure_datascience/session.clj: -------------------------------------------------------------------------------- 1 | (ns clojure-datascience.session 2 | (:require [cronj.core :refer [cronj]])) 3 | 4 | (defonce mem (atom {})) 5 | 6 | (defn- current-time [] 7 | (quot (System/currentTimeMillis) 1000)) 8 | 9 | (defn- expired? [[id session]] 10 | (pos? (- (:ring.middleware.session-timeout/idle-timeout session) (current-time)))) 11 | 12 | (defn clear-expired-sessions [] 13 | (clojure.core/swap! mem #(->> % (filter expired?) (into {})))) 14 | 15 | (def cleanup-job 16 | (cronj 17 | :entries 18 | [{:id "session-cleanup" 19 | :handler (fn [_ _] (clear-expired-sessions)) 20 | :schedule "* /30 * * * * *" 21 | :opts {}}])) 22 | -------------------------------------------------------------------------------- /src/clojure_datascience/views/resources.clj: -------------------------------------------------------------------------------- 1 | (ns clojure-datascience.views.resources) 2 | 3 | 4 | (defn alt-resource-view 5 | [{:keys [title url]}] 6 | [:a {:href url} title]) 7 | 8 | (defmulti attr-view 9 | (fn [attr & _] 10 | (if (#{:alt-resources :tags} attr) 11 | attr 12 | ::standard-attr-view))) 13 | 14 | (defmethod attr-view ::standard-attr-view 15 | [attr data] 16 | data) 17 | 18 | (defmethod attr-view :alt-resources 19 | [attr alt-resources] 20 | (map alt-resource-view alt-resources)) 21 | 22 | (defmethod attr-view :tags 23 | [attr tags] 24 | (clojure.string/join ", " tags)) 25 | 26 | 27 | (defn resource-view 28 | [{:keys [url author alt-resources] :as resource}] 29 | [:div.resource 30 | [:h4 31 | [:a {:href url} (:name resource)]] 32 | [:ul 33 | (for [attr [:author :alt-resources :tags :description]] 34 | (when-let [v (get resource attr)] 35 | (into [:li (str (name attr) ": ")] 36 | (attr-view attr v))))]]) 37 | 38 | (defn resources-view 39 | [resources] 40 | (->> resources 41 | (group-by :category) 42 | (map 43 | (fn [[cat cat-resources]] 44 | (into 45 | [:div.category 46 | [:h2 {:id (str "cat-" cat)} cat]] 47 | (map resource-view cat-resources)))) 48 | (into [:div#resources]))) 49 | 50 | -------------------------------------------------------------------------------- /test/clojure_datascience/test/handler.clj: -------------------------------------------------------------------------------- 1 | (ns clojure-datascience.test.handler 2 | (:use clojure.test 3 | ring.mock.request 4 | clojure-datascience.handler)) 5 | 6 | (deftest test-app 7 | (testing "main route" 8 | (let [response (app (request :get "/"))] 9 | (is (= 200 (:status response))))) 10 | 11 | (testing "not-found route" 12 | (let [response (app (request :get "/invalid"))] 13 | (is (= 404 (:status response)))))) 14 | --------------------------------------------------------------------------------