diff --git a/README.md b/README.md
index 3ed3b8e..e751ce5 100644
--- a/README.md
+++ b/README.md
@@ -1,109 +1,86 @@
# Clojure intermediate workshop
-# Setup Instructions
+## Introduction
+The project aims to introduce you to a few concepts of Clojure and it's
+tooling. It will equip you with knowledge and skills to write a REST API
+service in Clojure.
-Just do the following one by one, and you should be fine.
-## Java 8
+## Levels
+The project has different levels (chapters) each focusing on a particular area. You can switch to different levels by,
+```
+./level_up
+```
-You will need Java to work with this Clojure workshop content.
+You can start over again from
+```
+./restart
+```
-First, make sure you have Java 8.
- - Run `java -version` in your terminal.
- - If Java is not installed, please [download and install Java 8 from here](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html).
- - Once you are done, `java -version` should show you a Java 1.8.x version.
+## Setup instructions
-Notes:
+### Java 11
+You will need java to work with this workshop content.
- - If you have Java 9+, that should be OK too.
- - The LightTable editor is known to break with Java 9. Use Java 8 if you are keen on using LightTable.
- - We have not tested this project with Java 7 and earlier.
+First check if you have Java 11. You can do this by running the following command:
+`java -version`
+If Java is not installed please register at this link and download it for free from the following link.
+https://www.oracle.com/technetwork/java/javase/downloads/jdk11-downloads-5066655.html
-## Leiningen
+You can verify that java has been installed properly by running the following command
+`java -version`
-Follow [Leiningen setup instructions here](https://leiningen.org/).
+We have not tested this project with Java versions other than Java 11 hence it is highly recommended
+that you have this one installed
-### Fire up a REPL
+### Leiningen
+Follow Leiningen setup instructions mentioned here: https://leiningen.org/
- - Clone this project
- - Open your terminal, and do the following.
- - `cd` into this project's root directory
- - Use `lein repl` command to start a REPL with Leiningen.
- - Wait for it... the REPL will start and print out a message with some
- useful information
- - Locate the `port` and `host` information in the message. We will need this information soon.
+Fire up a REPL by running the following command `lein repl`. This should give you a repl
+prompt. Enter `(+ 1 2)` and press enter. You should get back `3` as the result.
-Note:
+Additionally run `lein --version` to ensure that the correct version of lein has been installed.
- - [Boot](http://boot-clj.com/) should be fine too, but you may need to generate your own _boot_ file(s).
+This project is tested with Leiningen 2.9.1. It is highly recommended that you use the same version.
+**WARNING** This project will break with Leiningen 3.0.0 or higher
-## Code Editor and Tooling
+### Intellij
-Set up an editor and figure out how to evaluate Clojure code with it.
-We are fine with you choosing the editor as long as your editor can do,
+You can download Intellij by following the instructions here: https://www.jetbrains.com/idea/download/index.html
- - Connect to a Clojure REPL from the editor
- - Evaluate snippets and/or entire namespaces in the connected REPL from the editor.
- - Code navigation
- - Paredit / Parinfer
+You are free to use the editor of your choice as long as you can do the following with it:
-Editors we can help out with
- - Emacs
- - Vim
- - Cursive
+* Jump to definition for Clojure or Java sources
+* Start or connect to a REPL
+* Structural editing with paredit / parinfer
-### Cursive (IntelliJ)
+We will be using Intellij + Cursive for this workshop hence it is highly recommended that you have it installed. For Intellij,
+we recommend version 2019.3.2 or later.
-If you don't have an editor setup, we suggest you use Cursive with IntelliJ. Please follow instructions from [here](https://cursive-ide.com/userguide/).
-Do note that you may need to use it in trial mode or get an appropriate license ahead of time. There's a cost-free license available for personal/non-commercial hacking.
+### Cursive
-# Further reading
+You can get started with Cursive by following the instructions here: https://cursive-ide.com/userguide/
-## Inspiring and insightful source code
-### [Clojure Core](https://github.com/clojure/clojure/tree/master/src/clj/clojure)
-This one is not a surprise, but reading sources in the core Clojure library (with the clojure.core namespace being a good starting point) is highly recommended. This one comes _directly_ from the masters, and it can not get more idiomatic (and simpler, really) than this.
+### Postman
+You can download Postman by following the instructions here: https://www.postman.com/downloads/
-### [Monger](https://github.com/michaelklishin/monger)
+The recommended version of Postman is 7.17 or later
-Monger uses official Java client for talking with Mongo. This is a standard way of writing database wrappers.
-Few interesting parts,
- - [monger.query/exec](https://github.com/michaelklishin/monger/blob/master/src/clojure/monger/query.clj#L87) uses doto to run a bunch of Java methods
- - [monger.query/exec](https://github.com/michaelklishin/monger/blob/master/src/clojure/monger/query.clj#L87) also uses Protocols [monger.conversion/ConvertToDBObject](https://github.com/michaelklishin/monger/blob/6bf528ed5b8a21153e3df1aa0cd1d88e08f31e3a/src/clojure/monger/conversion.clj#L52) and [monger.conversion/ConvertFromDBObject](https://github.com/michaelklishin/monger/blob/6bf528ed5b8a21153e3df1aa0cd1d88e08f31e3a/src/clojure/monger/conversion.clj#L108) to convert objects at boundary. Monger converts mutable data List/DBList to Clojure vectors.
-### [Carmine](https://github.com/ptaoussanis/carmine)
-Carmine is Redis client and is written almost from scratch.
- - API is built with a [macro](https://github.com/ptaoussanis/carmine/blob/master/src/taoensso/carmine.clj#L21)
- - An atom is used to collect [test](https://github.com/ptaoussanis/carmine/blob/d00b61afb25426c8ec44f24bf544ae85dc93a4af/test/taoensso/carmine/tests/main.clj#L249) results
- - Exercise - Can we use core.async here to remove dependency on Thread/sleep?
- - It uses [IConnectionPool](https://github.com/ptaoussanis/carmine/blob/d00b61afb25426c8ec44f24bf544ae85dc93a4af/src/taoensso/carmine/connections.clj#L42) to create different implementations of connection pool
- - It uses a [edn file](https://github.com/ptaoussanis/carmine/blob/7d0e6f054a42473af4c513869491b752567f3cec/src/commands.edn) to map list of commands Redis supports to Carmine API functions using [defcommands macro](https://github.com/ptaoussanis/carmine/blob/d00b61afb25426c8ec44f24bf544ae85dc93a4af/src/taoensso/carmine/commands.clj#L275)
-
-### [core.cache](https://github.com/clojure/core.cache)
-core.cache is Clojure contrib library. It provides in-memory implementations of different caching strategies
- - [core.cache/defcache](https://github.com/clojure/core.cache/blob/master/src/main/clojure/clojure/core/cache.clj#L67) is a macro that reduces repetition of defining a type
- - [This](https://github.com/clojure/core.cache/blob/master/src/main/clojure/clojure/core/cache.clj#L224) shows that mutation to cache creates a new value
-
-### [clucy](https://github.com/weavejester/clucy/blob/master/src/clucy/core.clj)
-This one is old, but it really stands out for a few reasons as far as learning goes
- - A concise example that shows Java inter-op
- - Careful design considerations, and thus what a succinct piece of code within a single namespace can achieve
- - Clever use of `meta` attributes on `vars`
-
-### [Durable Queue](https://github.com/Factual/durable-queue)
-This one pushes some limits on Clojure-Java interop without descending down into Java (language) land. An instructive piece of source, all within a single namespace.
-
-## Links
- - [Clojure ground up](https://aphyr.com/tags/Clojure-from-the-ground-up))
- - [Clojure toolbox - list of clojure projects/libraries](https://www.clojure-toolbox.com)
+# Credits
+ - [Joel Victor](https://github.com/joel-victor) and [Kapil Reddy](https://github.com/kapilreddy) for course design and being the core teaching staff at the second edition of this workshop at IN/Clojure 2020.
+ - [Ravindra Jaju](https://github.com/jaju) and [Kapil Reddy] (https://github.com/kapilreddy) for course design and being the core teaching staff at the second edition of this workshop at IN/Clojure 2019.
+ - All the workshop participants, and the many Clojurists who generously donated their time to make it successful.
+ - [inclojure-org](https://github.com/inclojure-org) for being the umbrella under which this work happened.
## Copyright and License
-Copyright � 2018-2019 [IN/Clojure](https://inclojure.org/).
+Copyright © 2017-2018 [IN/Clojure](http://inclojure.org/).
Distributed under the [MIT license](https://github.com/inclojure-org/clojure-by-example/blob/master/LICENSE).
diff --git a/level_up b/level_up
new file mode 100755
index 0000000..180cd95
--- /dev/null
+++ b/level_up
@@ -0,0 +1,41 @@
+#!/bin/bash
+total_chapters=10
+if [ -f .current_level ]
+then
+ current_level=$(<.current_level)
+else
+ current_level=1
+fi
+
+next_level=$[(current_level%total_chapters)+1]
+echo $next_level > .current_level
+echo "Moving to savepoint:" $next_level
+
+# if [ -f ./workshop-app/target/ ]
+# then
+# rm -r ./workshop-app/target/
+# fi
+
+# if [ -f ./workshop-app/.nrepl-port ]
+# then
+# rm -r ./workshop-app/.nrepl-port
+# fi
+
+# if [ -f ./workshop-app/prod_database_1.sqlite ]
+# then
+# rm ./workshop-app/prod_database_1.sqlite
+# fi
+
+## backing your solution
+# mkdir -p ./your-solutions/$current_level/src/ ./your-solutions/$current_level/test/
+# cp -R ./workshop-app/src/ ./your-solutions/$current_level/src/
+# cp -R ./workshop-app/test/ ./your-solutions/$current_level/test/
+
+rm -r ./workshop-app/src/ ./workshop-app/test/
+mkdir -p ./workshop-app/src/ ./workshop-app/test/
+
+cp -R ./save-points/$next_level/src/ ./workshop-app/src/
+if [ -d ./save-points/$next_level/test ]
+then
+ cp -R ./save-points/$next_level/test/ ./workshop-app/test/
+fi
diff --git a/project.clj b/project.clj
deleted file mode 100644
index 200a36b..0000000
--- a/project.clj
+++ /dev/null
@@ -1,30 +0,0 @@
-(def lucene-version "7.6.0")
-
-(defproject intermediate-clojure-workshop "0.0.1-SNAPSHOT"
-
- :description "Clojure Workshop - Next Steps"
- :url "https://github.com/jaju/intermediate-clojure-workshop"
- :license {:name "Eclipse Public License"
- :url "http://www.eclipse.org/legal/epl-v10.html"}
- :min-lein-version "2.8.1"
- :dependencies [[org.clojure/clojure "1.10.0"]
- [org.clojure/core.async "0.4.490"]
- [org.clojure/test.check "0.9.0"]
-
- [integrant "0.7.0"]
-
- [cheshire "5.8.1"]
- [ring/ring-json "0.4.0"]
- [compojure "1.6.1"]
- [aleph "0.4.6"]
-
- [org.clojure/data.csv "0.1.4"]
- [org.clojure/java.jdbc "0.7.8"]
- [org.hsqldb/hsqldb "2.4.1"]
- [com.zaxxer/HikariCP "3.3.0"]
-
- [org.apache.lucene/lucene-core ~lucene-version]
- [org.apache.lucene/lucene-queryparser ~lucene-version]
- [org.apache.lucene/lucene-analyzers-common ~lucene-version]]
-
- :profiles {:dev {:dependencies [[integrant/repl "0.3.1"]]}})
diff --git a/restart b/restart
new file mode 100755
index 0000000..c6552fe
--- /dev/null
+++ b/restart
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+
+level=${1:-1}
+echo $level > .current_level
+echo "Resetting to save point:" $level
+# if [ -f ./workshop-app/target/ ]
+# then
+# rm -r ./workshop-app/target/
+# fi
+
+# if [ -f ./workshop-app/.nrepl-port ]
+# then
+# rm -r ./workshop-app/.nrepl-port
+# fi
+
+
+# if [ -f ./workshop-app/prod_database_1.sqlite ]
+# then
+# rm ./workshop-app/prod_database_1.sqlite
+# fi
+
+# backing your solution
+# mkdir -p ./your-solutions/$level/src/ ./your-solutions/$level/test/
+# cp -R ./workshop-app/src/ ./your-solutions/$level/src/
+# cp -R ./workshop-app/test/ ./your-solutions/$level/test/
+
+rm -r ./workshop-app/src/ ./workshop-app/test/
+mkdir -p ./workshop-app/src/ ./workshop-app/test/
+
+cp -R ./save-points/$level/src/ ./workshop-app/src/
+if [ -d ./save-points/$next_level/test/ ]
+then
+ cp -R ./save-points/$next_level/test/ ./workshop-app/test/
+fi
diff --git a/save-points/.idea/.gitignore b/save-points/.idea/.gitignore
new file mode 100644
index 0000000..e7e9d11
--- /dev/null
+++ b/save-points/.idea/.gitignore
@@ -0,0 +1,2 @@
+# Default ignored files
+/workspace.xml
diff --git a/save-points/.idea/runConfigurations/WorkshopSavePointREPL.xml b/save-points/.idea/runConfigurations/WorkshopSavePointREPL.xml
new file mode 100644
index 0000000..9d1ed7f
--- /dev/null
+++ b/save-points/.idea/runConfigurations/WorkshopSavePointREPL.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/save-points/1/src/workshop_app/core.clj b/save-points/1/src/workshop_app/core.clj
new file mode 100644
index 0000000..0b5d183
--- /dev/null
+++ b/save-points/1/src/workshop_app/core.clj
@@ -0,0 +1,7 @@
+(ns workshop-app.core
+ (:gen-class))
+
+
+(defn -main
+ [& _]
+ (println "Hello World!!!"))
\ No newline at end of file
diff --git a/save-points/10/src/workshop_app/clj_to_java_interop.clj b/save-points/10/src/workshop_app/clj_to_java_interop.clj
new file mode 100644
index 0000000..fc2c2d6
--- /dev/null
+++ b/save-points/10/src/workshop_app/clj_to_java_interop.clj
@@ -0,0 +1,23 @@
+(ns workshop-app.clj-to-java-interop)
+
+(gen-class :name org.inclojure.Demo
+ :init init
+ :prefix "-"
+ :state "state"
+ :methods [[getName [] String]
+ [setName [String] void]])
+
+
+(defn -init []
+ "State a hash map."
+ [[] (atom {})])
+
+
+(defn -getName
+ [this]
+ (:name @(.state this)))
+
+
+(defn -setName
+ [this name]
+ (swap! (.state this) assoc :name name))
\ No newline at end of file
diff --git a/save-points/10/src/workshop_app/core.clj b/save-points/10/src/workshop_app/core.clj
new file mode 100644
index 0000000..de470e4
--- /dev/null
+++ b/save-points/10/src/workshop_app/core.clj
@@ -0,0 +1,20 @@
+(ns workshop-app.core
+ (:gen-class)
+ (:require [ring.adapter.jetty :as raj]
+ [ring.middleware.params :as rmp]
+ [ring.middleware.keyword-params :as rmkp]
+ [workshop-app.routes :as war]
+ [workshop-app.middlewares.users :as wamu]
+ [workshop-app.db.sqlite :as wads]))
+
+
+(defn -main
+ [& _]
+ (wads/create-table wads/conn)
+ (raj/run-jetty (-> war/app-routes
+ rmkp/wrap-keyword-params
+ rmp/wrap-params
+ wamu/handle-any-exception
+ wamu/reject-uri-ending-with-slash)
+ {:port 65535
+ :join? false}))
\ No newline at end of file
diff --git a/save-points/10/src/workshop_app/db/in_mem.clj b/save-points/10/src/workshop_app/db/in_mem.clj
new file mode 100644
index 0000000..45ea1a2
--- /dev/null
+++ b/save-points/10/src/workshop_app/db/in_mem.clj
@@ -0,0 +1,33 @@
+(ns workshop-app.db.in-mem
+ (:refer-clojure :rename {update cc-update
+ read cc-read}))
+
+
+(def conn (atom {}))
+
+(defn create!
+ "Create an entry for k in our given in memory datastore."
+ [c k v]
+ (assert (some? k) "key cannot be nil.")
+ (swap! c assoc k v))
+
+
+(defn update!
+ "Update the entry for k to value v or add it if it doesn't exist in our in memory datastore."
+ [c k v]
+ (assert (some? k) "key cannot be nil.")
+ (swap! c assoc k v))
+
+
+(defn delete!
+ "Delete the entry for k in our in memory datastore."
+ [c k]
+ (assert (some? k) "key cannot be nil.")
+ (swap! c dissoc k))
+
+
+(defn read
+ "Read the entry for k in our in memory datastore."
+ [c k]
+ (assert (some? k) "key cannot be nil.")
+ (get @c k))
\ No newline at end of file
diff --git a/save-points/10/src/workshop_app/db/mongo.clj b/save-points/10/src/workshop_app/db/mongo.clj
new file mode 100644
index 0000000..3a879e6
--- /dev/null
+++ b/save-points/10/src/workshop_app/db/mongo.clj
@@ -0,0 +1,14 @@
+(ns workshop-app.db.mongo)
+
+
+(defonce r-db (atom {:foo :bar}))
+
+(defn fetch
+ [k]
+ (Thread/sleep (rand-nth [100 300]))
+ (clojure.core/get @r-db k))
+
+(defn set
+ [k v]
+ (Thread/sleep (rand-nth [ 100 300]))
+ (swap! r-db assoc k v))
diff --git a/save-points/10/src/workshop_app/db/redis.clj b/save-points/10/src/workshop_app/db/redis.clj
new file mode 100644
index 0000000..eddf7c2
--- /dev/null
+++ b/save-points/10/src/workshop_app/db/redis.clj
@@ -0,0 +1,13 @@
+(ns workshop-app.db.redis)
+
+(defonce r-db (atom {}))
+
+(defn fetch
+ [k]
+ (Thread/sleep (rand-nth [20 20 20 20 20 20 20 300]))
+ (clojure.core/get @r-db k))
+
+(defn set
+ [k v]
+ (Thread/sleep (rand-nth [20 20 20 20 20 20 20 300]))
+ (swap! r-db assoc k v))
diff --git a/save-points/10/src/workshop_app/db/sqlite.clj b/save-points/10/src/workshop_app/db/sqlite.clj
new file mode 100644
index 0000000..112429d
--- /dev/null
+++ b/save-points/10/src/workshop_app/db/sqlite.clj
@@ -0,0 +1,79 @@
+(ns workshop-app.db.sqlite
+ (:refer-clojure :rename {update cc-update
+ read cc-read})
+ (:import (java.sql DriverManager Connection PreparedStatement)))
+
+;; Connection conn = DriverManager.getConnection("jdbc:sqlite:prod_database_1.sqlite");
+(defn init-conn!
+ [conn-string]
+ (DriverManager/getConnection conn-string))
+
+(def conn (init-conn! "jdbc:sqlite:prod_database_1.sqlite"))
+
+;; Statement statement = conn.createStatement();
+;; statement.executeUpdate("create table if not exists person(name string primary key, dob string)");
+;; statement.close()
+(defn create-table
+ [c]
+ (with-open [statement (.createStatement c)]
+ (.executeUpdate statement "create table if not exists person(name string primary key, dob string)")))
+
+
+;; String k, v;
+;; Statement statement = conn.prepareStatement("insert into person values(?,?)");
+;; statement.setString(1, k);
+;; statement.setString(2, v);
+;; statement.executeUpdate();
+;; statement.close();
+(defn create!
+ [conn k v]
+ (let [statement (.prepareStatement ^Connection conn "insert into person(name, dob) values (?,?)")]
+ (.setString ^PreparedStatement statement 1 k)
+ (.setString ^PreparedStatement statement 2 v)
+ (.executeUpdate statement)
+ (.close statement)))
+
+;; String k, v;
+;; Statement statement = conn.prepareStatement("insert into person values(?,?)");
+;; statement.setString(1, k);
+;; statement.setString(2, v);
+;; statement.executeUpdate();
+;; statement.close();
+(defn update!
+ [conn k v]
+ (with-open [statement (doto (.prepareStatement ^Connection conn "update person set dob=? where name=?")
+ (.setString 2 k)
+ (.setString 1 v))]
+ (.executeUpdate statement)))
+
+
+;; String k;
+;; Statement statement = conn.prepareStatement("delete from person where name=?");
+;; statement.setString(1, k);
+;; statement.executeUpdate();
+;; statement.close();
+(defn delete!
+ [conn k]
+ (with-open [statement (.prepareStatement ^Connection conn "delete from person where name=?")]
+ (.setString ^PreparedStatement statement 1 k)
+ (.executeUpdate statement)))
+
+
+;; Statement statement = conn.prepareStatement("select name, dob from person where name=?");
+;; statement.setString(1, k);
+;; ResultSet rs = statement.executeQuery();
+;; Result result = rs.next();
+;; ResultSetMetadata rsm = result.getMetaData();
+;; int columnCount = rsm.getColumnCount();
+(defn read
+ [conn k]
+ (with-open [statement (doto (.prepareStatement ^Connection conn "select name, dob from person where name=?")
+ (.setString 1 k))]
+ (with-open [rs (.executeQuery statement)]
+ (when (.next rs)
+ (let [rs-meta (.getMetaData rs)
+ column-count (.getColumnCount rs-meta)]
+ (into {}
+ (map (fn [idx] [(.getColumnLabel rs-meta (int idx))
+ (.getObject rs (int idx))])
+ (range 1 (inc column-count)))))))))
\ No newline at end of file
diff --git a/save-points/10/src/workshop_app/handlers/concurrency.clj b/save-points/10/src/workshop_app/handlers/concurrency.clj
new file mode 100644
index 0000000..d61c384
--- /dev/null
+++ b/save-points/10/src/workshop_app/handlers/concurrency.clj
@@ -0,0 +1,430 @@
+(ns workshop-app.handlers.concurrency
+ (:require [workshop-app.db.redis :as redis]
+ [workshop-app.db.mongo :as mongo]))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; Concurrency
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; A way to get safe access to limited resources by multiple
+;; actors/threads
+
+;; There are multiple paradigms models to do concurrency
+
+;; Locks are one way of doing it
+
+;; Clojure does an amazing job where there is immutable data by means of
+;; persistent data structures provided by Clojure core
+
+;; But what about mutating things?
+
+;; There are four ways,
+
+;; vars
+;; refs
+;; agents
+;; atoms
+
+;; But first a brief introduction to running things in different threads
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; Future
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+;; This will run on a different thread
+(comment
+ (future 1)
+
+ ;; This will block and wait till it's complete
+ (deref (future 1))
+
+ ;; A short hand for deref
+ @(future 1)
+
+ (def f (future (println "Running thread")
+ 1))
+
+ ;; Guess the output of second deref
+ @f
+ @f)
+
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; Promise
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; Different futures can pass on values with futures as well.
+
+(comment
+ (def p (promise))
+
+ ;; This will block and execute
+ (future (Thread/sleep 5000) (deliver p 1))
+
+ @p)
+
+
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; vars
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+(def x 1)
+
+(comment
+ (def a 1)
+ (future (println a))
+ (future (println a)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; Atoms
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; Use it to represent Shared state
+
+;; But first
+;; Problems with shared state and why it's messy
+
+;; int b = 1
+;; new Thread(() -> b = 3
+;; Thread.sleep (new Random().nextInt(1000))
+;; System.out.println(b)).start();
+;; new Thread(() -> b = 4
+;; Thread.sleep (new Random().nextInt(1000))
+;; System.out.println(b)).start();
+
+;; https://aphyr.com/posts/306-clojure-from-the-ground-up-state
+
+;; A counterpart in Clojure
+
+
+(comment
+ (do
+ (def b [])
+
+ (doseq [n (range 2000)]
+ (future (def b (conj b
+ n))))
+ (println (count b))))
+
+
+;; Let's slow things down to understand
+
+(comment
+ (do
+ (def b [])
+
+ (doseq [n (range 2000)]
+ (future (def b (conj b
+ (do (when (= n 5)
+ (println (count b))
+ (Thread/sleep 100))
+ n)))))
+ (Thread/sleep 100)
+ (println (count b))))
+
+;; We need transformation of state with stronger gaurantees
+
+
+;; Variables mix State + Identitiy
+
+
+;; A symbol in clojure is just an identity which points to a value / state
+
+(def a 1)
+
+;; It can point to an atom
+
+(def a (atom 1))
+
+;; Since atom's value can change we need to deref it to access it
+
+(deref a)
+
+;; A short hand to deref is @
+
+@a
+
+;; @TODO add example of swap! and reset!
+
+;; Let's try this with an atom
+
+(comment
+ (do
+ (def b (atom []))
+ (doseq [n (range 2000)]
+ (future (swap! b conj n)))
+ (println (count @b))))
+
+(comment
+ (do
+ (def b (atom []))
+
+ (doseq [n (range 2000)]
+ (future (swap! b
+ conj
+ (do (when (= n 5)
+ (println (count @b))
+ (Thread/sleep 100))
+ n))))
+ (Thread/sleep 100)
+ (println (count @b))))
+
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; refs
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; Refs ensure safe mutation of multiple shared states using STM
+
+;; STM to me looks something like this
+
+;; https://vimeo.com/106226560#t=10s
+
+;; dosync is macro to start transaction
+
+;; ref-set sets it to a value
+
+;; alter runs a function on the value
+
+;; Values held by refs must be immutable preferably clojure persistent
+;; structures
+
+(def a-ref-num (ref 0))
+(def b-ref-num (ref 0))
+
+(comment
+ (doseq [n (range 10)]
+ (future
+ (dosync
+ (println "Transaction - " n)
+ (ref-set a-ref-num n)
+ (Thread/sleep (rand-int 20))
+ (ref-set b-ref-num n)))))
+
+[@a-ref-num @b-ref-num]
+
+
+(do
+ (def a-ref (ref 0))
+ (def b-ref (ref 0))
+ (def a-atom (atom 0))
+ (def b-atom (atom 0)))
+
+(comment
+ (do
+ (doseq [n (range 100)]
+ (future
+ (dosync
+ (ref-set a-ref n)
+ (Thread/sleep (rand-int 200))
+ (ref-set b-ref n))
+
+ (reset! a-atom n)
+ (Thread/sleep (rand-int 200))
+ (reset! b-atom n)))
+
+ (doseq [n (range 5)]
+ (Thread/sleep 1000)
+ (println (format "Ref values A - %s, B - %s\nAtom values A - %s, B - %s\n"
+ @a-ref
+ @b-ref
+ @a-atom
+ @b-atom)))))
+
+
+(def a-alter (ref []))
+(def b-alter (ref []))
+
+(comment
+ (doseq [n (range 10)]
+ (future
+ (dosync
+ (println "Transaction - " n)
+ (alter a-alter conj n)
+ (Thread/sleep (rand-int 20))
+ (alter b-alter conj n)))))
+
+[@a-alter @b-alter]
+
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; Thundering herd
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+
+;; Thundering herd
+
+;; When many readers simultaneously request the same data element, there
+;; can be a database read overload,
+;; sometimes called the “Thundering Herd” problem.
+;; https://www.ehcache.org/documentation/2.8/recipes/thunderingherd.html
+
+
+;; Read from cache if not present read from database and update cache
+
+(defn read+update-1
+ [k]
+ (if-let [v (redis/fetch :foo)]
+ [v :redis]
+ (do
+ (let [v (mongo/fetch k)]
+ (redis/set k v)
+ [v :mongo]))))
+
+(comment
+
+ (do
+ (reset! redis/r-db {})
+
+ (doall
+ (frequencies (map second (map (fn [_]
+ (read+update-1 :foo))
+ (range 10))))))
+
+
+
+ ;; For 1000 concurrent requests there is a possibility of requests
+ ;; going to MongoDB
+
+ (do
+ (reset! redis/r-db {})
+
+ (def t-h-1
+ (doall
+ (map (fn [_]
+ (future (read+update-1 :foo)))
+ (range 1000))))
+
+ ;; To analyze this data first we need to get the values from futures
+
+ (frequencies (map second (map deref t-h-1)))))
+
+
+
+;;; Let's introduce atom!
+
+(def ongoing-updates-a (atom #{}))
+
+(defn read+update-2
+ [k]
+ (if-let [v (redis/fetch :foo)]
+ [v :redis]
+ (let [update-ongoing? (deref ongoing-updates-a)]
+ (if-not (update-ongoing? k)
+ (do
+ (swap! ongoing-updates-a conj k)
+ (let [v (mongo/fetch k)]
+ (redis/set k v)
+ (swap! ongoing-updates-a disj k)
+ [v :mongo]))
+ [1 :default]))))
+
+(comment
+
+ (do
+ (reset! redis/r-db {})
+
+ (def t-h-1
+ (doall
+ (map (fn [_]
+ (future (read+update-2 :foo)))
+ (range 10000))))
+
+
+ (frequencies (map second (map deref t-h-1)))))
+
+
+;; Let's use ref instead
+
+(def ongoing-updates-ref (ref #{}))
+
+(defn read+update-3
+ [k]
+ (if-let [v (redis/fetch :foo)]
+ [v :redis]
+ (let [update-ongoing? (dosync (when-not (get @ongoing-updates-ref k)
+ (alter ongoing-updates-ref conj k)))]
+ (if update-ongoing?
+ (let [v (mongo/fetch k)]
+ (redis/set k v)
+ (dosync (alter ongoing-updates-ref disj k))
+ [v :mongo])
+ [1 :default]))))
+
+
+
+(comment
+
+ (do
+ (reset! redis/r-db {})
+
+ (def t-h-2
+ (doall
+ (map (fn [_]
+ (future (read+update-3 :foo)))
+ (range 10000))))
+
+
+ (frequencies (map second (map deref t-h-2)))))
+
+
+
+;; But how do we fix the nils?
+
+(def ongoing-updates-ref-p (ref {}))
+
+
+(defn read+update-4
+ [k]
+ (if-let [v (redis/fetch :foo)]
+ [v :redis]
+ (let [[action p] (dosync (if-let [p (get @ongoing-updates-ref-p k)]
+ [:wait p]
+ (let [p (promise)]
+ (alter ongoing-updates-ref-p assoc k p)
+ [:update p])))]
+ (case action
+ :update (let [v (mongo/fetch k)]
+ (redis/set k v)
+ (dosync (alter ongoing-updates-ref-p dissoc k))
+ (deliver p v)
+ [v :mongo])
+ :wait [(deref p) :redis]))))
+
+
+(comment
+
+ (do
+ (reset! redis/r-db {})
+
+ (def t-h-3
+ (doall
+ (map (fn [_]
+ (future (read+update-4 :foo)))
+ (range 10000))))
+
+
+ (frequencies (map second (map deref t-h-3)))))
diff --git a/save-points/10/src/workshop_app/handlers/lazy.clj b/save-points/10/src/workshop_app/handlers/lazy.clj
new file mode 100644
index 0000000..346241b
--- /dev/null
+++ b/save-points/10/src/workshop_app/handlers/lazy.clj
@@ -0,0 +1,141 @@
+(ns workshop-app.handlers.lazy)
+
+
+;; 1 - Infinity
+
+;; Simple example of range
+
+(comment
+ (range 10))
+
+;; What happens when range is unbounded
+
+;; DON'T EVALUATE THIS
+(comment
+ (range))
+
+
+(comment
+ (take 10 (range))
+
+ (take 10 (map inc (range)))
+
+ (take 10 (filter even? (map inc (range)))))
+
+
+;; 2 - Maths
+
+;; Let's calculate square roots by Newton-Raphson Square Roots method
+
+;; a - approximation
+;; n - Number
+;; a = (a + n/a)/2
+;; (/ (+ a (/ n a)) 2)
+
+
+;; (f a0) = a1
+;; (f a1) = a2
+;; (f a2) = a3
+;; (f a4) = a4
+
+;; The algorithm is recursive
+;; ((f (f (f a0) a1) a2)..)
+
+;; But it can also be thought of a sequence
+;; [a0, f a0, f (f a0), f (f (f a0)), . . . ]
+
+;; We haven't talked about epsilon yet.
+
+;; But if we look at it as lazy sequence
+
+(def epsilon 1)
+
+(defn sq-root*
+ [a x]
+ (let [a1 (/ (+ a (/ x a)) 2)]
+ (cons [a a1]
+ (lazy-seq (sq-root* a1
+ x)))))
+
+(defn sq-root
+ [x]
+ (let [a 1]
+ #_(??? (fn [[a1 a2]]
+ (when (> epsilon (Math/abs (float (- a1 a2))))
+ (int a1)))
+ (sq-root* a x))))
+
+
+;; Working with lazy sequences you basically deal with three things
+
+;; - Generation
+;; sq-root* generates a sequence (infinite) of approximations
+
+;; - Processing
+;; You may choose to process further on these sequences
+
+;; - Realisation
+;; You realise the result at this point defining how much
+;; input is required is also necessary
+
+
+
+;; 3 - Real world - Files
+
+;; Size of the data does not matter as long as you are reducing it into
+;; a small enough set
+(with-open [r (clojure.java.io/reader "resources/data/albumlist.csv")]
+ (let [l-xs (line-seq r)]
+ (comment (count (filter #(= (:artist %)
+ "The Beatles")
+ (map parse-line l-xs))))))
+
+
+;; 3 - Real world - Databases
+
+;; You can create lazy sequences of IO operations as long as there is no
+;; side effect and you are ok with lazy sequences caching the result
+
+(defn- scroll-seq
+ "Given a scroll-id, fetch scroll results"
+ [num]
+ (comment
+ (lazy-seq
+ (let [result (:body (http/get (format "_search/scroll?scroll=%s"
+ num)))]
+ (when (seq result)
+ (cons result
+ (scroll-seq (inc num))))))))
+
+;; 4 - Gotchas
+
+;; a. Open connections closing early
+
+
+;; Always use with-open when generating lazy seq from external sources and realise the whole sequence inside with-open
+
+(let [l-xs (with-open [r (clojure.java.io/reader "resources/data/albumlist.csv")]
+ (line-seq r))]
+ (count l-xs))
+
+;; Creating lazy sequences from a database can bring complexities if
+;; connections are not handled properly
+
+
+;; b. Chunking - Well almost lazy
+
+(comment (count (take 10 (map println (range 100)))))
+
+
+;; c. Caching
+
+(comment (def lz (doall (take 10 (map println (range 100)))))
+ (count lz))
+
+;; Some laziness in the wild
+
+;; https://github.com/Factual/durable-queue/blob/master/src/durable_queue.clj#L741
+
+;; https://github.com/s312569/clj-biosequence
+
+;; https://github.com/Genscape/gregor/blob/master/src/gregor/core.clj#L241
diff --git a/save-points/10/src/workshop_app/handlers/users.clj b/save-points/10/src/workshop_app/handlers/users.clj
new file mode 100644
index 0000000..3a404e0
--- /dev/null
+++ b/save-points/10/src/workshop_app/handlers/users.clj
@@ -0,0 +1,63 @@
+(ns workshop-app.handlers.users
+ (:require [workshop-app.db.sqlite :as wads]
+ [workshop-app.utils :as wau]
+ [cheshire.core :as json]))
+
+
+(defn get-handler
+ [{:keys [params] :as request}]
+ (def r* request)
+ (let [{:keys [name surname]} params]
+ (if (and name surname)
+ {:status 200
+ :body (str "Hello, "
+ name
+ " "
+ surname
+ "!!!")}
+ {:status 400
+ :body "Missing name and surname."})))
+
+
+(defn add-person
+ [{:keys [name dob]}]
+ (if (and name dob)
+ (do (wads/create! wads/conn
+ name
+ dob)
+ {:status 201
+ :body "Created user."})
+ {:status 400
+ :body "User name or date of birth missing."}))
+
+
+(defn get-person
+ [dob now]
+ {:status 200
+ :headers {"content-type" "application/json"}
+ :body (when dob
+ (json/generate-string {:dob dob
+ :age (wau/years-between (wau/parse-dt-str dob)
+ now)}))})
+
+
+(defn update-person
+ [{:keys [name dob]}]
+ (if (and name dob)
+ (do (wads/update! wads/conn
+ name
+ dob)
+ {:status 200
+ :body "Updated user."})
+ {:status 400
+ :body "User name or dob is missing."}))
+
+
+(defn delete-person
+ [name]
+ (if name
+ (do (wads/delete! wads/conn name)
+ {:status 200
+ :body "Deleted user."})
+ {:status 400
+ :body "User name missing, cannot delete person without username."}))
\ No newline at end of file
diff --git a/save-points/10/src/workshop_app/interop.clj b/save-points/10/src/workshop_app/interop.clj
new file mode 100644
index 0000000..3a65ff1
--- /dev/null
+++ b/save-points/10/src/workshop_app/interop.clj
@@ -0,0 +1,130 @@
+(ns workshop-app.interop
+ (:refer-clojure :rename {get cc-get})
+ (:import (java.util Calendar Comparator ArrayList HashMap AbstractMap$SimpleEntry)
+ (java.time.temporal ChronoUnit)
+ (java.time LocalDate)))
+
+;; class access
+;; HashMap.class
+HashMap ;; a class
+
+;; new HashMap();
+(HashMap.) ;; an object created from the class
+
+;; HashMap hm = new HashMap();
+(def hm (HashMap.)) ;; bind the object to a var. More on var's later.
+
+;; hm.put("a", 10);
+(.put hm "a" 10)
+
+;; hm.put("b", 10);
+(.put ^HashMap hm "b" 10) ;; the first gives us a warning of how it cannot be resolved.
+
+;; Member access
+;; method access
+;; hm.get("a")
+(.get hm "a")
+
+;; hm.get("b")
+(.get hm "b")
+
+;; "joel".toUpperCase();
+(.toUpperCase "joel")
+
+;; field access
+;; new Point(10,20).x;
+(.-x (java.awt.Point. 10 20))
+
+;; static variables or methods access
+;; Calendar.ERA
+Calendar/ERA
+
+;; Math.PI
+Math/PI
+
+;; System.getProperties()
+(System/getProperties)
+
+;; dot special form
+;; hm.get("a");
+(. hm get "a")
+
+;; "joel".toUpperCase();
+(. "joel" toUpperCase)
+
+;; System.getProperties().get("os.name")
+(. (. System (getProperties)) (get "os.name"))
+
+;; double dot
+(.. System getProperties (get "os.name"))
+
+;; doto
+;; HashMap dotoHm = new HashMap();
+;; dotoHm.put("a", 10);
+;; dotoHm.put("b", 20);
+(def doto-hm (doto (HashMap.)
+ (.put "a" 10)
+ (.put "b" 20)))
+
+;; new
+;; HashMap newSyntaxHm = new HashMap();
+(def new-syntax-hm (new HashMap))
+
+;; accessing inner classes
+;; AbstractMap.SimpleEntry("a", "b")
+(AbstractMap$SimpleEntry. "a" "b")
+
+
+;; reify example. Sort an array list of array list using
+;; a custom comparator.
+;; List al = new ArrayList();
+;; List alChild1 = new ArrayList();
+;; alChild1.add(1);
+;; alChild1.add(2);
+;; List alChild2 = new ArrayList();
+;; alChild2.add(1);
+;; alChild2.add(2);
+;; List alChild3 = new ArrayList();
+;; alChild3.add(1);
+;; alChild3.add(2);
+;; List alChild1 = new ArrayList();
+;; alChild1.add(1);
+;; alChild1.add(2);
+;; al.add(alChild1);
+;; al.add(alChild2);
+;; al.add(alChild3);
+;; al.sort(new Compartor {
+;; @Override
+;; public int compare(ArrayList al1, ArrayList al2) {
+;; return al1.elementAt(0) - al2.elementAt(0);
+;; }
+;; })
+(let [l (doto (ArrayList.)
+ (.add (doto (ArrayList.)
+ (.add 1)
+ (.add 2)))
+ (.add (doto (ArrayList.)
+ (.add 2)
+ (.add 3)))
+ (.add (doto (ArrayList.)
+ (.add 3)
+ (.add 4))))]
+ ;; this modifies the list.
+ (.sort l
+ (reify Comparator
+ (compare [_ al1 al2]
+ (- (.get ^ArrayList al2 (int 0))
+ (.get ^ArrayList al1 (int 0))))))
+ l)
+
+
+;; a small interop task. of finding the days between today and first January
+;; following is a sample java code that you should translate to clojure interop code.
+;; LocalDate dateOne = LocalDate.of(2020,1,1);
+;; LocalDate dateTwo = LocalDate.now();
+;; long daysBetween = ChronoUnits.DAYS.between(dateOne, dateTwo);
+(let [date-one (LocalDate/of 2020 1 1)
+ date-two (LocalDate/now)]
+ (.between ChronoUnit/DAYS
+ date-one
+ date-two))
\ No newline at end of file
diff --git a/save-points/10/src/workshop_app/middlewares/users.clj b/save-points/10/src/workshop_app/middlewares/users.clj
new file mode 100644
index 0000000..399df82
--- /dev/null
+++ b/save-points/10/src/workshop_app/middlewares/users.clj
@@ -0,0 +1,22 @@
+(ns workshop-app.middlewares.users
+ (:require [clojure.string :as s]))
+
+
+(defn reject-uri-ending-with-slash
+ [handler]
+ (fn [{:keys [uri] :as request}]
+ (if (and (not= uri "/")
+ (s/ends-with? uri "/"))
+ {:status 400
+ :body "Bad request."}
+ (handler request))))
+
+
+(defn handle-any-exception
+ [handler]
+ (fn [request]
+ (try (handler request)
+ (catch Exception e
+ (.printStackTrace e)
+ {:status 500
+ :body "Internal server error."}))))
\ No newline at end of file
diff --git a/save-points/10/src/workshop_app/namespaces.clj b/save-points/10/src/workshop_app/namespaces.clj
new file mode 100644
index 0000000..eece9fa
--- /dev/null
+++ b/save-points/10/src/workshop_app/namespaces.clj
@@ -0,0 +1,11 @@
+(ns workshop-app.namespaces)
+
+;; to create a namespace
+#_(ns test-namespace)
+
+;; to switch to a namespace
+#_(in-ns 'test-namespace)
+
+#_*ns*
+
+#_(def a)
\ No newline at end of file
diff --git a/save-points/10/src/workshop_app/routes.clj b/save-points/10/src/workshop_app/routes.clj
new file mode 100644
index 0000000..2a5547a
--- /dev/null
+++ b/save-points/10/src/workshop_app/routes.clj
@@ -0,0 +1,15 @@
+(ns workshop-app.routes
+ (:require [workshop-app.handlers.users :as wahu]
+ [workshop-app.db.sqlite :as wads]
+ [compojure.core :refer [defroutes GET POST PUT DELETE ANY]])
+ (:import (java.time LocalDate)))
+
+
+(defroutes app-routes
+ (GET "/" _ wahu/get-handler)
+ (POST "/:name" {:keys [params]} (wahu/add-person params))
+ (GET "/:name" [name] (wahu/get-person (get (wads/read wads/conn name) "dob")
+ (LocalDate/now)))
+ (PUT "/:name" {:keys [params]} (wahu/update-person params))
+ (DELETE "/:name" [name] (wahu/delete-person name))
+ (ANY "*" _ {:status 404}))
\ No newline at end of file
diff --git a/save-points/10/src/workshop_app/utils.clj b/save-points/10/src/workshop_app/utils.clj
new file mode 100644
index 0000000..2761e2f
--- /dev/null
+++ b/save-points/10/src/workshop_app/utils.clj
@@ -0,0 +1,21 @@
+(ns workshop-app.utils
+ (:import (java.time LocalDate)
+ (java.time.temporal ChronoUnit)))
+
+
+(defn parse-dt-str
+ [dt-str]
+ (when (seq dt-str)
+ (LocalDate/parse dt-str)))
+
+
+(defn dt-after?
+ [d1 d2]
+ (.isAfter d1 d2))
+
+
+(defn years-between
+ [d1 d2]
+ (if (dt-after? d2 d1)
+ (.between ChronoUnit/YEARS d1 d2)
+ 0))
\ No newline at end of file
diff --git a/save-points/10/src/workshop_app/vars.clj b/save-points/10/src/workshop_app/vars.clj
new file mode 100644
index 0000000..1a255bf
--- /dev/null
+++ b/save-points/10/src/workshop_app/vars.clj
@@ -0,0 +1,22 @@
+(ns workshop-app.vars)
+
+;; this is a symbol
+'a
+
+;; to create a var we need to simply do
+(def a)
+
+#_(+ a 10)
+
+#_(alter-var-root #'a (constantly 20))
+
+#_(println a)
+
+#_(println (var-get #'a))
+
+(def ^:dynamic b)
+
+#_(+ b 20)
+
+(binding [b 10]
+ (println (+ b 10)))
\ No newline at end of file
diff --git a/save-points/10/test/workshop_app/handlers/users_pure_test.clj b/save-points/10/test/workshop_app/handlers/users_pure_test.clj
new file mode 100644
index 0000000..34f7cb7
--- /dev/null
+++ b/save-points/10/test/workshop_app/handlers/users_pure_test.clj
@@ -0,0 +1,12 @@
+(ns workshop-app.handlers.users-pure-test
+ (:require [clojure.test :refer :all]
+ [workshop-app.handlers.users :as wahu])
+ (:import (java.time LocalDate)))
+
+
+(deftest pure-get-person-test
+ (is (= {:status 200
+ :headers {"content-type" "application/json"}
+ :body "{\"dob\":\"2000-01-01\",\"age\":20}"}
+ (wahu/get-person "2000-01-01" (LocalDate/parse "2020-02-14")))
+ "Is our pure get handler working as expected."))
\ No newline at end of file
diff --git a/save-points/10/test/workshop_app/handlers/users_test.clj b/save-points/10/test/workshop_app/handlers/users_test.clj
new file mode 100644
index 0000000..29fe783
--- /dev/null
+++ b/save-points/10/test/workshop_app/handlers/users_test.clj
@@ -0,0 +1,38 @@
+(ns workshop-app.handlers.users-test
+ (:require [clojure.test :refer :all]
+ [workshop-app.handlers.users :as wahu]
+ [workshop-app.db.sqlite :as wads]))
+
+
+;; Pattern 1
+;; Using fixtures to actually bootstrap and teardown.
+;; Scenario: I want to test against a actual instance
+;; but I want to test against a different database.
+(use-fixtures :each (fn [t]
+ (let [conn (wads/init-conn! "jdbc:sqlite::memory:")]
+ (with-redefs [wads/conn conn]
+ (wads/create-table conn)
+ (t)))))
+
+
+(deftest add-person-test
+ (is (= {:status 201
+ :body "Created user."}
+ (wahu/add-person {:name "Joel Victor"
+ :dob "2001-01-01"}))
+ "Is the creation handler working as expected."))
+
+
+(deftest update-person-test
+ (is (= {:status 200
+ :body "Updated user."}
+ (wahu/update-person {:name "Joel Victor"
+ :dob "2000-01-01"}))
+ "Is the update handler working as expected."))
+
+
+(deftest delete-person-test
+ (is (= {:status 200
+ :body "Deleted user."}
+ (wahu/delete-person "Joel Victor"))
+ "Is the deletion handler working as expected."))
\ No newline at end of file
diff --git a/save-points/10/test/workshop_app/handlers/users_test_2.clj b/save-points/10/test/workshop_app/handlers/users_test_2.clj
new file mode 100644
index 0000000..9f1a6fe
--- /dev/null
+++ b/save-points/10/test/workshop_app/handlers/users_test_2.clj
@@ -0,0 +1,27 @@
+(ns workshop-app.handlers.users-test-2
+ (:require [clojure.test :refer :all]
+ [workshop-app.handlers.users :as wahu]
+ [workshop-app.db.sqlite :as wads]
+ [workshop-app.db.in-mem :as wadim]))
+
+;; pattern 2. don't create the connection but instead redefine
+;; it to an inmemory implementation.
+;; these are useful when you communicate to a database over
+;; a network
+(use-fixtures :each (fn [t]
+ (with-redefs [wads/conn wadim/conn
+ wads/create! wadim/create!
+ wads/update! wadim/update!
+ wads/read (fn [conn k] {"dob" (wadim/read conn k)})
+ wads/delete! wadim/delete!]
+ (t))))
+
+
+(deftest all-handlers-test
+ (testing "Testing all handlers in one go."
+ (are [expected-response actual-response] (= expected-response actual-response)
+ {:status 201 :body "Created user."} (wahu/add-person {:name "Joel Victor"
+ :dob "2001-01-01"})
+ {:status 200 :body "Updated user."} (wahu/update-person {:name "Joel Victor"
+ :dob "2000-01-01"})
+ {:status 200 :body "Deleted user."} (wahu/delete-person "Joel Victor"))))
\ No newline at end of file
diff --git a/save-points/10/test/workshop_app/intro_to_testing.clj b/save-points/10/test/workshop_app/intro_to_testing.clj
new file mode 100644
index 0000000..591d0fb
--- /dev/null
+++ b/save-points/10/test/workshop_app/intro_to_testing.clj
@@ -0,0 +1,20 @@
+(ns workshop-app.intro-to-testing
+ (:require [clojure.test :refer :all]))
+
+
+;; Defining a test
+(deftest test-name-1
+ (is (= 1 1)
+ "Optional message for assertion")
+
+ (are [x y] (= x y)
+ 2 (+ 1 1)
+ 3 (+ 1 2)))
+
+
+(deftest test-name-2
+ (testing "Msg to add context to all the test run inside this"
+ (is (= 1 2)
+ "One should not equal 2."))
+
+ (is (thrown? RuntimeException ((fn [] (throw (RuntimeException.)))))))
\ No newline at end of file
diff --git a/save-points/10/test/workshop_app/middlewares/users_test.clj b/save-points/10/test/workshop_app/middlewares/users_test.clj
new file mode 100644
index 0000000..675af60
--- /dev/null
+++ b/save-points/10/test/workshop_app/middlewares/users_test.clj
@@ -0,0 +1,25 @@
+(ns workshop-app.middlewares.users-test
+ (:require [clojure.test :refer :all]
+ [workshop-app.middlewares.users :as wamu]))
+
+
+(deftest pure-reject-uri-ending-with-slash-middleware-test
+ (testing "Middleware should reject all uri's ending with /"
+ (are [expected actual] (= expected actual)
+ {:uri "/"} ((wamu/reject-uri-ending-with-slash identity)
+ {:uri "/"})
+ {:status 400 :body "Bad request."} ((wamu/reject-uri-ending-with-slash identity)
+ {:uri "/joel/"})
+ {:uri "/joel"} ((wamu/reject-uri-ending-with-slash identity)
+ {:uri "/joel"}))))
+
+
+;; by using higher order functions you can also test behavior without
+;; mutating any code.
+(deftest pure-handle-any-exception
+ (testing "Middleware should reject all uri's ending with /"
+ (are [expected actual] (= expected actual)
+ {:status 500 :body "Internal server error."} ((wamu/handle-any-exception (fn [_] (throw (Exception.))))
+ {})
+ {} ((wamu/handle-any-exception identity)
+ {}))))
\ No newline at end of file
diff --git a/save-points/10/test/workshop_app/property_based_test.clj b/save-points/10/test/workshop_app/property_based_test.clj
new file mode 100644
index 0000000..1007bd7
--- /dev/null
+++ b/save-points/10/test/workshop_app/property_based_test.clj
@@ -0,0 +1,36 @@
+(ns workshop-app.property-based-test
+ (:require [clojure.test :refer :all]
+ [clojure.test.check :as tc]
+ [clojure.test.check.clojure-test :as tcct]
+ [clojure.test.check.generators :as ctcg]
+ [clojure.test.check.properties :as ctcp]
+ [workshop-app.utils :as wau])
+ (:import (java.time LocalDate)))
+
+
+(def date-tuple-generator
+ (ctcg/let [month (ctcg/choose 1 12)]
+ (case month
+ 2 (ctcg/tuple (ctcg/choose 1900 2021) (ctcg/return month) (ctcg/choose 1 28))
+ (1 3 5 7 8 10 12) (ctcg/tuple (ctcg/choose 1900 2021) (ctcg/return month) (ctcg/choose 1 31))
+ (4 6 9 11) (ctcg/tuple (ctcg/choose 1900 2021) (ctcg/return month) (ctcg/choose 1 30)))))
+
+(def date-object-generator
+ (ctcg/fmap (fn [[y m d]]
+ (LocalDate/of y m d))
+ date-tuple-generator))
+
+(def age-property-fn (some-fn pos? zero?))
+
+(def age-property
+ (ctcp/for-all [st-dt date-object-generator
+ end-dt date-object-generator]
+ (age-property-fn (wau/years-between st-dt end-dt))))
+
+#_(tc/quick-check 1000 age-property)
+
+
+(tcct/defspec age-property-2 100
+ (ctcp/for-all [st-dt date-object-generator
+ end-dt date-object-generator]
+ (age-property-fn (wau/years-between st-dt end-dt))))
\ No newline at end of file
diff --git a/save-points/2/src/workshop_app/core.clj b/save-points/2/src/workshop_app/core.clj
new file mode 100644
index 0000000..4923d64
--- /dev/null
+++ b/save-points/2/src/workshop_app/core.clj
@@ -0,0 +1,28 @@
+(ns workshop-app.core
+ (:gen-class)
+ (:require [ring.adapter.jetty :as raj]
+ [clojure.string :as s]
+ [compojure.core :refer [defroutes GET POST PUT DELETE ANY]]))
+
+;;; Task: Change the handler to say hello to the person whose
+;;; name & surname is coming in from the request.
+;;; Return a response like "Hello, !!!"
+;;; Query using the API to capture the request and inspect it
+;;; and then write your code.
+(defn get-handler
+ [request]
+ (def r* request)
+ {:status 200
+ :body "Hello world!!!"})
+
+
+(defroutes app-routes
+ (GET "/" _ get-handler)
+ (ANY "*" _ {:status 404}))
+
+
+(defn -main
+ [& _]
+ (raj/run-jetty app-routes
+ {:port 65535
+ :join? false}))
\ No newline at end of file
diff --git a/save-points/3/src/workshop_app/core.clj b/save-points/3/src/workshop_app/core.clj
new file mode 100644
index 0000000..7c42b3f
--- /dev/null
+++ b/save-points/3/src/workshop_app/core.clj
@@ -0,0 +1,17 @@
+(ns workshop-app.core
+ (:gen-class)
+ (:require [ring.adapter.jetty :as raj]
+ [ring.middleware.params :as rmp]
+ [ring.middleware.keyword-params :as rmkp]
+ [workshop-app.middlewares.users :as wamu]
+ [workshop-app.routes :as war]))
+
+
+(defn -main
+ [& _]
+ (raj/run-jetty (-> war/app-routes
+ rmkp/wrap-keyword-params
+ rmp/wrap-params
+ wamu/handle-any-exception)
+ {:port 65535
+ :join? false}))
\ No newline at end of file
diff --git a/save-points/3/src/workshop_app/handlers/users.clj b/save-points/3/src/workshop_app/handlers/users.clj
new file mode 100644
index 0000000..a38a9e2
--- /dev/null
+++ b/save-points/3/src/workshop_app/handlers/users.clj
@@ -0,0 +1,16 @@
+(ns workshop-app.handlers.users)
+
+
+(defn get-handler
+ [{:keys [params] :as request}]
+ (def r* request)
+ (let [{:keys [name surname]} params]
+ (if (and name surname)
+ {:status 200
+ :body (str "Hello, "
+ name
+ " "
+ surname
+ "!!!")}
+ {:status 400
+ :body "Missing name and surname."})))
\ No newline at end of file
diff --git a/save-points/3/src/workshop_app/middlewares/users.clj b/save-points/3/src/workshop_app/middlewares/users.clj
new file mode 100644
index 0000000..ed88756
--- /dev/null
+++ b/save-points/3/src/workshop_app/middlewares/users.clj
@@ -0,0 +1,20 @@
+(ns workshop-app.middlewares.users
+ (:require [clojure.string :as s]))
+
+
+;; Task: Write a middleware and place
+;; use it to reject issues that come through with
+;; / at the end.
+(defn reject-uri-ending-with-slash
+ [handler]
+ (fn [request]))
+
+
+(defn handle-any-exception
+ [handler]
+ (fn [request]
+ (try (handler request)
+ (catch Exception e
+ (.printStackTrace e)
+ {:status 500
+ :body "Internal server error."}))))
\ No newline at end of file
diff --git a/save-points/3/src/workshop_app/routes.clj b/save-points/3/src/workshop_app/routes.clj
new file mode 100644
index 0000000..483166c
--- /dev/null
+++ b/save-points/3/src/workshop_app/routes.clj
@@ -0,0 +1,8 @@
+(ns workshop-app.routes
+ (:require [workshop-app.handlers.users :as wahu]
+ [compojure.core :refer [defroutes GET POST PUT DELETE ANY]]))
+
+
+(defroutes app-routes
+ (GET "/" _ wahu/get-handler)
+ (ANY "*" _ {:status 404}))
\ No newline at end of file
diff --git a/save-points/4/src/workshop_app/core.clj b/save-points/4/src/workshop_app/core.clj
new file mode 100644
index 0000000..be8c8da
--- /dev/null
+++ b/save-points/4/src/workshop_app/core.clj
@@ -0,0 +1,18 @@
+(ns workshop-app.core
+ (:gen-class)
+ (:require [ring.adapter.jetty :as raj]
+ [ring.middleware.params :as rmp]
+ [ring.middleware.keyword-params :as rmkp]
+ [workshop-app.middlewares.users :as wamu]
+ [workshop-app.routes :as war]))
+
+
+(defn -main
+ [& _]
+ (raj/run-jetty (-> war/app-routes
+ rmkp/wrap-keyword-params
+ rmp/wrap-params
+ wamu/handle-any-exception
+ wamu/reject-uri-ending-with-slash)
+ {:port 65535
+ :join? false}))
\ No newline at end of file
diff --git a/save-points/4/src/workshop_app/db/in_mem.clj b/save-points/4/src/workshop_app/db/in_mem.clj
new file mode 100644
index 0000000..45ea1a2
--- /dev/null
+++ b/save-points/4/src/workshop_app/db/in_mem.clj
@@ -0,0 +1,33 @@
+(ns workshop-app.db.in-mem
+ (:refer-clojure :rename {update cc-update
+ read cc-read}))
+
+
+(def conn (atom {}))
+
+(defn create!
+ "Create an entry for k in our given in memory datastore."
+ [c k v]
+ (assert (some? k) "key cannot be nil.")
+ (swap! c assoc k v))
+
+
+(defn update!
+ "Update the entry for k to value v or add it if it doesn't exist in our in memory datastore."
+ [c k v]
+ (assert (some? k) "key cannot be nil.")
+ (swap! c assoc k v))
+
+
+(defn delete!
+ "Delete the entry for k in our in memory datastore."
+ [c k]
+ (assert (some? k) "key cannot be nil.")
+ (swap! c dissoc k))
+
+
+(defn read
+ "Read the entry for k in our in memory datastore."
+ [c k]
+ (assert (some? k) "key cannot be nil.")
+ (get @c k))
\ No newline at end of file
diff --git a/save-points/4/src/workshop_app/handlers/users.clj b/save-points/4/src/workshop_app/handlers/users.clj
new file mode 100644
index 0000000..9d7e7b9
--- /dev/null
+++ b/save-points/4/src/workshop_app/handlers/users.clj
@@ -0,0 +1,39 @@
+(ns workshop-app.handlers.users
+ (:require [workshop-app.db.in-mem :as wadim]
+ [cheshire.core :as json]))
+
+
+(defn get-handler
+ [{:keys [params] :as request}]
+ (def r* request)
+ (let [{:keys [name surname]} params]
+ (if (and name surname)
+ {:status 200
+ :body (str "Hello, "
+ name
+ " "
+ surname
+ "!!!")}
+ {:status 400
+ :body "Missing name and surname."})))
+
+
+(defn add-person
+ [{:keys [name dob]}]
+ (if (and name dob)
+ (do (wadim/create! wadim/conn
+ name
+ dob)
+ {:status 201
+ :body "Created user."})
+ {:status 400
+ :body "User name or date of birth missing."}))
+
+
+(defn get-person
+ [name]
+ (let [dob (wadim/read wadim/conn name)]
+ {:status 200
+ :headers {"content-type" "application/json"}
+ :body (when dob
+ (json/generate-string {:dob dob}))}))
diff --git a/save-points/4/src/workshop_app/middlewares/users.clj b/save-points/4/src/workshop_app/middlewares/users.clj
new file mode 100644
index 0000000..399df82
--- /dev/null
+++ b/save-points/4/src/workshop_app/middlewares/users.clj
@@ -0,0 +1,22 @@
+(ns workshop-app.middlewares.users
+ (:require [clojure.string :as s]))
+
+
+(defn reject-uri-ending-with-slash
+ [handler]
+ (fn [{:keys [uri] :as request}]
+ (if (and (not= uri "/")
+ (s/ends-with? uri "/"))
+ {:status 400
+ :body "Bad request."}
+ (handler request))))
+
+
+(defn handle-any-exception
+ [handler]
+ (fn [request]
+ (try (handler request)
+ (catch Exception e
+ (.printStackTrace e)
+ {:status 500
+ :body "Internal server error."}))))
\ No newline at end of file
diff --git a/save-points/4/src/workshop_app/routes.clj b/save-points/4/src/workshop_app/routes.clj
new file mode 100644
index 0000000..4a800cd
--- /dev/null
+++ b/save-points/4/src/workshop_app/routes.clj
@@ -0,0 +1,14 @@
+(ns workshop-app.routes
+ (:require [workshop-app.handlers.users :as wahu]
+ [compojure.core :refer [defroutes GET POST PUT DELETE ANY]]))
+
+
+(defroutes app-routes
+ (GET "/" _ wahu/get-handler)
+ (POST "/:name" {:keys [params]} (wahu/add-person params))
+ (GET "/:name" [name] (wahu/get-person name))
+ ;; Add POST and DELETE route for updating and deleting
+ ;; the record respectively.
+ #_(_ _ _ _)
+ #_(_ _ _ _)
+ (ANY "*" _ {:status 404}))
\ No newline at end of file
diff --git a/save-points/5/src/workshop_app/core.clj b/save-points/5/src/workshop_app/core.clj
new file mode 100644
index 0000000..be8c8da
--- /dev/null
+++ b/save-points/5/src/workshop_app/core.clj
@@ -0,0 +1,18 @@
+(ns workshop-app.core
+ (:gen-class)
+ (:require [ring.adapter.jetty :as raj]
+ [ring.middleware.params :as rmp]
+ [ring.middleware.keyword-params :as rmkp]
+ [workshop-app.middlewares.users :as wamu]
+ [workshop-app.routes :as war]))
+
+
+(defn -main
+ [& _]
+ (raj/run-jetty (-> war/app-routes
+ rmkp/wrap-keyword-params
+ rmp/wrap-params
+ wamu/handle-any-exception
+ wamu/reject-uri-ending-with-slash)
+ {:port 65535
+ :join? false}))
\ No newline at end of file
diff --git a/save-points/5/src/workshop_app/db/in_mem.clj b/save-points/5/src/workshop_app/db/in_mem.clj
new file mode 100644
index 0000000..45ea1a2
--- /dev/null
+++ b/save-points/5/src/workshop_app/db/in_mem.clj
@@ -0,0 +1,33 @@
+(ns workshop-app.db.in-mem
+ (:refer-clojure :rename {update cc-update
+ read cc-read}))
+
+
+(def conn (atom {}))
+
+(defn create!
+ "Create an entry for k in our given in memory datastore."
+ [c k v]
+ (assert (some? k) "key cannot be nil.")
+ (swap! c assoc k v))
+
+
+(defn update!
+ "Update the entry for k to value v or add it if it doesn't exist in our in memory datastore."
+ [c k v]
+ (assert (some? k) "key cannot be nil.")
+ (swap! c assoc k v))
+
+
+(defn delete!
+ "Delete the entry for k in our in memory datastore."
+ [c k]
+ (assert (some? k) "key cannot be nil.")
+ (swap! c dissoc k))
+
+
+(defn read
+ "Read the entry for k in our in memory datastore."
+ [c k]
+ (assert (some? k) "key cannot be nil.")
+ (get @c k))
\ No newline at end of file
diff --git a/save-points/5/src/workshop_app/handlers/users.clj b/save-points/5/src/workshop_app/handlers/users.clj
new file mode 100644
index 0000000..4a156d6
--- /dev/null
+++ b/save-points/5/src/workshop_app/handlers/users.clj
@@ -0,0 +1,62 @@
+(ns workshop-app.handlers.users
+ (:require [workshop-app.db.in-mem :as wadim]
+ [cheshire.core :as json]))
+
+
+(defn get-handler
+ [{:keys [params] :as request}]
+ (def r* request)
+ (let [{:keys [name surname]} params]
+ (if (and name surname)
+ {:status 200
+ :body (str "Hello, "
+ name
+ " "
+ surname
+ "!!!")}
+ {:status 400
+ :body "Missing name and surname."})))
+
+
+(defn add-person
+ [{:keys [name dob]}]
+ (if (and name dob)
+ (do (wadim/create! wadim/conn
+ name
+ dob)
+ {:status 201
+ :body "Created user."})
+ {:status 400
+ :body "User name or date of birth missing."}))
+
+
+(defn get-person
+ [name]
+ (let [dob (wadim/read wadim/conn name)]
+ {:status 200
+ :headers {"content-type" "application/json"}
+ :body (when dob
+ (json/generate-string {:dob dob}))}))
+
+
+
+(defn update-person
+ [{:keys [name dob]}]
+ (if (and name dob)
+ (do (wadim/update! wadim/conn
+ name
+ dob)
+ {:status 200
+ :body "Updated user."})
+ {:status 400
+ :body "User name or dob is missing."}))
+
+
+(defn delete-person
+ [name]
+ (if name
+ (do (wadim/delete! wadim/conn name)
+ {:status 200
+ :body "Deleted user."})
+ {:status 400
+ :body "User name missing, cannot delete person without username."}))
\ No newline at end of file
diff --git a/save-points/5/src/workshop_app/interop.clj b/save-points/5/src/workshop_app/interop.clj
new file mode 100644
index 0000000..5db6799
--- /dev/null
+++ b/save-points/5/src/workshop_app/interop.clj
@@ -0,0 +1,129 @@
+(ns workshop-app.interop
+ (:refer-clojure :rename {get cc-get})
+ (:import (java.util Calendar Comparator ArrayList HashMap AbstractMap$SimpleEntry)
+ (java.time.temporal ChronoUnit)
+ (java.time LocalDate)))
+
+;; class access
+;; HashMap.class
+HashMap ;; a class
+
+;; new HashMap();
+(HashMap.) ;; an object created from the class
+
+;; HashMap hm = new HashMap();
+(def hm (HashMap.)) ;; bind the object to a var. More on var's later.
+
+;; hm.put("a", 10);
+(.put hm "a" 10)
+
+;; hm.put("b", 10);
+(.put ^HashMap hm "b" 10) ;; the first gives us a warning of how it cannot be resolved.
+
+;; Member access
+;; method access
+;; hm.get("a")
+(.get hm "a")
+
+;; hm.get("b")
+(.get hm "b")
+
+;; "joel".toUpperCase();
+(.toUpperCase "joel")
+
+;; field access
+;; new Point(10,20).x;
+(.-x (java.awt.Point. 10 20))
+
+;; static variables or methods access
+;; Calendar.ERA
+Calendar/ERA
+
+;; Math.PI
+Math/PI
+
+;; System.getProperties()
+(System/getProperties)
+
+;; dot special form
+;; hm.get("a");
+(. hm get "a")
+
+;; "joel".toUpperCase();
+(. "joel" toUpperCase)
+
+;; System.getProperties().get("os.name")
+(. (. System (getProperties)) (get "os.name"))
+
+;; double dot
+(.. System getProperties (get "os.name"))
+
+;; doto
+;; HashMap dotoHm = new HashMap();
+;; dotoHm.put("a", 10);
+;; dotoHm.put("b", 20);
+(def doto-hm (doto (HashMap.)
+ (.put "a" 10)
+ (.put "b" 20)))
+
+;; new
+;; HashMap newSyntaxHm = new HashMap();
+(def new-syntax-hm (new HashMap))
+
+;; accessing inner classes
+;; AbstractMap.SimpleEntry("a", "b")
+(AbstractMap$SimpleEntry. "a" "b")
+
+
+;; reify example. Sort an array list of array list using
+;; a custom comparator.
+;; List al = new ArrayList();
+;; List alChild1 = new ArrayList();
+;; alChild1.add(1);
+;; alChild1.add(2);
+;; List alChild2 = new ArrayList();
+;; alChild2.add(1);
+;; alChild2.add(2);
+;; List alChild3 = new ArrayList();
+;; alChild3.add(1);
+;; alChild3.add(2);
+;; List alChild1 = new ArrayList();
+;; alChild1.add(1);
+;; alChild1.add(2);
+;; al.add(alChild1);
+;; al.add(alChild2);
+;; al.add(alChild3);
+;; al.sort(new Compartor {
+;; @Override
+;; public int compare(ArrayList al1, ArrayList al2) {
+;; return al1.elementAt(0) - al2.elementAt(0);
+;; }
+;; })
+(let [l (doto (ArrayList.)
+ (.add (doto (ArrayList.)
+ (.add 1)
+ (.add 2)))
+ (.add (doto (ArrayList.)
+ (.add 2)
+ (.add 3)))
+ (.add (doto (ArrayList.)
+ (.add 3)
+ (.add 4))))]
+ ;; this modifies the list.
+ (.sort l
+ (reify Comparator
+ (compare [_ al1 al2]
+ (- (.get ^ArrayList al2 (int 0))
+ (.get ^ArrayList al1 (int 0))))))
+ l)
+
+
+;; a small interop task. of finding the days between today and first January
+;; following is a sample java code that you should translate to clojure interop code.
+;; LocalDate dateOne = LocalDate.of(2020,1,1);
+;; LocalDate dateTwo = LocalDate.now();
+;; long daysBetween = ChronoUnits.DAYS.between(dateOne, dateTwo);
+#_(let [date-one _
+ date-two _]
+ (_ _ date-one date-two))
+
diff --git a/save-points/5/src/workshop_app/middlewares/users.clj b/save-points/5/src/workshop_app/middlewares/users.clj
new file mode 100644
index 0000000..399df82
--- /dev/null
+++ b/save-points/5/src/workshop_app/middlewares/users.clj
@@ -0,0 +1,22 @@
+(ns workshop-app.middlewares.users
+ (:require [clojure.string :as s]))
+
+
+(defn reject-uri-ending-with-slash
+ [handler]
+ (fn [{:keys [uri] :as request}]
+ (if (and (not= uri "/")
+ (s/ends-with? uri "/"))
+ {:status 400
+ :body "Bad request."}
+ (handler request))))
+
+
+(defn handle-any-exception
+ [handler]
+ (fn [request]
+ (try (handler request)
+ (catch Exception e
+ (.printStackTrace e)
+ {:status 500
+ :body "Internal server error."}))))
\ No newline at end of file
diff --git a/save-points/5/src/workshop_app/routes.clj b/save-points/5/src/workshop_app/routes.clj
new file mode 100644
index 0000000..3a362cc
--- /dev/null
+++ b/save-points/5/src/workshop_app/routes.clj
@@ -0,0 +1,12 @@
+(ns workshop-app.routes
+ (:require [workshop-app.handlers.users :as wahu]
+ [compojure.core :refer [defroutes GET POST PUT DELETE ANY]]))
+
+
+(defroutes app-routes
+ (GET "/" _ wahu/get-handler)
+ (POST "/:name" {:keys [params]} (wahu/add-person params))
+ (GET "/:name" [name] (wahu/get-person name))
+ (PUT "/:name" {:keys [params]} (wahu/update-person params))
+ (DELETE "/:name" [name] (wahu/delete-person name))
+ (ANY "*" _ {:status 404}))
\ No newline at end of file
diff --git a/save-points/6/src/workshop_app/core.clj b/save-points/6/src/workshop_app/core.clj
new file mode 100644
index 0000000..de470e4
--- /dev/null
+++ b/save-points/6/src/workshop_app/core.clj
@@ -0,0 +1,20 @@
+(ns workshop-app.core
+ (:gen-class)
+ (:require [ring.adapter.jetty :as raj]
+ [ring.middleware.params :as rmp]
+ [ring.middleware.keyword-params :as rmkp]
+ [workshop-app.routes :as war]
+ [workshop-app.middlewares.users :as wamu]
+ [workshop-app.db.sqlite :as wads]))
+
+
+(defn -main
+ [& _]
+ (wads/create-table wads/conn)
+ (raj/run-jetty (-> war/app-routes
+ rmkp/wrap-keyword-params
+ rmp/wrap-params
+ wamu/handle-any-exception
+ wamu/reject-uri-ending-with-slash)
+ {:port 65535
+ :join? false}))
\ No newline at end of file
diff --git a/save-points/6/src/workshop_app/db/in_mem.clj b/save-points/6/src/workshop_app/db/in_mem.clj
new file mode 100644
index 0000000..45ea1a2
--- /dev/null
+++ b/save-points/6/src/workshop_app/db/in_mem.clj
@@ -0,0 +1,33 @@
+(ns workshop-app.db.in-mem
+ (:refer-clojure :rename {update cc-update
+ read cc-read}))
+
+
+(def conn (atom {}))
+
+(defn create!
+ "Create an entry for k in our given in memory datastore."
+ [c k v]
+ (assert (some? k) "key cannot be nil.")
+ (swap! c assoc k v))
+
+
+(defn update!
+ "Update the entry for k to value v or add it if it doesn't exist in our in memory datastore."
+ [c k v]
+ (assert (some? k) "key cannot be nil.")
+ (swap! c assoc k v))
+
+
+(defn delete!
+ "Delete the entry for k in our in memory datastore."
+ [c k]
+ (assert (some? k) "key cannot be nil.")
+ (swap! c dissoc k))
+
+
+(defn read
+ "Read the entry for k in our in memory datastore."
+ [c k]
+ (assert (some? k) "key cannot be nil.")
+ (get @c k))
\ No newline at end of file
diff --git a/save-points/6/src/workshop_app/db/sqlite.clj b/save-points/6/src/workshop_app/db/sqlite.clj
new file mode 100644
index 0000000..a31769f
--- /dev/null
+++ b/save-points/6/src/workshop_app/db/sqlite.clj
@@ -0,0 +1,80 @@
+(ns workshop-app.db.sqlite
+ (:refer-clojure :rename {update cc-update
+ read cc-read})
+ (:import (java.sql DriverManager Connection PreparedStatement)))
+
+;; Connection conn = DriverManager.getConnection("jdbc:sqlite:prod_database_1.sqlite");
+(defn init-conn!
+ [conn-string]
+ (DriverManager/getConnection conn-string))
+
+(def conn (init-conn! "jdbc:sqlite:prod_database_1.sqlite"))
+
+;; Statement statement = conn.createStatement();
+;; statement.executeUpdate("create table if not exists person(name string primary key, dob string)");
+;; statement.close()
+(defn create-table
+ [c]
+ (with-open [statement (.createStatement c)]
+ (.executeUpdate statement "create table if not exists person(name string primary key, dob string)")))
+
+
+;; String k, v;
+;; Statement statement = conn.prepareStatement("insert into person values(?,?)");
+;; statement.setString(1, k);
+;; statement.setString(2, v);
+;; statement.executeUpdate();
+;; statement.close();
+(defn create!
+ [conn k v]
+ (let [statement (.prepareStatement ^Connection conn "insert into person(name, dob) values (?,?)")]
+ (.setString ^PreparedStatement statement 1 k)
+ (.setString ^PreparedStatement statement 2 v)
+ (.executeUpdate statement)
+ (.close statement)))
+
+;; String k, v;
+;; Statement statement = conn.prepareStatement("insert into person values(?,?)");
+;; statement.setString(1, k);
+;; statement.setString(2, v);
+;; statement.executeUpdate();
+;; statement.close();
+(defn update!
+ [conn k v]
+ #_(_ [statement (_ (.prepareStatement ^Connection conn "update person set dob=? where name=?")
+ (.setString 2 k)
+ (.setString 1 v))]
+ (.executeUpdate statement)))
+
+
+;; String k;
+;; Statement statement = conn.prepareStatement("delete from person where name=?");
+;; statement.setString(1, k);
+;; statement.executeUpdate();
+;; statement.close();
+(defn delete!
+ [conn k]
+ #_(let [statement _]
+ (.setString ^PreparedStatement statement 1 k)
+ (.executeUpdate statement)
+ _))
+
+
+;; Statement statement = conn.prepareStatement("select name, dob from person where name=?");
+;; statement.setString(1, k);
+;; ResultSet rs = statement.executeQuery();
+;; Result result = rs.next();
+;; ResultSetMetadata rsm = result.getMetaData();
+;; int columnCount = rsm.getColumnCount();
+(defn read
+ [conn k]
+ (with-open [statement (doto (.prepareStatement ^Connection conn "select name, dob from person where name=?")
+ (.setString 1 k))]
+ (with-open [rs (.executeQuery statement)]
+ (when (.next rs)
+ (let [rs-meta (.getMetaData rs)
+ column-count (.getColumnCount rs-meta)]
+ (into {}
+ (map (fn [idx] [(.getColumnLabel rs-meta (int idx))
+ (.getObject rs (int idx))])
+ (range 1 (inc column-count)))))))))
\ No newline at end of file
diff --git a/save-points/6/src/workshop_app/handlers/users.clj b/save-points/6/src/workshop_app/handlers/users.clj
new file mode 100644
index 0000000..d7781d9
--- /dev/null
+++ b/save-points/6/src/workshop_app/handlers/users.clj
@@ -0,0 +1,61 @@
+(ns workshop-app.handlers.users
+ (:require [workshop-app.db.sqlite :as wads]
+ [cheshire.core :as json]))
+
+
+(defn get-handler
+ [{:keys [params] :as request}]
+ (def r* request)
+ (let [{:keys [name surname]} params]
+ (if (and name surname)
+ {:status 200
+ :body (str "Hello, "
+ name
+ " "
+ surname
+ "!!!")}
+ {:status 400
+ :body "Missing name and surname."})))
+
+
+(defn add-person
+ [{:keys [name dob]}]
+ (if (and name dob)
+ (do (wads/create! wads/conn
+ name
+ dob)
+ {:status 201
+ :body "Created user."})
+ {:status 400
+ :body "User name or date of birth missing."}))
+
+
+(defn get-person
+ [name]
+ (let [{:strs [dob]} (wads/read wads/conn name)]
+ {:status 200
+ :headers {"content-type" "application/json"}
+ :body (when dob
+ (json/generate-string {:dob dob}))}))
+
+
+(defn update-person
+ [{:keys [name dob]}]
+ (if (and name dob)
+ (do #_(_/update! wads/conn
+ name
+ dob)
+ {:status 200
+ :body "Updated user."})
+ {:status 400
+ :body "User name or dob is missing."}))
+
+
+(defn delete-person
+ [name]
+ (if name
+ (do #_(_/delete! wads/conn name)
+ {:status 200
+ :body "Deleted user."})
+ {:status 400
+ :body "User name missing, cannot delete person without username."}))
\ No newline at end of file
diff --git a/save-points/6/src/workshop_app/interop.clj b/save-points/6/src/workshop_app/interop.clj
new file mode 100644
index 0000000..3a65ff1
--- /dev/null
+++ b/save-points/6/src/workshop_app/interop.clj
@@ -0,0 +1,130 @@
+(ns workshop-app.interop
+ (:refer-clojure :rename {get cc-get})
+ (:import (java.util Calendar Comparator ArrayList HashMap AbstractMap$SimpleEntry)
+ (java.time.temporal ChronoUnit)
+ (java.time LocalDate)))
+
+;; class access
+;; HashMap.class
+HashMap ;; a class
+
+;; new HashMap();
+(HashMap.) ;; an object created from the class
+
+;; HashMap hm = new HashMap();
+(def hm (HashMap.)) ;; bind the object to a var. More on var's later.
+
+;; hm.put("a", 10);
+(.put hm "a" 10)
+
+;; hm.put("b", 10);
+(.put ^HashMap hm "b" 10) ;; the first gives us a warning of how it cannot be resolved.
+
+;; Member access
+;; method access
+;; hm.get("a")
+(.get hm "a")
+
+;; hm.get("b")
+(.get hm "b")
+
+;; "joel".toUpperCase();
+(.toUpperCase "joel")
+
+;; field access
+;; new Point(10,20).x;
+(.-x (java.awt.Point. 10 20))
+
+;; static variables or methods access
+;; Calendar.ERA
+Calendar/ERA
+
+;; Math.PI
+Math/PI
+
+;; System.getProperties()
+(System/getProperties)
+
+;; dot special form
+;; hm.get("a");
+(. hm get "a")
+
+;; "joel".toUpperCase();
+(. "joel" toUpperCase)
+
+;; System.getProperties().get("os.name")
+(. (. System (getProperties)) (get "os.name"))
+
+;; double dot
+(.. System getProperties (get "os.name"))
+
+;; doto
+;; HashMap dotoHm = new HashMap();
+;; dotoHm.put("a", 10);
+;; dotoHm.put("b", 20);
+(def doto-hm (doto (HashMap.)
+ (.put "a" 10)
+ (.put "b" 20)))
+
+;; new
+;; HashMap newSyntaxHm = new HashMap();
+(def new-syntax-hm (new HashMap))
+
+;; accessing inner classes
+;; AbstractMap.SimpleEntry("a", "b")
+(AbstractMap$SimpleEntry. "a" "b")
+
+
+;; reify example. Sort an array list of array list using
+;; a custom comparator.
+;; List al = new ArrayList();
+;; List alChild1 = new ArrayList();
+;; alChild1.add(1);
+;; alChild1.add(2);
+;; List alChild2 = new ArrayList();
+;; alChild2.add(1);
+;; alChild2.add(2);
+;; List alChild3 = new ArrayList();
+;; alChild3.add(1);
+;; alChild3.add(2);
+;; List alChild1 = new ArrayList();
+;; alChild1.add(1);
+;; alChild1.add(2);
+;; al.add(alChild1);
+;; al.add(alChild2);
+;; al.add(alChild3);
+;; al.sort(new Compartor {
+;; @Override
+;; public int compare(ArrayList al1, ArrayList al2) {
+;; return al1.elementAt(0) - al2.elementAt(0);
+;; }
+;; })
+(let [l (doto (ArrayList.)
+ (.add (doto (ArrayList.)
+ (.add 1)
+ (.add 2)))
+ (.add (doto (ArrayList.)
+ (.add 2)
+ (.add 3)))
+ (.add (doto (ArrayList.)
+ (.add 3)
+ (.add 4))))]
+ ;; this modifies the list.
+ (.sort l
+ (reify Comparator
+ (compare [_ al1 al2]
+ (- (.get ^ArrayList al2 (int 0))
+ (.get ^ArrayList al1 (int 0))))))
+ l)
+
+
+;; a small interop task. of finding the days between today and first January
+;; following is a sample java code that you should translate to clojure interop code.
+;; LocalDate dateOne = LocalDate.of(2020,1,1);
+;; LocalDate dateTwo = LocalDate.now();
+;; long daysBetween = ChronoUnits.DAYS.between(dateOne, dateTwo);
+(let [date-one (LocalDate/of 2020 1 1)
+ date-two (LocalDate/now)]
+ (.between ChronoUnit/DAYS
+ date-one
+ date-two))
\ No newline at end of file
diff --git a/save-points/6/src/workshop_app/middlewares/users.clj b/save-points/6/src/workshop_app/middlewares/users.clj
new file mode 100644
index 0000000..80bdfb5
--- /dev/null
+++ b/save-points/6/src/workshop_app/middlewares/users.clj
@@ -0,0 +1,25 @@
+(ns workshop-app.middlewares.users
+ (:require [clojure.string :as s]))
+
+
+;; Task: Write a middleware and place
+;; use it to reject issues that come through with
+;; / at the end.
+(defn reject-uri-ending-with-slash
+ [handler]
+ (fn [{:keys [uri] :as request}]
+ (if (and (not= uri "/")
+ (s/ends-with? uri "/"))
+ {:status 400
+ :body "Bad request."}
+ (handler request))))
+
+
+(defn handle-any-exception
+ [handler]
+ (fn [request]
+ (try (handler request)
+ (catch Exception e
+ (.printStackTrace e)
+ {:status 500
+ :body "Internal server error."}))))
\ No newline at end of file
diff --git a/save-points/6/src/workshop_app/routes.clj b/save-points/6/src/workshop_app/routes.clj
new file mode 100644
index 0000000..3a362cc
--- /dev/null
+++ b/save-points/6/src/workshop_app/routes.clj
@@ -0,0 +1,12 @@
+(ns workshop-app.routes
+ (:require [workshop-app.handlers.users :as wahu]
+ [compojure.core :refer [defroutes GET POST PUT DELETE ANY]]))
+
+
+(defroutes app-routes
+ (GET "/" _ wahu/get-handler)
+ (POST "/:name" {:keys [params]} (wahu/add-person params))
+ (GET "/:name" [name] (wahu/get-person name))
+ (PUT "/:name" {:keys [params]} (wahu/update-person params))
+ (DELETE "/:name" [name] (wahu/delete-person name))
+ (ANY "*" _ {:status 404}))
\ No newline at end of file
diff --git a/save-points/7/src/workshop_app/core.clj b/save-points/7/src/workshop_app/core.clj
new file mode 100644
index 0000000..de470e4
--- /dev/null
+++ b/save-points/7/src/workshop_app/core.clj
@@ -0,0 +1,20 @@
+(ns workshop-app.core
+ (:gen-class)
+ (:require [ring.adapter.jetty :as raj]
+ [ring.middleware.params :as rmp]
+ [ring.middleware.keyword-params :as rmkp]
+ [workshop-app.routes :as war]
+ [workshop-app.middlewares.users :as wamu]
+ [workshop-app.db.sqlite :as wads]))
+
+
+(defn -main
+ [& _]
+ (wads/create-table wads/conn)
+ (raj/run-jetty (-> war/app-routes
+ rmkp/wrap-keyword-params
+ rmp/wrap-params
+ wamu/handle-any-exception
+ wamu/reject-uri-ending-with-slash)
+ {:port 65535
+ :join? false}))
\ No newline at end of file
diff --git a/save-points/7/src/workshop_app/db/in_mem.clj b/save-points/7/src/workshop_app/db/in_mem.clj
new file mode 100644
index 0000000..45ea1a2
--- /dev/null
+++ b/save-points/7/src/workshop_app/db/in_mem.clj
@@ -0,0 +1,33 @@
+(ns workshop-app.db.in-mem
+ (:refer-clojure :rename {update cc-update
+ read cc-read}))
+
+
+(def conn (atom {}))
+
+(defn create!
+ "Create an entry for k in our given in memory datastore."
+ [c k v]
+ (assert (some? k) "key cannot be nil.")
+ (swap! c assoc k v))
+
+
+(defn update!
+ "Update the entry for k to value v or add it if it doesn't exist in our in memory datastore."
+ [c k v]
+ (assert (some? k) "key cannot be nil.")
+ (swap! c assoc k v))
+
+
+(defn delete!
+ "Delete the entry for k in our in memory datastore."
+ [c k]
+ (assert (some? k) "key cannot be nil.")
+ (swap! c dissoc k))
+
+
+(defn read
+ "Read the entry for k in our in memory datastore."
+ [c k]
+ (assert (some? k) "key cannot be nil.")
+ (get @c k))
\ No newline at end of file
diff --git a/save-points/7/src/workshop_app/db/sqlite.clj b/save-points/7/src/workshop_app/db/sqlite.clj
new file mode 100644
index 0000000..112429d
--- /dev/null
+++ b/save-points/7/src/workshop_app/db/sqlite.clj
@@ -0,0 +1,79 @@
+(ns workshop-app.db.sqlite
+ (:refer-clojure :rename {update cc-update
+ read cc-read})
+ (:import (java.sql DriverManager Connection PreparedStatement)))
+
+;; Connection conn = DriverManager.getConnection("jdbc:sqlite:prod_database_1.sqlite");
+(defn init-conn!
+ [conn-string]
+ (DriverManager/getConnection conn-string))
+
+(def conn (init-conn! "jdbc:sqlite:prod_database_1.sqlite"))
+
+;; Statement statement = conn.createStatement();
+;; statement.executeUpdate("create table if not exists person(name string primary key, dob string)");
+;; statement.close()
+(defn create-table
+ [c]
+ (with-open [statement (.createStatement c)]
+ (.executeUpdate statement "create table if not exists person(name string primary key, dob string)")))
+
+
+;; String k, v;
+;; Statement statement = conn.prepareStatement("insert into person values(?,?)");
+;; statement.setString(1, k);
+;; statement.setString(2, v);
+;; statement.executeUpdate();
+;; statement.close();
+(defn create!
+ [conn k v]
+ (let [statement (.prepareStatement ^Connection conn "insert into person(name, dob) values (?,?)")]
+ (.setString ^PreparedStatement statement 1 k)
+ (.setString ^PreparedStatement statement 2 v)
+ (.executeUpdate statement)
+ (.close statement)))
+
+;; String k, v;
+;; Statement statement = conn.prepareStatement("insert into person values(?,?)");
+;; statement.setString(1, k);
+;; statement.setString(2, v);
+;; statement.executeUpdate();
+;; statement.close();
+(defn update!
+ [conn k v]
+ (with-open [statement (doto (.prepareStatement ^Connection conn "update person set dob=? where name=?")
+ (.setString 2 k)
+ (.setString 1 v))]
+ (.executeUpdate statement)))
+
+
+;; String k;
+;; Statement statement = conn.prepareStatement("delete from person where name=?");
+;; statement.setString(1, k);
+;; statement.executeUpdate();
+;; statement.close();
+(defn delete!
+ [conn k]
+ (with-open [statement (.prepareStatement ^Connection conn "delete from person where name=?")]
+ (.setString ^PreparedStatement statement 1 k)
+ (.executeUpdate statement)))
+
+
+;; Statement statement = conn.prepareStatement("select name, dob from person where name=?");
+;; statement.setString(1, k);
+;; ResultSet rs = statement.executeQuery();
+;; Result result = rs.next();
+;; ResultSetMetadata rsm = result.getMetaData();
+;; int columnCount = rsm.getColumnCount();
+(defn read
+ [conn k]
+ (with-open [statement (doto (.prepareStatement ^Connection conn "select name, dob from person where name=?")
+ (.setString 1 k))]
+ (with-open [rs (.executeQuery statement)]
+ (when (.next rs)
+ (let [rs-meta (.getMetaData rs)
+ column-count (.getColumnCount rs-meta)]
+ (into {}
+ (map (fn [idx] [(.getColumnLabel rs-meta (int idx))
+ (.getObject rs (int idx))])
+ (range 1 (inc column-count)))))))))
\ No newline at end of file
diff --git a/save-points/7/src/workshop_app/handlers/users.clj b/save-points/7/src/workshop_app/handlers/users.clj
new file mode 100644
index 0000000..026a89f
--- /dev/null
+++ b/save-points/7/src/workshop_app/handlers/users.clj
@@ -0,0 +1,65 @@
+(ns workshop-app.handlers.users
+ (:require [workshop-app.db.sqlite :as wads]
+ [workshop-app.utils :as wau]
+ [cheshire.core :as json])
+ (:import (java.time LocalDate)))
+
+
+(defn get-handler
+ [{:keys [params] :as request}]
+ (def r* request)
+ (let [{:keys [name surname]} params]
+ (if (and name surname)
+ {:status 200
+ :body (str "Hello, "
+ name
+ " "
+ surname
+ "!!!")}
+ {:status 400
+ :body "Missing name and surname."})))
+
+
+(defn add-person
+ [{:keys [name dob]}]
+ (if (and name dob)
+ (do (wads/create! wads/conn
+ name
+ dob)
+ {:status 201
+ :body "Created user."})
+ {:status 400
+ :body "User name or date of birth missing."}))
+
+
+(defn get-person
+ [name]
+ (let [{:strs [dob]} (wads/read wads/conn name)]
+ {:status 200
+ :headers {"content-type" "application/json"}
+ :body (when dob
+ (json/generate-string {:dob dob
+ :age (wau/years-between (wau/parse-dt-str dob)
+ (LocalDate/now))}))}))
+
+
+(defn update-person
+ [{:keys [name dob]}]
+ (if (and name dob)
+ (do (wads/update! wads/conn
+ name
+ dob)
+ {:status 200
+ :body "Updated user."})
+ {:status 400
+ :body "User name or dob is missing."}))
+
+
+(defn delete-person
+ [name]
+ (if name
+ (do (wads/delete! wads/conn name)
+ {:status 200
+ :body "Deleted user."})
+ {:status 400
+ :body "User name missing, cannot delete person without username."}))
\ No newline at end of file
diff --git a/save-points/7/src/workshop_app/interop.clj b/save-points/7/src/workshop_app/interop.clj
new file mode 100644
index 0000000..3a65ff1
--- /dev/null
+++ b/save-points/7/src/workshop_app/interop.clj
@@ -0,0 +1,130 @@
+(ns workshop-app.interop
+ (:refer-clojure :rename {get cc-get})
+ (:import (java.util Calendar Comparator ArrayList HashMap AbstractMap$SimpleEntry)
+ (java.time.temporal ChronoUnit)
+ (java.time LocalDate)))
+
+;; class access
+;; HashMap.class
+HashMap ;; a class
+
+;; new HashMap();
+(HashMap.) ;; an object created from the class
+
+;; HashMap hm = new HashMap();
+(def hm (HashMap.)) ;; bind the object to a var. More on var's later.
+
+;; hm.put("a", 10);
+(.put hm "a" 10)
+
+;; hm.put("b", 10);
+(.put ^HashMap hm "b" 10) ;; the first gives us a warning of how it cannot be resolved.
+
+;; Member access
+;; method access
+;; hm.get("a")
+(.get hm "a")
+
+;; hm.get("b")
+(.get hm "b")
+
+;; "joel".toUpperCase();
+(.toUpperCase "joel")
+
+;; field access
+;; new Point(10,20).x;
+(.-x (java.awt.Point. 10 20))
+
+;; static variables or methods access
+;; Calendar.ERA
+Calendar/ERA
+
+;; Math.PI
+Math/PI
+
+;; System.getProperties()
+(System/getProperties)
+
+;; dot special form
+;; hm.get("a");
+(. hm get "a")
+
+;; "joel".toUpperCase();
+(. "joel" toUpperCase)
+
+;; System.getProperties().get("os.name")
+(. (. System (getProperties)) (get "os.name"))
+
+;; double dot
+(.. System getProperties (get "os.name"))
+
+;; doto
+;; HashMap dotoHm = new HashMap();
+;; dotoHm.put("a", 10);
+;; dotoHm.put("b", 20);
+(def doto-hm (doto (HashMap.)
+ (.put "a" 10)
+ (.put "b" 20)))
+
+;; new
+;; HashMap newSyntaxHm = new HashMap();
+(def new-syntax-hm (new HashMap))
+
+;; accessing inner classes
+;; AbstractMap.SimpleEntry("a", "b")
+(AbstractMap$SimpleEntry. "a" "b")
+
+
+;; reify example. Sort an array list of array list using
+;; a custom comparator.
+;; List al = new ArrayList();
+;; List alChild1 = new ArrayList();
+;; alChild1.add(1);
+;; alChild1.add(2);
+;; List alChild2 = new ArrayList();
+;; alChild2.add(1);
+;; alChild2.add(2);
+;; List alChild3 = new ArrayList();
+;; alChild3.add(1);
+;; alChild3.add(2);
+;; List alChild1 = new ArrayList();
+;; alChild1.add(1);
+;; alChild1.add(2);
+;; al.add(alChild1);
+;; al.add(alChild2);
+;; al.add(alChild3);
+;; al.sort(new Compartor {
+;; @Override
+;; public int compare(ArrayList al1, ArrayList al2) {
+;; return al1.elementAt(0) - al2.elementAt(0);
+;; }
+;; })
+(let [l (doto (ArrayList.)
+ (.add (doto (ArrayList.)
+ (.add 1)
+ (.add 2)))
+ (.add (doto (ArrayList.)
+ (.add 2)
+ (.add 3)))
+ (.add (doto (ArrayList.)
+ (.add 3)
+ (.add 4))))]
+ ;; this modifies the list.
+ (.sort l
+ (reify Comparator
+ (compare [_ al1 al2]
+ (- (.get ^ArrayList al2 (int 0))
+ (.get ^ArrayList al1 (int 0))))))
+ l)
+
+
+;; a small interop task. of finding the days between today and first January
+;; following is a sample java code that you should translate to clojure interop code.
+;; LocalDate dateOne = LocalDate.of(2020,1,1);
+;; LocalDate dateTwo = LocalDate.now();
+;; long daysBetween = ChronoUnits.DAYS.between(dateOne, dateTwo);
+(let [date-one (LocalDate/of 2020 1 1)
+ date-two (LocalDate/now)]
+ (.between ChronoUnit/DAYS
+ date-one
+ date-two))
\ No newline at end of file
diff --git a/save-points/7/src/workshop_app/middlewares/users.clj b/save-points/7/src/workshop_app/middlewares/users.clj
new file mode 100644
index 0000000..399df82
--- /dev/null
+++ b/save-points/7/src/workshop_app/middlewares/users.clj
@@ -0,0 +1,22 @@
+(ns workshop-app.middlewares.users
+ (:require [clojure.string :as s]))
+
+
+(defn reject-uri-ending-with-slash
+ [handler]
+ (fn [{:keys [uri] :as request}]
+ (if (and (not= uri "/")
+ (s/ends-with? uri "/"))
+ {:status 400
+ :body "Bad request."}
+ (handler request))))
+
+
+(defn handle-any-exception
+ [handler]
+ (fn [request]
+ (try (handler request)
+ (catch Exception e
+ (.printStackTrace e)
+ {:status 500
+ :body "Internal server error."}))))
\ No newline at end of file
diff --git a/save-points/7/src/workshop_app/routes.clj b/save-points/7/src/workshop_app/routes.clj
new file mode 100644
index 0000000..54a1407
--- /dev/null
+++ b/save-points/7/src/workshop_app/routes.clj
@@ -0,0 +1,14 @@
+(ns workshop-app.routes
+ (:require [workshop-app.handlers.users :as wahu]
+ [workshop-app.db.sqlite :as wads]
+ [compojure.core :refer [defroutes GET POST PUT DELETE ANY]])
+ (:import (java.time LocalDate)))
+
+
+(defroutes app-routes
+ (GET "/" _ wahu/get-handler)
+ (POST "/:name" {:keys [params]} (wahu/add-person params))
+ (GET "/:name" [name] (wahu/get-person name))
+ (PUT "/:name" {:keys [params]} (wahu/update-person params))
+ (DELETE "/:name" [name] (wahu/delete-person name))
+ (ANY "*" _ {:status 404}))
\ No newline at end of file
diff --git a/save-points/7/src/workshop_app/utils.clj b/save-points/7/src/workshop_app/utils.clj
new file mode 100644
index 0000000..f0093fd
--- /dev/null
+++ b/save-points/7/src/workshop_app/utils.clj
@@ -0,0 +1,14 @@
+(ns workshop-app.utils
+ (:import (java.time LocalDate)
+ (java.time.temporal ChronoUnit)))
+
+
+(defn parse-dt-str
+ [dt-str]
+ (when (seq dt-str)
+ (LocalDate/parse dt-str)))
+
+
+(defn years-between
+ [d1 d2]
+ (.between ChronoUnit/YEARS d1 d2))
\ No newline at end of file
diff --git a/save-points/7/test/workshop_app/fixtures.clj b/save-points/7/test/workshop_app/fixtures.clj
new file mode 100644
index 0000000..349baf6
--- /dev/null
+++ b/save-points/7/test/workshop_app/fixtures.clj
@@ -0,0 +1,45 @@
+(ns workshop-app.fixtures
+ (:require [clojure.test :refer :all]
+ [workshop-app.handlers.users :as wahu]
+ [workshop-app.db.sqlite :as wads]))
+
+
+;; Pattern 1
+;; Using fixtures to actually bootstrap and teardown.
+;; Scenario: I want to test against a actual instance
+;; but I want to test against a different database.
+(use-fixtures :each (fn [t]
+ (let [conn (wads/init-conn! "jdbc:sqlite::memory:")]
+ (with-redefs [wads/conn conn]
+ (wads/create-table conn)
+ (t)))))
+
+
+(deftest add-person-test
+ (is (= {:status 201
+ :body "Created user."}
+ (wahu/add-person {:name "Joel Victor"
+ :dob "2001-01-01"}))
+ "Is the creation handler working as expected."))
+
+(deftest get-person-test
+ (is (= {:status 200
+ :headers {"content-type" "application/json"}
+ :body nil}
+ (wahu/get-person "Joel Victor"))
+ "Is the read handler working as expected."))
+
+
+(deftest update-person-test
+ (is (= {:status 200
+ :body "Updated user."}
+ (wahu/update-person {:name "Joel Victor"
+ :dob "2000-01-01"}))
+ "Is the update handler working as expected."))
+
+
+(deftest delete-person-test
+ (is (= {:status 200
+ :body "Deleted user."}
+ (wahu/delete-person "Joel Victor"))
+ "Is the deletion handler working as expected."))
\ No newline at end of file
diff --git a/save-points/7/test/workshop_app/intro_to_testing.clj b/save-points/7/test/workshop_app/intro_to_testing.clj
new file mode 100644
index 0000000..591d0fb
--- /dev/null
+++ b/save-points/7/test/workshop_app/intro_to_testing.clj
@@ -0,0 +1,20 @@
+(ns workshop-app.intro-to-testing
+ (:require [clojure.test :refer :all]))
+
+
+;; Defining a test
+(deftest test-name-1
+ (is (= 1 1)
+ "Optional message for assertion")
+
+ (are [x y] (= x y)
+ 2 (+ 1 1)
+ 3 (+ 1 2)))
+
+
+(deftest test-name-2
+ (testing "Msg to add context to all the test run inside this"
+ (is (= 1 2)
+ "One should not equal 2."))
+
+ (is (thrown? RuntimeException ((fn [] (throw (RuntimeException.)))))))
\ No newline at end of file
diff --git a/save-points/7/test/workshop_app/mocking_fns.clj b/save-points/7/test/workshop_app/mocking_fns.clj
new file mode 100644
index 0000000..812c969
--- /dev/null
+++ b/save-points/7/test/workshop_app/mocking_fns.clj
@@ -0,0 +1,27 @@
+(ns workshop-app.mocking-fns
+ (:require [clojure.test :refer :all]
+ [workshop-app.handlers.users :as wahu]
+ [workshop-app.db.sqlite :as wads]
+ [workshop-app.db.in-mem :as wadim]))
+
+;; pattern 2. don't create the connection but instead redefine
+;; it to an inmemory implementation.
+;; these are useful when you communicate to a database over
+;; a network
+(use-fixtures :each (fn [t]
+ (with-redefs [wads/conn wadim/conn
+ wads/create! wadim/create!
+ wads/update! wadim/update!
+ wads/read (fn [conn k] {"dob" (wadim/read conn k)})
+ wads/delete! wadim/delete!]
+ (t))))
+
+(deftest all-handlers-test
+ (testing "Testing all handlers in one go."
+ (are [expected-response actual-response] (= expected-response actual-response)
+ {:status 201 :body "Created user."} (wahu/add-person {:name "Joel Victor"
+ :dob "2001-01-01"})
+ {:status 200 :body "Updated user."} (wahu/update-person {:name "Joel Victor"
+ :dob "2000-01-01"})
+ ;; {:status 200} (wahu/get-person "Joel Victor")
+ {:status 200 :body "Deleted user."} (wahu/delete-person "Joel Victor"))))
\ No newline at end of file
diff --git a/save-points/7/test/workshop_app/property_based_test.clj b/save-points/7/test/workshop_app/property_based_test.clj
new file mode 100644
index 0000000..1007bd7
--- /dev/null
+++ b/save-points/7/test/workshop_app/property_based_test.clj
@@ -0,0 +1,36 @@
+(ns workshop-app.property-based-test
+ (:require [clojure.test :refer :all]
+ [clojure.test.check :as tc]
+ [clojure.test.check.clojure-test :as tcct]
+ [clojure.test.check.generators :as ctcg]
+ [clojure.test.check.properties :as ctcp]
+ [workshop-app.utils :as wau])
+ (:import (java.time LocalDate)))
+
+
+(def date-tuple-generator
+ (ctcg/let [month (ctcg/choose 1 12)]
+ (case month
+ 2 (ctcg/tuple (ctcg/choose 1900 2021) (ctcg/return month) (ctcg/choose 1 28))
+ (1 3 5 7 8 10 12) (ctcg/tuple (ctcg/choose 1900 2021) (ctcg/return month) (ctcg/choose 1 31))
+ (4 6 9 11) (ctcg/tuple (ctcg/choose 1900 2021) (ctcg/return month) (ctcg/choose 1 30)))))
+
+(def date-object-generator
+ (ctcg/fmap (fn [[y m d]]
+ (LocalDate/of y m d))
+ date-tuple-generator))
+
+(def age-property-fn (some-fn pos? zero?))
+
+(def age-property
+ (ctcp/for-all [st-dt date-object-generator
+ end-dt date-object-generator]
+ (age-property-fn (wau/years-between st-dt end-dt))))
+
+#_(tc/quick-check 1000 age-property)
+
+
+(tcct/defspec age-property-2 100
+ (ctcp/for-all [st-dt date-object-generator
+ end-dt date-object-generator]
+ (age-property-fn (wau/years-between st-dt end-dt))))
\ No newline at end of file
diff --git a/save-points/7/test/workshop_app/pure_fns.clj b/save-points/7/test/workshop_app/pure_fns.clj
new file mode 100644
index 0000000..b755e37
--- /dev/null
+++ b/save-points/7/test/workshop_app/pure_fns.clj
@@ -0,0 +1,31 @@
+(ns workshop-app.pure-fns
+ (:require [clojure.test :refer :all]
+ [workshop-app.handlers.users :as wahu]
+ [workshop-app.middlewares.users :as wamu])
+ (:import (java.time LocalDate)))
+
+
+(deftest pure-get-person-test
+ (is (= {:status 200
+ :headers {"content-type" "application/json"}
+ :body "{\"dob\":\"2000-01-01\",\"age\":20}"}
+ #_(wahu/get-person "2000-01-01" (LocalDate/parse "2020-02-14")))
+ "Is our pure get handler working as expected."))
+
+
+(deftest pure-reject-uri-ending-with-slash-middleware-test
+ (testing "Middleware should reject all uri's ending with /"
+ (are [expected actual] (= expected actual)
+ {:uri "/"} _
+ {:status 400 :body "Bad request."} _
+ {:uri "/joel"} _)))
+
+;; by using higher order functions you can also test behavior without
+;; mutating any code.
+(deftest pure-handle-any-exception
+ (testing "Middleware should reject all uri's ending with /"
+ (are [expected actual] (= expected actual)
+ {:status 500 :body "Internal server error."} ((wamu/handle-any-exception (fn [_] (throw (Exception.))))
+ {})
+ {} ((wamu/handle-any-exception identity)
+ {}))))
\ No newline at end of file
diff --git a/save-points/8/src/workshop_app/clj_to_java_interop.clj b/save-points/8/src/workshop_app/clj_to_java_interop.clj
new file mode 100644
index 0000000..fc2c2d6
--- /dev/null
+++ b/save-points/8/src/workshop_app/clj_to_java_interop.clj
@@ -0,0 +1,23 @@
+(ns workshop-app.clj-to-java-interop)
+
+(gen-class :name org.inclojure.Demo
+ :init init
+ :prefix "-"
+ :state "state"
+ :methods [[getName [] String]
+ [setName [String] void]])
+
+
+(defn -init []
+ "State a hash map."
+ [[] (atom {})])
+
+
+(defn -getName
+ [this]
+ (:name @(.state this)))
+
+
+(defn -setName
+ [this name]
+ (swap! (.state this) assoc :name name))
\ No newline at end of file
diff --git a/save-points/8/src/workshop_app/core.clj b/save-points/8/src/workshop_app/core.clj
new file mode 100644
index 0000000..de470e4
--- /dev/null
+++ b/save-points/8/src/workshop_app/core.clj
@@ -0,0 +1,20 @@
+(ns workshop-app.core
+ (:gen-class)
+ (:require [ring.adapter.jetty :as raj]
+ [ring.middleware.params :as rmp]
+ [ring.middleware.keyword-params :as rmkp]
+ [workshop-app.routes :as war]
+ [workshop-app.middlewares.users :as wamu]
+ [workshop-app.db.sqlite :as wads]))
+
+
+(defn -main
+ [& _]
+ (wads/create-table wads/conn)
+ (raj/run-jetty (-> war/app-routes
+ rmkp/wrap-keyword-params
+ rmp/wrap-params
+ wamu/handle-any-exception
+ wamu/reject-uri-ending-with-slash)
+ {:port 65535
+ :join? false}))
\ No newline at end of file
diff --git a/save-points/8/src/workshop_app/db/in_mem.clj b/save-points/8/src/workshop_app/db/in_mem.clj
new file mode 100644
index 0000000..45ea1a2
--- /dev/null
+++ b/save-points/8/src/workshop_app/db/in_mem.clj
@@ -0,0 +1,33 @@
+(ns workshop-app.db.in-mem
+ (:refer-clojure :rename {update cc-update
+ read cc-read}))
+
+
+(def conn (atom {}))
+
+(defn create!
+ "Create an entry for k in our given in memory datastore."
+ [c k v]
+ (assert (some? k) "key cannot be nil.")
+ (swap! c assoc k v))
+
+
+(defn update!
+ "Update the entry for k to value v or add it if it doesn't exist in our in memory datastore."
+ [c k v]
+ (assert (some? k) "key cannot be nil.")
+ (swap! c assoc k v))
+
+
+(defn delete!
+ "Delete the entry for k in our in memory datastore."
+ [c k]
+ (assert (some? k) "key cannot be nil.")
+ (swap! c dissoc k))
+
+
+(defn read
+ "Read the entry for k in our in memory datastore."
+ [c k]
+ (assert (some? k) "key cannot be nil.")
+ (get @c k))
\ No newline at end of file
diff --git a/save-points/8/src/workshop_app/db/sqlite.clj b/save-points/8/src/workshop_app/db/sqlite.clj
new file mode 100644
index 0000000..112429d
--- /dev/null
+++ b/save-points/8/src/workshop_app/db/sqlite.clj
@@ -0,0 +1,79 @@
+(ns workshop-app.db.sqlite
+ (:refer-clojure :rename {update cc-update
+ read cc-read})
+ (:import (java.sql DriverManager Connection PreparedStatement)))
+
+;; Connection conn = DriverManager.getConnection("jdbc:sqlite:prod_database_1.sqlite");
+(defn init-conn!
+ [conn-string]
+ (DriverManager/getConnection conn-string))
+
+(def conn (init-conn! "jdbc:sqlite:prod_database_1.sqlite"))
+
+;; Statement statement = conn.createStatement();
+;; statement.executeUpdate("create table if not exists person(name string primary key, dob string)");
+;; statement.close()
+(defn create-table
+ [c]
+ (with-open [statement (.createStatement c)]
+ (.executeUpdate statement "create table if not exists person(name string primary key, dob string)")))
+
+
+;; String k, v;
+;; Statement statement = conn.prepareStatement("insert into person values(?,?)");
+;; statement.setString(1, k);
+;; statement.setString(2, v);
+;; statement.executeUpdate();
+;; statement.close();
+(defn create!
+ [conn k v]
+ (let [statement (.prepareStatement ^Connection conn "insert into person(name, dob) values (?,?)")]
+ (.setString ^PreparedStatement statement 1 k)
+ (.setString ^PreparedStatement statement 2 v)
+ (.executeUpdate statement)
+ (.close statement)))
+
+;; String k, v;
+;; Statement statement = conn.prepareStatement("insert into person values(?,?)");
+;; statement.setString(1, k);
+;; statement.setString(2, v);
+;; statement.executeUpdate();
+;; statement.close();
+(defn update!
+ [conn k v]
+ (with-open [statement (doto (.prepareStatement ^Connection conn "update person set dob=? where name=?")
+ (.setString 2 k)
+ (.setString 1 v))]
+ (.executeUpdate statement)))
+
+
+;; String k;
+;; Statement statement = conn.prepareStatement("delete from person where name=?");
+;; statement.setString(1, k);
+;; statement.executeUpdate();
+;; statement.close();
+(defn delete!
+ [conn k]
+ (with-open [statement (.prepareStatement ^Connection conn "delete from person where name=?")]
+ (.setString ^PreparedStatement statement 1 k)
+ (.executeUpdate statement)))
+
+
+;; Statement statement = conn.prepareStatement("select name, dob from person where name=?");
+;; statement.setString(1, k);
+;; ResultSet rs = statement.executeQuery();
+;; Result result = rs.next();
+;; ResultSetMetadata rsm = result.getMetaData();
+;; int columnCount = rsm.getColumnCount();
+(defn read
+ [conn k]
+ (with-open [statement (doto (.prepareStatement ^Connection conn "select name, dob from person where name=?")
+ (.setString 1 k))]
+ (with-open [rs (.executeQuery statement)]
+ (when (.next rs)
+ (let [rs-meta (.getMetaData rs)
+ column-count (.getColumnCount rs-meta)]
+ (into {}
+ (map (fn [idx] [(.getColumnLabel rs-meta (int idx))
+ (.getObject rs (int idx))])
+ (range 1 (inc column-count)))))))))
\ No newline at end of file
diff --git a/save-points/8/src/workshop_app/handlers/users.clj b/save-points/8/src/workshop_app/handlers/users.clj
new file mode 100644
index 0000000..3a404e0
--- /dev/null
+++ b/save-points/8/src/workshop_app/handlers/users.clj
@@ -0,0 +1,63 @@
+(ns workshop-app.handlers.users
+ (:require [workshop-app.db.sqlite :as wads]
+ [workshop-app.utils :as wau]
+ [cheshire.core :as json]))
+
+
+(defn get-handler
+ [{:keys [params] :as request}]
+ (def r* request)
+ (let [{:keys [name surname]} params]
+ (if (and name surname)
+ {:status 200
+ :body (str "Hello, "
+ name
+ " "
+ surname
+ "!!!")}
+ {:status 400
+ :body "Missing name and surname."})))
+
+
+(defn add-person
+ [{:keys [name dob]}]
+ (if (and name dob)
+ (do (wads/create! wads/conn
+ name
+ dob)
+ {:status 201
+ :body "Created user."})
+ {:status 400
+ :body "User name or date of birth missing."}))
+
+
+(defn get-person
+ [dob now]
+ {:status 200
+ :headers {"content-type" "application/json"}
+ :body (when dob
+ (json/generate-string {:dob dob
+ :age (wau/years-between (wau/parse-dt-str dob)
+ now)}))})
+
+
+(defn update-person
+ [{:keys [name dob]}]
+ (if (and name dob)
+ (do (wads/update! wads/conn
+ name
+ dob)
+ {:status 200
+ :body "Updated user."})
+ {:status 400
+ :body "User name or dob is missing."}))
+
+
+(defn delete-person
+ [name]
+ (if name
+ (do (wads/delete! wads/conn name)
+ {:status 200
+ :body "Deleted user."})
+ {:status 400
+ :body "User name missing, cannot delete person without username."}))
\ No newline at end of file
diff --git a/save-points/8/src/workshop_app/interop.clj b/save-points/8/src/workshop_app/interop.clj
new file mode 100644
index 0000000..3a65ff1
--- /dev/null
+++ b/save-points/8/src/workshop_app/interop.clj
@@ -0,0 +1,130 @@
+(ns workshop-app.interop
+ (:refer-clojure :rename {get cc-get})
+ (:import (java.util Calendar Comparator ArrayList HashMap AbstractMap$SimpleEntry)
+ (java.time.temporal ChronoUnit)
+ (java.time LocalDate)))
+
+;; class access
+;; HashMap.class
+HashMap ;; a class
+
+;; new HashMap();
+(HashMap.) ;; an object created from the class
+
+;; HashMap hm = new HashMap();
+(def hm (HashMap.)) ;; bind the object to a var. More on var's later.
+
+;; hm.put("a", 10);
+(.put hm "a" 10)
+
+;; hm.put("b", 10);
+(.put ^HashMap hm "b" 10) ;; the first gives us a warning of how it cannot be resolved.
+
+;; Member access
+;; method access
+;; hm.get("a")
+(.get hm "a")
+
+;; hm.get("b")
+(.get hm "b")
+
+;; "joel".toUpperCase();
+(.toUpperCase "joel")
+
+;; field access
+;; new Point(10,20).x;
+(.-x (java.awt.Point. 10 20))
+
+;; static variables or methods access
+;; Calendar.ERA
+Calendar/ERA
+
+;; Math.PI
+Math/PI
+
+;; System.getProperties()
+(System/getProperties)
+
+;; dot special form
+;; hm.get("a");
+(. hm get "a")
+
+;; "joel".toUpperCase();
+(. "joel" toUpperCase)
+
+;; System.getProperties().get("os.name")
+(. (. System (getProperties)) (get "os.name"))
+
+;; double dot
+(.. System getProperties (get "os.name"))
+
+;; doto
+;; HashMap dotoHm = new HashMap();
+;; dotoHm.put("a", 10);
+;; dotoHm.put("b", 20);
+(def doto-hm (doto (HashMap.)
+ (.put "a" 10)
+ (.put "b" 20)))
+
+;; new
+;; HashMap newSyntaxHm = new HashMap();
+(def new-syntax-hm (new HashMap))
+
+;; accessing inner classes
+;; AbstractMap.SimpleEntry("a", "b")
+(AbstractMap$SimpleEntry. "a" "b")
+
+
+;; reify example. Sort an array list of array list using
+;; a custom comparator.
+;; List al = new ArrayList();
+;; List alChild1 = new ArrayList();
+;; alChild1.add(1);
+;; alChild1.add(2);
+;; List alChild2 = new ArrayList();
+;; alChild2.add(1);
+;; alChild2.add(2);
+;; List alChild3 = new ArrayList();
+;; alChild3.add(1);
+;; alChild3.add(2);
+;; List alChild1 = new ArrayList();
+;; alChild1.add(1);
+;; alChild1.add(2);
+;; al.add(alChild1);
+;; al.add(alChild2);
+;; al.add(alChild3);
+;; al.sort(new Compartor {
+;; @Override
+;; public int compare(ArrayList al1, ArrayList al2) {
+;; return al1.elementAt(0) - al2.elementAt(0);
+;; }
+;; })
+(let [l (doto (ArrayList.)
+ (.add (doto (ArrayList.)
+ (.add 1)
+ (.add 2)))
+ (.add (doto (ArrayList.)
+ (.add 2)
+ (.add 3)))
+ (.add (doto (ArrayList.)
+ (.add 3)
+ (.add 4))))]
+ ;; this modifies the list.
+ (.sort l
+ (reify Comparator
+ (compare [_ al1 al2]
+ (- (.get ^ArrayList al2 (int 0))
+ (.get ^ArrayList al1 (int 0))))))
+ l)
+
+
+;; a small interop task. of finding the days between today and first January
+;; following is a sample java code that you should translate to clojure interop code.
+;; LocalDate dateOne = LocalDate.of(2020,1,1);
+;; LocalDate dateTwo = LocalDate.now();
+;; long daysBetween = ChronoUnits.DAYS.between(dateOne, dateTwo);
+(let [date-one (LocalDate/of 2020 1 1)
+ date-two (LocalDate/now)]
+ (.between ChronoUnit/DAYS
+ date-one
+ date-two))
\ No newline at end of file
diff --git a/save-points/8/src/workshop_app/middlewares/users.clj b/save-points/8/src/workshop_app/middlewares/users.clj
new file mode 100644
index 0000000..399df82
--- /dev/null
+++ b/save-points/8/src/workshop_app/middlewares/users.clj
@@ -0,0 +1,22 @@
+(ns workshop-app.middlewares.users
+ (:require [clojure.string :as s]))
+
+
+(defn reject-uri-ending-with-slash
+ [handler]
+ (fn [{:keys [uri] :as request}]
+ (if (and (not= uri "/")
+ (s/ends-with? uri "/"))
+ {:status 400
+ :body "Bad request."}
+ (handler request))))
+
+
+(defn handle-any-exception
+ [handler]
+ (fn [request]
+ (try (handler request)
+ (catch Exception e
+ (.printStackTrace e)
+ {:status 500
+ :body "Internal server error."}))))
\ No newline at end of file
diff --git a/save-points/8/src/workshop_app/namespaces.clj b/save-points/8/src/workshop_app/namespaces.clj
new file mode 100644
index 0000000..eece9fa
--- /dev/null
+++ b/save-points/8/src/workshop_app/namespaces.clj
@@ -0,0 +1,11 @@
+(ns workshop-app.namespaces)
+
+;; to create a namespace
+#_(ns test-namespace)
+
+;; to switch to a namespace
+#_(in-ns 'test-namespace)
+
+#_*ns*
+
+#_(def a)
\ No newline at end of file
diff --git a/save-points/8/src/workshop_app/routes.clj b/save-points/8/src/workshop_app/routes.clj
new file mode 100644
index 0000000..2a5547a
--- /dev/null
+++ b/save-points/8/src/workshop_app/routes.clj
@@ -0,0 +1,15 @@
+(ns workshop-app.routes
+ (:require [workshop-app.handlers.users :as wahu]
+ [workshop-app.db.sqlite :as wads]
+ [compojure.core :refer [defroutes GET POST PUT DELETE ANY]])
+ (:import (java.time LocalDate)))
+
+
+(defroutes app-routes
+ (GET "/" _ wahu/get-handler)
+ (POST "/:name" {:keys [params]} (wahu/add-person params))
+ (GET "/:name" [name] (wahu/get-person (get (wads/read wads/conn name) "dob")
+ (LocalDate/now)))
+ (PUT "/:name" {:keys [params]} (wahu/update-person params))
+ (DELETE "/:name" [name] (wahu/delete-person name))
+ (ANY "*" _ {:status 404}))
\ No newline at end of file
diff --git a/save-points/8/src/workshop_app/utils.clj b/save-points/8/src/workshop_app/utils.clj
new file mode 100644
index 0000000..f0093fd
--- /dev/null
+++ b/save-points/8/src/workshop_app/utils.clj
@@ -0,0 +1,14 @@
+(ns workshop-app.utils
+ (:import (java.time LocalDate)
+ (java.time.temporal ChronoUnit)))
+
+
+(defn parse-dt-str
+ [dt-str]
+ (when (seq dt-str)
+ (LocalDate/parse dt-str)))
+
+
+(defn years-between
+ [d1 d2]
+ (.between ChronoUnit/YEARS d1 d2))
\ No newline at end of file
diff --git a/save-points/8/src/workshop_app/vars.clj b/save-points/8/src/workshop_app/vars.clj
new file mode 100644
index 0000000..1a255bf
--- /dev/null
+++ b/save-points/8/src/workshop_app/vars.clj
@@ -0,0 +1,22 @@
+(ns workshop-app.vars)
+
+;; this is a symbol
+'a
+
+;; to create a var we need to simply do
+(def a)
+
+#_(+ a 10)
+
+#_(alter-var-root #'a (constantly 20))
+
+#_(println a)
+
+#_(println (var-get #'a))
+
+(def ^:dynamic b)
+
+#_(+ b 20)
+
+(binding [b 10]
+ (println (+ b 10)))
\ No newline at end of file
diff --git a/save-points/8/test/workshop_app/handlers/users_pure_test.clj b/save-points/8/test/workshop_app/handlers/users_pure_test.clj
new file mode 100644
index 0000000..34f7cb7
--- /dev/null
+++ b/save-points/8/test/workshop_app/handlers/users_pure_test.clj
@@ -0,0 +1,12 @@
+(ns workshop-app.handlers.users-pure-test
+ (:require [clojure.test :refer :all]
+ [workshop-app.handlers.users :as wahu])
+ (:import (java.time LocalDate)))
+
+
+(deftest pure-get-person-test
+ (is (= {:status 200
+ :headers {"content-type" "application/json"}
+ :body "{\"dob\":\"2000-01-01\",\"age\":20}"}
+ (wahu/get-person "2000-01-01" (LocalDate/parse "2020-02-14")))
+ "Is our pure get handler working as expected."))
\ No newline at end of file
diff --git a/save-points/8/test/workshop_app/handlers/users_test.clj b/save-points/8/test/workshop_app/handlers/users_test.clj
new file mode 100644
index 0000000..29fe783
--- /dev/null
+++ b/save-points/8/test/workshop_app/handlers/users_test.clj
@@ -0,0 +1,38 @@
+(ns workshop-app.handlers.users-test
+ (:require [clojure.test :refer :all]
+ [workshop-app.handlers.users :as wahu]
+ [workshop-app.db.sqlite :as wads]))
+
+
+;; Pattern 1
+;; Using fixtures to actually bootstrap and teardown.
+;; Scenario: I want to test against a actual instance
+;; but I want to test against a different database.
+(use-fixtures :each (fn [t]
+ (let [conn (wads/init-conn! "jdbc:sqlite::memory:")]
+ (with-redefs [wads/conn conn]
+ (wads/create-table conn)
+ (t)))))
+
+
+(deftest add-person-test
+ (is (= {:status 201
+ :body "Created user."}
+ (wahu/add-person {:name "Joel Victor"
+ :dob "2001-01-01"}))
+ "Is the creation handler working as expected."))
+
+
+(deftest update-person-test
+ (is (= {:status 200
+ :body "Updated user."}
+ (wahu/update-person {:name "Joel Victor"
+ :dob "2000-01-01"}))
+ "Is the update handler working as expected."))
+
+
+(deftest delete-person-test
+ (is (= {:status 200
+ :body "Deleted user."}
+ (wahu/delete-person "Joel Victor"))
+ "Is the deletion handler working as expected."))
\ No newline at end of file
diff --git a/save-points/8/test/workshop_app/handlers/users_test_2.clj b/save-points/8/test/workshop_app/handlers/users_test_2.clj
new file mode 100644
index 0000000..9f1a6fe
--- /dev/null
+++ b/save-points/8/test/workshop_app/handlers/users_test_2.clj
@@ -0,0 +1,27 @@
+(ns workshop-app.handlers.users-test-2
+ (:require [clojure.test :refer :all]
+ [workshop-app.handlers.users :as wahu]
+ [workshop-app.db.sqlite :as wads]
+ [workshop-app.db.in-mem :as wadim]))
+
+;; pattern 2. don't create the connection but instead redefine
+;; it to an inmemory implementation.
+;; these are useful when you communicate to a database over
+;; a network
+(use-fixtures :each (fn [t]
+ (with-redefs [wads/conn wadim/conn
+ wads/create! wadim/create!
+ wads/update! wadim/update!
+ wads/read (fn [conn k] {"dob" (wadim/read conn k)})
+ wads/delete! wadim/delete!]
+ (t))))
+
+
+(deftest all-handlers-test
+ (testing "Testing all handlers in one go."
+ (are [expected-response actual-response] (= expected-response actual-response)
+ {:status 201 :body "Created user."} (wahu/add-person {:name "Joel Victor"
+ :dob "2001-01-01"})
+ {:status 200 :body "Updated user."} (wahu/update-person {:name "Joel Victor"
+ :dob "2000-01-01"})
+ {:status 200 :body "Deleted user."} (wahu/delete-person "Joel Victor"))))
\ No newline at end of file
diff --git a/save-points/8/test/workshop_app/intro_to_testing.clj b/save-points/8/test/workshop_app/intro_to_testing.clj
new file mode 100644
index 0000000..591d0fb
--- /dev/null
+++ b/save-points/8/test/workshop_app/intro_to_testing.clj
@@ -0,0 +1,20 @@
+(ns workshop-app.intro-to-testing
+ (:require [clojure.test :refer :all]))
+
+
+;; Defining a test
+(deftest test-name-1
+ (is (= 1 1)
+ "Optional message for assertion")
+
+ (are [x y] (= x y)
+ 2 (+ 1 1)
+ 3 (+ 1 2)))
+
+
+(deftest test-name-2
+ (testing "Msg to add context to all the test run inside this"
+ (is (= 1 2)
+ "One should not equal 2."))
+
+ (is (thrown? RuntimeException ((fn [] (throw (RuntimeException.)))))))
\ No newline at end of file
diff --git a/save-points/8/test/workshop_app/middlewares/users_test.clj b/save-points/8/test/workshop_app/middlewares/users_test.clj
new file mode 100644
index 0000000..675af60
--- /dev/null
+++ b/save-points/8/test/workshop_app/middlewares/users_test.clj
@@ -0,0 +1,25 @@
+(ns workshop-app.middlewares.users-test
+ (:require [clojure.test :refer :all]
+ [workshop-app.middlewares.users :as wamu]))
+
+
+(deftest pure-reject-uri-ending-with-slash-middleware-test
+ (testing "Middleware should reject all uri's ending with /"
+ (are [expected actual] (= expected actual)
+ {:uri "/"} ((wamu/reject-uri-ending-with-slash identity)
+ {:uri "/"})
+ {:status 400 :body "Bad request."} ((wamu/reject-uri-ending-with-slash identity)
+ {:uri "/joel/"})
+ {:uri "/joel"} ((wamu/reject-uri-ending-with-slash identity)
+ {:uri "/joel"}))))
+
+
+;; by using higher order functions you can also test behavior without
+;; mutating any code.
+(deftest pure-handle-any-exception
+ (testing "Middleware should reject all uri's ending with /"
+ (are [expected actual] (= expected actual)
+ {:status 500 :body "Internal server error."} ((wamu/handle-any-exception (fn [_] (throw (Exception.))))
+ {})
+ {} ((wamu/handle-any-exception identity)
+ {}))))
\ No newline at end of file
diff --git a/save-points/8/test/workshop_app/property_based_test.clj b/save-points/8/test/workshop_app/property_based_test.clj
new file mode 100644
index 0000000..1007bd7
--- /dev/null
+++ b/save-points/8/test/workshop_app/property_based_test.clj
@@ -0,0 +1,36 @@
+(ns workshop-app.property-based-test
+ (:require [clojure.test :refer :all]
+ [clojure.test.check :as tc]
+ [clojure.test.check.clojure-test :as tcct]
+ [clojure.test.check.generators :as ctcg]
+ [clojure.test.check.properties :as ctcp]
+ [workshop-app.utils :as wau])
+ (:import (java.time LocalDate)))
+
+
+(def date-tuple-generator
+ (ctcg/let [month (ctcg/choose 1 12)]
+ (case month
+ 2 (ctcg/tuple (ctcg/choose 1900 2021) (ctcg/return month) (ctcg/choose 1 28))
+ (1 3 5 7 8 10 12) (ctcg/tuple (ctcg/choose 1900 2021) (ctcg/return month) (ctcg/choose 1 31))
+ (4 6 9 11) (ctcg/tuple (ctcg/choose 1900 2021) (ctcg/return month) (ctcg/choose 1 30)))))
+
+(def date-object-generator
+ (ctcg/fmap (fn [[y m d]]
+ (LocalDate/of y m d))
+ date-tuple-generator))
+
+(def age-property-fn (some-fn pos? zero?))
+
+(def age-property
+ (ctcp/for-all [st-dt date-object-generator
+ end-dt date-object-generator]
+ (age-property-fn (wau/years-between st-dt end-dt))))
+
+#_(tc/quick-check 1000 age-property)
+
+
+(tcct/defspec age-property-2 100
+ (ctcp/for-all [st-dt date-object-generator
+ end-dt date-object-generator]
+ (age-property-fn (wau/years-between st-dt end-dt))))
\ No newline at end of file
diff --git a/save-points/9/src/workshop_app/clj_to_java_interop.clj b/save-points/9/src/workshop_app/clj_to_java_interop.clj
new file mode 100644
index 0000000..fc2c2d6
--- /dev/null
+++ b/save-points/9/src/workshop_app/clj_to_java_interop.clj
@@ -0,0 +1,23 @@
+(ns workshop-app.clj-to-java-interop)
+
+(gen-class :name org.inclojure.Demo
+ :init init
+ :prefix "-"
+ :state "state"
+ :methods [[getName [] String]
+ [setName [String] void]])
+
+
+(defn -init []
+ "State a hash map."
+ [[] (atom {})])
+
+
+(defn -getName
+ [this]
+ (:name @(.state this)))
+
+
+(defn -setName
+ [this name]
+ (swap! (.state this) assoc :name name))
\ No newline at end of file
diff --git a/save-points/9/src/workshop_app/core.clj b/save-points/9/src/workshop_app/core.clj
new file mode 100644
index 0000000..de470e4
--- /dev/null
+++ b/save-points/9/src/workshop_app/core.clj
@@ -0,0 +1,20 @@
+(ns workshop-app.core
+ (:gen-class)
+ (:require [ring.adapter.jetty :as raj]
+ [ring.middleware.params :as rmp]
+ [ring.middleware.keyword-params :as rmkp]
+ [workshop-app.routes :as war]
+ [workshop-app.middlewares.users :as wamu]
+ [workshop-app.db.sqlite :as wads]))
+
+
+(defn -main
+ [& _]
+ (wads/create-table wads/conn)
+ (raj/run-jetty (-> war/app-routes
+ rmkp/wrap-keyword-params
+ rmp/wrap-params
+ wamu/handle-any-exception
+ wamu/reject-uri-ending-with-slash)
+ {:port 65535
+ :join? false}))
\ No newline at end of file
diff --git a/save-points/9/src/workshop_app/db/in_mem.clj b/save-points/9/src/workshop_app/db/in_mem.clj
new file mode 100644
index 0000000..45ea1a2
--- /dev/null
+++ b/save-points/9/src/workshop_app/db/in_mem.clj
@@ -0,0 +1,33 @@
+(ns workshop-app.db.in-mem
+ (:refer-clojure :rename {update cc-update
+ read cc-read}))
+
+
+(def conn (atom {}))
+
+(defn create!
+ "Create an entry for k in our given in memory datastore."
+ [c k v]
+ (assert (some? k) "key cannot be nil.")
+ (swap! c assoc k v))
+
+
+(defn update!
+ "Update the entry for k to value v or add it if it doesn't exist in our in memory datastore."
+ [c k v]
+ (assert (some? k) "key cannot be nil.")
+ (swap! c assoc k v))
+
+
+(defn delete!
+ "Delete the entry for k in our in memory datastore."
+ [c k]
+ (assert (some? k) "key cannot be nil.")
+ (swap! c dissoc k))
+
+
+(defn read
+ "Read the entry for k in our in memory datastore."
+ [c k]
+ (assert (some? k) "key cannot be nil.")
+ (get @c k))
\ No newline at end of file
diff --git a/save-points/9/src/workshop_app/db/mongo.clj b/save-points/9/src/workshop_app/db/mongo.clj
new file mode 100644
index 0000000..3a879e6
--- /dev/null
+++ b/save-points/9/src/workshop_app/db/mongo.clj
@@ -0,0 +1,14 @@
+(ns workshop-app.db.mongo)
+
+
+(defonce r-db (atom {:foo :bar}))
+
+(defn fetch
+ [k]
+ (Thread/sleep (rand-nth [100 300]))
+ (clojure.core/get @r-db k))
+
+(defn set
+ [k v]
+ (Thread/sleep (rand-nth [ 100 300]))
+ (swap! r-db assoc k v))
diff --git a/save-points/9/src/workshop_app/db/redis.clj b/save-points/9/src/workshop_app/db/redis.clj
new file mode 100644
index 0000000..eddf7c2
--- /dev/null
+++ b/save-points/9/src/workshop_app/db/redis.clj
@@ -0,0 +1,13 @@
+(ns workshop-app.db.redis)
+
+(defonce r-db (atom {}))
+
+(defn fetch
+ [k]
+ (Thread/sleep (rand-nth [20 20 20 20 20 20 20 300]))
+ (clojure.core/get @r-db k))
+
+(defn set
+ [k v]
+ (Thread/sleep (rand-nth [20 20 20 20 20 20 20 300]))
+ (swap! r-db assoc k v))
diff --git a/save-points/9/src/workshop_app/db/sqlite.clj b/save-points/9/src/workshop_app/db/sqlite.clj
new file mode 100644
index 0000000..112429d
--- /dev/null
+++ b/save-points/9/src/workshop_app/db/sqlite.clj
@@ -0,0 +1,79 @@
+(ns workshop-app.db.sqlite
+ (:refer-clojure :rename {update cc-update
+ read cc-read})
+ (:import (java.sql DriverManager Connection PreparedStatement)))
+
+;; Connection conn = DriverManager.getConnection("jdbc:sqlite:prod_database_1.sqlite");
+(defn init-conn!
+ [conn-string]
+ (DriverManager/getConnection conn-string))
+
+(def conn (init-conn! "jdbc:sqlite:prod_database_1.sqlite"))
+
+;; Statement statement = conn.createStatement();
+;; statement.executeUpdate("create table if not exists person(name string primary key, dob string)");
+;; statement.close()
+(defn create-table
+ [c]
+ (with-open [statement (.createStatement c)]
+ (.executeUpdate statement "create table if not exists person(name string primary key, dob string)")))
+
+
+;; String k, v;
+;; Statement statement = conn.prepareStatement("insert into person values(?,?)");
+;; statement.setString(1, k);
+;; statement.setString(2, v);
+;; statement.executeUpdate();
+;; statement.close();
+(defn create!
+ [conn k v]
+ (let [statement (.prepareStatement ^Connection conn "insert into person(name, dob) values (?,?)")]
+ (.setString ^PreparedStatement statement 1 k)
+ (.setString ^PreparedStatement statement 2 v)
+ (.executeUpdate statement)
+ (.close statement)))
+
+;; String k, v;
+;; Statement statement = conn.prepareStatement("insert into person values(?,?)");
+;; statement.setString(1, k);
+;; statement.setString(2, v);
+;; statement.executeUpdate();
+;; statement.close();
+(defn update!
+ [conn k v]
+ (with-open [statement (doto (.prepareStatement ^Connection conn "update person set dob=? where name=?")
+ (.setString 2 k)
+ (.setString 1 v))]
+ (.executeUpdate statement)))
+
+
+;; String k;
+;; Statement statement = conn.prepareStatement("delete from person where name=?");
+;; statement.setString(1, k);
+;; statement.executeUpdate();
+;; statement.close();
+(defn delete!
+ [conn k]
+ (with-open [statement (.prepareStatement ^Connection conn "delete from person where name=?")]
+ (.setString ^PreparedStatement statement 1 k)
+ (.executeUpdate statement)))
+
+
+;; Statement statement = conn.prepareStatement("select name, dob from person where name=?");
+;; statement.setString(1, k);
+;; ResultSet rs = statement.executeQuery();
+;; Result result = rs.next();
+;; ResultSetMetadata rsm = result.getMetaData();
+;; int columnCount = rsm.getColumnCount();
+(defn read
+ [conn k]
+ (with-open [statement (doto (.prepareStatement ^Connection conn "select name, dob from person where name=?")
+ (.setString 1 k))]
+ (with-open [rs (.executeQuery statement)]
+ (when (.next rs)
+ (let [rs-meta (.getMetaData rs)
+ column-count (.getColumnCount rs-meta)]
+ (into {}
+ (map (fn [idx] [(.getColumnLabel rs-meta (int idx))
+ (.getObject rs (int idx))])
+ (range 1 (inc column-count)))))))))
\ No newline at end of file
diff --git a/save-points/9/src/workshop_app/handlers/concurrency.clj b/save-points/9/src/workshop_app/handlers/concurrency.clj
new file mode 100644
index 0000000..5b4d12e
--- /dev/null
+++ b/save-points/9/src/workshop_app/handlers/concurrency.clj
@@ -0,0 +1,448 @@
+(ns workshop-app.handlers.concurrency
+ (:require [workshop-app.db.redis :as redis]
+ [workshop-app.db.mongo :as mongo]))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; Concurrency
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; A way to get safe access to limited resources by multiple
+;; actors/threads
+
+;; There are multiple paradigms models to do concurrency
+
+;; Locks are one way of doing it
+
+;; Clojure does an amazing job where there is immutable data by means of
+;; persistent data structures provided by Clojure core
+
+;; But what about mutating things?
+
+;; There are four ways,
+
+;; vars
+;; refs
+;; agents
+;; atoms
+
+;; But first a brief introduction to running things in different threads
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; Future
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+;; This will run on a different thread
+(comment
+ (future 1)
+
+ ;; This will block and wait till it's complete
+ (deref (future 1))
+
+ ;; A short hand for deref
+ @(future 1)
+
+ (def f (future (println "Running thread")
+ 1))
+
+ ;; Guess the output of second deref
+ @f
+ @f)
+
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; Promise
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; Different futures can pass on values with futures as well.
+
+(comment
+ (def p (promise))
+
+ ;; This will block and execute
+ (future (Thread/sleep 5000) (deliver p 1))
+
+ @p)
+
+
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; vars
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+(def x 1)
+
+(comment
+ (def a 1)
+ (future (println a))
+ (future (println a)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; Atoms
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; Use it to represent Shared state
+
+;; But first
+;; Problems with shared state and why it's messy
+
+;; int b = 1
+;; new Thread(() -> b = 3
+;; Thread.sleep (new Random().nextInt(1000))
+;; System.out.println(b)).start();
+;; new Thread(() -> b = 4
+;; Thread.sleep (new Random().nextInt(1000))
+;; System.out.println(b)).start();
+
+;; https://aphyr.com/posts/306-clojure-from-the-ground-up-state
+
+;; A counterpart in Clojure
+
+
+(comment
+ (do
+ (def b [])
+
+ (doseq [n (range 2000)]
+ (future (def b (conj b
+ n))))
+ (println (count b))))
+
+
+;; Let's slow things down to understand
+
+(comment
+ (do
+ (def b [])
+
+ (doseq [n (range 2000)]
+ (future (def b (conj b
+ (do (when (= n 5)
+ (println (count b))
+ (Thread/sleep 100))
+ n)))))
+ (Thread/sleep 100)
+ (println (count b))))
+
+;; We need transformation of state with stronger gaurantees
+
+
+;; Variables mix State + Identitiy
+
+
+;; A symbol in clojure is just an identity which points to a value / state
+
+(def a 1)
+
+;; It can point to an atom
+
+(def a (atom 1))
+
+;; Since atom's value can change we need to deref it to access it
+
+(deref a)
+
+;; A short hand to deref is @
+
+@a
+
+;; @TODO add example of swap! and reset!
+
+;; Let's try this with an atom
+
+(comment
+ (do
+ (def b (atom []))
+ (doseq [n (range 2000)]
+ (future (swap! b conj n)))
+ (println (count @b))))
+
+(comment
+ (do
+ (def b (atom []))
+
+ (doseq [n (range 2000)]
+ (future (swap! b
+ conj
+ (do (when (= n 5)
+ (println (count @b))
+ (Thread/sleep 100))
+ n))))
+ (Thread/sleep 100)
+ (println (count @b))))
+
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; refs
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; Refs ensure safe mutation of multiple shared states using STM
+
+;; STM to me looks something like this
+
+;; https://vimeo.com/106226560#t=10s
+
+;; dosync is macro to start transaction
+
+;; ref-set sets it to a value
+
+;; alter runs a function on the value
+
+;; Values held by refs must be immutable preferably clojure persistent
+;; structures
+
+(def a-ref-num (ref 0))
+(def b-ref-num (ref 0))
+
+(comment
+ (doseq [n (range 10)]
+ (future
+ (dosync
+ (println "Transaction - " n)
+ (ref-set a-ref-num n)
+ (Thread/sleep (rand-int 20))
+ (ref-set b-ref-num n)))))
+
+[@a-ref-num @b-ref-num]
+
+
+(do
+ (def a-ref (ref 0))
+ (def b-ref (ref 0))
+ (def a-atom (atom 0))
+ (def b-atom (atom 0)))
+
+(comment
+ (do
+ (doseq [n (range 100)]
+ (future
+ (dosync
+ (ref-set a-ref n)
+ (Thread/sleep (rand-int 200))
+ (ref-set b-ref n))
+
+ (reset! a-atom n)
+ (Thread/sleep (rand-int 200))
+ (reset! b-atom n)))
+
+ (doseq [n (range 5)]
+ (Thread/sleep 1000)
+ (println (format "Ref values A - %s, B - %s\nAtom values A - %s, B - %s\n"
+ @a-ref
+ @b-ref
+ @a-atom
+ @b-atom)))))
+
+
+(def a-alter (ref []))
+(def b-alter (ref []))
+
+(comment
+ (doseq [n (range 10)]
+ (future
+ (dosync
+ (println "Transaction - " n)
+ (alter a-alter conj n)
+ (Thread/sleep (rand-int 20))
+ (alter b-alter conj n)))))
+
+[@a-alter @b-alter]
+
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; Thundering herd
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+
+;; Thundering herd
+
+;; When many readers simultaneously request the same data element, there
+;; can be a database read overload,
+;; sometimes called the “Thundering Herd” problem.
+;; https://www.ehcache.org/documentation/2.8/recipes/thunderingherd.html
+
+
+;; Read from cache if not present read from database and update cache
+
+(defn read+update-1
+ [k]
+ (if-let [v (redis/fetch :foo)]
+ [v :redis]
+ (do
+ (let [v (mongo/fetch k)]
+ (redis/set k v)
+ [v :mongo]))))
+
+(comment
+
+ (do
+ (reset! redis/r-db {})
+
+ (doall
+ (frequencies (map second (map (fn [_]
+ (read+update-1 :foo))
+ (range 10))))))
+
+
+
+ ;; For 1000 concurrent requests there is a possibility of requests
+ ;; going to MongoDB
+
+ (do
+ (reset! redis/r-db {})
+
+ (def t-h-1
+ (doall
+ (map (fn [_]
+ (future (read+update-1 :foo)))
+ (range 1000))))
+
+ ;; To analyze this data first we need to get the values from futures
+
+ (frequencies (map second (map #_FIXME t-h-1)))))
+
+
+
+;;; Let's introduce atom!
+
+(def ongoing-updates-a (#_FIXME #{}))
+
+(defn read+update-2
+ [k]
+ (if-let [v (redis/fetch :foo)]
+ [v :redis]
+ (let [update-ongoing? (#_FIXME ongoing-updates-a)]
+ (if-not (update-ongoing? k)
+ (do
+ (#_FIXME ongoing-updates-a conj k)
+ (let [v (mongo/fetch k)]
+ (redis/set k v)
+ (#_FIXME ongoing-updates-a disj k)
+ [v :mongo]))
+ [1 :default]))))
+
+(comment
+
+ (do
+ (reset! redis/r-db {})
+
+ (def t-h-1
+ (doall
+ (map (fn [_]
+ (future (read+update-2 :foo)))
+ (range 10000))))
+
+
+ (frequencies (map second (map deref t-h-1)))))
+
+
+;; Let's use ref instead
+
+(def ongoing-updates-ref (#_FIXME #{}))
+
+(defn read+update-3
+ [k]
+ (if-let [v (redis/fetch :foo)]
+ [v :redis]
+ (let [update-ongoing? (#_FIXME (when-not (get @ongoing-updates-ref k)
+ (#_FIXME ongoing-updates-ref conj k)))]
+ (if update-ongoing?
+ (let [v (mongo/fetch k)]
+ (redis/set k v)
+ (#_FIXME (#_FIXME ongoing-updates-ref disj k))
+ [v :mongo])
+ [1 :default]))))
+
+
+
+(comment
+
+ (do
+ (reset! redis/r-db {})
+
+ (def t-h-2
+ (doall
+ (map (fn [_]
+ (future (read+update-3 :foo)))
+ (range 10000))))
+
+
+ (frequencies (map second (map deref t-h-2)))))
+
+
+
+;; But how do we fix the nils?
+
+(def ongoing-updates-ref-p (ref #_FIXME))
+
+
+(defn read+update-4
+ [k]
+ (if-let [v (redis/fetch :foo)]
+ [v :redis]
+ (let [[action p] (dosync (if-let [p (get @ongoing-updates-ref-p k)]
+ [:wait (get @ongoing-updates-ref-p k)]
+ (let [p #_FIXME]
+ (alter ongoing-updates-ref-p #_FIXME k p)
+ [:update p])))]
+ (case action
+ :update (let [v (mongo/fetch k)]
+ (redis/set k v)
+ (dosync (alter ongoing-updates-ref-p #_FIXME k))
+ (#_FIXME p v)
+ [v :mongo])
+ :wait [(#_FIXME p) :redis]))))
+
+
+(defn read+update-4
+ [k]
+ (if-let [v (redis/fetch :foo)]
+ [v :redis]
+ (let [[action p] (dosync (if-let [p (get @ongoing-updates-ref-p k)]
+ [:wait (get @ongoing-updates-ref-p k)]
+ (let [p (promise)]
+ (alter ongoing-updates-ref-p assoc k p)
+ [:update p])))]
+ (case action
+ :update (let [v (mongo/fetch k)]
+ (redis/set k v)
+ (dosync (alter ongoing-updates-ref-p dissoc k))
+ (deliver p v)
+ [v :mongo])
+ :wait [(deref p) :redis]))))
+
+
+(comment
+
+ (do
+ (reset! redis/r-db {})
+
+ (def t-h-3
+ (doall
+ (map (fn [_]
+ (future (read+update-4 :foo)))
+ (range 10000))))
+
+
+ (frequencies (map second (map deref t-h-3)))))
diff --git a/save-points/9/src/workshop_app/handlers/lazy.clj b/save-points/9/src/workshop_app/handlers/lazy.clj
new file mode 100644
index 0000000..346241b
--- /dev/null
+++ b/save-points/9/src/workshop_app/handlers/lazy.clj
@@ -0,0 +1,141 @@
+(ns workshop-app.handlers.lazy)
+
+
+;; 1 - Infinity
+
+;; Simple example of range
+
+(comment
+ (range 10))
+
+;; What happens when range is unbounded
+
+;; DON'T EVALUATE THIS
+(comment
+ (range))
+
+
+(comment
+ (take 10 (range))
+
+ (take 10 (map inc (range)))
+
+ (take 10 (filter even? (map inc (range)))))
+
+
+;; 2 - Maths
+
+;; Let's calculate square roots by Newton-Raphson Square Roots method
+
+;; a - approximation
+;; n - Number
+;; a = (a + n/a)/2
+;; (/ (+ a (/ n a)) 2)
+
+
+;; (f a0) = a1
+;; (f a1) = a2
+;; (f a2) = a3
+;; (f a4) = a4
+
+;; The algorithm is recursive
+;; ((f (f (f a0) a1) a2)..)
+
+;; But it can also be thought of a sequence
+;; [a0, f a0, f (f a0), f (f (f a0)), . . . ]
+
+;; We haven't talked about epsilon yet.
+
+;; But if we look at it as lazy sequence
+
+(def epsilon 1)
+
+(defn sq-root*
+ [a x]
+ (let [a1 (/ (+ a (/ x a)) 2)]
+ (cons [a a1]
+ (lazy-seq (sq-root* a1
+ x)))))
+
+(defn sq-root
+ [x]
+ (let [a 1]
+ #_(??? (fn [[a1 a2]]
+ (when (> epsilon (Math/abs (float (- a1 a2))))
+ (int a1)))
+ (sq-root* a x))))
+
+
+;; Working with lazy sequences you basically deal with three things
+
+;; - Generation
+;; sq-root* generates a sequence (infinite) of approximations
+
+;; - Processing
+;; You may choose to process further on these sequences
+
+;; - Realisation
+;; You realise the result at this point defining how much
+;; input is required is also necessary
+
+
+
+;; 3 - Real world - Files
+
+;; Size of the data does not matter as long as you are reducing it into
+;; a small enough set
+(with-open [r (clojure.java.io/reader "resources/data/albumlist.csv")]
+ (let [l-xs (line-seq r)]
+ (comment (count (filter #(= (:artist %)
+ "The Beatles")
+ (map parse-line l-xs))))))
+
+
+;; 3 - Real world - Databases
+
+;; You can create lazy sequences of IO operations as long as there is no
+;; side effect and you are ok with lazy sequences caching the result
+
+(defn- scroll-seq
+ "Given a scroll-id, fetch scroll results"
+ [num]
+ (comment
+ (lazy-seq
+ (let [result (:body (http/get (format "_search/scroll?scroll=%s"
+ num)))]
+ (when (seq result)
+ (cons result
+ (scroll-seq (inc num))))))))
+
+;; 4 - Gotchas
+
+;; a. Open connections closing early
+
+
+;; Always use with-open when generating lazy seq from external sources and realise the whole sequence inside with-open
+
+(let [l-xs (with-open [r (clojure.java.io/reader "resources/data/albumlist.csv")]
+ (line-seq r))]
+ (count l-xs))
+
+;; Creating lazy sequences from a database can bring complexities if
+;; connections are not handled properly
+
+
+;; b. Chunking - Well almost lazy
+
+(comment (count (take 10 (map println (range 100)))))
+
+
+;; c. Caching
+
+(comment (def lz (doall (take 10 (map println (range 100)))))
+ (count lz))
+
+;; Some laziness in the wild
+
+;; https://github.com/Factual/durable-queue/blob/master/src/durable_queue.clj#L741
+
+;; https://github.com/s312569/clj-biosequence
+
+;; https://github.com/Genscape/gregor/blob/master/src/gregor/core.clj#L241
diff --git a/save-points/9/src/workshop_app/handlers/users.clj b/save-points/9/src/workshop_app/handlers/users.clj
new file mode 100644
index 0000000..3a404e0
--- /dev/null
+++ b/save-points/9/src/workshop_app/handlers/users.clj
@@ -0,0 +1,63 @@
+(ns workshop-app.handlers.users
+ (:require [workshop-app.db.sqlite :as wads]
+ [workshop-app.utils :as wau]
+ [cheshire.core :as json]))
+
+
+(defn get-handler
+ [{:keys [params] :as request}]
+ (def r* request)
+ (let [{:keys [name surname]} params]
+ (if (and name surname)
+ {:status 200
+ :body (str "Hello, "
+ name
+ " "
+ surname
+ "!!!")}
+ {:status 400
+ :body "Missing name and surname."})))
+
+
+(defn add-person
+ [{:keys [name dob]}]
+ (if (and name dob)
+ (do (wads/create! wads/conn
+ name
+ dob)
+ {:status 201
+ :body "Created user."})
+ {:status 400
+ :body "User name or date of birth missing."}))
+
+
+(defn get-person
+ [dob now]
+ {:status 200
+ :headers {"content-type" "application/json"}
+ :body (when dob
+ (json/generate-string {:dob dob
+ :age (wau/years-between (wau/parse-dt-str dob)
+ now)}))})
+
+
+(defn update-person
+ [{:keys [name dob]}]
+ (if (and name dob)
+ (do (wads/update! wads/conn
+ name
+ dob)
+ {:status 200
+ :body "Updated user."})
+ {:status 400
+ :body "User name or dob is missing."}))
+
+
+(defn delete-person
+ [name]
+ (if name
+ (do (wads/delete! wads/conn name)
+ {:status 200
+ :body "Deleted user."})
+ {:status 400
+ :body "User name missing, cannot delete person without username."}))
\ No newline at end of file
diff --git a/save-points/9/src/workshop_app/interop.clj b/save-points/9/src/workshop_app/interop.clj
new file mode 100644
index 0000000..3a65ff1
--- /dev/null
+++ b/save-points/9/src/workshop_app/interop.clj
@@ -0,0 +1,130 @@
+(ns workshop-app.interop
+ (:refer-clojure :rename {get cc-get})
+ (:import (java.util Calendar Comparator ArrayList HashMap AbstractMap$SimpleEntry)
+ (java.time.temporal ChronoUnit)
+ (java.time LocalDate)))
+
+;; class access
+;; HashMap.class
+HashMap ;; a class
+
+;; new HashMap();
+(HashMap.) ;; an object created from the class
+
+;; HashMap hm = new HashMap();
+(def hm (HashMap.)) ;; bind the object to a var. More on var's later.
+
+;; hm.put("a", 10);
+(.put hm "a" 10)
+
+;; hm.put("b", 10);
+(.put ^HashMap hm "b" 10) ;; the first gives us a warning of how it cannot be resolved.
+
+;; Member access
+;; method access
+;; hm.get("a")
+(.get hm "a")
+
+;; hm.get("b")
+(.get hm "b")
+
+;; "joel".toUpperCase();
+(.toUpperCase "joel")
+
+;; field access
+;; new Point(10,20).x;
+(.-x (java.awt.Point. 10 20))
+
+;; static variables or methods access
+;; Calendar.ERA
+Calendar/ERA
+
+;; Math.PI
+Math/PI
+
+;; System.getProperties()
+(System/getProperties)
+
+;; dot special form
+;; hm.get("a");
+(. hm get "a")
+
+;; "joel".toUpperCase();
+(. "joel" toUpperCase)
+
+;; System.getProperties().get("os.name")
+(. (. System (getProperties)) (get "os.name"))
+
+;; double dot
+(.. System getProperties (get "os.name"))
+
+;; doto
+;; HashMap dotoHm = new HashMap();
+;; dotoHm.put("a", 10);
+;; dotoHm.put("b", 20);
+(def doto-hm (doto (HashMap.)
+ (.put "a" 10)
+ (.put "b" 20)))
+
+;; new
+;; HashMap newSyntaxHm = new HashMap();
+(def new-syntax-hm (new HashMap))
+
+;; accessing inner classes
+;; AbstractMap.SimpleEntry("a", "b")
+(AbstractMap$SimpleEntry. "a" "b")
+
+
+;; reify example. Sort an array list of array list using
+;; a custom comparator.
+;; List al = new ArrayList();
+;; List alChild1 = new ArrayList();
+;; alChild1.add(1);
+;; alChild1.add(2);
+;; List alChild2 = new ArrayList();
+;; alChild2.add(1);
+;; alChild2.add(2);
+;; List alChild3 = new ArrayList();
+;; alChild3.add(1);
+;; alChild3.add(2);
+;; List alChild1 = new ArrayList();
+;; alChild1.add(1);
+;; alChild1.add(2);
+;; al.add(alChild1);
+;; al.add(alChild2);
+;; al.add(alChild3);
+;; al.sort(new Compartor {
+;; @Override
+;; public int compare(ArrayList al1, ArrayList al2) {
+;; return al1.elementAt(0) - al2.elementAt(0);
+;; }
+;; })
+(let [l (doto (ArrayList.)
+ (.add (doto (ArrayList.)
+ (.add 1)
+ (.add 2)))
+ (.add (doto (ArrayList.)
+ (.add 2)
+ (.add 3)))
+ (.add (doto (ArrayList.)
+ (.add 3)
+ (.add 4))))]
+ ;; this modifies the list.
+ (.sort l
+ (reify Comparator
+ (compare [_ al1 al2]
+ (- (.get ^ArrayList al2 (int 0))
+ (.get ^ArrayList al1 (int 0))))))
+ l)
+
+
+;; a small interop task. of finding the days between today and first January
+;; following is a sample java code that you should translate to clojure interop code.
+;; LocalDate dateOne = LocalDate.of(2020,1,1);
+;; LocalDate dateTwo = LocalDate.now();
+;; long daysBetween = ChronoUnits.DAYS.between(dateOne, dateTwo);
+(let [date-one (LocalDate/of 2020 1 1)
+ date-two (LocalDate/now)]
+ (.between ChronoUnit/DAYS
+ date-one
+ date-two))
\ No newline at end of file
diff --git a/save-points/9/src/workshop_app/middlewares/users.clj b/save-points/9/src/workshop_app/middlewares/users.clj
new file mode 100644
index 0000000..399df82
--- /dev/null
+++ b/save-points/9/src/workshop_app/middlewares/users.clj
@@ -0,0 +1,22 @@
+(ns workshop-app.middlewares.users
+ (:require [clojure.string :as s]))
+
+
+(defn reject-uri-ending-with-slash
+ [handler]
+ (fn [{:keys [uri] :as request}]
+ (if (and (not= uri "/")
+ (s/ends-with? uri "/"))
+ {:status 400
+ :body "Bad request."}
+ (handler request))))
+
+
+(defn handle-any-exception
+ [handler]
+ (fn [request]
+ (try (handler request)
+ (catch Exception e
+ (.printStackTrace e)
+ {:status 500
+ :body "Internal server error."}))))
\ No newline at end of file
diff --git a/save-points/9/src/workshop_app/namespaces.clj b/save-points/9/src/workshop_app/namespaces.clj
new file mode 100644
index 0000000..eece9fa
--- /dev/null
+++ b/save-points/9/src/workshop_app/namespaces.clj
@@ -0,0 +1,11 @@
+(ns workshop-app.namespaces)
+
+;; to create a namespace
+#_(ns test-namespace)
+
+;; to switch to a namespace
+#_(in-ns 'test-namespace)
+
+#_*ns*
+
+#_(def a)
\ No newline at end of file
diff --git a/save-points/9/src/workshop_app/routes.clj b/save-points/9/src/workshop_app/routes.clj
new file mode 100644
index 0000000..2a5547a
--- /dev/null
+++ b/save-points/9/src/workshop_app/routes.clj
@@ -0,0 +1,15 @@
+(ns workshop-app.routes
+ (:require [workshop-app.handlers.users :as wahu]
+ [workshop-app.db.sqlite :as wads]
+ [compojure.core :refer [defroutes GET POST PUT DELETE ANY]])
+ (:import (java.time LocalDate)))
+
+
+(defroutes app-routes
+ (GET "/" _ wahu/get-handler)
+ (POST "/:name" {:keys [params]} (wahu/add-person params))
+ (GET "/:name" [name] (wahu/get-person (get (wads/read wads/conn name) "dob")
+ (LocalDate/now)))
+ (PUT "/:name" {:keys [params]} (wahu/update-person params))
+ (DELETE "/:name" [name] (wahu/delete-person name))
+ (ANY "*" _ {:status 404}))
\ No newline at end of file
diff --git a/save-points/9/src/workshop_app/utils.clj b/save-points/9/src/workshop_app/utils.clj
new file mode 100644
index 0000000..2761e2f
--- /dev/null
+++ b/save-points/9/src/workshop_app/utils.clj
@@ -0,0 +1,21 @@
+(ns workshop-app.utils
+ (:import (java.time LocalDate)
+ (java.time.temporal ChronoUnit)))
+
+
+(defn parse-dt-str
+ [dt-str]
+ (when (seq dt-str)
+ (LocalDate/parse dt-str)))
+
+
+(defn dt-after?
+ [d1 d2]
+ (.isAfter d1 d2))
+
+
+(defn years-between
+ [d1 d2]
+ (if (dt-after? d2 d1)
+ (.between ChronoUnit/YEARS d1 d2)
+ 0))
\ No newline at end of file
diff --git a/save-points/9/src/workshop_app/vars.clj b/save-points/9/src/workshop_app/vars.clj
new file mode 100644
index 0000000..1a255bf
--- /dev/null
+++ b/save-points/9/src/workshop_app/vars.clj
@@ -0,0 +1,22 @@
+(ns workshop-app.vars)
+
+;; this is a symbol
+'a
+
+;; to create a var we need to simply do
+(def a)
+
+#_(+ a 10)
+
+#_(alter-var-root #'a (constantly 20))
+
+#_(println a)
+
+#_(println (var-get #'a))
+
+(def ^:dynamic b)
+
+#_(+ b 20)
+
+(binding [b 10]
+ (println (+ b 10)))
\ No newline at end of file
diff --git a/save-points/9/test/workshop_app/handlers/users_pure_test.clj b/save-points/9/test/workshop_app/handlers/users_pure_test.clj
new file mode 100644
index 0000000..34f7cb7
--- /dev/null
+++ b/save-points/9/test/workshop_app/handlers/users_pure_test.clj
@@ -0,0 +1,12 @@
+(ns workshop-app.handlers.users-pure-test
+ (:require [clojure.test :refer :all]
+ [workshop-app.handlers.users :as wahu])
+ (:import (java.time LocalDate)))
+
+
+(deftest pure-get-person-test
+ (is (= {:status 200
+ :headers {"content-type" "application/json"}
+ :body "{\"dob\":\"2000-01-01\",\"age\":20}"}
+ (wahu/get-person "2000-01-01" (LocalDate/parse "2020-02-14")))
+ "Is our pure get handler working as expected."))
\ No newline at end of file
diff --git a/save-points/9/test/workshop_app/handlers/users_test.clj b/save-points/9/test/workshop_app/handlers/users_test.clj
new file mode 100644
index 0000000..29fe783
--- /dev/null
+++ b/save-points/9/test/workshop_app/handlers/users_test.clj
@@ -0,0 +1,38 @@
+(ns workshop-app.handlers.users-test
+ (:require [clojure.test :refer :all]
+ [workshop-app.handlers.users :as wahu]
+ [workshop-app.db.sqlite :as wads]))
+
+
+;; Pattern 1
+;; Using fixtures to actually bootstrap and teardown.
+;; Scenario: I want to test against a actual instance
+;; but I want to test against a different database.
+(use-fixtures :each (fn [t]
+ (let [conn (wads/init-conn! "jdbc:sqlite::memory:")]
+ (with-redefs [wads/conn conn]
+ (wads/create-table conn)
+ (t)))))
+
+
+(deftest add-person-test
+ (is (= {:status 201
+ :body "Created user."}
+ (wahu/add-person {:name "Joel Victor"
+ :dob "2001-01-01"}))
+ "Is the creation handler working as expected."))
+
+
+(deftest update-person-test
+ (is (= {:status 200
+ :body "Updated user."}
+ (wahu/update-person {:name "Joel Victor"
+ :dob "2000-01-01"}))
+ "Is the update handler working as expected."))
+
+
+(deftest delete-person-test
+ (is (= {:status 200
+ :body "Deleted user."}
+ (wahu/delete-person "Joel Victor"))
+ "Is the deletion handler working as expected."))
\ No newline at end of file
diff --git a/save-points/9/test/workshop_app/handlers/users_test_2.clj b/save-points/9/test/workshop_app/handlers/users_test_2.clj
new file mode 100644
index 0000000..9f1a6fe
--- /dev/null
+++ b/save-points/9/test/workshop_app/handlers/users_test_2.clj
@@ -0,0 +1,27 @@
+(ns workshop-app.handlers.users-test-2
+ (:require [clojure.test :refer :all]
+ [workshop-app.handlers.users :as wahu]
+ [workshop-app.db.sqlite :as wads]
+ [workshop-app.db.in-mem :as wadim]))
+
+;; pattern 2. don't create the connection but instead redefine
+;; it to an inmemory implementation.
+;; these are useful when you communicate to a database over
+;; a network
+(use-fixtures :each (fn [t]
+ (with-redefs [wads/conn wadim/conn
+ wads/create! wadim/create!
+ wads/update! wadim/update!
+ wads/read (fn [conn k] {"dob" (wadim/read conn k)})
+ wads/delete! wadim/delete!]
+ (t))))
+
+
+(deftest all-handlers-test
+ (testing "Testing all handlers in one go."
+ (are [expected-response actual-response] (= expected-response actual-response)
+ {:status 201 :body "Created user."} (wahu/add-person {:name "Joel Victor"
+ :dob "2001-01-01"})
+ {:status 200 :body "Updated user."} (wahu/update-person {:name "Joel Victor"
+ :dob "2000-01-01"})
+ {:status 200 :body "Deleted user."} (wahu/delete-person "Joel Victor"))))
\ No newline at end of file
diff --git a/save-points/9/test/workshop_app/intro_to_testing.clj b/save-points/9/test/workshop_app/intro_to_testing.clj
new file mode 100644
index 0000000..591d0fb
--- /dev/null
+++ b/save-points/9/test/workshop_app/intro_to_testing.clj
@@ -0,0 +1,20 @@
+(ns workshop-app.intro-to-testing
+ (:require [clojure.test :refer :all]))
+
+
+;; Defining a test
+(deftest test-name-1
+ (is (= 1 1)
+ "Optional message for assertion")
+
+ (are [x y] (= x y)
+ 2 (+ 1 1)
+ 3 (+ 1 2)))
+
+
+(deftest test-name-2
+ (testing "Msg to add context to all the test run inside this"
+ (is (= 1 2)
+ "One should not equal 2."))
+
+ (is (thrown? RuntimeException ((fn [] (throw (RuntimeException.)))))))
\ No newline at end of file
diff --git a/save-points/9/test/workshop_app/middlewares/users_test.clj b/save-points/9/test/workshop_app/middlewares/users_test.clj
new file mode 100644
index 0000000..675af60
--- /dev/null
+++ b/save-points/9/test/workshop_app/middlewares/users_test.clj
@@ -0,0 +1,25 @@
+(ns workshop-app.middlewares.users-test
+ (:require [clojure.test :refer :all]
+ [workshop-app.middlewares.users :as wamu]))
+
+
+(deftest pure-reject-uri-ending-with-slash-middleware-test
+ (testing "Middleware should reject all uri's ending with /"
+ (are [expected actual] (= expected actual)
+ {:uri "/"} ((wamu/reject-uri-ending-with-slash identity)
+ {:uri "/"})
+ {:status 400 :body "Bad request."} ((wamu/reject-uri-ending-with-slash identity)
+ {:uri "/joel/"})
+ {:uri "/joel"} ((wamu/reject-uri-ending-with-slash identity)
+ {:uri "/joel"}))))
+
+
+;; by using higher order functions you can also test behavior without
+;; mutating any code.
+(deftest pure-handle-any-exception
+ (testing "Middleware should reject all uri's ending with /"
+ (are [expected actual] (= expected actual)
+ {:status 500 :body "Internal server error."} ((wamu/handle-any-exception (fn [_] (throw (Exception.))))
+ {})
+ {} ((wamu/handle-any-exception identity)
+ {}))))
\ No newline at end of file
diff --git a/save-points/9/test/workshop_app/property_based_test.clj b/save-points/9/test/workshop_app/property_based_test.clj
new file mode 100644
index 0000000..1007bd7
--- /dev/null
+++ b/save-points/9/test/workshop_app/property_based_test.clj
@@ -0,0 +1,36 @@
+(ns workshop-app.property-based-test
+ (:require [clojure.test :refer :all]
+ [clojure.test.check :as tc]
+ [clojure.test.check.clojure-test :as tcct]
+ [clojure.test.check.generators :as ctcg]
+ [clojure.test.check.properties :as ctcp]
+ [workshop-app.utils :as wau])
+ (:import (java.time LocalDate)))
+
+
+(def date-tuple-generator
+ (ctcg/let [month (ctcg/choose 1 12)]
+ (case month
+ 2 (ctcg/tuple (ctcg/choose 1900 2021) (ctcg/return month) (ctcg/choose 1 28))
+ (1 3 5 7 8 10 12) (ctcg/tuple (ctcg/choose 1900 2021) (ctcg/return month) (ctcg/choose 1 31))
+ (4 6 9 11) (ctcg/tuple (ctcg/choose 1900 2021) (ctcg/return month) (ctcg/choose 1 30)))))
+
+(def date-object-generator
+ (ctcg/fmap (fn [[y m d]]
+ (LocalDate/of y m d))
+ date-tuple-generator))
+
+(def age-property-fn (some-fn pos? zero?))
+
+(def age-property
+ (ctcp/for-all [st-dt date-object-generator
+ end-dt date-object-generator]
+ (age-property-fn (wau/years-between st-dt end-dt))))
+
+#_(tc/quick-check 1000 age-property)
+
+
+(tcct/defspec age-property-2 100
+ (ctcp/for-all [st-dt date-object-generator
+ end-dt date-object-generator]
+ (age-property-fn (wau/years-between st-dt end-dt))))
\ No newline at end of file
diff --git a/save-points/final/src/workshop_app/clj_to_java_interop.clj b/save-points/final/src/workshop_app/clj_to_java_interop.clj
new file mode 100644
index 0000000..fc2c2d6
--- /dev/null
+++ b/save-points/final/src/workshop_app/clj_to_java_interop.clj
@@ -0,0 +1,23 @@
+(ns workshop-app.clj-to-java-interop)
+
+(gen-class :name org.inclojure.Demo
+ :init init
+ :prefix "-"
+ :state "state"
+ :methods [[getName [] String]
+ [setName [String] void]])
+
+
+(defn -init []
+ "State a hash map."
+ [[] (atom {})])
+
+
+(defn -getName
+ [this]
+ (:name @(.state this)))
+
+
+(defn -setName
+ [this name]
+ (swap! (.state this) assoc :name name))
\ No newline at end of file
diff --git a/save-points/final/src/workshop_app/core.clj b/save-points/final/src/workshop_app/core.clj
new file mode 100644
index 0000000..de470e4
--- /dev/null
+++ b/save-points/final/src/workshop_app/core.clj
@@ -0,0 +1,20 @@
+(ns workshop-app.core
+ (:gen-class)
+ (:require [ring.adapter.jetty :as raj]
+ [ring.middleware.params :as rmp]
+ [ring.middleware.keyword-params :as rmkp]
+ [workshop-app.routes :as war]
+ [workshop-app.middlewares.users :as wamu]
+ [workshop-app.db.sqlite :as wads]))
+
+
+(defn -main
+ [& _]
+ (wads/create-table wads/conn)
+ (raj/run-jetty (-> war/app-routes
+ rmkp/wrap-keyword-params
+ rmp/wrap-params
+ wamu/handle-any-exception
+ wamu/reject-uri-ending-with-slash)
+ {:port 65535
+ :join? false}))
\ No newline at end of file
diff --git a/save-points/final/src/workshop_app/db/in_mem.clj b/save-points/final/src/workshop_app/db/in_mem.clj
new file mode 100644
index 0000000..45ea1a2
--- /dev/null
+++ b/save-points/final/src/workshop_app/db/in_mem.clj
@@ -0,0 +1,33 @@
+(ns workshop-app.db.in-mem
+ (:refer-clojure :rename {update cc-update
+ read cc-read}))
+
+
+(def conn (atom {}))
+
+(defn create!
+ "Create an entry for k in our given in memory datastore."
+ [c k v]
+ (assert (some? k) "key cannot be nil.")
+ (swap! c assoc k v))
+
+
+(defn update!
+ "Update the entry for k to value v or add it if it doesn't exist in our in memory datastore."
+ [c k v]
+ (assert (some? k) "key cannot be nil.")
+ (swap! c assoc k v))
+
+
+(defn delete!
+ "Delete the entry for k in our in memory datastore."
+ [c k]
+ (assert (some? k) "key cannot be nil.")
+ (swap! c dissoc k))
+
+
+(defn read
+ "Read the entry for k in our in memory datastore."
+ [c k]
+ (assert (some? k) "key cannot be nil.")
+ (get @c k))
\ No newline at end of file
diff --git a/save-points/final/src/workshop_app/db/mongo.clj b/save-points/final/src/workshop_app/db/mongo.clj
new file mode 100644
index 0000000..3a879e6
--- /dev/null
+++ b/save-points/final/src/workshop_app/db/mongo.clj
@@ -0,0 +1,14 @@
+(ns workshop-app.db.mongo)
+
+
+(defonce r-db (atom {:foo :bar}))
+
+(defn fetch
+ [k]
+ (Thread/sleep (rand-nth [100 300]))
+ (clojure.core/get @r-db k))
+
+(defn set
+ [k v]
+ (Thread/sleep (rand-nth [ 100 300]))
+ (swap! r-db assoc k v))
diff --git a/save-points/final/src/workshop_app/db/redis.clj b/save-points/final/src/workshop_app/db/redis.clj
new file mode 100644
index 0000000..eddf7c2
--- /dev/null
+++ b/save-points/final/src/workshop_app/db/redis.clj
@@ -0,0 +1,13 @@
+(ns workshop-app.db.redis)
+
+(defonce r-db (atom {}))
+
+(defn fetch
+ [k]
+ (Thread/sleep (rand-nth [20 20 20 20 20 20 20 300]))
+ (clojure.core/get @r-db k))
+
+(defn set
+ [k v]
+ (Thread/sleep (rand-nth [20 20 20 20 20 20 20 300]))
+ (swap! r-db assoc k v))
diff --git a/save-points/final/src/workshop_app/db/sqlite.clj b/save-points/final/src/workshop_app/db/sqlite.clj
new file mode 100644
index 0000000..112429d
--- /dev/null
+++ b/save-points/final/src/workshop_app/db/sqlite.clj
@@ -0,0 +1,79 @@
+(ns workshop-app.db.sqlite
+ (:refer-clojure :rename {update cc-update
+ read cc-read})
+ (:import (java.sql DriverManager Connection PreparedStatement)))
+
+;; Connection conn = DriverManager.getConnection("jdbc:sqlite:prod_database_1.sqlite");
+(defn init-conn!
+ [conn-string]
+ (DriverManager/getConnection conn-string))
+
+(def conn (init-conn! "jdbc:sqlite:prod_database_1.sqlite"))
+
+;; Statement statement = conn.createStatement();
+;; statement.executeUpdate("create table if not exists person(name string primary key, dob string)");
+;; statement.close()
+(defn create-table
+ [c]
+ (with-open [statement (.createStatement c)]
+ (.executeUpdate statement "create table if not exists person(name string primary key, dob string)")))
+
+
+;; String k, v;
+;; Statement statement = conn.prepareStatement("insert into person values(?,?)");
+;; statement.setString(1, k);
+;; statement.setString(2, v);
+;; statement.executeUpdate();
+;; statement.close();
+(defn create!
+ [conn k v]
+ (let [statement (.prepareStatement ^Connection conn "insert into person(name, dob) values (?,?)")]
+ (.setString ^PreparedStatement statement 1 k)
+ (.setString ^PreparedStatement statement 2 v)
+ (.executeUpdate statement)
+ (.close statement)))
+
+;; String k, v;
+;; Statement statement = conn.prepareStatement("insert into person values(?,?)");
+;; statement.setString(1, k);
+;; statement.setString(2, v);
+;; statement.executeUpdate();
+;; statement.close();
+(defn update!
+ [conn k v]
+ (with-open [statement (doto (.prepareStatement ^Connection conn "update person set dob=? where name=?")
+ (.setString 2 k)
+ (.setString 1 v))]
+ (.executeUpdate statement)))
+
+
+;; String k;
+;; Statement statement = conn.prepareStatement("delete from person where name=?");
+;; statement.setString(1, k);
+;; statement.executeUpdate();
+;; statement.close();
+(defn delete!
+ [conn k]
+ (with-open [statement (.prepareStatement ^Connection conn "delete from person where name=?")]
+ (.setString ^PreparedStatement statement 1 k)
+ (.executeUpdate statement)))
+
+
+;; Statement statement = conn.prepareStatement("select name, dob from person where name=?");
+;; statement.setString(1, k);
+;; ResultSet rs = statement.executeQuery();
+;; Result result = rs.next();
+;; ResultSetMetadata rsm = result.getMetaData();
+;; int columnCount = rsm.getColumnCount();
+(defn read
+ [conn k]
+ (with-open [statement (doto (.prepareStatement ^Connection conn "select name, dob from person where name=?")
+ (.setString 1 k))]
+ (with-open [rs (.executeQuery statement)]
+ (when (.next rs)
+ (let [rs-meta (.getMetaData rs)
+ column-count (.getColumnCount rs-meta)]
+ (into {}
+ (map (fn [idx] [(.getColumnLabel rs-meta (int idx))
+ (.getObject rs (int idx))])
+ (range 1 (inc column-count)))))))))
\ No newline at end of file
diff --git a/save-points/final/src/workshop_app/handlers/concurrency.clj b/save-points/final/src/workshop_app/handlers/concurrency.clj
new file mode 100644
index 0000000..d61c384
--- /dev/null
+++ b/save-points/final/src/workshop_app/handlers/concurrency.clj
@@ -0,0 +1,430 @@
+(ns workshop-app.handlers.concurrency
+ (:require [workshop-app.db.redis :as redis]
+ [workshop-app.db.mongo :as mongo]))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; Concurrency
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; A way to get safe access to limited resources by multiple
+;; actors/threads
+
+;; There are multiple paradigms models to do concurrency
+
+;; Locks are one way of doing it
+
+;; Clojure does an amazing job where there is immutable data by means of
+;; persistent data structures provided by Clojure core
+
+;; But what about mutating things?
+
+;; There are four ways,
+
+;; vars
+;; refs
+;; agents
+;; atoms
+
+;; But first a brief introduction to running things in different threads
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; Future
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+;; This will run on a different thread
+(comment
+ (future 1)
+
+ ;; This will block and wait till it's complete
+ (deref (future 1))
+
+ ;; A short hand for deref
+ @(future 1)
+
+ (def f (future (println "Running thread")
+ 1))
+
+ ;; Guess the output of second deref
+ @f
+ @f)
+
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; Promise
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; Different futures can pass on values with futures as well.
+
+(comment
+ (def p (promise))
+
+ ;; This will block and execute
+ (future (Thread/sleep 5000) (deliver p 1))
+
+ @p)
+
+
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; vars
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+(def x 1)
+
+(comment
+ (def a 1)
+ (future (println a))
+ (future (println a)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; Atoms
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; Use it to represent Shared state
+
+;; But first
+;; Problems with shared state and why it's messy
+
+;; int b = 1
+;; new Thread(() -> b = 3
+;; Thread.sleep (new Random().nextInt(1000))
+;; System.out.println(b)).start();
+;; new Thread(() -> b = 4
+;; Thread.sleep (new Random().nextInt(1000))
+;; System.out.println(b)).start();
+
+;; https://aphyr.com/posts/306-clojure-from-the-ground-up-state
+
+;; A counterpart in Clojure
+
+
+(comment
+ (do
+ (def b [])
+
+ (doseq [n (range 2000)]
+ (future (def b (conj b
+ n))))
+ (println (count b))))
+
+
+;; Let's slow things down to understand
+
+(comment
+ (do
+ (def b [])
+
+ (doseq [n (range 2000)]
+ (future (def b (conj b
+ (do (when (= n 5)
+ (println (count b))
+ (Thread/sleep 100))
+ n)))))
+ (Thread/sleep 100)
+ (println (count b))))
+
+;; We need transformation of state with stronger gaurantees
+
+
+;; Variables mix State + Identitiy
+
+
+;; A symbol in clojure is just an identity which points to a value / state
+
+(def a 1)
+
+;; It can point to an atom
+
+(def a (atom 1))
+
+;; Since atom's value can change we need to deref it to access it
+
+(deref a)
+
+;; A short hand to deref is @
+
+@a
+
+;; @TODO add example of swap! and reset!
+
+;; Let's try this with an atom
+
+(comment
+ (do
+ (def b (atom []))
+ (doseq [n (range 2000)]
+ (future (swap! b conj n)))
+ (println (count @b))))
+
+(comment
+ (do
+ (def b (atom []))
+
+ (doseq [n (range 2000)]
+ (future (swap! b
+ conj
+ (do (when (= n 5)
+ (println (count @b))
+ (Thread/sleep 100))
+ n))))
+ (Thread/sleep 100)
+ (println (count @b))))
+
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; refs
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; Refs ensure safe mutation of multiple shared states using STM
+
+;; STM to me looks something like this
+
+;; https://vimeo.com/106226560#t=10s
+
+;; dosync is macro to start transaction
+
+;; ref-set sets it to a value
+
+;; alter runs a function on the value
+
+;; Values held by refs must be immutable preferably clojure persistent
+;; structures
+
+(def a-ref-num (ref 0))
+(def b-ref-num (ref 0))
+
+(comment
+ (doseq [n (range 10)]
+ (future
+ (dosync
+ (println "Transaction - " n)
+ (ref-set a-ref-num n)
+ (Thread/sleep (rand-int 20))
+ (ref-set b-ref-num n)))))
+
+[@a-ref-num @b-ref-num]
+
+
+(do
+ (def a-ref (ref 0))
+ (def b-ref (ref 0))
+ (def a-atom (atom 0))
+ (def b-atom (atom 0)))
+
+(comment
+ (do
+ (doseq [n (range 100)]
+ (future
+ (dosync
+ (ref-set a-ref n)
+ (Thread/sleep (rand-int 200))
+ (ref-set b-ref n))
+
+ (reset! a-atom n)
+ (Thread/sleep (rand-int 200))
+ (reset! b-atom n)))
+
+ (doseq [n (range 5)]
+ (Thread/sleep 1000)
+ (println (format "Ref values A - %s, B - %s\nAtom values A - %s, B - %s\n"
+ @a-ref
+ @b-ref
+ @a-atom
+ @b-atom)))))
+
+
+(def a-alter (ref []))
+(def b-alter (ref []))
+
+(comment
+ (doseq [n (range 10)]
+ (future
+ (dosync
+ (println "Transaction - " n)
+ (alter a-alter conj n)
+ (Thread/sleep (rand-int 20))
+ (alter b-alter conj n)))))
+
+[@a-alter @b-alter]
+
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; Thundering herd
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+
+;; Thundering herd
+
+;; When many readers simultaneously request the same data element, there
+;; can be a database read overload,
+;; sometimes called the “Thundering Herd” problem.
+;; https://www.ehcache.org/documentation/2.8/recipes/thunderingherd.html
+
+
+;; Read from cache if not present read from database and update cache
+
+(defn read+update-1
+ [k]
+ (if-let [v (redis/fetch :foo)]
+ [v :redis]
+ (do
+ (let [v (mongo/fetch k)]
+ (redis/set k v)
+ [v :mongo]))))
+
+(comment
+
+ (do
+ (reset! redis/r-db {})
+
+ (doall
+ (frequencies (map second (map (fn [_]
+ (read+update-1 :foo))
+ (range 10))))))
+
+
+
+ ;; For 1000 concurrent requests there is a possibility of requests
+ ;; going to MongoDB
+
+ (do
+ (reset! redis/r-db {})
+
+ (def t-h-1
+ (doall
+ (map (fn [_]
+ (future (read+update-1 :foo)))
+ (range 1000))))
+
+ ;; To analyze this data first we need to get the values from futures
+
+ (frequencies (map second (map deref t-h-1)))))
+
+
+
+;;; Let's introduce atom!
+
+(def ongoing-updates-a (atom #{}))
+
+(defn read+update-2
+ [k]
+ (if-let [v (redis/fetch :foo)]
+ [v :redis]
+ (let [update-ongoing? (deref ongoing-updates-a)]
+ (if-not (update-ongoing? k)
+ (do
+ (swap! ongoing-updates-a conj k)
+ (let [v (mongo/fetch k)]
+ (redis/set k v)
+ (swap! ongoing-updates-a disj k)
+ [v :mongo]))
+ [1 :default]))))
+
+(comment
+
+ (do
+ (reset! redis/r-db {})
+
+ (def t-h-1
+ (doall
+ (map (fn [_]
+ (future (read+update-2 :foo)))
+ (range 10000))))
+
+
+ (frequencies (map second (map deref t-h-1)))))
+
+
+;; Let's use ref instead
+
+(def ongoing-updates-ref (ref #{}))
+
+(defn read+update-3
+ [k]
+ (if-let [v (redis/fetch :foo)]
+ [v :redis]
+ (let [update-ongoing? (dosync (when-not (get @ongoing-updates-ref k)
+ (alter ongoing-updates-ref conj k)))]
+ (if update-ongoing?
+ (let [v (mongo/fetch k)]
+ (redis/set k v)
+ (dosync (alter ongoing-updates-ref disj k))
+ [v :mongo])
+ [1 :default]))))
+
+
+
+(comment
+
+ (do
+ (reset! redis/r-db {})
+
+ (def t-h-2
+ (doall
+ (map (fn [_]
+ (future (read+update-3 :foo)))
+ (range 10000))))
+
+
+ (frequencies (map second (map deref t-h-2)))))
+
+
+
+;; But how do we fix the nils?
+
+(def ongoing-updates-ref-p (ref {}))
+
+
+(defn read+update-4
+ [k]
+ (if-let [v (redis/fetch :foo)]
+ [v :redis]
+ (let [[action p] (dosync (if-let [p (get @ongoing-updates-ref-p k)]
+ [:wait p]
+ (let [p (promise)]
+ (alter ongoing-updates-ref-p assoc k p)
+ [:update p])))]
+ (case action
+ :update (let [v (mongo/fetch k)]
+ (redis/set k v)
+ (dosync (alter ongoing-updates-ref-p dissoc k))
+ (deliver p v)
+ [v :mongo])
+ :wait [(deref p) :redis]))))
+
+
+(comment
+
+ (do
+ (reset! redis/r-db {})
+
+ (def t-h-3
+ (doall
+ (map (fn [_]
+ (future (read+update-4 :foo)))
+ (range 10000))))
+
+
+ (frequencies (map second (map deref t-h-3)))))
diff --git a/save-points/final/src/workshop_app/handlers/lazy.clj b/save-points/final/src/workshop_app/handlers/lazy.clj
new file mode 100644
index 0000000..346241b
--- /dev/null
+++ b/save-points/final/src/workshop_app/handlers/lazy.clj
@@ -0,0 +1,141 @@
+(ns workshop-app.handlers.lazy)
+
+
+;; 1 - Infinity
+
+;; Simple example of range
+
+(comment
+ (range 10))
+
+;; What happens when range is unbounded
+
+;; DON'T EVALUATE THIS
+(comment
+ (range))
+
+
+(comment
+ (take 10 (range))
+
+ (take 10 (map inc (range)))
+
+ (take 10 (filter even? (map inc (range)))))
+
+
+;; 2 - Maths
+
+;; Let's calculate square roots by Newton-Raphson Square Roots method
+
+;; a - approximation
+;; n - Number
+;; a = (a + n/a)/2
+;; (/ (+ a (/ n a)) 2)
+
+
+;; (f a0) = a1
+;; (f a1) = a2
+;; (f a2) = a3
+;; (f a4) = a4
+
+;; The algorithm is recursive
+;; ((f (f (f a0) a1) a2)..)
+
+;; But it can also be thought of a sequence
+;; [a0, f a0, f (f a0), f (f (f a0)), . . . ]
+
+;; We haven't talked about epsilon yet.
+
+;; But if we look at it as lazy sequence
+
+(def epsilon 1)
+
+(defn sq-root*
+ [a x]
+ (let [a1 (/ (+ a (/ x a)) 2)]
+ (cons [a a1]
+ (lazy-seq (sq-root* a1
+ x)))))
+
+(defn sq-root
+ [x]
+ (let [a 1]
+ #_(??? (fn [[a1 a2]]
+ (when (> epsilon (Math/abs (float (- a1 a2))))
+ (int a1)))
+ (sq-root* a x))))
+
+
+;; Working with lazy sequences you basically deal with three things
+
+;; - Generation
+;; sq-root* generates a sequence (infinite) of approximations
+
+;; - Processing
+;; You may choose to process further on these sequences
+
+;; - Realisation
+;; You realise the result at this point defining how much
+;; input is required is also necessary
+
+
+
+;; 3 - Real world - Files
+
+;; Size of the data does not matter as long as you are reducing it into
+;; a small enough set
+(with-open [r (clojure.java.io/reader "resources/data/albumlist.csv")]
+ (let [l-xs (line-seq r)]
+ (comment (count (filter #(= (:artist %)
+ "The Beatles")
+ (map parse-line l-xs))))))
+
+
+;; 3 - Real world - Databases
+
+;; You can create lazy sequences of IO operations as long as there is no
+;; side effect and you are ok with lazy sequences caching the result
+
+(defn- scroll-seq
+ "Given a scroll-id, fetch scroll results"
+ [num]
+ (comment
+ (lazy-seq
+ (let [result (:body (http/get (format "_search/scroll?scroll=%s"
+ num)))]
+ (when (seq result)
+ (cons result
+ (scroll-seq (inc num))))))))
+
+;; 4 - Gotchas
+
+;; a. Open connections closing early
+
+
+;; Always use with-open when generating lazy seq from external sources and realise the whole sequence inside with-open
+
+(let [l-xs (with-open [r (clojure.java.io/reader "resources/data/albumlist.csv")]
+ (line-seq r))]
+ (count l-xs))
+
+;; Creating lazy sequences from a database can bring complexities if
+;; connections are not handled properly
+
+
+;; b. Chunking - Well almost lazy
+
+(comment (count (take 10 (map println (range 100)))))
+
+
+;; c. Caching
+
+(comment (def lz (doall (take 10 (map println (range 100)))))
+ (count lz))
+
+;; Some laziness in the wild
+
+;; https://github.com/Factual/durable-queue/blob/master/src/durable_queue.clj#L741
+
+;; https://github.com/s312569/clj-biosequence
+
+;; https://github.com/Genscape/gregor/blob/master/src/gregor/core.clj#L241
diff --git a/save-points/final/src/workshop_app/handlers/users.clj b/save-points/final/src/workshop_app/handlers/users.clj
new file mode 100644
index 0000000..3a404e0
--- /dev/null
+++ b/save-points/final/src/workshop_app/handlers/users.clj
@@ -0,0 +1,63 @@
+(ns workshop-app.handlers.users
+ (:require [workshop-app.db.sqlite :as wads]
+ [workshop-app.utils :as wau]
+ [cheshire.core :as json]))
+
+
+(defn get-handler
+ [{:keys [params] :as request}]
+ (def r* request)
+ (let [{:keys [name surname]} params]
+ (if (and name surname)
+ {:status 200
+ :body (str "Hello, "
+ name
+ " "
+ surname
+ "!!!")}
+ {:status 400
+ :body "Missing name and surname."})))
+
+
+(defn add-person
+ [{:keys [name dob]}]
+ (if (and name dob)
+ (do (wads/create! wads/conn
+ name
+ dob)
+ {:status 201
+ :body "Created user."})
+ {:status 400
+ :body "User name or date of birth missing."}))
+
+
+(defn get-person
+ [dob now]
+ {:status 200
+ :headers {"content-type" "application/json"}
+ :body (when dob
+ (json/generate-string {:dob dob
+ :age (wau/years-between (wau/parse-dt-str dob)
+ now)}))})
+
+
+(defn update-person
+ [{:keys [name dob]}]
+ (if (and name dob)
+ (do (wads/update! wads/conn
+ name
+ dob)
+ {:status 200
+ :body "Updated user."})
+ {:status 400
+ :body "User name or dob is missing."}))
+
+
+(defn delete-person
+ [name]
+ (if name
+ (do (wads/delete! wads/conn name)
+ {:status 200
+ :body "Deleted user."})
+ {:status 400
+ :body "User name missing, cannot delete person without username."}))
\ No newline at end of file
diff --git a/save-points/final/src/workshop_app/interop.clj b/save-points/final/src/workshop_app/interop.clj
new file mode 100644
index 0000000..3a65ff1
--- /dev/null
+++ b/save-points/final/src/workshop_app/interop.clj
@@ -0,0 +1,130 @@
+(ns workshop-app.interop
+ (:refer-clojure :rename {get cc-get})
+ (:import (java.util Calendar Comparator ArrayList HashMap AbstractMap$SimpleEntry)
+ (java.time.temporal ChronoUnit)
+ (java.time LocalDate)))
+
+;; class access
+;; HashMap.class
+HashMap ;; a class
+
+;; new HashMap();
+(HashMap.) ;; an object created from the class
+
+;; HashMap hm = new HashMap();
+(def hm (HashMap.)) ;; bind the object to a var. More on var's later.
+
+;; hm.put("a", 10);
+(.put hm "a" 10)
+
+;; hm.put("b", 10);
+(.put ^HashMap hm "b" 10) ;; the first gives us a warning of how it cannot be resolved.
+
+;; Member access
+;; method access
+;; hm.get("a")
+(.get hm "a")
+
+;; hm.get("b")
+(.get hm "b")
+
+;; "joel".toUpperCase();
+(.toUpperCase "joel")
+
+;; field access
+;; new Point(10,20).x;
+(.-x (java.awt.Point. 10 20))
+
+;; static variables or methods access
+;; Calendar.ERA
+Calendar/ERA
+
+;; Math.PI
+Math/PI
+
+;; System.getProperties()
+(System/getProperties)
+
+;; dot special form
+;; hm.get("a");
+(. hm get "a")
+
+;; "joel".toUpperCase();
+(. "joel" toUpperCase)
+
+;; System.getProperties().get("os.name")
+(. (. System (getProperties)) (get "os.name"))
+
+;; double dot
+(.. System getProperties (get "os.name"))
+
+;; doto
+;; HashMap dotoHm = new HashMap();
+;; dotoHm.put("a", 10);
+;; dotoHm.put("b", 20);
+(def doto-hm (doto (HashMap.)
+ (.put "a" 10)
+ (.put "b" 20)))
+
+;; new
+;; HashMap newSyntaxHm = new HashMap();
+(def new-syntax-hm (new HashMap))
+
+;; accessing inner classes
+;; AbstractMap.SimpleEntry("a", "b")
+(AbstractMap$SimpleEntry. "a" "b")
+
+
+;; reify example. Sort an array list of array list using
+;; a custom comparator.
+;; List al = new ArrayList();
+;; List alChild1 = new ArrayList();
+;; alChild1.add(1);
+;; alChild1.add(2);
+;; List alChild2 = new ArrayList();
+;; alChild2.add(1);
+;; alChild2.add(2);
+;; List alChild3 = new ArrayList();
+;; alChild3.add(1);
+;; alChild3.add(2);
+;; List alChild1 = new ArrayList();
+;; alChild1.add(1);
+;; alChild1.add(2);
+;; al.add(alChild1);
+;; al.add(alChild2);
+;; al.add(alChild3);
+;; al.sort(new Compartor {
+;; @Override
+;; public int compare(ArrayList al1, ArrayList al2) {
+;; return al1.elementAt(0) - al2.elementAt(0);
+;; }
+;; })
+(let [l (doto (ArrayList.)
+ (.add (doto (ArrayList.)
+ (.add 1)
+ (.add 2)))
+ (.add (doto (ArrayList.)
+ (.add 2)
+ (.add 3)))
+ (.add (doto (ArrayList.)
+ (.add 3)
+ (.add 4))))]
+ ;; this modifies the list.
+ (.sort l
+ (reify Comparator
+ (compare [_ al1 al2]
+ (- (.get ^ArrayList al2 (int 0))
+ (.get ^ArrayList al1 (int 0))))))
+ l)
+
+
+;; a small interop task. of finding the days between today and first January
+;; following is a sample java code that you should translate to clojure interop code.
+;; LocalDate dateOne = LocalDate.of(2020,1,1);
+;; LocalDate dateTwo = LocalDate.now();
+;; long daysBetween = ChronoUnits.DAYS.between(dateOne, dateTwo);
+(let [date-one (LocalDate/of 2020 1 1)
+ date-two (LocalDate/now)]
+ (.between ChronoUnit/DAYS
+ date-one
+ date-two))
\ No newline at end of file
diff --git a/save-points/final/src/workshop_app/middlewares/users.clj b/save-points/final/src/workshop_app/middlewares/users.clj
new file mode 100644
index 0000000..399df82
--- /dev/null
+++ b/save-points/final/src/workshop_app/middlewares/users.clj
@@ -0,0 +1,22 @@
+(ns workshop-app.middlewares.users
+ (:require [clojure.string :as s]))
+
+
+(defn reject-uri-ending-with-slash
+ [handler]
+ (fn [{:keys [uri] :as request}]
+ (if (and (not= uri "/")
+ (s/ends-with? uri "/"))
+ {:status 400
+ :body "Bad request."}
+ (handler request))))
+
+
+(defn handle-any-exception
+ [handler]
+ (fn [request]
+ (try (handler request)
+ (catch Exception e
+ (.printStackTrace e)
+ {:status 500
+ :body "Internal server error."}))))
\ No newline at end of file
diff --git a/save-points/final/src/workshop_app/namespaces.clj b/save-points/final/src/workshop_app/namespaces.clj
new file mode 100644
index 0000000..eece9fa
--- /dev/null
+++ b/save-points/final/src/workshop_app/namespaces.clj
@@ -0,0 +1,11 @@
+(ns workshop-app.namespaces)
+
+;; to create a namespace
+#_(ns test-namespace)
+
+;; to switch to a namespace
+#_(in-ns 'test-namespace)
+
+#_*ns*
+
+#_(def a)
\ No newline at end of file
diff --git a/save-points/final/src/workshop_app/routes.clj b/save-points/final/src/workshop_app/routes.clj
new file mode 100644
index 0000000..2a5547a
--- /dev/null
+++ b/save-points/final/src/workshop_app/routes.clj
@@ -0,0 +1,15 @@
+(ns workshop-app.routes
+ (:require [workshop-app.handlers.users :as wahu]
+ [workshop-app.db.sqlite :as wads]
+ [compojure.core :refer [defroutes GET POST PUT DELETE ANY]])
+ (:import (java.time LocalDate)))
+
+
+(defroutes app-routes
+ (GET "/" _ wahu/get-handler)
+ (POST "/:name" {:keys [params]} (wahu/add-person params))
+ (GET "/:name" [name] (wahu/get-person (get (wads/read wads/conn name) "dob")
+ (LocalDate/now)))
+ (PUT "/:name" {:keys [params]} (wahu/update-person params))
+ (DELETE "/:name" [name] (wahu/delete-person name))
+ (ANY "*" _ {:status 404}))
\ No newline at end of file
diff --git a/save-points/final/src/workshop_app/utils.clj b/save-points/final/src/workshop_app/utils.clj
new file mode 100644
index 0000000..2761e2f
--- /dev/null
+++ b/save-points/final/src/workshop_app/utils.clj
@@ -0,0 +1,21 @@
+(ns workshop-app.utils
+ (:import (java.time LocalDate)
+ (java.time.temporal ChronoUnit)))
+
+
+(defn parse-dt-str
+ [dt-str]
+ (when (seq dt-str)
+ (LocalDate/parse dt-str)))
+
+
+(defn dt-after?
+ [d1 d2]
+ (.isAfter d1 d2))
+
+
+(defn years-between
+ [d1 d2]
+ (if (dt-after? d2 d1)
+ (.between ChronoUnit/YEARS d1 d2)
+ 0))
\ No newline at end of file
diff --git a/save-points/final/src/workshop_app/vars.clj b/save-points/final/src/workshop_app/vars.clj
new file mode 100644
index 0000000..1a255bf
--- /dev/null
+++ b/save-points/final/src/workshop_app/vars.clj
@@ -0,0 +1,22 @@
+(ns workshop-app.vars)
+
+;; this is a symbol
+'a
+
+;; to create a var we need to simply do
+(def a)
+
+#_(+ a 10)
+
+#_(alter-var-root #'a (constantly 20))
+
+#_(println a)
+
+#_(println (var-get #'a))
+
+(def ^:dynamic b)
+
+#_(+ b 20)
+
+(binding [b 10]
+ (println (+ b 10)))
\ No newline at end of file
diff --git a/save-points/final/test/workshop_app/handlers/users_pure_test.clj b/save-points/final/test/workshop_app/handlers/users_pure_test.clj
new file mode 100644
index 0000000..34f7cb7
--- /dev/null
+++ b/save-points/final/test/workshop_app/handlers/users_pure_test.clj
@@ -0,0 +1,12 @@
+(ns workshop-app.handlers.users-pure-test
+ (:require [clojure.test :refer :all]
+ [workshop-app.handlers.users :as wahu])
+ (:import (java.time LocalDate)))
+
+
+(deftest pure-get-person-test
+ (is (= {:status 200
+ :headers {"content-type" "application/json"}
+ :body "{\"dob\":\"2000-01-01\",\"age\":20}"}
+ (wahu/get-person "2000-01-01" (LocalDate/parse "2020-02-14")))
+ "Is our pure get handler working as expected."))
\ No newline at end of file
diff --git a/save-points/final/test/workshop_app/handlers/users_test.clj b/save-points/final/test/workshop_app/handlers/users_test.clj
new file mode 100644
index 0000000..29fe783
--- /dev/null
+++ b/save-points/final/test/workshop_app/handlers/users_test.clj
@@ -0,0 +1,38 @@
+(ns workshop-app.handlers.users-test
+ (:require [clojure.test :refer :all]
+ [workshop-app.handlers.users :as wahu]
+ [workshop-app.db.sqlite :as wads]))
+
+
+;; Pattern 1
+;; Using fixtures to actually bootstrap and teardown.
+;; Scenario: I want to test against a actual instance
+;; but I want to test against a different database.
+(use-fixtures :each (fn [t]
+ (let [conn (wads/init-conn! "jdbc:sqlite::memory:")]
+ (with-redefs [wads/conn conn]
+ (wads/create-table conn)
+ (t)))))
+
+
+(deftest add-person-test
+ (is (= {:status 201
+ :body "Created user."}
+ (wahu/add-person {:name "Joel Victor"
+ :dob "2001-01-01"}))
+ "Is the creation handler working as expected."))
+
+
+(deftest update-person-test
+ (is (= {:status 200
+ :body "Updated user."}
+ (wahu/update-person {:name "Joel Victor"
+ :dob "2000-01-01"}))
+ "Is the update handler working as expected."))
+
+
+(deftest delete-person-test
+ (is (= {:status 200
+ :body "Deleted user."}
+ (wahu/delete-person "Joel Victor"))
+ "Is the deletion handler working as expected."))
\ No newline at end of file
diff --git a/save-points/final/test/workshop_app/handlers/users_test_2.clj b/save-points/final/test/workshop_app/handlers/users_test_2.clj
new file mode 100644
index 0000000..9f1a6fe
--- /dev/null
+++ b/save-points/final/test/workshop_app/handlers/users_test_2.clj
@@ -0,0 +1,27 @@
+(ns workshop-app.handlers.users-test-2
+ (:require [clojure.test :refer :all]
+ [workshop-app.handlers.users :as wahu]
+ [workshop-app.db.sqlite :as wads]
+ [workshop-app.db.in-mem :as wadim]))
+
+;; pattern 2. don't create the connection but instead redefine
+;; it to an inmemory implementation.
+;; these are useful when you communicate to a database over
+;; a network
+(use-fixtures :each (fn [t]
+ (with-redefs [wads/conn wadim/conn
+ wads/create! wadim/create!
+ wads/update! wadim/update!
+ wads/read (fn [conn k] {"dob" (wadim/read conn k)})
+ wads/delete! wadim/delete!]
+ (t))))
+
+
+(deftest all-handlers-test
+ (testing "Testing all handlers in one go."
+ (are [expected-response actual-response] (= expected-response actual-response)
+ {:status 201 :body "Created user."} (wahu/add-person {:name "Joel Victor"
+ :dob "2001-01-01"})
+ {:status 200 :body "Updated user."} (wahu/update-person {:name "Joel Victor"
+ :dob "2000-01-01"})
+ {:status 200 :body "Deleted user."} (wahu/delete-person "Joel Victor"))))
\ No newline at end of file
diff --git a/save-points/final/test/workshop_app/intro_to_testing.clj b/save-points/final/test/workshop_app/intro_to_testing.clj
new file mode 100644
index 0000000..591d0fb
--- /dev/null
+++ b/save-points/final/test/workshop_app/intro_to_testing.clj
@@ -0,0 +1,20 @@
+(ns workshop-app.intro-to-testing
+ (:require [clojure.test :refer :all]))
+
+
+;; Defining a test
+(deftest test-name-1
+ (is (= 1 1)
+ "Optional message for assertion")
+
+ (are [x y] (= x y)
+ 2 (+ 1 1)
+ 3 (+ 1 2)))
+
+
+(deftest test-name-2
+ (testing "Msg to add context to all the test run inside this"
+ (is (= 1 2)
+ "One should not equal 2."))
+
+ (is (thrown? RuntimeException ((fn [] (throw (RuntimeException.)))))))
\ No newline at end of file
diff --git a/save-points/final/test/workshop_app/middlewares/users_test.clj b/save-points/final/test/workshop_app/middlewares/users_test.clj
new file mode 100644
index 0000000..675af60
--- /dev/null
+++ b/save-points/final/test/workshop_app/middlewares/users_test.clj
@@ -0,0 +1,25 @@
+(ns workshop-app.middlewares.users-test
+ (:require [clojure.test :refer :all]
+ [workshop-app.middlewares.users :as wamu]))
+
+
+(deftest pure-reject-uri-ending-with-slash-middleware-test
+ (testing "Middleware should reject all uri's ending with /"
+ (are [expected actual] (= expected actual)
+ {:uri "/"} ((wamu/reject-uri-ending-with-slash identity)
+ {:uri "/"})
+ {:status 400 :body "Bad request."} ((wamu/reject-uri-ending-with-slash identity)
+ {:uri "/joel/"})
+ {:uri "/joel"} ((wamu/reject-uri-ending-with-slash identity)
+ {:uri "/joel"}))))
+
+
+;; by using higher order functions you can also test behavior without
+;; mutating any code.
+(deftest pure-handle-any-exception
+ (testing "Middleware should reject all uri's ending with /"
+ (are [expected actual] (= expected actual)
+ {:status 500 :body "Internal server error."} ((wamu/handle-any-exception (fn [_] (throw (Exception.))))
+ {})
+ {} ((wamu/handle-any-exception identity)
+ {}))))
\ No newline at end of file
diff --git a/save-points/final/test/workshop_app/property_based_test.clj b/save-points/final/test/workshop_app/property_based_test.clj
new file mode 100644
index 0000000..1007bd7
--- /dev/null
+++ b/save-points/final/test/workshop_app/property_based_test.clj
@@ -0,0 +1,36 @@
+(ns workshop-app.property-based-test
+ (:require [clojure.test :refer :all]
+ [clojure.test.check :as tc]
+ [clojure.test.check.clojure-test :as tcct]
+ [clojure.test.check.generators :as ctcg]
+ [clojure.test.check.properties :as ctcp]
+ [workshop-app.utils :as wau])
+ (:import (java.time LocalDate)))
+
+
+(def date-tuple-generator
+ (ctcg/let [month (ctcg/choose 1 12)]
+ (case month
+ 2 (ctcg/tuple (ctcg/choose 1900 2021) (ctcg/return month) (ctcg/choose 1 28))
+ (1 3 5 7 8 10 12) (ctcg/tuple (ctcg/choose 1900 2021) (ctcg/return month) (ctcg/choose 1 31))
+ (4 6 9 11) (ctcg/tuple (ctcg/choose 1900 2021) (ctcg/return month) (ctcg/choose 1 30)))))
+
+(def date-object-generator
+ (ctcg/fmap (fn [[y m d]]
+ (LocalDate/of y m d))
+ date-tuple-generator))
+
+(def age-property-fn (some-fn pos? zero?))
+
+(def age-property
+ (ctcp/for-all [st-dt date-object-generator
+ end-dt date-object-generator]
+ (age-property-fn (wau/years-between st-dt end-dt))))
+
+#_(tc/quick-check 1000 age-property)
+
+
+(tcct/defspec age-property-2 100
+ (ctcp/for-all [st-dt date-object-generator
+ end-dt date-object-generator]
+ (age-property-fn (wau/years-between st-dt end-dt))))
\ No newline at end of file
diff --git a/save-points/project.clj b/save-points/project.clj
new file mode 100644
index 0000000..adb1602
--- /dev/null
+++ b/save-points/project.clj
@@ -0,0 +1,27 @@
+(defproject inclojure/workshop-app "0.1.0"
+ :dependencies [[org.clojure/clojure "1.10.1"]
+ [ring/ring "1.8.0"]
+ [compojure "1.6.1"]
+ [cheshire "5.9.0"]
+ [org.xerial/sqlite-jdbc "3.30.1"]]
+ :main workshop-app.core
+ :aot [workshop-app.clj-to-java-interop]
+ :global-vars {*warn-on-reflection* true}
+ :source-paths ["1/src" ;; simple hello world printer
+ "2/src" ;; hello world server and ring introduction
+ "3/src" ;; hello name server filled and middleware intro
+ "4/src" ;; middlewares solved with compojure intro
+ "5/src" ;; interop
+ "6/src" ;; interop+ with jdbc
+ "7/src" ;; introduction to testing
+ "8/src" ;; property based testing, vars & namespaces, clj to java interop
+ "9/src" ;; laziness solved with concurrency introduction
+ "10/src" ;; concurrency solution
+ "final/src"]
+ :test-paths ["7/test" ;; introduction to testing
+ "8/test" ;; property based testing, vars & namespaces, clj to java interop
+ "9/test" ;; laziness solved with concurrency introduction
+ "10/test" ;; concurrency solution
+ "final/test"]
+ :profiles {:test {:dependencies [[org.clojure/test.check "0.10.0"]]}}
+ :local-repo ".local-m2")
\ No newline at end of file
diff --git a/src/icw/async/albums_stream.clj b/src/icw/async/albums_stream.clj
deleted file mode 100644
index 8b492e5..0000000
--- a/src/icw/async/albums_stream.clj
+++ /dev/null
@@ -1,33 +0,0 @@
-;; [[file:~/github/intermediate-clojure-workshop/content/async/albums_stream.org::*Preamble][Preamble:1]]
-(ns icw.async.albums-stream
- (:require [clojure.core.async :as a
- :refer [chan go go-loop ! take! put!]]
- [icw.async.rlsc :as rlsc]
- [icw.data.gen :as data-gen]))
-;; Preamble:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/async/albums_stream.org::*Outline][Outline:2]]
-(defonce counter (atom 0))
-(def observing-mapper (map (fn [e]
- (swap! counter inc)
- e)))
-
-(def in-ch (a/chan (a/dropping-buffer 32) observing-mapper))
-
-(defonce enabled? (atom false))
-(defonce quit? (atom false))
-
-(defonce generator-loop
- (go-loop [stream (data-gen/get-albums-xs)]
- #_("Introduce an appropriate delay")
- (if-not @quit?
- (do
- (if @enabled?
- (a/>! in-ch #_(FIXME stream) :dummy))
- (recur #_(FIXME stream) :dummy)))))
-
-(defn enable-stream! []
- (reset! enabled? true))
-(defn disable-stream! []
- (reset! enabled? false))
-;; Outline:2 ends here
diff --git a/src/icw/async/core.clj b/src/icw/async/core.clj
deleted file mode 100644
index fe3f38b..0000000
--- a/src/icw/async/core.clj
+++ /dev/null
@@ -1,32 +0,0 @@
-;; [[file:~/github/intermediate-clojure-workshop/content/async/core.org::*Preamble%20Code][Preamble Code:1]]
-(ns icw.async.core
- (:require [clojure.core.async :as a
- :refer [chan go go-loop ! take! put!]]
- [icw.async.rlsc :as rlsc]
- [icw.data.gen :as data-gen]))
-
-(defonce counter (atom 0))
-(def observing-mapper (map (fn [e]
- (swap! counter inc)
- e)))
-
-(def in-ch (a/chan (a/dropping-buffer 32) observing-mapper))
-
-(defonce enabled? (atom false))
-(defonce quit? (atom false))
-
-(defn enable-stream! []
- (reset! enabled? true))
-(defn disable-stream! []
- (reset! enabled? false))
-
-(defonce generator-loop
- (go-loop [stream (data-gen/get-albums-xs)]
- ; FIXME
- ; (a/! in-ch (first stream)))
- (recur (rest stream))))))
-;; Preamble Code:1 ends here
diff --git a/src/icw/async/intro.clj b/src/icw/async/intro.clj
deleted file mode 100644
index 3e03d4a..0000000
--- a/src/icw/async/intro.clj
+++ /dev/null
@@ -1,101 +0,0 @@
-(ns icw.async.chapter1
- (:require [clojure.core.async :refer
- [go chan ! >!! chan ----> Process 2
-
-;; In context of `go`, put is `>!` and take is `! c 1))
-
-
- ;; process #2
- ;; get something from channel
- (go (pprint "Hello from process #2 " (!! c {:data "hello world"})
- (pprint "Fetching data from Mongodb completed"))
-
- ;; process #2
- (go (pprint "Data from mongodb #2 " (!! c1 {:data (str "hello world from process 1 time " time)})))
-
- ;; process #2
- (future (let [time (rand-int 1000)]
- (Thread/sleep time)
- (>!! c2 {:data (str "hello world from process 2 time " time)})))
-
- ;; process #3 how do we get output of the fastest result
- (pprint (!! c1 {:data (str "hello world from process 1 time " time)})
- (pprint "released process 1!")))
-
- ;; process #2
- (future (let [time (rand-int 1000)]
- (Thread/sleep time)
- (>!! c2 {:data (str "hello world from process 2 time " time)})
- (pprint "released process 2!")))
-
- ;; process #3
- (let [[v c] (alts!! [c1 c2 t])]
- (pprint v))))
-
-
-
-
-;; Exercise - 1
-;; Modify alts! example. If both requests take more than 300msec print
-;; "Wubalubadubdub" and close both request channels
-;; Hint - timeout generates a channel which will close after given time
-;; On closing the channel it gets nil as the final value
diff --git a/src/icw/async/intro_part2.clj b/src/icw/async/intro_part2.clj
deleted file mode 100644
index 3b6b08f..0000000
--- a/src/icw/async/intro_part2.clj
+++ /dev/null
@@ -1,170 +0,0 @@
-;; [[file:~/github/intermediate-clojure-workshop/content/async/intro.org::*Preamble][Preamble:1]]
-(ns icw.async.intro-part2
- (:require [icw.common :refer :all]
- [clojure.core.async :as a
- :refer [go go-loop chan close!
- ! !! take! put!
- alts! alt! thread
- buffer sliding-buffer dropping-buffer]]))
-;; Preamble:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/async/intro.org::*chan][chan:1]]
-(chan)
-;; chan:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/async/intro.org::*Put,%20and%20then%20take.][Put, and then take.:1]]
-; A simple put, followed by a take
-(let [ch (chan)]
- (put! ch "hello, orderly world!")
- (take! ch pprint))
-;; Put, and then take.:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/async/intro.org::*Take,%20and%20put?][Take, and put?:1]]
-(let [ch (chan)]
-
- ;; take!, and put!, are non-blocking operations
- (take! ch pprint)
-
- ; The above returned immediately,
- ; event though there was nothing to consume
- (pprint "Now, we put!")
- (put! ch "hello, world!")
-
- (take! ch pprint)
- (put! ch "hello, again, world!"))
-;; Take, and put?:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/async/intro.org::*Pushing%20the%20limits%20-%20put%20and%20take][Pushing the limits - put and take:1]]
-; How many puts can a channel take at once?
-(let [num-msgs 1025
- ch (chan)]
- (doseq [i (range num-msgs)]
- (put! ch i))
-
- (doseq [i (range num-msgs)]
- (take! ch identity)))
-;; Pushing the limits - put and take:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/async/intro.org::*Add%20a%20cushion][Add a cushion:1]]
-(let [chan-buf-sz 32
- put-limit 1025
- take-limit 64
- ;; Try using any of buffer, dropping-buffer and sliding-buffer
- ch (chan (buffer chan-buf-sz))]
-
- (doseq [i (range put-limit)]
- (put! ch i))
-
- (doseq [i (range take-limit)]
- (take! ch pprint)))
-;; Add a cushion:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/async/intro.org::*Let's%20go!][Let's go!:1]]
-(let [c (chan)]
-
- (go (pprint "We have a new message: " (! c "Did you have to wait too long?")))
-;; Let's go!:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/async/intro.org::*Going,%20going,%20go-loop!][Going, going, go-loop!:1]]
-; Let's create a looping go process.
-(defonce looping-ch-stop? (atom false))
-(def looping-ch
- (let [ch (chan)]
- (go-loop [msg ( 1 are ready?
- ; Note that the out channel is very likely ready for action,
- ; since we control when the action happens.
- (condp = port
- ch1 (pprint "We had an input '" val "' on ch1")
- ch2 (pprint "We had an input '" val "' on ch2")
- out-ch (pprint "Nothing came in. So, we sent out on out-ch"))))
-
- (if (zero? (rand-int 2))
- (put! ch1 "Hello, ch1")
- (put! ch2 "Hello, ch2")))
-;; What's async without multiple actors?:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/async/intro.org::*And,%20what's%20clojure%20without%20convenience?][And, what's clojure without convenience?:1]]
-(let [ch1 (chan)
- ch2 (chan)
- out-ch (chan 1)
- priority? false] ;; Try variations
-
- (go (alt!
- ch1 ([val] (pprint "We had an input \"" val "\" on ch1"))
- ch2 ([val] (pprint "We had an input \"" val "\" on ch2"))
- ;; Caution - It's a vector of vectors below.
- [[out-ch "Out!"]] ([_] (pprint "Nothing came in. So, we sent out on out-ch"))
- :priority priority?))
- (put! ch1 "Hello, ch1"))
-;; And, what's clojure without convenience?:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/async/intro.org::*Where%20do%20the%20async%20'processes'%20run?][Where do the async 'processes' run?:1]]
-(thread (pprint "Hello"))
-
-(go (pprint "Hello"))
-
-(let [c (chan)]
- (go (pprint "In value: " (! c "HereIsAChannelMessage")))
-
-(let [c (chan 8)]
- (put! c "Hello, put!"
- (fn [& args]
- (pprint "On put! callback"))
- false)
- (take! c (fn [& [args]] (pprint "On take! callback" args)) false))
-
-
-(let [put-take-counter (atom 0)
- c (chan (sliding-buffer 128))
- count++ (fn [] (swap! put-take-counter inc))]
-
- (def on-my-thread true)
- (def on-core-async-thread false)
-
- (defn put-cb [v]
- (pprint "put! = " v))
- (defn take-cb [x]
- (pprint "take! = " x))
- (defn put-counter! [on-thread?]
- (put! c (count++) put-cb on-thread?))
- (defn take-counter! [on-thread?]
- (take! c take-cb on-thread?))
-
- (comment
- ;; Mix the put-s and take-s. Letting one run ahead of the other.
- ;; Observe where (the thread) the prints happen.
- (put-counter! on-core-async-thread)
- (take-counter! on-core-async-thread)
- (take-counter! on-my-thread)
- (doseq [_ (range 1024)]
- (put-counter! on-core-async-thread))
- (doseq [_ (range 128)]
- (take-counter! on-my-thread))))
-;; Where do the async 'processes' run?:1 ends here
diff --git a/src/icw/async/rlsc.clj b/src/icw/async/rlsc.clj
deleted file mode 100644
index 6eb0bc5..0000000
--- a/src/icw/async/rlsc.clj
+++ /dev/null
@@ -1,116 +0,0 @@
-;; [[file:~/github/intermediate-clojure-workshop/content/async/rlsc.org::*The%20Template][The Template:1]]
-(ns icw.async.rlsc
- (:require [clojure.core.async :as a
- :refer [go go-loop
- chan close! timeout thread
- ! take! put! >!! BoundedCounter (atom ulimit) (atom (or initial 0))))
-
-(comment
- ; Let's test our bounded counter.
- ; Increment-tests first. Try modifying the limits
- (let [c (new-counter 10 5)]
- (doseq [_ (range 7)]
- (println "Current count: " (counter c))
- (println "++ " (counter++ c))))
-
- ; Decrement tests
- (let [c (new-counter 10 13)]
- (doseq [_ (range 7)]
- (println "Current count: " (counter c))
- (println "-- " (counter-- c)))))
-;; The Template:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/async/rlsc.org::*The%20API][The API:1]]
-(defprotocol RLSCController
- (start! [this])
- (modify-burst! [this new-burst-count])
- (zero! [this])
- (shutdown! [this]))
-;; The API:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/async/rlsc.org::*Outline][Outline:1]]
-(defrecord RLSC
- [in-ch out-ch process-fn time-gap-ms burst-count]
- ;; Any internal state to track??
-
- RLSCController
- ; We need two go processes
- ; 1. One that tracks consumption and burst-rate limits
- ; 2. The other processes messages per the policy
- (start! [_]
- (go-loop []
- #_("A periodic process that increments tokens"))
- (go-loop [#_v #_("read a message")]
- "If we have capacity process, else simply pass on to the output channel"
- (recur #_("read next message if no shutdown signal"))))
-
- ; Policy change at run-time
- ; This needs to be conveyed to the go-blocks
- ; which help us conform to policy
- (modify-burst! [this new-burst-count]
- #_("update the burst-count")
- #_("update tokens available"))
-
- ; Stop all transformation
- ; Signal the go-block logic to clamp down on transformations.
- (zero! [this]
- #_("special case of modify-burst! Is it?"))
-
- ; Stop the go blocks.
- ; How do we communicate with the go-blocks started in another place?
- (shutdown! [this]))
-
-(defn new-rate-limiter [in-ch out-ch process-fn time-gap-ms burst-count]
- (->RLSC in-ch out-ch process-fn time-gap-ms
- (atom burst-count)))
-;; Outline:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/async/rlsc.org::*Test%20runs][Test runs:1]]
-(comment
- (let [count (atom 0)]
- (defn work-counter [] @count)
- (defn work-counter++ [] (swap! count inc)))
-
- (def in-ch (chan (dropping-buffer 8)))
-
- (def out-ch (chan (dropping-buffer 8)
- (map pprint)
- (constantly nil)))
-
- (defn process-value [v]
- (assoc v :heavy? true))
- (def r (new-rate-limiter in-ch out-ch
- (fn [v]
- (assoc v :heavy? true))
- 1000 3))
- (start! r)
-
- (zero! r)
- (doseq [_ (range 5)]
- (put! in-ch {:payload (work-counter++)})))
-;; Test runs:1 ends here
diff --git a/src/icw/common.clj b/src/icw/common.clj
deleted file mode 100644
index ed1c9ec..0000000
--- a/src/icw/common.clj
+++ /dev/null
@@ -1,15 +0,0 @@
-;; [[file:~/github/intermediate-clojure-workshop/content/common.org::*Common%20utils][Common utils:1]]
-(ns icw.common
- (:require [clojure.pprint :as pp]
- [clojure.datafy :refer [datafy]]))
-
-(defn thread-name []
- (-> (Thread/currentThread)
- .getName))
-
-(let [pp-lock (Object.)]
- (defn pprint
- [& s]
- (locking pp-lock
- (pp/pprint (apply str "[" (thread-name) "] " s)))))
-;; Common utils:1 ends here
diff --git a/src/icw/core.clj b/src/icw/core.clj
deleted file mode 100644
index 1a8ac64..0000000
--- a/src/icw/core.clj
+++ /dev/null
@@ -1,99 +0,0 @@
-(ns icw.core
- (:require [icw.system :as system]
- [icw.data.process]
- [icw.data.process-faster]
- ;; [icw.java-interop.intro]
- ;; [icw.search.reader]
- [icw.async.intro]
- [icw.async.rlsc]
- [icw.web.core :as web])
- (:gen-class))
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Introduction
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-;; Start HTTP server
-
-(comment
- (system/start!))
-
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Chapter 1 - Lazy sequences
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-;; http://localhost:6789/albums
-
-;; Where is it located ? Let's jump there
-web/app
-
-icw.data.process/populate-db
-
-;; git checkout solutions src/
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Chapter 2 - Concurrency
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-;; Can we make make populate-db faster?
-
-'icw.data.process-faster
-
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Chapter 3 - Java interop
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-;; 'icw.java-interop.intro
-
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Chapter 4 - Java interop (search)
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-;; Go to
-;; http://localhost:6789/search/beatles
-
-;; Let's go back to the routes
-web/app
-
-;; We need fix search
-;; icw.search.reader/search
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Chapter 5 - core.async introduction
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-;; Let's write some core.async
-
-'icw.async.intro
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Chapter 6 - core.async exercise
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-'icw.async.rlsc
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Summary
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-;; How to choose libraries and build projects ???
-
-;; Start from smallest unit of data
-;; Build transformations based on your case
-;; Deal with constraints
-;; Bring in databases and dependencies at very last
-
-
-;; When choosing libraries
-;; It should work with immutable data structure
-;; It should produce immutable data structure
-;; It should produce lazy sequences when it can
-;; Prefer libraries over frameworks
-;; It shouldn't use dynamic vars
-
-;; Do concurrency when you actually have to
-;; Adding concurrency primitives does add complexity to the system
-;; When in doubt go for the simplest construct
diff --git a/src/icw/data.clj b/src/icw/data.clj
deleted file mode 100644
index eca3621..0000000
--- a/src/icw/data.clj
+++ /dev/null
@@ -1,24 +0,0 @@
-;; [[file:~/github/intermediate-clojure-workshop/content/data.org::*All%20Data][All Data:1]]
-(ns icw.data
- (:require [clojure.data.csv :as csv]
- [clojure.java.io :as io]
- [clojure.string :as string]))
-
-(defonce album-csv-file "data/albumlist.csv")
-
-(defn load-album-csv-file [album-csv-file]
- (let [data (-> album-csv-file
- io/resource
- io/as-file
- slurp)
- csv (csv/read-csv data)
- header (first csv)
- header (map string/lower-case header)
- data (rest csv)
- processed (map zipmap
- (->> header
- (map keyword)
- repeat)
- data)]
- processed))
-;; All Data:1 ends here
diff --git a/src/icw/data/gen.clj b/src/icw/data/gen.clj
deleted file mode 100644
index 3524a8d..0000000
--- a/src/icw/data/gen.clj
+++ /dev/null
@@ -1,38 +0,0 @@
-(ns icw.data.gen
- (:require [clojure.test.check.generators :as gen]
- [clojure.string :as cs]))
-
-(def artist-gen (gen/elements ["The Beatles" "Bob Dylan" "The Rolling Stones" "Bruce Springsteen" "The Who" "Radiohead" "David Bowie" "Various Artists" "Elton John" "Led Zeppelin" "U2" "The Velvet Underground" "The Byrds" "Talking Heads" "Stevie Wonder" "Sly & The Family Stone" "The Smiths" "Pink Floyd" "The Police" "Ray Charles" "Al Green" "Muddy Waters" "Grateful Dead" "The Beach Boys" "The Doors" "Nirvana" "Steely Dan" "Elvis Presley" "Simon & Garfunkel" "The Clash" "Prince" "Miles Davis" "Bob Marley & The Wailers" "Kanye West" "Big Star" "Tom Waits" "Otis Redding" "James Brown" "R.E.M." "Cream" "Michael Jackson" "The Kinks" "The Jimi Hendrix Experience" "Jackson Browne" "Madonna" "Randy Newman" "Marvin Gaye" "Jay Z" "Black Sabbath" "Neil Young" "B.B. King" "Joni Mitchell" "Santana" "Public Enemy" "Johnny Cash" "Fleetwood Mac" "Creedence Clearwater Revival" "Aerosmith" "The Wailers" "Mott the Hoople" "PJ Harvey" "AC/DC" "Metallica" "The White Stripes" "Funkadelic" "Paul Simon" "The Notorious B.I.G." "Frank Sinatra" "Howlin' Wolf" "Ramones" "Run D.M.C." "Van Morrison" "The Band" "The Mothers of Invention" "Eagles" "MC5" "Neil Young & Crazy Horse" "Pixies" "Sam Cooke" "KISS" "Roxy Music" "Green Day"]))
-
-(def album-name-gen (gen/elements ["Sgt." "Pepper's" "Lonely" "Hearts" "Club" "Band" "Pet" "Sounds" "Revolver" "Highway" "61" "Revisited" "Rubber" "Soul" "What's" "Going" "On" "Exile" "on" "Main" "St." "London" "Calling" "Blonde" "on" "Blonde" "The" "Beatles" "(\"The" "White" "Album\")" "The" "Sun" "Sessions" "Kind" "of" "Blue" "The" "Velvet" "Underground" "&" "Nico" "Abbey" "Road" "Are" "You" "Experienced" "Blood" "on" "the" "Tracks" "Nevermind" "Born" "to" "Run" "Astral" "Weeks" "Thriller" "The" "Great" "Twenty_Eight" "The" "Complete" "Recordings" "John" "Lennon/Plastic" "Ono" "Band" "Innervisions" "Live" "at" "the" "Apollo," "1962" "Rumours" "The" "Joshua" "Tree" "Who's" "Next" "Led" "Zeppelin" "Blue" "Bringing" "It" "All" "Back" "Home" "Let" "It" "Bleed" "Ramones" "Music" "From" "Big" "Pink" "The" "Rise" "and" "Fall" "of" "Ziggy" "Stardust" "and" "the" "Spiders" "From" "Mars" "Tapestry" "Hotel" "California" "The" "Anthology" "Please" "Please" "Me" "Forever" "Changes" "Never" "Mind" "the" "Bollocks" "Here's" "the" "Sex" "Pistols" "The" "Doors" "The" "Dark" "Side" "of" "the" "Moon" "Horses" "The" "Band" "(\"The" "Brown" "Album\")" "Legend:" "The" "Best" "of" "Bob" "Marley" "and" "The" "Wailers" "A" "Love" "Supreme" "It" "Takes" "a" "Nation" "of" "Millions" "to" "Hold" "Us" "Back" "At" "Fillmore" "East" "Here's" "Little" "Richard" "Bridge" "Over" "Troubled" "Water" "Greatest" "Hits" "Meet" "The" "Beatles!" "The" "Birth" "of" "Soul" "Electric" "Ladyland" "Elvis" "Presley" "Songs" "in" "the" "Key" "of" "Life" "Beggars" "Banquet" "Chronicle:" "The" "20" "Greatest" "Hits" "Trout" "Mask" "Replica" "Greatest" "Hits" "Appetite" "for" "Destruction" "Achtung" "Baby" "Sticky" "Fingers" "Back" "to" "Mono" "(1958-1969)" "Moondance" "Kid" "A" "Off" "the" "Wall" "[Led" "Zeppelin" "IV]" "The" "Stranger" "Graceland" "Superfly" "Physical" "Graffiti" "After" "the" "Gold" "Rush" "Star" "Time" "Purple" "Rain" "Back" "in" "Black" "Otis" "Blue:" "Otis" "Redding" "Sings" "Soul" "Led" "Zeppelin" "II" "Imagine" "The" "Clash" "Harvest" "Axis:" "Bold" "as" "Love" "I" "Never" "Loved" "a" "Man" "the" "Way" "I" "Love" "You" "Lady" "Soul" "Born" "in" "the" "U.S.A." "The" "Wall" "At" "Folsom" "Prison" "Dusty" "in" "Memphis" "Talking" "Book" "Goodbye" "Yellow" "Brick" "Road" "20" "Golden" "Greats" "Sign" "\"Peace\"" "the" "Times" "40" "Greatest" "Hits" "Bitches" "Brew" "Tommy" "The" "Freewheelin'" "Bob" "Dylan" "This" "Year's" "Model" "There's" "a" "Riot" "Goin'" "On" "Odessey" "and" "Oracle" "In" "the" "Wee" "Small" "Hours" "Fresh" "Cream" "Giant" "Steps" "Sweet" "Baby" "James" "Modern" "Sounds" "in" "Country" "and" "Western" "Music" "Rocket" "to" "Russia" "Portrait" "of" "a" "Legend" "1951-1964" "Hunky" "Dory" "Aftermath" "Loaded" "The" "Bends" "If" "You" "Can" "Believe" "Your" "Eyes" "and" "Ears" "Court" "and" "Spark" "Disraeli" "Gears" "The" "Who" "Sell" "Out" "Out" "of" "Our" "Heads" "Layla" "and" "Other" "Assorted" "Love" "Songs" "Late" "Registration" "At" "Last!" "Sweetheart" "of" "the" "Rodeo" "Stand!" "The" "Harder" "They" "Come" "Raising" "Hell" "Moby" "Grape" "Pearl" "Catch" "a" "Fire" "Younger" "Than" "Yesterday" "Raw" "Power" "Remain" "in" "Light" "Marquee" "Moon" "Paranoid" "Saturday" "Night" "Fever:" "The" "Original" "Movie" "Sound" "Track" "The" "Wild," "the" "Innocent" "&" "the" "E" "Street" "Shuffle" "Ready" "to" "Die" "Slanted" "and" "Enchanted" "Greatest" "Hits" "Tim" "The" "Chronic" "Rejuvenation" "Parallel" "Lines" "Live" "at" "the" "Regal" "A" "Christmas" "Gift" "for" "You" "From" "Phil" "Spector" "Gris-Gris" "Straight" "Outta" "Compton" "Aja" "Surrealistic" "Pillow" "Deja" "vu" "Houses" "of" "the" "Holy" "Santana" "Darkness" "on" "the" "Edge" "of" "Town" "Funeral" "The" "B" "52's" "/" "Play" "Loud" "The" "Low" "End" "Theory" "Moanin'" "in" "the" "Moonlight" "Pretenders" "Paul's" "Boutique" "Closer" "Captain" "Fantastic" "and" "the" "Brown" "Dirt" "Cowboy" "Alive!" "Electric" "Warrior" "The" "Dock" "of" "the" "Bay" "OK" "Computer" "1999" "The" "Very" "Best" "of" "Linda" "Ronstadt" "Let's" "Get" "It" "On" "Imperial" "Bedroom" "Master" "of" "Puppets" "My" "Aim" "Is" "True" "Exodus" "Live" "at" "Leeds" "The" "Notorious" "Byrd" "Brothers" "Every" "Picture" "Tells" "a" "Story" "Something/Anything?" "Desire" "Close" "to" "You" "Rocks" "One" "Nation" "Under" "a" "Groove" "The" "Anthology:" "1961-1977" "The" "Definitive" "Collection" "The" "Rolling" "Stones," "Now!" "Natty" "Dread" "Fleetwood" "Mac" "Red" "Headed" "Stranger" "The" "Immaculate" "Collection" "The" "Stooges" "Fresh" "So" "Buffalo" "Springfield" "Again" "Happy" "Trails" "From" "Elvis" "in" "Memphis" "Fun" "House" "The" "Gilded" "Palace" "of" "Sin" "Dookie" "Transformer" "Blues" "Breakers" "With" "Eric" "Clapton" "(\"The" "Beano" "Album\")" "Nuggets:" "Original" "Artyfacts" "From" "the" "First" "Psychedelic" "Era," "1965-1968" "Murmur" "The" "Best" "of" "Little" "Walter" "Is" "This" "It" "Highway" "to" "Hell" "The" "Downward" "Spiral" "Parsley," "Sage," "Rosemary" "and" "Thyme" "Bad" "Modern" "Times" "Wheels" "of" "Fire" "Dirty" "Mind" "Abraxas" "Tea" "for" "the" "Tillerman" "Ten" "Everybody" "Knows" "This" "Is" "Nowhere" "Wish" "You" "Were" "Here" "Crooked" "Rain" "Crooked" "Rain" "Tattoo" "You" "Proud" "Mary:" "The" "Best" "of" "Ike" "and" "Tina" "Turner" "New" "York" "Dolls" "Bo" "Diddley" "/" "Go" "Bo" "Diddley" "Two" "Steps" "From" "the" "Blues" "The" "Queen" "Is" "Dead" "Licensed" "to" "Ill" "Look-Ka" "Py" "Py" "Loveless" "New" "Orleans" "Piano" "War" "The" "Neil" "Diamond" "Collection" "American" "Idiot" "Nebraska" "Doolittle" "Paid" "in" "Full" "Toys" "in" "the" "Attic" "Nick" "of" "Time" "A" "Night" "at" "the" "Opera" "The" "Kink" "Kronikles" "Mr." "Tambourine" "Man" "Bookends" "The" "Ultimate" "Collection" "Mr." "Excitement!" "My" "Generation" "Howlin'" "Wolf" "Like" "a" "Prayer" "Can't" "Buy" "a" "Thrill" "Let" "It" "Be" "Run" "D.M.C." "Black" "Sabbath" "The" "Marshall" "Mathers" "LP" "All" "Killer" "No" "Filler!" "The" "Jerry" "Lee" "Lewis" "Anthology" "Freak" "Out!" "Live/Dead" "The" "Shape" "of" "Jazz" "to" "Come" "Automatic" "for" "the" "People" "Reasonable" "Doubt" "Low" "The" "Blueprint" "The" "River" "Complete" "&" "Unbelievable:" "The" "Otis" "Redding" "Dictionary" "of" "Soul" "Metallica" "(\"The" "Black" "Album\")" "Trans" "Europa" "Express" "Whitney" "Houston" "The" "Kinks" "Are" "The" "Village" "Green" "Preservation" "Society" "The" "Velvet" "Rope" "Stardust" "American" "Beauty" "Crosby," "Stills" "&" "Nash" "Tracy" "Chapman" "Workingman's" "Dead" "The" "Genius" "of" "Ray" "Charles" "Child" "Is" "Father" "to" "the" "Man" "Quadrophenia" "Paul" "Simon" "Psychocandy" "Some" "Girls" "The" "Beach" "Boys" "Today!" "Dig" "Me" "Out" "Going" "to" "a" "Go-Go" "Nightbirds" "The" "Slim" "Shady" "LP" "Mothership" "Connection" "Rhythm" "Nation" "1814" "Anthology" "of" "American" "Folk" "Music" "Aladdin" "Sane" "All" "That" "You" "Can't" "Leave" "Behind" "My" "Life" "Folk" "Singer" "Can't" "Get" "Enough" "The" "Cars" "Music" "of" "My" "Mind" "I'm" "Still" "in" "Love" "With" "You" "Los" "Angeles" "Anthem" "of" "the" "Sun" "Something" "Else" "by" "The" "Kinks" "Call" "Me" "Talking" "Heads:" "77" "The" "Basement" "Tapes" "White" "Light/White" "Heat" "Kick" "Out" "the" "Jams" "Songs" "of" "Love" "and" "Hate" "Meat" "Is" "Murder" "We're" "Only" "in" "It" "for" "the" "Money" "The" "College" "Dropout" "Weezer" "(Blue" "Album)" "Master" "of" "Reality" "Coat" "of" "Many" "Colors" "Fear" "of" "a" "Black" "Planet" "John" "Wesley" "Harding" "Grace" "Car" "Wheels" "on" "a" "Gravel" "Road" "Odelay" "A" "Hard" "Day's" "Night" "Songs" "for" "Swingin'" "Lovers!" "Willy" "and" "the" "Poor" "Boys" "Blood" "Sugar" "Sex" "Magik" "The" "Sun" "Records" "Collection" "Nothing's" "Shocking" "MTV" "Unplugged" "in" "New" "York" "The" "Miseducation" "of" "Lauryn" "Hill" "Damn" "the" "Torpedoes" "The" "Velvet" "Underground" "Surfer" "Rosa" "Back" "Stabbers" "Burnin'" "Amnesiac" "Pink" "Moon"]))
-
-(def genre-gen (gen/elements ["Electronic" "Ambient" "Ambient dub" "Dark ambient" "Drone music" "Space music" "Illbient" "Psybient" "Isolationism" "Asian Underground" "Breakbeat" "Acid breaks" "Baltimore club" "Big beat" "Broken beat" "Florida breaks" "Nu-funk" "Miami bass" "Jersey club" "Nu skool breaks" "Disco" "Afro / Cosmic disco" "Disco polo" "Euro disco" "Italo disco" "Nu-disco" "Space disco" "Downtempo" "Acid jazz" "Chill-out" "New-age music" "Space music" "Trip hop" "Drum and bass" "Liquid funk" "Neurofunk" "Neurohop" "Jump-up" "Darkstep" "Drumstep" "Extreme Ambient" "Funkstep" "Hardstep" "EDM (electronic dance music)" "Sambass" "Techstep" "Dub" "Ambient dub" "Dancehall" "Dub poetry" "Dub reggae" "Dub techno" "Dubstep" "Dubtronica" "Electro music" "Electro Swing" "Freestyle music" "Electroacoustic music" "Acousmatic music" "Musique concrète" "Electronic rock" "Alternative dance" "Indietronica" "Coldwave" "Dance-punk" "Dark wave" "Electroclash" "Electronicore" "Electropunk" "Ethereal wave" "Krautrock" "Minimal wave" "New rave" "Nu-gaze" "Space rock" "Synthpop" "Electronica" "Berlin School" "Dubtronica" "Ethnic electronica" "Folktronica" "Funktronica" "Laptronica" "Livetronica" "Trap" "Future bass" "Hardcore" "Gabba" "4-beat" "Breakbeat hardcore" "Bouncy techno" "Breakcore" "Hardbass" "Russian Hardbass" "Digital hardcore" "Darkcore" "Happy hardcore" "Mákina" "Frenchcore" "Speedcore" "Terrorcore" "UK hardcore" "Uptempo" "Doomcore" "Hardstyle" "Dubstyle" "Jumpstyle" "Lento violento" "Hi-NRG" "Eurobeat" "Eurodance" "Bubblegum dance" "Italo dance" "House music" "Acid house" "Ambient house" "Balearic beat" "Chicago house" "Deep house" "Future house" "Bass house" "G house" "Tropical house" "Diva house" "Electro house" "Big room house" "Complextro" "Fidget house" "Dutch house" "Moombahton" "Moombahcore" "French house" "Funky house" "Garage house" "Ghetto house" "Ghettotech" "Hardbag" "Hard house" "Car music" "Hard dance" "Hard NRG" "Hip house" "Italo house" "Jazz house" "Kidandali" "Kwaito" "Latin house" "Microhouse/Minimal house" "New beat" "Outsider house" "Progressive house" "Rara tech" "Tech house" "Tribal house" "Trival" "Witch house" "Industrial music" "Aggrotech" "Cybergrind" "Electro-industrial" "Dark electro" "Electronic body music" "Futurepop" "Industrial metal" "Industrial rock" "Japanoise" "Neue Deutsche Härte" "Power electronics" "Death industrial" "Power noise" "IDM" "Glitch" "Glitch Hop" "Wonky" "Jungle" "Darkcore jungle" "Raggacore" "Post-disco" "Boogie" "Electropop" "Chillwave" "Vaporwave" "Hardvapour" "Dance-pop" "Dance-rock" "Techno" "Acid techno" "Detroit techno" "Dub techno" "Free tekno" "Minimal techno" "Nortec" "Tecno brega" "Techdombe" "Trance music" "Acid trance" "Balearic trance" "Dream trance" "Euro-trance" "Hard trance" "Nitzhonot" "Psychedelic trance" "Full on" "Suomisaundi" "Goa trance" "Progressive trance" "Tech trance" "Uplifting trance" "Vocal trance" "Chill" "Chillstep" "UK garage" "2-step garage" "Dubstep" "Deathstep" "Brostep" "Chillstep" "Drumstep" "Reggaestep" "Trapstep" "Metalstep" "Club" "Breakstep" "Future garage" "Grime" "Grindie" "Speed garage" "Bassline/4x4 garage" "UK funky" "Nightcore" "Nightstep" "Video game music" "Chiptune" "Bitpop" "Game Boy music" "Shiny" "Skweee" "Nintendocore" "Tetris music" "American folk revival" "Anti-folk" "British folk revival" "Celtic music" "Chalga" "Contemporary folk" "Filk music" "Folk rock" "Folktronica" "Freak folk" "Indie folk" "Industrial folk" "Neofolk" "Progressive folk" "Protest song" "Psychedelic folk" "Singer-songwriter movement" "Skiffle" "Sung poetry" "Cowboy/Western music" "Alternative hip hop" "Australian hip hop" "Bongo Flava" "Boom bap" "British hip hop" "Chap hop" "Chopper rap" "Christian hip hop" "Cloud rap" "Conscious hip hop" "Crunk" "Crunkcore" "Drill" "Electro music" "Emo hip hop" "Experimental hip hop" "G-funk" "Ghetto house" "Ghettotech" "Golden age hip hop" "Grime" "Hardcore hip hop" "Hip house" "Hiplife" "Hip pop" "Hyphy" "Industrial hip hop" "Instrumental hip hop" "Jazz rap" "Jersey club" "Kwaito" "Lyrical hip hop" "Low Bap" "Merenrap" "Motswako" "Mumble rap" "Nerdcore" "New jack swing" "New school hip hop" "Old school hip hop" "Political hip hop" "Ragga" "Reggaeton" "Snap" "Jerkin'" "Trap" "Urban Pasifika" "Emo rap" "K-rap" "J-rap" "Nederhop (Dutch rap)" "Urban street" "Horrorcore"]))
-
-(def album-gen (gen/tuple artist-gen
- (gen/vector album-name-gen
- 2 5)
- genre-gen
- (gen/vector genre-gen
- 2 5)))
-
-
-
-(defn get-albums-xs
- []
- (map (fn [[artist album-xs genre sub-genre-xs] year id]
- (cs/join \,
- [id
- (str year)
- (cs/join \space
- album-xs)
- genre
- (str "\""
- (cs/join \,
- sub-genre-xs)
- "\"")]))
- (gen/sample-seq album-gen)
- (mapcat (fn [year]
- (repeat (rand-nth (range 10 15))
- year))
- (drop 2040 (range)))
- (drop 501 (range))))
diff --git a/src/icw/data/process.clj b/src/icw/data/process.clj
deleted file mode 100644
index 31e76f4..0000000
--- a/src/icw/data/process.clj
+++ /dev/null
@@ -1,247 +0,0 @@
-(ns icw.data.process
- (:require [clojure.string :as cs]
- [icw.java-interop.jdbc :as jdbc]
- [clojure.data.csv :as csv]
- [clojure.string :as cs]
- [icw.data.gen :as data-gen]))
-
-
-;; Reading and processing data from resources/data/albumlist.csv
-
-(defonce album-lines (drop 1
- (line-seq (clojure.java.io/reader
- "resources/data/albumlist.csv"))))
-
-(comment
- (first album-lines))
-
-;; Parsing
-
-(defn parse-line
- "Input
- line -> \"1,1967,Sgt. Pepper's Lonely Hearts Club Band,The Beatles,Rock,\"Rock & Roll, Psychedelic Rock\"
-
- Output
- [\"1\"
- \"1967\"
- \"Sgt. Pepper's Lonely Hearts Club BandThe Beatles\"
- \"The Beatles\"
- \"Rock\"
- [\"Rock & Roll\" \"Psychedelic Rock\"]"
- [line]
- )
-
-
-(comment
- (= (parse-line "1,1967,Sgt. Pepper's Lonely Hearts Club Band,The Beatles,Rock,\"Rock & Roll,Psychedelic Rock\"")
- ["1"
- "1967"
- "Sgt. Pepper's Lonely Hearts Club Band"
- "The Beatles"
- "Rock"
- ["Rock & Roll"
- "Psychedelic Rock"]]))
-
-(comment
- (take 2 (map parse-line album-lines)))
-
-
-(defn line-vec->line-map
- "xs -> [\"1\"
- \"1967\"
- \"Sgt. Pepper's Lonely Hearts Club BandThe Beatles\"
- \"The beatles\"
- \"Rock\"
- [\"Rock & Roll\"
- \"Psychedelic Rock\"]]
- Output
- {:number \"1\"
- :year \"1967\"
- :artist \"The beatles\"
- :album \"Sgt. Pepper's Lonely Hearts Club Band\"
- :genre \"Rock\"
- :subgenre-xs [\"Rock & Roll\"
- \"Psychedelic Rock\"]}"
- [xs]
- (let [[id year album artist genre subgenre-xs] xs]
- {:number id
- :year year
- :album album
- :artist artist
- :genre genre
- :subgenre subgenre-xs}))
-
-(comment
- (= (line-vec->line-map ["1"
- "1967"
- "Sgt. Pepper's Lonely Hearts Club Band"
- "The Beatles"
- "Rock"
- ["Rock & Roll"
- "Psychedelic Rock"]])
- {:number "1"
- :year "1967"
- :artist "The Beatles"
- :album "Sgt. Pepper's Lonely Hearts Club BandThe Beatles"
- :genre "Rock"
- :subgenre ["Rock & Roll"
- "Psychedelic Rock"]}))
-
-(comment
- (take 10
- (map #_FIXME
- (map #_FIXME
- album-lines))))
-
-
-(defn line-xs->album-xs
- [line-xs]
- ;; Use parse-line to convert list of strings to list of vectors
- ;; Use line-vec->line-map to convert list of vectors to list of map
- )
-
-(defn populate-db
- []
- (jdbc/init-db)
- (let [albums (line-xs->album-xs album-lines)]
- (doseq [album albums]
- (jdbc/insert! (update-in album
- [:subgenre]
- #(cs/join "," %))))))
-
-;; Check http://localhost:6789/albums
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Some more exploration with sequences
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-(defn line-xs->rock-albums-xs
- [line-xs]
- (comment (map #_FIXME
- (filter #_FIXME
- #_FIXME))))
-
-(comment (= (take 5 (line-xs->rock-albums-xs album-lines))
- '("Sgt. Pepper's Lonely Hearts Club Band"
- "Pet Sounds"
- "Revolver"
- "Highway 61 Revisited"
- "Exile on Main St.")))
-
-
-(defn line-xs->albums-xs-before
- "Lists all albums before 'year'"
- [line-xs year]
- (comment (filter #_FIXME
- (map #_FIXME
- #_FIXME))))
-
-(comment (= (take 5 (map (juxt :year :album)
- (line-xs->albums-xs-before 1987
- album-lines)))
- '([1967 "Sgt. Pepper's Lonely Hearts Club Band"]
- [1966 "Pet Sounds"]
- [1966 "Revolver"]
- [1965 "Highway 61 Revisited"]
- [1965 "Rubber Soul"])))
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Some more exploration with reduce
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-(defn artists-with-range
- "Artists who have most genres"
- [line-xs]
- (map #_FIXME
- (sort-by #_FIXME
- (reduce #_FIXME
- {}
- (map #_FIXME
- line-xs)))))
-
-(comment (= (take 5 (artists-with-range album-lines))
- '("Various Artists"
- "Bob Dylan"
- "Ray Charles"
- "Muddy Waters"
- "Talking Heads")))
-
-
-
-(defn find-popular-year
- ;; Find top years for album releases
- [line-xs]
- (sort-by #_FIXME
- (reduce #_FIXME
- {}
- (map #_ME
- line-xs))))
-
-(comment (= (take 5 (find-popular-year album-lines))
- '(["1970" 26]
- ["1972" 24]
- ["1973" 23]
- ["1969" 22]
- ["1971" 21])))
-
-(defn find-popular-artists
- [line-xs]
- )
-
-
-(comment (= (find-popular-artists album-lines)
- '(["The Beatles" 10]
- ["Bob Dylan" 10]
- ["The Rolling Stones" 9]
- ["Bruce Springsteen" 7]
- ["The Who" 7])))
-
-
-;; We can transform data a great deal with just map, reduce and filter
-
-;; Generally there are three patterns
-;; Seq of N -> Seq of N (map)
-;; Seq of N -> Seq of M (N > M) (filter)
-;; Seq of N -> Any data structure (reduce)
-
-;; This is great but what about lazy sequences all we processed till now was in-memory data
-
-
-;; A stream from future timeline from 2040
-data-gen/get-albums-xs
-
-;; DONT evaluate the function in REPL it's a lazy sequence.
-;; It's a list of albums generated from 2040 till infinity
-
-(take 10 (data-gen/get-albums-xs))
-
-
-;; Let's some of the functions we created till now
-(take 10 (line-xs->rock-albums-xs (data-gen/get-albums-xs)))
-
-;; We can use line-xs->albums-xs-before to just get limited set
-
-;; Right?
-
-(comment
- ;; This will evaluate for infinity since filter does not stop evaluation
- ;; We will just get infinite list of nils after year 2045
- (line-xs->albums-xs-before (data-gen/get-albums-xs)
- 2045))
-
-
-;; https://clojure.org/api/cheatsheet
-;; Espeically look for seq in and seq out
-
-(#_FIXME identity
- (line-xs->albums-xs-before (data-gen/get-albums-xs)
- 2045))
-
-;; Try applying functions we have created till now
-
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; END of chapter 1
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-;; Jump back to icw.core
diff --git a/src/icw/data/process_faster.clj b/src/icw/data/process_faster.clj
deleted file mode 100644
index 9197ffb..0000000
--- a/src/icw/data/process_faster.clj
+++ /dev/null
@@ -1,383 +0,0 @@
-(ns icw.data.process-faster
- (:require [icw.java-interop.jdbc :as jdbc]
- [clojure.string :as cs]
- [icw.data.process :as idp]))
-
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Concurrency
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-;; A way to get safe access to limited resources by multiple
-;; actors/threads
-
-;; There are multiple paradigms models to do concurrency
-
-;; Locks are one way of doing it
-
-;; Most accurate Visualization of using locks
-;; https://twitter.com/valarauca1/status/921542529962557440
-
-
-;; Clojure does an amazing job where there is immutable data by means of
-;; persistant data structures provided by Clojure core
-
-;; But what about mutating things?
-
-;; There are four ways,
-
-;; vars
-;; refs
-;; agents
-;; atoms
-
-
-;; But first a brief introduction to running things in different threads
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Future
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-
-;; This will run on a different thread
-(future 1)
-
-;; This will block and wait till it's complete
-(deref (future 1))
-
-;; A short hand for deref
-@(future 1)
-
-(def f (future (println "hello")
- 1))
-
-;; Guess the output of second deref
-@f
-@f
-
-;; What's the point of deref then?
-
-;; Deref tries to get current value it
-;; works on multiple things alongwith future
-
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; vars
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-(def x)
-
-(def x 1)
-
-;; Usually vars are static
-
-;; But they can be dynamic and have different value per thread
-
-(def ^:dynamic x 1)
-
-x
-
-(comment (binding [x 10]
- (println x)
- (future (println x))))
-
-;; There are several reasons why dynamic variables are a bad idea
-;; More on that later
-
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; refs
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-;; Refs ensure safe mutation of shared state using STM
-
-;; STM to me looks something like this
-
-;; https://vimeo.com/106226560#t=10s
-
-;; dosync is macro to start transaction
-
-;; ref-set sets it to a value
-
-;; alter runs a function on the value
-
-;; Values held by refs must be immutable preferably clojure persistent
-;; structures
-
-(def a-set (ref []))
-(def b-set (ref []))
-
-(comment
- (doseq [n (range 10)]
- (future (dosync
- (println "Transaction - " n)
- (ref-set a-set n)
- (Thread/sleep (rand-int 20))
- (ref-set b-set n)))))
-
-;; Why are there so many prints for just one run of 10 threads?
-[@a-set @b-set]
-
-
-(def a-alter (ref []))
-(def b-alter (ref []))
-
-(comment
- (doseq [n (range 10)]
- (future
- (dosync
- (println "Transaction - " n)
- (alter a-alter conj n)
- (Thread/sleep (rand-int 20))
- (alter b-alter conj n)))))
-
-[@a-alter @b-alter]
-
-;; There is commute as well but we will not into details
-
-;; Exercise
-
-;; We want to keep record of last five database insertions that failed
-
-(comment
- (let [failures-ref (ref [])
- failure-num (ref 0)]
- (doseq [n (range 10)]
- (future (let [failure? (> (rand-int 100)
- 30)]
- (when failure?
- (dosync
- ;; Just to add randomness
- (Thread/sleep (rand-int 20))
-
- (#_FIME failures-ref
- #_FIXME
- {:thread-id (str "Thread-" n)
- :failure-num @failure-num})
-
- (#_FIME failure-num #_FIXME)
- (when (> (count @failures-ref) 5)
- (#_FIXME failures-ref
- (into []
- (drop (- (count @failures-ref)
- 5)
- @failures-ref)))))))))
- (Thread/sleep 1000)
- (println @failures-ref)
- (not= @failures-ref [])))
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; agent
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-(def agent-a (agent 1))
-
-(send agent-a inc)
-@agent-a
-
-(comment
- (send agent-a (fn [n]
- (/ n 0)))
-
- (agent-error agent-a)
- (restart-agent agent-a 1)
-
- @agent-a
- (send agent-a inc))
-
-;; Agents are part of STM and send is parked work unless transaction is
-;; complete
-
-(do (future (dosync (send agent-a inc)
- (Thread/sleep 400)))
- (println "1. Printing value of agent - " @agent-a)
- (Thread/sleep 400)
- (println "2. Printing value of agent - " @agent-a))
-
-;; Agents are a great way to do async work on a value
-;; Another good use case is to convert an thread unsafe API into a
-;; thread safe one
-
-(comment
- (defn thread-unsafe-api
- [a x]
- (println "Thread unsafe API - " x)
- x)
-
- (def api-agent (agent "Message"))
-
- (send api-agent thread-unsafe-api "New message"))
-
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Atoms
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-;; Atoms are also meant to hold immutable values. Preferably Clojure's
-;; persistent data structures
-
-(def state (atom {}))
-
-;; You can change the information of an atom using swap! or reset!
-
-(swap! state assoc :a 1)
-
-;; You can set a value using reset!
-
-(reset! state {:a 2})
-
-;; Derefing the state will give you the current value
-@state
-
-;; Values changed in swap! are atomic if there is a conflict the swap!
-;; is retried
-
-;; Atoms are great way of doing sychrnous changes to shared state
-
-;; Atoms are used as a cache as well sometimes but be aware!
-;; Atoms do not provide a way to limit memory usage. It's up to the user
-
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Promise
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-;; There are some more constructs which help co-ordiate between threads
-
-;; Deliver works only once on a promise
-;; Deref works on promise as well
-(let [p1 (promise)]
- (future (Thread/sleep (rand-int 100))
- (deliver p1 "Thread 1"))
-
- (future (Thread/sleep (rand-int 100))
- (deliver p1 "Thread 2"))
-
- @p1)
-
-;; Promises are good to collect the first result from multiple threads/sources
-
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Delay
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-;; A very subtle difference than future. The body runs only after it's
-;; deref and it only runs ons
-
-;; How many times "hello" will be printed?
-(let [a (delay (println "hello")
- 1)]
- @a
- @a)
-
-;; Compared to delay check when 'hello' is printed
-(let [a (future (println "hello")
- 1)]
- @a
- @a)
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Rock paper scissors
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-(def BEATS {:rock :scissors, :paper :rock, :scissors :paper})
-
-(defn judge
- [[name-1 move-1] [name-2 move-2]]
- (cond
- (= move-1 move-2) ::no-one
- (= move-2 (BEATS move-1)) name-1
- :else name-2))
-
-(defn run-a-turn
- [game-room]
- (let [[player-1 player-2] (keys (:players game-room))
- winner (judge [player-1 (rand-nth (keys BEATS))]
- [player-2 (rand-nth (keys BEATS))])]
- (if-not (= ::no-one winner)
- (update-in game-room [:players winner] inc)
- game-room)))
-
-
-;;; Using agent
-
-(defn create-a-room
- [name-1 name-2 room-name]
- (agent {:players {name-1 0
- name-2 0}
- :name room-name}))
-
-(defn run-game
- [room-a]
- (future (loop []
- (Thread/sleep 10)
- ;; room-a is an agent we want to run a turn after every 10
- ;; msecs
- (#_FIXME room-a #_FIXME)
- (recur))))
-
-
-(defn run-multiple-games
- "Run multiple games of rock-paper-scissors
- On halt stop running the games and declare winners"
- [n]
- (let [game-rooms (map #(create-a-room "player-1"
- "player-2"
- (str "room-" %))
- (range n))
- running-games (doall (map (fn [room]
- (run-game room))
- game-rooms))]
-
-
- (Thread/sleep 5000)
-
- (doseq [game running-games]
- (future-cancel game))
-
- (map (fn [a]
- [(:name @a)
- (-> (sort-by (comp - second)
- (:players @a))
- first
- first)])
- game-rooms)))
-
-
-;; Take home task modify run-multiple-games to count number of turns
-;; that happened in each game
-
-
-;; Coming back to populate-db
-
-
-(defn populate-db
- []
- (jdbc/init-db)
-
- (with-open [rdr (clojure.java.io/reader "resources/data/albumlist.csv")]
- (let [lines (line-seq rdr)
- albums (idp/line-xs->album-xs idp/album-lines)]
- (doseq [album albums]
- ;; Running it in another thread will help
- (jdbc/insert! (update-in album
- [:subgenre]
- #(cs/join "," %)))))))
-
-
-(comment (time (populate-db)))
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Take home exercise
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Make sure all the `jdbc/insert!` succeed
-
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Caveats
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-;; Limited threadpool for agent and future
-
-;; shutdown-agents needs to be called while exiting a service
diff --git a/src/icw/java_interop/intro.clj b/src/icw/java_interop/intro.clj
deleted file mode 100644
index c4175ce..0000000
--- a/src/icw/java_interop/intro.clj
+++ /dev/null
@@ -1,111 +0,0 @@
-;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/intro.org::*Preamble][Preamble:1]]
-(ns icw.java-interop.intro
- (:require [clojure.datafy :refer [datafy]]))
-;; Preamble:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/intro.org::*Simple%20method%20calls][Simple method calls:1]]
-(do
- ; Clojure strings are naturally Java String instances
- (def sample-string "Hello, IN/Clojure!")
-
- (.length sample-string)
- ; OR
- (. sample-string length)
-
- (.toUpperCase sample-string)
- ; OR
- (. sample-string toUpperCase)
-
- (ns-unmap *ns* 'sample-string)
- )
-;; Simple method calls:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/intro.org::*Constructing,%20and%20property%20access][Constructing, and property access:1]]
-(do
-
- ;; Constructor - note the dot in Point.
- (def sample-point (java.awt.Point. 10 20))
- ; OR
- (def sample-point (new java.awt.Point 10 20))
-
- ;; Member access
- (.-x sample-point)
- ; OR
- (. sample-point -x)
-
- (.-y sample-point)
- ; OR
- (. sample-point -y)
-
- ; Assignment
- (set! (. sample-point y) 50)
-
- ;; Multiple, probably side-effecting, serial actions
- (doto sample-point
- (-> .-x println)
- (-> .-y println)) ; The doto expression evaluates to the
- ; supplied object. It may have mutated along the way within the doto.
-
- ; Bean there, done that?
- (:x (bean sample-point))
-
- (ns-unmap *ns* 'sample-point)
- )
-;; Constructing, and property access:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/intro.org::*Static%20access][Static access:1]]
-(do
-
- ;; Static methods
- (System/getProperty "java.vm.version")
-
- ;; Static values
- Math/PI
-
- ; Threading-equivalent
- (.. System getProperties (get "os.name")))
-;; Static access:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/intro.org::*Building%20fluidly][Building fluidly:1]]
-(do
- (doto (java.util.HashMap.)
- (.put :a :A)
- (.put :b :B))
-
- (doto (java.util.HashSet.)
- (.add :a)
- (.add :b)
- (.add :a))
-
- (doto (java.util.ArrayList.)
- (.add 1)
- (.add 2)))
-;; Building fluidly:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/intro.org::*Objects][Objects:1]]
-(clojure.pprint/pprint (do (datafy "hello world")))
-;; Objects:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/intro.org::*Objects][Objects:2]]
-(comment (clojure.pprint/pprint (do (datafy sample-point))))
-;; Objects:2 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/intro.org::*Objects][Objects:3]]
-(comment (clojure.pprint/pprint (do (bean sample-point))))
-;; Objects:3 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/intro.org::*Objects][Objects:4]]
-(clojure.pprint/pprint (do (bean {:a :A :b :B})))
-;; Objects:4 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/intro.org::*Class][Class:1]]
-(clojure.pprint/pprint (do (datafy java.awt.Point)))
-;; Class:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/intro.org::*Class][Class:2]]
-(clojure.pprint/pprint (do (datafy java.util.Collections)))
-;; Class:2 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/intro.org::*Namespaces][Namespaces:1]]
-(clojure.pprint/pprint (do (datafy *ns*)))
-;; Namespaces:1 ends here
diff --git a/src/icw/java_interop/jdbc.clj b/src/icw/java_interop/jdbc.clj
deleted file mode 100644
index 24acd37..0000000
--- a/src/icw/java_interop/jdbc.clj
+++ /dev/null
@@ -1,76 +0,0 @@
-(ns icw.java-interop.jdbc
- (:require [clojure.data.csv :as csv]
- [clojure.java.io :as io]
- [clojure.string :as string]
- [clojure.java.jdbc :as jdbc])
- (:import [com.zaxxer.hikari HikariConfig HikariDataSource]
- [com.zaxxer.hikari.pool HikariPool]))
-
-(def hsqldb {:dbtype "hsqldb"
- :dbname "albums"})
-(def albums-drop-table-ddl
- (jdbc/drop-table-ddl :albums {:conditional? true}))
-
-(def albums-table-create-ddl
- (jdbc/create-table-ddl
- :albums
- [[:number :int "PRIMARY KEY"]
- [:year :int]
- [:album "varchar(128)"]
- [:artist "varchar(128)"]
- [:genre "varchar(128)"]
- [:subgenre "varchar(128)"]]
- {:conditional? true}))
-
-(defn db-drop-albums-table! [db-spec]
- (jdbc/db-do-commands
- db-spec
- [albums-drop-table-ddl]))
-
-(defn db-create-albums-table! [db-spec]
- (jdbc/db-do-commands
- db-spec
- [albums-table-create-ddl
- "CREATE INDEX year_idx ON albums (year)"]))
-
-(defn db-load! [db-spec table-name data]
- (jdbc/insert-multi! db-spec
- table-name
- data))
-
-(defn init-db
- []
- (db-drop-albums-table! hsqldb)
- (db-create-albums-table! hsqldb))
-
-(defn insert!
- [row]
- (Thread/sleep (rand-int 20))
- (jdbc/insert! hsqldb
- :albums
- row))
-
-(defn list-albums
- []
- (jdbc/query hsqldb
- ["SELECT * from albums;"]))
-
-
-(do (db-drop-albums-table! hsqldb)
- (db-create-albums-table! hsqldb))
-
-(comment
- (jdbc/query hsqldb ["SELECT count(*) from albums limit 1"])
-
- (defonce hc (HikariConfig.))
- (doto hc
- (.setJdbcUrl "jdbc:hsqldb:albums"))
- (defonce hds (HikariDataSource. hc))
- (let [conn (.getConnection hds)
- stmt (.createStatement conn)
- res (.executeQuery stmt "SELECT * from albums limit 5")]
- (jdbc/result-set-seq res))
-
- (let [conn (.getConnection hds)
- prepped-stmt (jdbc/prepare-statement conn "SELECT count(*) from albums")]
- (jdbc/query conn [prepped-stmt])))
diff --git a/src/icw/java_interop/types.clj b/src/icw/java_interop/types.clj
deleted file mode 100644
index a96c558..0000000
--- a/src/icw/java_interop/types.clj
+++ /dev/null
@@ -1,108 +0,0 @@
-;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*The%20Preamble][The Preamble:1]]
-(ns icw.java-interop.types
- (:require [icw.common :refer :all]
- [clojure.datafy :refer [datafy]])
- (:import [java.util ArrayList Collections]
- [java.util.concurrent Callable ForkJoinPool]))
-
-(defn java-interfaces [class-list]
- (->> class-list
- (map #(.getName %))
- (filter #(re-find #"^java\." %))))
-;; The Preamble:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*A%20core%20function][A core function:1]]
-(-> + class ancestors java-interfaces)
-;; A core function:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*Map][Map:1]]
-(-> {:a :A :b :B} class ancestors)
-;; Map:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*Set][Set:1]]
-(-> #{:a :A :b :B} class ancestors)
-;; Set:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*Vector][Vector:1]]
-(-> [1 2 3 4 5] class ancestors)
-;; Vector:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*List][List:1]]
-(-> (list 1 2 3 4 5) class ancestors)
-;; List:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*Record][Record:1]]
-(defrecord FooBarBaz [foo bar baz])
-(-> FooBarBaz ancestors)
-;; Record:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*Comparable][Comparable:1]]
-(let [a (ArrayList. [2 1 2 3 4 10 11 15 9])]
- (Collections/sort a <)
- ;; Hello, mutable-land!
- a)
-;; Comparable:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*Search][Search:1]]
-; You can directly use Clojure vectors with java.util.Collections
-(Collections/binarySearch [1 2 3 4 5] 3)
-;; Search:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*General%20ops][General ops:1]]
-; Collections works well with vectors and lists
-(Collections/min [1 2 3 4 5])
-(Collections/max '(1 2 3 4 5))
-(Collections/max [1 2 3 4 5])
-(Collections/reverse [1 2 3 4 5])
-;; General ops:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*General%20ops][General ops:2]]
-;; And, does it work with sets?
-(Collections/min #{3 4 1 7 10 Integer/MIN_VALUE})
-;; General ops:2 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*General%20ops][General ops:3]]
-;; What about maps?
-(Collections/min {1 :one 2 :two 3 :three})
-;; General ops:3 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*Threads][Threads:1]]
-; "Functions are first-class" should mean something, no?
-(let [f (fn [] (println "Hello, from a runnable function."))
- t (Thread. f)]
- ; Yes, IFn is Runnable!
- (.run t))
-;; Threads:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*Executors][Executors:1]]
-; Runnable is a sad little thing. We want something happy!
-(let [f (fn [] :woo-hoo!)
- p (ForkJoinPool/commonPool)
- ; Yes, IFn is Callable!
- res (.submit p ^Callable f)]
- ; Which means, we should be "get"ting results of our execution.
- (.get res))
-
-;; And the result is a... happy one! :woo-hoo!
-;; Executors:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*Records%20and%20Interfaces][Records and Interfaces:1]]
-(clojure.pprint/pprint (do (do
- "Yes, in Java, *everything* is an Object. So are our records"
- (defrecord FooRecord [foo bar]
- ; Which means, we make our toString a pretty one.
- Object
- (toString [_] (str "I am a holder of foo and bar. They are: " foo " and " bar)))
-
- ; Who are FooRecord's ancestors?
- (ancestors FooRecord)
-
- ; Let's invoke our over-ridden method above...
- (str (->FooRecord "foo" "bar"))
- ; OR
- (.toString (->FooRecord "foo" "bar")))))
-;; Records and Interfaces:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*Records%20and%20Interfaces][Records and Interfaces:2]]
-"I am a holder of foo and bar. They are: foo and bar"
-;; Records and Interfaces:2 ends here
diff --git a/src/icw/search/common.clj b/src/icw/search/common.clj
deleted file mode 100644
index 7cced12..0000000
--- a/src/icw/search/common.clj
+++ /dev/null
@@ -1,8 +0,0 @@
-;; [[file:~/github/intermediate-clojure-workshop/content/search/common.org::*Common%20Search%20Routines][Common Search Routines:1]]
-(ns icw.search.common
- (:import [org.apache.lucene.store Directory]
- [org.apache.lucene.analysis.standard StandardAnalyzer]))
-
-(defn standard-analyzer []
- (StandardAnalyzer.))
-;; Common Search Routines:1 ends here
diff --git a/src/icw/search/core.clj b/src/icw/search/core.clj
deleted file mode 100644
index e109daa..0000000
--- a/src/icw/search/core.clj
+++ /dev/null
@@ -1,34 +0,0 @@
-;; [[file:~/github/intermediate-clojure-workshop/content/search/core.org::*The%20Preamble][The Preamble:1]]
-(ns icw.search.core
- (:require [icw.search
- [common :refer :all]
- [writer :as w]
- [reader :as r]]))
-
-(defonce index (atom nil))
-
-(defn reset-index! []
- (when @index
- (.close @index)
- (reset! index nil)))
-
-(defn init! [docs tokenized-fields & [re-init?]]
-
- (when (or re-init? false)
- (reset-index!))
-
- (when (nil? @index)
- (let [d (w/directory)
- analyzer (standard-analyzer)
- iwc (w/index-writer-config analyzer)
- iw (w/index-writer d iwc)]
- (w/index! iw docs {:tokenized-fields tokenized-fields})
- (.close iw)
- (reset! index d))))
-
-(defn search [field query-term]
- (r/search @index field query-term (standard-analyzer)))
-
-(comment
- (search :subgenre "classic rock"))
-;; The Preamble:1 ends here
diff --git a/src/icw/search/reader.clj b/src/icw/search/reader.clj
deleted file mode 100644
index bea5eba..0000000
--- a/src/icw/search/reader.clj
+++ /dev/null
@@ -1,41 +0,0 @@
-;; [[file:~/github/intermediate-clojure-workshop/content/search/reader.org::*Code%20Template][Code Template:1]]
-(ns icw.search.reader
- (:require [icw.search.common :refer :all])
- (:import [org.apache.lucene.store Directory]
- [org.apache.lucene.analysis Analyzer]
- [org.apache.lucene.index IndexReader DirectoryReader]
- [org.apache.lucene.document Field Document]
- [org.apache.lucene.search Query IndexSearcher ScoreDoc]
- [org.apache.lucene.util QueryBuilder]))
-;; Code Template:1 ends here
-
-;; [[file:~/github/intermediate-clojure-workshop/content/search/reader.org::*Outline][Outline:1]]
-(defn ^IndexReader index-reader [^Directory directory])
-
-(defn ^IndexSearcher searcher [^Directory directory])
-
-(defn field->kv [^Field f])
-
-(defn doc->map [^Document doc])
-
-(defn ^Query query [^clojure.lang.Keyword field
- ^String term
- ^Analyzer analyzer])
-
-(defn score-docs->ids [^"[Lorg.apache.lucene.search.ScoreDoc;" score-docs])
-
-(defn doc-ids->docs [^IndexSearcher searcher doc-ids])
-
-; Create IndexSearcher given the index (Directory instance)
-; Create a Query object given the field, term and analyzer
-; .search on the IndexSearcher the generated Query
-; Get .scoreDocs from the response
-; ScoreDoc instances give you document-ids as handles
-; Using the document-ids, get the Document instances from the IndexSearcher
-; Convert the collection to maps with doc->map
-; Enjoy the convenience of Clojure's support for Iterable
-(defn search [^Directory directory
- ^clojure.lang.Keyword field
- ^String search-term
- ^Analyzer analyzer])
-;; Outline:1 ends here
diff --git a/src/icw/search/writer.clj b/src/icw/search/writer.clj
deleted file mode 100644
index 9ff0732..0000000
--- a/src/icw/search/writer.clj
+++ /dev/null
@@ -1,54 +0,0 @@
-;; [[file:~/github/intermediate-clojure-workshop/content/search/writer.org::*The%20Preamble][The Preamble:1]]
-(ns icw.search.writer
- (:require [icw.search.common :refer :all])
- (:import [org.apache.lucene.store Directory ByteBuffersDirectory]
- [org.apache.lucene.analysis.standard StandardAnalyzer]
- [org.apache.lucene.index IndexWriter IndexWriterConfig IndexOptions]
- [org.apache.lucene.document Field FieldType Document]))
-
-(defn index-writer-config [analyzer]
- (IndexWriterConfig. analyzer))
-
-(defn index-writer [directory index-writer-config]
- (IndexWriter. directory index-writer-config))
-
-(defn directory [] (ByteBuffersDirectory.))
-
-(defn field-type [{:keys [tokenize?]}]
- (doto (FieldType.)
- (.setIndexOptions IndexOptions/DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS)
- (.setStored true)
- (.setTokenized tokenize?)))
-
-(defn field [k v opts]
- (let [ft (field-type opts)
- v (str v)]
- (Field. (name k) v ft)))
-
-(defn map->doc [m {:keys [tokenized-fields]}]
- (let [ks (keys m)
- d (Document.)]
- (doseq [k ks]
- (.add d (field k
- (get m k)
- {:tokenize? (contains? tokenized-fields k)})))
- d))
-
-(defn index! [index-writer doc-maps opts]
- (println (str "Indexing " (count doc-maps) " documents."))
- (doseq [doc-map doc-maps]
- (.addDocument index-writer (map->doc doc-map opts))))
-
-(comment
- (def albums (:data/db.albums @icw.system/system))
- (first albums)
- (def d (directory))
- (def analyzer (standard-analyzer))
- (def iwc (index-writer-config analyzer))
- (def iw (index-writer d iwc))
- (def opts {:tokenized-fields #{:album :artist :genre :subgenre}})
- (index! iw albums opts)
- (.close iw)
- (map icw.search.reader/doc->map (icw.search.reader/search d :album "revolver" analyzer))
- )
-;; The Preamble:1 ends here
diff --git a/src/icw/system.clj b/src/icw/system.clj
deleted file mode 100644
index 09fd5f9..0000000
--- a/src/icw/system.clj
+++ /dev/null
@@ -1,58 +0,0 @@
-;; [[file:~/github/intermediate-clojure-workshop/content/system.org::*System][System:1]]
-(ns icw.system
- (:require [integrant.core :as ig]
- [icw.data :as data]
- [icw.search.core :as search]
- [icw.web.core :as web]
- [aleph.http :as http]
- [icw.data]))
-
-(def config
- {:data/db.albums {}
- :data/db {:album-data (ig/ref :data/db.albums)}
- :data/lucene {:album-data (ig/ref :data/db.albums)}
- :services/streams.twitter {}
- :services/streams.facebook {}
- :services/streams.linkedin {}
- :http/server {:port 6789, :handler (ig/ref :http/app)}
- :http/app {}})
-
-(defmethod ig/init-key :data/db.albums [_ _]
- (data/load-album-csv-file data/album-csv-file))
-
-(defmethod ig/init-key :data/db [_ _])
-
-(defmethod ig/init-key :data/lucene [_ {:keys [album-data]}]
- (search/init! album-data #{:album :artist :genre :subgenre}))
-
-(defmethod ig/init-key :services/streams.twitter [_ _])
-
-(defmethod ig/init-key :services/streams.facebook [_ _])
-
-(defmethod ig/init-key :services/streams.linkedin [_ _])
-
-(defmethod ig/init-key :http/server [_ {:keys [handler] :as opts}]
- (http/start-server handler (dissoc opts :handler)))
-(defmethod ig/halt-key! :http/server [_ server]
- (.close server))
-
-(defmethod ig/init-key :http/app [_ _]
- #'web/app)
-
-(defonce system (atom nil))
-
-(defn start! []
- (if (nil? @system)
- (do
- (reset! system (ig/init config))
- nil)
- (println "System already booted.")))
-
-(defn stop! []
- (when (some? @system)
- (ig/halt! @system)
- (reset! system nil)))
-
-(comment
- (start!))
-;; System:1 ends here
diff --git a/src/icw/web/core.clj b/src/icw/web/core.clj
deleted file mode 100644
index 30e8021..0000000
--- a/src/icw/web/core.clj
+++ /dev/null
@@ -1,42 +0,0 @@
-(ns icw.web.core
- (:require [compojure.core :as c]
- [aleph.http :as http]
- [ring.middleware
- [keyword-params :refer [wrap-keyword-params]]
- [params :refer [wrap-params]]
- [json :refer [wrap-json-response]]]
- [icw.web.handlers.albums :as albums]
- [icw.search.core :as search]))
-
-(defn wrap-body [response-body]
- {:status 200
- :body response-body})
-
-(def albums-context
- (c/context "/albums" [:as request]
- (c/GET "/" []
- (wrap-body
- (albums/list-albums)))
- (c/GET "/:id" [id]
- (wrap-body {:message (str "Here be your JSON of an album with id " id)}))))
-
-(def search-context
- (c/context "/search" [:as request]
- (c/GET "/:term" [term]
- (let [{:keys [field]} (:params request)
- field (or field "album")]
- (or
- (search/search (keyword field) term)
- (wrap-body {:message (str "Looking for " term " in field " field ", eh?")}))))))
-
-(c/defroutes app*
- albums-context
- search-context)
-
-(def app
- (c/routes
- (-> #'app*
- wrap-json-response
- wrap-keyword-params
- wrap-params)))
-;; Tying Together:1 ends here
diff --git a/src/icw/web/handlers/albums.clj b/src/icw/web/handlers/albums.clj
deleted file mode 100644
index 589cba6..0000000
--- a/src/icw/web/handlers/albums.clj
+++ /dev/null
@@ -1,9 +0,0 @@
-(ns icw.web.handlers.albums
- (:require [icw.java-interop.jdbc :as jdbc]))
-
-(defn list-albums
- []
- (let [d (jdbc/list-albums)]
- (if (seq d)
- {:albums (jdbc/list-albums)}
- {:message "Data is missing. Please populate the database."})))
diff --git a/src/user.clj b/src/user.clj
deleted file mode 100644
index 63d4d9d..0000000
--- a/src/user.clj
+++ /dev/null
@@ -1,11 +0,0 @@
-;; [[file:~/github/intermediate-clojure-workshop/content/user.org::*User%20namespace%20-%20defaults][User namespace - defaults:1]]
-(ns user
- (:require [clojure.pprint :as pp]
- [clojure.datafy :refer [datafy]]
- [integrant.core :as ig]
- [integrant.repl :refer [clear go halt prep init reset reset-all]]
- [icw.system :refer [start! stop!]]))
-
-(comment
- (start!))
-;; User namespace - defaults:1 ends here
diff --git a/start b/start
new file mode 100755
index 0000000..4f33c29
--- /dev/null
+++ b/start
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+
+./restart 1
diff --git a/workshop-app/.idea/runConfigurations/Webserver.xml b/workshop-app/.idea/runConfigurations/Webserver.xml
new file mode 100644
index 0000000..df91d43
--- /dev/null
+++ b/workshop-app/.idea/runConfigurations/Webserver.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/workshop-app/.idea/runConfigurations/WorkshoAppRemoteREPL.xml b/workshop-app/.idea/runConfigurations/WorkshoAppRemoteREPL.xml
new file mode 100644
index 0000000..d7843a7
--- /dev/null
+++ b/workshop-app/.idea/runConfigurations/WorkshoAppRemoteREPL.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/workshop-app/.idea/runConfigurations/WorkshopAppLocalREPL.xml b/workshop-app/.idea/runConfigurations/WorkshopAppLocalREPL.xml
new file mode 100644
index 0000000..a646e83
--- /dev/null
+++ b/workshop-app/.idea/runConfigurations/WorkshopAppLocalREPL.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/workshop-app/.idea/runConfigurations/WorkshopAppTestREPL.xml b/workshop-app/.idea/runConfigurations/WorkshopAppTestREPL.xml
new file mode 100644
index 0000000..51f63cc
--- /dev/null
+++ b/workshop-app/.idea/runConfigurations/WorkshopAppTestREPL.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/workshop-app/Inclojure workshop collection.postman_collection.json b/workshop-app/Inclojure workshop collection.postman_collection.json
new file mode 100644
index 0000000..ce063c1
--- /dev/null
+++ b/workshop-app/Inclojure workshop collection.postman_collection.json
@@ -0,0 +1,151 @@
+{
+ "info": {
+ "_postman_id": "1ffbb028-fcfa-4f2b-bb2a-6eac2c49d627",
+ "name": "Inclojure workshop collection",
+ "description": "Collection for inclojure 2020 workshop. This collection will assist us in building the REST API.",
+ "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
+ },
+ "item": [
+ {
+ "name": "Hello world",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": {
+ "raw": "http://localhost:65535?name=Joel&surname=Victor",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "65535",
+ "query": [
+ {
+ "key": "name",
+ "value": "Joel"
+ },
+ {
+ "key": "surname",
+ "value": "Victor"
+ }
+ ]
+ },
+ "description": "Query our hello world web server."
+ },
+ "response": []
+ },
+ {
+ "name": "Create a user",
+ "request": {
+ "method": "POST",
+ "header": [],
+ "url": {
+ "raw": "http://localhost:65535/joel?dob=2000-01-01",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "65535",
+ "path": [
+ "joel"
+ ],
+ "query": [
+ {
+ "key": "dob",
+ "value": "2000-01-01"
+ }
+ ]
+ },
+ "description": "Given a name and an age create an entry in the backend."
+ },
+ "response": []
+ },
+ {
+ "name": "Get details about a user",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": {
+ "raw": "http://localhost:65535/joel",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "65535",
+ "path": [
+ "joel"
+ ]
+ },
+ "description": "Get details for a given user."
+ },
+ "response": []
+ },
+ {
+ "name": "Update a user",
+ "request": {
+ "method": "PUT",
+ "header": [],
+ "url": {
+ "raw": "http://localhost:65535/joel?dob=2001-01-01",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "65535",
+ "path": [
+ "joel"
+ ],
+ "query": [
+ {
+ "key": "dob",
+ "value": "2001-01-01"
+ }
+ ]
+ },
+ "description": "For a given user update his dob."
+ },
+ "response": []
+ },
+ {
+ "name": "Delete a given user",
+ "request": {
+ "method": "DELETE",
+ "header": [],
+ "url": {
+ "raw": "http://localhost:65535/joel",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "65535",
+ "path": [
+ "joel"
+ ]
+ },
+ "description": "Delete the user. We don't want to be liable #gdpr."
+ },
+ "response": []
+ },
+ {
+ "name": "Invalid request",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": {
+ "raw": "http://localhost:65535/joel/",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "65535",
+ "path": [
+ "joel",
+ ""
+ ]
+ },
+ "description": "Any request with a trailing slash."
+ },
+ "response": []
+ }
+ ],
+ "protocolProfileBehavior": {}
+}
\ No newline at end of file
diff --git a/workshop-app/project.clj b/workshop-app/project.clj
new file mode 100644
index 0000000..472a4f6
--- /dev/null
+++ b/workshop-app/project.clj
@@ -0,0 +1,9 @@
+(defproject inclojure/workshop-app "0.1.0"
+ :dependencies [[org.clojure/clojure "1.10.1"]
+ [ring/ring "1.8.0"]
+ [compojure "1.6.1"]
+ [cheshire "5.9.0"]
+ [org.xerial/sqlite-jdbc "3.30.1"]]
+ :main workshop-app.core
+ :profiles {:test {:dependencies [[org.clojure/test.check "0.10.0"]]}}
+ :local-repo ".local-m2")
\ No newline at end of file
diff --git a/resources/data/albumlist.csv b/workshop-app/resources/data/albumlist.csv
similarity index 100%
rename from resources/data/albumlist.csv
rename to workshop-app/resources/data/albumlist.csv