├── .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 | [](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 |
10 |
11 |
14 |
15 |
16 | -
17 | Home
18 |
19 | -
20 | About
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | {% block content %}
29 | {% endblock %}
30 |
31 |
32 |
33 |
34 | 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 |
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 |
--------------------------------------------------------------------------------