implemented so many things...

This commit is contained in:
didier amyot committed 2018-09-09 12:20:12 -04:00
1 parent fd843a5425
commit bce64fc8f6
24 files changed
+734 -59

No files matched your search

+1
View File
@@ -16,3 +16,4 @@ full-stack
fixtures fixtures
node_modules node_modules
figwheel_server.log figwheel_server.log
.rebel_readline_history
+1
View File
@@ -1,4 +1,5 @@
FROM clojure FROM clojure
MAINTAINER Didier Amyot <didier.amyot@gmail.com>
# Set the working directory to /app # Set the working directory to /app
WORKDIR /app WORKDIR /app
+2 -1
View File
@@ -31,12 +31,13 @@ file should be self documented.
#### Server #### Server
* [clojure](https://clojure.org/) for programming language * [clojure](https://clojure.org/) for programming language
* [kafka](http://kafka.apache.org/) for message broker/database * [kafka](http://kafka.apache.org/) for message broker/database
* [datomic-free](https://www.datomic.com/) for transaction needs
#### In the browser #### In the browser
* [clojure script](https://clojurescript.org/) for programming language * [clojure script](https://clojurescript.org/) for programming language
* [sparks](https://aftertheflood.com/projects/sparks/) for trivial charts * [sparks](https://aftertheflood.com/projects/sparks/) for trivial charts
* [metricsgraphicsjs](http://metricsgraphicsjs.org/) for complex charts * [metricsgraphicsjs](http://metricsgraphicsjs.org/) for complex charts
* [dc-js](http://dc-js.github.io/dc.js/) for some charting.
## License ## License
+32
View File
@@ -0,0 +1,32 @@
FROM openjdk:11-jdk
MAINTAINER Didier Amyot <didier.amyot@gmail.com>
ENV DATOMIC_VERSION 0.9.5656
ADD https://my.datomic.com/downloads/free/${DATOMIC_VERSION} /tmp/datomic.zip
RUN unzip /tmp/datomic.zip && rm /tmp/datomic.zip
WORKDIR datomic-free-${DATOMIC_VERSION}
RUN cp config/samples/free-transactor-template.properties transactor.properties
ADD https://clojars.org/repo/slingshot/slingshot/0.12.2/slingshot-0.12.2.jar /tmp/slingshot-0.12.2.jar
RUN mv /tmp/slingshot-0.12.2.jar lib/
RUN sed "s/host=localhost/host=0.0.0.0/" -i transactor.properties
RUN mkdir /data
RUN sed "s/# data-dir=data/data-dir=\/data/" -i transactor.properties
VOLUME /data
RUN mkdir /log
RUN sed "s/# log-dir=log/log-dir=\/log/" -i transactor.properties
VOLUME /log
ADD start.sh ./
RUN chmod +x start.sh
EXPOSE 4334 4335 4336
CMD ["./start.sh"]
+4
View File
@@ -0,0 +1,4 @@
#!/usr/bin/env bash
sed "/host=0.0.0.0/a alt-host=${ALT_HOST:-127.0.0.1}" -i transactor.properties
exec bin/transactor transactor.properties
+32 -22
View File
@@ -4,7 +4,7 @@ version: '2'
services: services:
zoo1: zoo1:
image: zookeeper:3.4.9 image: zookeeper:3.4.9
restart: unless-stopped restart: "unless-stopped"
hostname: zoo1 hostname: zoo1
ports: ports:
- "2181:2181" - "2181:2181"
@@ -150,26 +150,36 @@ services:
depends_on: depends_on:
- zoo1 - zoo1
# skirmish # datomic
datomic-transactors:
skirmish-api: build: "datomic/"
image: skirmish restart: "unless-stopped"
command: ["lein", "run", "api-server"]
depends_on:
- kafka-topics-ui # basically, we need all kafka
environment:
PORT: 1602 # https://en.wikipedia.org/wiki/Euronext_Amsterdam
KAFKA_BOOTSTRAP_SERVERS: "kafka1:9092"
ports: ports:
- "1602:1602" - "4334:4334"
- "4335:4335"
- "4336:4336"
skirmish-exchange-core:
image: skirmish # skirmish itself
command: ["lein", "run", "exchange-core"]
depends_on: # skirmish-api:
- skirmish-api # basically, we need all kafka # build: .
environment: # command: ["lein", "run", "api-server"]
PORT: 1602 # https://en.wikipedia.org/wiki/Euronext_Amsterdam # depends_on:
KAFKA_BOOTSTRAP_SERVERS: "kafka1:9092" # - kafka-topics-ui # basically, we need all kafka
ports: # environment:
- "1602:1602" # PORT: 1602 # https://en.wikipedia.org/wiki/Euronext_Amsterdam
# KAFKA_BOOTSTRAP_SERVERS: "kafka1:9092"
# ports:
# - "1602:1602"
#
# skirmish-exchange-core:
# build: .
# command: ["lein", "run", "exchange-core"]
# depends_on:
# - skirmish-api # basically, we need all kafka
# environment:
# PORT: 1602 # https://en.wikipedia.org/wiki/Euronext_Amsterdam
# KAFKA_BOOTSTRAP_SERVERS: "kafka1:9092"
# ports:
# - "1602:1602"
+6
View File
@@ -3,8 +3,14 @@
[prone.middleware :refer [wrap-exceptions]] [prone.middleware :refer [wrap-exceptions]]
[ring.middleware.reload :refer [wrap-reload]])) [ring.middleware.reload :refer [wrap-reload]]))
(defn custom-dev-middleware [handler]
(fn [request]
(let [response (handler request)]
response)))
(defn wrap-middleware [handler] (defn wrap-middleware [handler]
(-> handler (-> handler
(wrap-defaults site-defaults) (wrap-defaults site-defaults)
custom-dev-middleware
wrap-exceptions wrap-exceptions
wrap-reload)) wrap-reload))
+9 -4
View File
@@ -4,9 +4,15 @@
:license {:name "Eclipse Public License" :license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"} :url "http://www.eclipse.org/legal/epl-v10.html"}
:resource-paths ["resources"
"target/cljsbuild"]
:dependencies [[org.clojure/clojure "1.9.0"] :dependencies [[org.clojure/clojure "1.9.0"]
[org.clojure/core.async "0.4.474"] [org.clojure/core.async "0.4.474"]
[org.clojure/math.combinatorics "0.1.4"] [org.clojure/math.combinatorics "0.1.4"]
[com.datomic/datomic-free "0.9.5697"]
[com.google.guava/guava "22.0"]
;; server ;; server
[aero "1.1.3"] [aero "1.1.3"]
@@ -15,8 +21,8 @@
[com.taoensso/nippy "2.14.0"] [com.taoensso/nippy "2.14.0"]
[ring-cors "0.1.12"] [ring-cors "0.1.12"]
[javax.servlet/javax.servlet-api "3.1.0"] [javax.servlet/javax.servlet-api "3.1.0"]
[org.apache.kafka/kafka-streams "1.1.0"]
[org.apache.kafka/kafka-clients "1.1.0"] ;; backends
[spootnik/kinsky "0.1.22"] [spootnik/kinsky "0.1.22"]
;;utils ;;utils
@@ -64,7 +70,6 @@
[:cljsbuild :builds :app :compiler :output-to]] [:cljsbuild :builds :app :compiler :output-to]]
:source-paths ["src/clj" "src/cljc"] :source-paths ["src/clj" "src/cljc"]
:resource-paths ["resources" "target/cljsbuild"]
:minify-assets :minify-assets
{:assets {:assets
@@ -94,7 +99,7 @@
:devcards :devcards
{:source-paths ["src/cljs" "src/cljc" "env/dev/cljs"] {:source-paths ["src/cljs" "src/cljc" "env/dev/cljs"]
:figwheel {:devcards true} :figwheel {:devcards false}
:compiler {:main "skirmish.wui.cards" :compiler {:main "skirmish.wui.cards"
:asset-path "js/devcards_out" :asset-path "js/devcards_out"
:output-to "target/cljsbuild/public/js/app_devcards.js" :output-to "target/cljsbuild/public/js/app_devcards.js"
+36 -4
View File
@@ -5,6 +5,9 @@
;; If set to true, orchestra will instrument all the spec'ed functions ;; If set to true, orchestra will instrument all the spec'ed functions
:instrument-spec true :instrument-spec true
:datomic
{:transactor-url "datomic:free://localhost:4334/skirmish"}
;; all the kafka configuration goes bellow. ;; all the kafka configuration goes bellow.
:kafka :kafka
{:bootstrap.servers #or [#env KAFKA_BOOTSTRAP_SERVERS "localhost:9092"] {:bootstrap.servers #or [#env KAFKA_BOOTSTRAP_SERVERS "localhost:9092"]
@@ -24,15 +27,44 @@
:max-body 524288 ;; 512kb, small but enough :max-body 524288 ;; 512kb, small but enough
:max-ws 524288} :max-ws 524288}
:ledger-server
{:port #long #or [#env LEDGER_SERVER_PORT 1604]
:ip "0.0.0.0"
:max-body 524288 ;; 512kb, small but enough
:max-ws 524288}
:wui :wui
{:port #long #or [#env WUI_PORT 3000]} {:port #long #or [#env WUI_PORT 3000]}
;; list of tradable securities ;; list of tradable securities
:securities :securities
[{:sec-id :foo :name "the foo company" :description "Sell names to developer."} [{:sec-id :dapm
{:sec-id :bar :name "bar international" :description "Steal company."} :currency :dapm
{:sec-id :toto :name "La compagnie des toto" :description "Serait trop long pour ecrire ici."} :symbol "ლ"
{:sec-id :tata :name "Unicorn startup" :description "ambition: conquerir le monde!"}] :name "didier amyot paper money"
:interest-rate 1.1 ;; 10% per day
:emitter "emitter publick key"
:description "The default currency."}
{:sec-id :foo
:currency :dapm
:name "the foo company"
:description "Sell names to developer."}
{:sec-id :bar
:currency :dapm
:name "bar international"
:description "Steal company."}
{:sec-id :toto
:currency :dapm
:name "La compagnie des toto"
:description "Serait trop long pour ecrire ici."}
{:sec-id :tata
:currency :dapm
:name "Unicorn startup"
:description "ambition: conquerir le monde!"}]
;; stupid parameters about benchmarking tools ;; stupid parameters about benchmarking tools
:benchmark { :benchmark {
+1 -1
View File
@@ -9,7 +9,7 @@
(defn -main [config exchange-apis] (defn -main [config exchange-apis]
(let [bookkeeper-config (:api-bookkeeper config) (let [bookkeeper-config (:api-bookkeeper config)
api-routes (create-routes-for-bookkeeper exchange-apis config)] api-routes (create-routes-for-bookkeeper exchange-apis config)]
(println "Running server with bellow config") (println "Running skirmish.bookkeeper.core server with bellow config\n" bookkeeper-config)
(println bookkeeper-config) (println bookkeeper-config)
(run-server (run-server
(-> api-routes api (wrap-cors :access-control-allow-origin #"http://localhost:3449" (-> api-routes api (wrap-cors :access-control-allow-origin #"http://localhost:3449"
+5
View File
@@ -1,4 +1,9 @@
(ns skirmish.broker.core) (ns skirmish.broker.core)
(def portfolio {:dapm {:amount 10}
:foo {:amount 5 :long-exposure 10 :short-exposure 7}
:bar {:amount 100 :long-exposure 10 :short-exposure 7}})
(defn -main [config exchange-apis]) (defn -main [config exchange-apis])
+3 -1
View File
@@ -5,6 +5,7 @@
[skirmish.wui.server] [skirmish.wui.server]
[skirmish.bookkeeper.core] [skirmish.bookkeeper.core]
[skirmish.exchange-gateway.core] [skirmish.exchange-gateway.core]
[skirmish.ledger.core]
[skirmish.middleware.core :refer [create-exchange-apis-for]] [skirmish.middleware.core :refer [create-exchange-apis-for]]
[aero.core :refer (read-config)] [aero.core :refer (read-config)]
[orchestra.spec.test :as st] [orchestra.spec.test :as st]
@@ -16,11 +17,12 @@
{:api-server skirmish.exchange-gateway.core/-main {:api-server skirmish.exchange-gateway.core/-main
:exchange-core skirmish.exchange.core/-main ;; should be skirmish.exchange.core/api-main :exchange-core skirmish.exchange.core/-main ;; should be skirmish.exchange.core/api-main
:api-bookkeeper skirmish.bookkeeper.core/-main :api-bookkeeper skirmish.bookkeeper.core/-main
:ledger skirmish.ledger.core/-main
:wui skirmish.wui.server/-main}) :wui skirmish.wui.server/-main})
(defn run-all-targets-in-separate-threads [target-fns config exchange-apis] (defn run-all-targets-in-separate-threads [target-fns config exchange-apis]
(println "running multiple targets. This is okay in dev but horrible in production") (println "running multiple targets. This is okay in dev but horrible in production")
(println target-fns) (println (clojure.string/join "\n" target-fns))
(let [threads (map #(async/thread (% config exchange-apis)) target-fns) (let [threads (map #(async/thread (% config exchange-apis)) target-fns)
finished-threads (async/merge threads)] finished-threads (async/merge threads)]
(loop [finished-thread (async/<!! finished-threads)] (loop [finished-thread (async/<!! finished-threads)]
+1 -2
View File
@@ -10,8 +10,7 @@
(defn -main [config exchange-apis] (defn -main [config exchange-apis]
(let [webserver-config (:api-server config) (let [webserver-config (:api-server config)
api-routes (create-routes-for-exchange exchange-apis)] api-routes (create-routes-for-exchange exchange-apis)]
(println "Running server with bellow config") (println "Running skirmish.exchange-gateway.core server with bellow config\n" webserver-config)
(println webserver-config)
(run-server (api api-routes) webserver-config))) (run-server (api api-routes) webserver-config)))
@@ -5,12 +5,11 @@
[compojure.handler :refer [api]] [compojure.handler :refer [api]]
[compojure.core :refer :all] [compojure.core :refer :all]
[skirmish.order] ;; for spec side effect [skirmish.order] ;; for spec side effect
[skirmish.rest.util :refer [build-response build-response-body]] [skirmish.rest.util :refer [build-response build-response-body get-body]]
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
[kinsky.client] [kinsky.client]
[slingshot.slingshot :refer [try+ throw+]] [slingshot.slingshot :refer [try+ throw+]]
[clojure.core.async :refer [chan go go-loop close! <! >!! sub]] [clojure.core.async :refer [chan go go-loop close! <! >!! sub]]))
[clojure.edn :as edn]))
(def order-id (atom -1)) (def order-id (atom -1))
@@ -28,13 +27,6 @@
:book.order/entry-timestamp now}))) :book.order/entry-timestamp now})))
(defn get-body [body]
(try+
(-> body clojure.java.io/reader slurp edn/read-string)
(catch Exception e
(throw+ {:error-type :4XX :status 400 :body {:error "Invalid body format. Body must be edn format."}}))))
(defn get-exchange-api [sec-id exchange-apis] (defn get-exchange-api [sec-id exchange-apis]
(if (contains? exchange-apis sec-id) (if (contains? exchange-apis sec-id)
(get exchange-apis sec-id) (get exchange-apis sec-id)
+93 -1
View File
@@ -1 +1,93 @@
(ns skirmish.ledger.accounts) (ns skirmish.ledger.accounts
(:require [datomic.api :as d]
[slingshot.slingshot :refer [throw+]]))
(def db-uri "datomic:free://localhost:4334/skirmish")
(def create-securities
[
{:db/ident :skirmish.ledger/create-security
:db/doc "Create a security in the system."
:db/fn (d/function {:lang "clojure"
:requires ['[slingshot.slingshot]]
:params '[db sec-id interest-rate]
:code '(let [security (d/pull db '[:db/id] [:skirmish.security/sec-id sec-id])]
(when (-> security :db/id some?)
(slingshot.slingshot/throw+ {:error-type :4XX :status 400 :body {:error "The security already exists."}}))
[#:skirmish.security{:sec-id sec-id :interest-rate interest-rate}])})}])
(def print-securities
[
{:db/ident :skirmish.ledger/emit-securities
:db/doc "Print securities fresh from the press."
:db/fn (d/function {:lang "clojure"
:requires ['[slingshot.slingshot]]
:params '[db sec-id amount account-public-key]
:code '(let [security (d/pull db '[:db/id] [:skirmish.security/sec-id sec-id])
account (d/pull db '[:skirmish.ledger/amount :skirmish.security/sec] [:skirmish.ledger/owner-public-key account-public-key])
base-amount (get account :skirmish.ledger/amount 0)
entity (:db/id account "new-account")]
(when (-> security :db/id nil?)
(slingshot.slingshot/throw+ {:error-type :4XX :status 404 :body {:error "No security with such id."}}))
(when (and (not= (:skirmish.security/sec account) (:db/id security)) (some? (:db/id account)))
(slingshot.slingshot/throw+ {:error-type :4XX :status 400 :body {:error "The security emitted does not match the account security."}}))
[[:db/add entity :skirmish.ledger/amount (+ base-amount amount)]
[:db/add entity :skirmish.ledger/owner-public-key account-public-key]
[:db/add entity :skirmish.ledger/sec (:db/id security)]])})}])
;; there are different types of securities...
;; 1. currencies, which pays interest rates in their one sec-id.
;; 2. equities, which do not pay interest rates.
;; 3. bonds, which pays interest rates in another currency.
(def schema-securities
[
{
:db/ident :skirmish.security/sec-id
:db/valueType :db.type/keyword
:db/cardinality :db.cardinality/one
:db/unique :db.unique/identity
:db/doc "The sec id."}
{
:db/ident :skirmish.security/interest-rate
:db/valueType :db.type/long
:db/cardinality :db.cardinality/one
:db/doc "The interest-rate payed by the security as a multiple of 0.01% (100 is 1% interest rate)."}])
(def ledger-schema
[
{
:db/ident :skirmish.ledger/owner-public-key
:db/unique :db.unique/identity
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db/doc "The public key of the owner"}
{
:db/ident :skirmish.ledger/amount
:db/index false
:db/valueType :db.type/long
:db/cardinality :db.cardinality/one
:db/doc "The quantity of currency owned by this account."}
{
:db/ident :skirmish.ledger/sec
:db/index false
:db/valueType :db.type/ref
:db/cardinality :db.cardinality/one
:db/doc "The sec kept in this account."}])
(def schema (-> []
(concat schema-securities)
(concat ledger-schema)
(concat create-securities)
(concat print-securities)))
(defn create-connection [uri]
(let [new-db? (d/create-database uri)
conn (d/connect uri)]
(when new-db?
@(d/transact conn schema))
conn))
+174 -1
View File
@@ -1 +1,174 @@
(ns skirmish.ledger.core) (ns skirmish.ledger.core
(:require [org.httpkit.server :refer [run-server]]
[compojure.handler :refer [api]]
[skirmish.util :refer [block-thread]]
[slingshot.slingshot :refer [try+ throw+]]
[compojure.route :refer [not-found]]
[skirmish.middleware.core :refer [create-exchange-apis-for]]
[skirmish.exchange-gateway.exchange-gateway :refer [create-routes-for-exchange]]
[skirmish.rest.util :refer [build-response get-body]]
[skirmish.ledger.accounts]
[datomic.api :as d]
[compojure.core :refer :all]
[clojure.spec.alpha :as s]
[ring.middleware.cors :refer [wrap-cors]]))
;; the ledger is the central
;; an :account-id is the public key of a
;; an :account-secret is an :acount-id private key
(defn get-account-body [request account sec]
(if (nil? sec)
(build-response request {:status 404})
(let [sec-url (str "/api/1/securities/" (-> sec :skirmish.security/sec-id name))
beautify-account { :amount (:skirmish.ledger/amount account)
:sec-id (:skirmish.security/sec-id sec)
:account-public-key (:skirmish.ledger/owner-public-key account)}
ret {:account beautify-account :sec-url sec-url}]
(build-response request ret))))
(defn get-account [conn]
"Returns the value of an account."
(fn [{{account-id :account-id} :route-params :as request}]
(let [db (d/db conn)
account (d/pull db '[*] [:skirmish.ledger/owner-public-key account-id])
sec-eid (-> account :skirmish.ledger/sec :db/id)
sec (d/entity db sec-eid)]
(get-account-body request account sec))))
(s/def ::transaction (s/cat :type #{:create}
:sec-id keyword?
:amount int?
:account-id string?))
(defn get-transaction [raw-transact]
(if (s/valid? ::transaction raw-transact)
(s/conform ::transaction raw-transact)
(throw+ {:error-type :4XX :status 400 :body {:error "Invalid transaction format."}})))
(s/def ::security (s/cat :sec-id keyword?
:interest-rate nat-int?))
(defn get-new-security [raw-securities]
(if (s/valid? ::security raw-securities)
raw-securities
(throw+ {:error-type :4XX :status 400 :body {:error "Invalid security format."}})))
(defn get-security-from-db [conn sec-id]
(let [e (d/pull (d/db conn) '[*] [:skirmish.security/sec-id sec-id])
{interest-rate :skirmish.security/interest-rate id :db/id} e
ret {:sec-id sec-id :interest-rate interest-rate}]
(if (nil? id)
(throw+ {:error-type :4XX :status 404 :body {:error "Security not found."}})
ret)))
(defn create-security [conn]
"Create a fresh new security."
(fn [{body :body :as request}]
(try+
(let [[sec-id interest-rate] (-> body get-body get-new-security)]
@(d/transact conn [[:skirmish.ledger/create-security sec-id interest-rate]])
(build-response request {:body {:success true}
:headers {"location" (str "/api/1" "/ledger/securities/" (name sec-id))}}))
(catch [:error-type :4XX] e (build-response request e))
(catch [:error-type :5XX] e (build-response request e))
(catch Object _
(println (:throwable &throw-context) "unexpected error")))))
(defn get-security [conn]
"Returns the security."
(fn [{{sec-id-str :sec-id} :route-params :as request}]
(try+
(let [sec-id (keyword sec-id-str)]
(build-response request (get-security-from-db conn sec-id)))
(catch [:error-type :4XX] e (build-response request e))
(catch [:error-type :5XX] e (build-response request e)))))
(defn update-security [conn]
"Update a security with new features."
(fn [request]
(try+
(catch [:error-type :4XX] e (build-response request e))
(catch [:error-type :5XX] e (build-response request e)))))
(defn get-available-securities-body [securities]
(let [sec-id->url #(str "/api/1/ledger/securities/" (name %))
to-security-description #(hash-map :sec-id % :url (sec-id->url %))
xf (comp cat (map to-security-description))
securities-info (sequence xf securities)];;
{:securities securities-info}))
(defn get-available-securities [conn]
(fn [request]
(let [securities (d/q '[:find ?s :where [?e :skirmish.security/sec-id ?s]] (d/db conn))]
(build-response request (get-available-securities-body securities)))))
(s/def ::emit-securities (s/cat :sec-id keyword?
:amount nat-int?
:account string?))
(defn get-emit-securities-request [[amount account] sec-id]
(if (s/valid? ::emit-securities [sec-id amount account])
[:skirmish.ledger/emit-securities sec-id amount account]
(throw+ {:error-type :4XX :status 400 :body {:error "Invalid emit request."}})))
(defn emit-securities [conn]
(fn [{{sec-id-str :sec-id} :route-params body :body :as request}]
(try+
(let [emit-request (-> body get-body (get-emit-securities-request (keyword sec-id-str)))]
@(d/transact conn [emit-request])
(build-response request {}))
(catch [:error-type :4XX] e (build-response request e))
(catch [:error-type :5XX] e (build-response request e)))))
(defn get-available-accounts-body [accounts]
(let [sec-id->url #(str "/api/1/ledger/accounts/" (name %))
to-security-description #(hash-map :account-public-key % :url (sec-id->url %))
xf (comp cat (map to-security-description))
securities-info (sequence xf accounts)];;
{:accounts securities-info}))
(defn get-available-accounts [conn]
(fn [request]
(let [securities (d/q '[:find ?pk :where [?e :skirmish.ledger/owner-public-key ?pk]] (d/db conn))]
(build-response request (get-available-accounts-body securities)))))
(defn get-api-description [request]
(let [body [{:url "ledger/accounts" :name "accounts"}
{:url "ledger/securities" :name "securities"}]]
(build-response request body)))
(defn create-routes-for-ledger [securities conn]
(routes
(context "/api/1" []
(GET "/" get-api-description)
(GET "/ledger/accounts" [] (get-available-accounts conn))
(GET "/ledger/accounts/:account-id" [] (get-account conn))
(GET "/ledger/securities" [] (get-available-securities conn)) ;; done
(POST "/ledger/securities" [] (create-security conn)) ;; done
(GET "/ledger/securities/:sec-id" [] (get-security conn)) ;; done
(PUT "/ledger/securities/:sec-id" [] (update-security conn)) ;; not done
(POST "/ledger/securities/:sec-id" [] (emit-securities conn))) ;; done
(not-found "not found")))
(defn -main [config exchange-apis]
(let [ledger-config (:ledger-server config)
conn (skirmish.ledger.accounts/create-connection (-> config :datomic :transactor-url))
api-routes (create-routes-for-ledger (:securities config) conn)]
(println "Running skirmish.ledger.core server with bellow config\n" ledger-config)
(run-server (api api-routes) ledger-config)))
+22 -4
View File
@@ -1,5 +1,7 @@
(ns skirmish.rest.util (ns skirmish.rest.util
(:require [cheshire.core :refer [generate-string]] (:require [cheshire.core :refer [generate-string]]
[slingshot.slingshot :refer [try+ throw+]]
[clojure.edn :as edn]
[clojure.string :refer [split starts-with? ends-with?]])) [clojure.string :refer [split starts-with? ends-with?]]))
;; Access-Control-Allow-Origin ;; Access-Control-Allow-Origin
@@ -40,8 +42,24 @@
(let [[_ serializer] (get-best-serializer request)] (let [[_ serializer] (get-best-serializer request)]
(serializer body))) (serializer body)))
(defn build-response [request {status :status body :body :as response}] (defn build-response [request {status :status body :body headers :headers :as response}]
(let [[content-type serializer] (get-best-serializer request)] (let [[content-type serializer] (get-best-serializer request)
ret-headers (if (some? headers) headers {})]
{:body (serializer (if (nil? body) response body)) {:body (serializer (if (nil? body) response body))
:headers {"Content-Type" content-type} :headers (into {"Content-Type" content-type} ret-headers)
:status (get-status status request)})) :status (get-status status request)}))
(defn get-body [body]
(try+
(-> body clojure.java.io/reader slurp edn/read-string)
(catch Exception e
(throw+ {:error-type :4XX :status 400 :body {:error "Invalid body format. Body must be edn format."}}))))
(defn string->stream
([s] (string->stream s "UTF-8"))
([s encoding]
(-> s
(.getBytes encoding)
(java.io.ByteArrayInputStream.))))
(def edn->stream (comp string->stream str))
+18 -1
View File
@@ -10,4 +10,21 @@
(defn block-thread [] (defn block-thread []
"Block the current thread until chuck norris gets weaker." "Block the current thread until chuck norris gets weaker."
(async/<!! (async/chan))) (async/<!! (async/chan)))
(defn- get-overlapping-for-specific-event [event sorted-events]
(let [event-end (:calendar.event/end event)]
(take-while #(< (:calendar.event/start %) event-end) sorted-events)))
(defn get-overlapping-events [events]
(let [sorted-events (sort-by :calendar.event/start events)]
(loop [events sorted-events ret []]
(if (seq events)
(let [first-event (first events)
rest-events (rest events)
overlapping-events (get-overlapping-for-specific-event first-event rest-events)
overlapping-pairs (map vector (repeat first-event) overlapping-events)
new-ret (if (seq overlapping-pairs) (into ret overlapping-pairs) ret)]
(recur rest-events new-ret))
ret))))
@@ -1,4 +1,50 @@
(ns skirmish.wui.components.create-order-page) (ns skirmish.wui.components.create-order-page
(:require [reagent.core :refer [atom]]))
(defn set-value-in-atom [atm key evt]
(swap! atm #(assoc % key (-> evt .-target .-value))))
(defn select-type [order]
[:div.control
[:div.select
[:select {:on-change (partial set-value-in-atom order :type)}
[:option {:value :buy} "Buy"]
[:option {:value :sell} "Sell"]]]])
(defn select-securities [order]
[:div.control
[:div.select
[:select {:on-change (partial set-value-in-atom order :sec-id)}
[:option {:value :nype} ":nype"]
[:option {:value :osko} ":osko"]
[:option {:value :oops} ":oops"]]]])
(defn select-quantities [order]
[:div.control
[:input.input {:type "number"
:on-change (partial set-value-in-atom order :quantity)}]])
(def action-button
[:div.control
[:button.button.is-primary "Submit"]])
(defn page [] (defn page []
[:h1 "create order page"]) (let [order (atom {})]
(fn []
[:div
[:div.columns
[:div.column.content
[:div.columns.is-centered
[:column
[:div.field.has-addons
(select-type order)
(select-securities order)
(select-quantities order)
action-button]]]]]
[:div.columns
[:div.column.content
[:pre (str @order)]]]])))
@@ -1,4 +1,16 @@
(ns skirmish.wui.components.home-page) (ns skirmish.wui.components.home-page
(:require [reagent.core :refer [atom]]))
(def lorem-ipsum ["Lorem ipsum dolor amet pickled bushwick next level adaptogen ugh franzen salvia hoodie man bun organic kinfolk. Hammock pickled banjo, artisan single-origin coffee brunch 90's occupy taxidermy. Put a bird on it gentrify offal tilde. Authentic put a bird on it prism semiotics, roof party banh mi typewriter. XOXO schlitz sriracha godard."
"Succulents shoreditch twee, VHS hammock hot chicken distillery. Edison bulb aesthetic pork belly meggings. 3 wolf moon artisan keytar blue bottle. Actually pok pok asymmetrical, banh mi before they sold out PBR&B portland tumblr."
"Gentrify palo santo enamel pin, tumeric jianbing thundercats shaman green juice chambray yr. Kale chips synth meditation chambray thundercats helvetica, cornhole whatever kickstarter ennui pop-up lo-fi godard. Austin unicorn brunch woke hammock. Coloring book kombucha occupy tofu wayfarers."])
(defn page [state] (defn page [state]
[:h1 "Hello world sdjjksd dsklsd"]) [:div
[:section.section
[:div.container
[:h1.title "lorem ipsum"]
(for [sentence lorem-ipsum]
[:p sentence])]]])
@@ -1,4 +1,21 @@
(ns skirmish.wui.components.portfolio-page) (ns skirmish.wui.components.portfolio-page
(:require [reagent.core :refer [atom]]
[skirmish.wui.components.secunit :refer [secunit]]))
(defn make-portfolio-summary-table [portfolio]
[:table
[:thead
[:tr [:th "Security"] [:th "Amount"] [:th "Face Value"]]]
[:tbody
(for [sec-summary portfolio]
[:tr
[:td (:sec-id sec-summary)]
[:td [secunit (:sec-id sec-summary) (:amount sec-summary)]]
[:td [secunit :dapm (:face-value sec-summary)]]])]])
(defn page [] (defn page []
[:h1 "portfolio page"]) (let [porfolio [{:sec-id :foo :amount 100 :face-value 1265}
{:sec-id :toto :amount 100 :face-value 1265}
{:sec-id :foo :amount 100 :face-value 1265}]]
[:div.content
(make-portfolio-summary-table porfolio)]))
@@ -0,0 +1,5 @@
(ns skirmish.wui.components.secunit)
(defn secunit [sec-id amount]
(let [href (str "/securities/" sec-id)]
[:a {:href href} (str amount)]))
+182 -1
View File
@@ -1,2 +1,183 @@
(ns skirmish.ledger.test-core (ns skirmish.ledger.test-core
(:require [clojure.test :refer :all])) (:require [midje.sweet :refer :all]
[datomic.api :as d]
[clojure.edn]
[skirmish.rest.util :refer [build-response]]
[skirmish.ledger.core :refer :all]
[skirmish.rest.util :refer [edn->stream]]
[clojure.spec.alpha :as s]
[skirmish.ledger.accounts :refer [create-connection schema]]))
(defn spec-check [spec]
#(s/valid? spec %))
(defn body [resp]
(-> resp :body clojure.edn/read-string))
(s/def ::ok #(<= 200 (:status %) 299))
(s/def ::status-404 #(= (:status %) 404))
(s/def ::status-400 #(= (:status %) 400))
(defn create-conn []
(let [mem-url "datomic:mem://skirmish"]
(d/delete-database mem-url)
(create-connection mem-url)))
(defn location? [path]
#(-> % :headers (get "location") (clojure.string/ends-with? path)))
(def sec->hash-set #(apply hash-set %))
(defn create-some-securities [conn]
(let [cs (create-security conn)
create-security-1 {:body (edn->stream [:sec-1 100]) :headers {"accept" "application/edn"}}
create-security-2 {:body (edn->stream [:sec-2 200]) :headers {"accept" "application/edn"}}
create-security-3 {:body (edn->stream [:sec-3 300]) :headers {"accept" "application/edn"}}
create-security-4 {:body (edn->stream [:zimbabwean-dollar 1000000]) :headers {"accept" "application/edn"}}]
(->> create-security-1 cs (s/assert ::ok))
(->> create-security-2 cs (s/assert ::ok))
(->> create-security-3 cs (s/assert ::ok))
(->> create-security-4 cs (s/assert ::ok))
conn))
(defn create-some-accounts [conn]
(let [es (emit-securities conn)
emit-sec-req-1 {:body (edn->stream [100 "1-sec-1"]) :route-params {:sec-id "sec-1"}}
emit-sec-req-2 {:body (edn->stream [200 "2-sec-1"]) :route-params {:sec-id "sec-1"}}
emit-sec-req-3 {:body (edn->stream [1000 "1-sec-2"]) :route-params {:sec-id "sec-2"}}
emit-sec-req-4 {:body (edn->stream [2000 "2-sec-2"]) :route-params {:sec-id "sec-2"}}]
(->> emit-sec-req-1 es (s/assert ::ok))
(->> emit-sec-req-2 es (s/assert ::ok))
(->> emit-sec-req-3 es (s/assert ::ok))
(->> emit-sec-req-4 es (s/assert ::ok))
conn))
(def create-some-securities-and-accounts (comp create-some-accounts create-some-securities))
(facts "about the securities"
(fact "We should be able to create a security"
(let [conn (create-conn)
security [:new-currency 100]
request {:body (edn->stream security) :headers {"accept" "application/edn"}}
resp ((create-security conn) request)]
resp => (spec-check ::ok)
resp => (location? "/api/1/ledger/securities/new-currency"))) ;;
(fact "We should not be able to create a security that already exists"
(let [conn (create-conn)
security [:new-currency 100]
request {:body (edn->stream security) :headers {"accept" "application/edn"}}
resp-1 ((create-security conn) request)
resp-2 ((create-security conn) request)]
resp-1 => (spec-check ::ok)
resp-2 => (spec-check ::status-400)))
(fact "We should be able to update a security when it exists"
:todo => :todo)
(fact "we should be able to get-security if it does exists"
(let [conn (create-conn)
security [:new-currency 100]
create-request {:body (edn->stream security) :headers {"accept" "application/edn"}}
_ ((create-security conn) create-request)
request {:route-params {:sec-id :new-currency} :headers {"accept" "application/edn"}}
resp ((get-security conn) request)]
resp => (spec-check ::ok)
(body resp) => {:sec-id :new-currency :interest-rate 100}))
(fact "we should not be able to get-security if it does not exists"
(let [conn (create-conn)
request {:route-params {:sec-id :new-currency} :headers {"accept" "application/edn"}}
resp ((get-security conn) request)]
resp => (spec-check ::status-404)
resp => #(-> % :sec-id nil?)))
(fact "we should be able to get a list of all securities"
(let [conn (create-conn)
request {:route-params {} :headers {"accept" "application/edn"}}
empty-res ((get-available-securities conn) request)
create-security-1 {:body (edn->stream [:new-currency 100]) :headers {"accept" "application/edn"}}
_ ((create-security conn) create-security-1)
one-res ((get-available-securities conn) request)]
empty-res => (spec-check ::ok)
one-res => (spec-check ::ok)
(body empty-res) => {:securities []}
(body one-res) => {:securities [{:sec-id :new-currency :url "/api/1/ledger/securities/new-currency"}]})))
(facts "about the emitting securities"
(fact "should not work if invalid request"
(let [conn (create-some-securities (create-conn))
req-1 {:body (edn->stream [10 :not-a-string]) :route-params {:sec-id "sec-1"} :headers {"accept" "application/edn"}}
req-2 {:body (edn->stream [-10 "foo"]) :route-params {:sec-id "sec-1"} :headers {"accept" "application/edn"}}
req-3 {:body (edn->stream [90 "account-id"]) :route-params {:sec-id "invalid-sec-id"} :headers {"accept" "application/edn"}}]
((emit-securities conn) req-1) => (spec-check ::status-400)
((emit-securities conn) req-2) => (spec-check ::status-400)
((emit-securities conn) req-3) => (spec-check ::status-404)))
(fact "should create new account if targeted account does not exists"
(let [conn (create-some-securities (create-conn))
get-account-req {:route-params {:account-id "new-account-id"} :headers {"accept" "application/edn"}}
emit-sec-req {:body (edn->stream [90 "new-account-id"]) :route-params {:sec-id "sec-1"} :headers {"accept" "application/edn"}}]
((get-account conn) get-account-req) => (spec-check ::status-404)
((emit-securities conn) emit-sec-req) => (spec-check ::ok)
(body ((get-account conn) get-account-req)) => #(-> % :account :amount (= 90))))
(fact "should add to account if targeted account exists"
(let [conn (create-some-securities (create-conn))
get-account-req {:route-params {:account-id "new-account-id"} :headers {"accept" "application/edn"}}
emit-sec-req-1 {:body (edn->stream [90 "new-account-id"]) :route-params {:sec-id "sec-1"} :headers {"accept" "application/edn"}}
emit-sec-req-2 {:body (edn->stream [100 "new-account-id"]) :route-params {:sec-id "sec-1"} :headers {"accept" "application/edn"}}]
((get-account conn) get-account-req) => (spec-check ::status-404)
((emit-securities conn) emit-sec-req-1) => (spec-check ::ok)
((emit-securities conn) emit-sec-req-2) => (spec-check ::ok)
(body ((get-account conn) get-account-req)) => #(-> % :account :amount (= 190)))))
(facts "about accounts"
(fact "we should be able to get list of all accounts"
(let [conn (create-some-securities-and-accounts (create-conn))
req {:route-params {} :headers {"accept" "application/edn"}}
expt [{:account-public-key "1-sec-1" :url "/api/1/ledger/accounts/1-sec-1"}
{:account-public-key "1-sec-2" :url "/api/1/ledger/accounts/1-sec-2"}
{:account-public-key "2-sec-1" :url "/api/1/ledger/accounts/2-sec-1"}
{:account-public-key "2-sec-2" :url "/api/1/ledger/accounts/2-sec-2"}]]
((get-available-accounts conn) req) => (spec-check ::ok)
(-> ((get-available-accounts conn) req) body :accounts sec->hash-set) => (sec->hash-set expt)))
(fact "we should be able to get account"
(let [conn (create-some-securities-and-accounts (create-conn))
req {:route-params {:account-id "2-sec-1"} :headers {"accept" "application/edn"}}]
((get-account conn) req) => (spec-check ::ok)
(body ((get-account conn) req)) => {:account {:account-public-key "2-sec-1" :amount 200 :sec-id :sec-1}
:sec-url "/api/1/securities/sec-1"}))
(fact "the interest should be payed when getting the account"
(let [conn (create-some-securities-and-accounts (create-conn))
req {:route-params {:account-id "2-sec-1"} :headers {"accept" "application/edn"}}]
((get-account conn) req) => (spec-check ::ok)
(body ((get-account conn) req)) => {:account {:account-public-key "2-sec-1" :amount 200 :sec-id :sec-1}
:sec-url "/api/1/securities/sec-1"}))
(fact "we should not get an account if it does not exists"
(let [conn (create-some-securities-and-accounts (create-conn))
req {:route-params {:account-id "2-sec-10"} :headers {"accept" "application/edn"}}]
((get-account conn) req) => (spec-check ::status-404))))
;; TODO
;; 4. transfer sec from one account to the other.
;; 5. account for interest
+24
View File
@@ -0,0 +1,24 @@
(ns skirmish.test-util
(:require [midje.sweet :refer :all]
[skirmish.util :refer :all]))
;; midje testing
(facts "About events overlapping"
(let [evt-1 #:calendar.event{:start 10 :end 15}
evt-2 #:calendar.event{:start 12 :end 17}
evt-3 #:calendar.event{:start 15 :end 25}
evt-4 #:calendar.event{:start 30 :end 40}
evt-5 #:calendar.event{:start 33 :end 34}]
(fact "about edge cases"
(get-overlapping-events nil) => empty?
(get-overlapping-events []) => empty?)
(fact "about regular situations"
(get-overlapping-events [evt-1 evt-2]) => [[evt-1 evt-2]]
(get-overlapping-events [evt-1 evt-2 evt-3]) => [[evt-1 evt-2] [evt-2 evt-3]]
(get-overlapping-events [evt-4 evt-5]) => [[evt-4 evt-5]])))
;;