implement basic login in the wui

This commit is contained in:
didier amyot committed 2018-10-13 16:35:19 -04:00
1 parent d4bc2490ff
commit addff66470
20 files changed
+216 -82

No files matched your search

+31 -3
View File
@@ -3,7 +3,7 @@
![logo](doc/logo.svg "Skrimish")
A stock exchange simulator. The scope of this project is not yet defined but it should
allow end user(s?) to exchange securities via the common exchange rules.
allow end user(s?) to exchange securities via a kind of stock exchange
## Getting Started
@@ -26,8 +26,15 @@ All configuration are set in the `resources/config.edn` file.
Configuration uses [aero](https://github.com/juxt/aero). The configuration
file should be self documented.
### Technologies
#### Big architecture
* [docker](https://www.docker.com/) because it is trendy.
* [docker-compose](https://docs.docker.com/compose/) to manage system dependencies.
* [jvm](https://en.wikipedia.org/wiki/Java_virtual_machine) because nothing beats it.
#### Server
* [clojure](https://clojure.org/) for programming language
* [kafka](http://kafka.apache.org/) for message broker/database
@@ -35,9 +42,30 @@ file should be self documented.
#### In the browser
* [clojure script](https://clojurescript.org/) for programming language
* [sparks](https://aftertheflood.com/projects/sparks/) for trivial charts
* [reagent](https://reagent-project.github.io/) as frontend framework. Simple and powerful.
* [metricsgraphicsjs](http://metricsgraphicsjs.org/) for complex charts
* [dc-js](http://dc-js.github.io/dc.js/) for some charting.
* [dc-js](http://dc-js.github.io/dc.js/) for some charting. Looks awesome!.
### Architecture shenanigans
As any personal project, over engineering it the only solution. Moreover the
scope of the project is unknown. Consequently, there is a plethora of
micro-servicesh.
#### Micro services
A micro service does one thing, and does it well. Micro services communicate to the world
via REST api and collaborate together via a global state kept in kafka.
| name | description | REST |
|:----------------------------|:--------------------------------------------------------------------------|:-----:|
| `skirmish.exchange` | Exchange engine. Knows nothing about what it trades. | `yes` |
| `skirmish.exchange-gateway` | Public api to `skirmish.exchange`. Dispatch an order to the proper place | `no` |
| `skirmish.ledger` | Source of truth for who owns what. | `yes` |
| `skirmish.bookkeeper` | Source of truth for the marker data. Publishes market data to the world. | `yes` |
## License
+7
View File
@@ -33,6 +33,13 @@
:max-body 524288 ;; 512kb, small but enough
:max-ws 524288}
:api-broker
{:port #long #or [#env BROKER_SERVER_PORT 1605]
:ip "0.0.0.0"
:max-body 524288 ;; 512kb, small but enough
:max-ws 524288}
:wui
{:port #long #or [#env WUI_PORT 3000]}
+7 -7
View File
@@ -4564,8 +4564,8 @@ a.navbar-item,
.navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link, .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link {
background-color: transparent !important; }
.navbar.is-transparent .navbar-dropdown a.navbar-item:hover {
background-color: #0a0a0a;
color: #0a0a0a; }
background-color: #363636;
color: none; }
.navbar.is-transparent .navbar-dropdown a.navbar-item.is-active {
background-color: #0a0a0a;
color: #00d1b2; }
@@ -4582,7 +4582,7 @@ a.navbar-item,
.navbar-item.has-dropdown-up .navbar-link::after {
transform: rotate(135deg) translate(0.25em, -0.25em); }
.navbar-item.has-dropdown-up .navbar-dropdown {
border-bottom: 2px solid #dbdbdb;
border-bottom: 1px solid #00d1b2;
border-radius: 6px 6px 0 0;
border-top: none;
bottom: 100%;
@@ -4604,10 +4604,10 @@ a.navbar-item,
justify-content: flex-end;
margin-left: auto; }
.navbar-dropdown {
background-color: white;
background-color: #363636;
border-bottom-left-radius: 6px;
border-bottom-right-radius: 6px;
border-top: 2px solid #dbdbdb;
border-top: 1px solid #00d1b2;
box-shadow: 0 8px 8px rgba(10, 10, 10, 0.1);
display: none;
font-size: 0.875rem;
@@ -4622,8 +4622,8 @@ a.navbar-item,
.navbar-dropdown a.navbar-item {
padding-right: 3rem; }
.navbar-dropdown a.navbar-item:hover {
background-color: #0a0a0a;
color: #0a0a0a; }
background-color: #363636;
color: none; }
.navbar-dropdown a.navbar-item.is-active {
background-color: #0a0a0a;
color: #00d1b2; }
+4 -4
View File
@@ -1,5 +1,5 @@
(ns skirmish.bookkeeper.bookkeeper
(:require [skirmish.exchange.exchange :refer [empty-book create-exchange-for-security]]
(:require [skirmish.exchange.exchange :refer [empty-book]]
[org.httpkit.server :refer [run-server with-channel send! close on-close]]
[compojure.route :refer [not-found]]
[compojure.handler :refer [api]]
@@ -73,14 +73,14 @@
(fn [{{sec-id-str :sec-id} :route-params :as request}]
(with-channel request ws-chan
(let [status-to-listen-on (get-status-to-listen-on request)
order-channel (get-new-order-channel exchange-apis (keyword sec-id-str) status-to-listen-on)]
order-channel (get-new-order-channel @exchange-apis (keyword sec-id-str) status-to-listen-on)]
(chan-to-websocket request order-channel ws-chan)))))
(defn listen-to-trades [exchange-apis]
(fn [{{sec-id-str :sec-id} :route-params :as request}]
(with-channel request ws-chan
(let [order-channel (get-new-trades-channel exchange-apis (keyword sec-id-str))]
(let [order-channel (get-new-trades-channel @exchange-apis (keyword sec-id-str))]
(chan-to-websocket request order-channel ws-chan)))))
@@ -108,7 +108,7 @@
(defn create-routes-for-bookkeeper [exchange-apis config]
(let [books (map-values #(create-book %) exchange-apis)]
(let [books (map-values #(create-book %) @exchange-apis)]
(routes
(context "/api/1" []
(GET "/" [] api-description)
+3 -1
View File
@@ -2,11 +2,13 @@
(:require [org.httpkit.server :refer [run-server]]
[compojure.handler :refer [api]]
[skirmish.util :refer [block-thread]]
[skirmish.middleware.core :refer [create-map-exchange-apis]]
[skirmish.bookkeeper.bookkeeper :refer [create-routes-for-bookkeeper]]
[ring.middleware.cors :refer [wrap-cors]]))
(defn -main [config exchange-apis]
(defn -main [config securities-api exchange-apis-mult-chan]
(let [bookkeeper-config (:api-bookkeeper config)
exchange-apis (create-map-exchange-apis exchange-apis-mult-chan)
api-routes (create-routes-for-bookkeeper exchange-apis config)]
(println "Running skirmish.bookkeeper.core server with bellow config\n" bookkeeper-config)
(println bookkeeper-config)
+12 -7
View File
@@ -1,9 +1,14 @@
(ns skirmish.broker.core)
(ns skirmish.broker.core
(:require [org.httpkit.server :refer [run-server]]
[compojure.handler :refer [api]]
[skirmish.util :refer [block-thread]]
[skirmish.exchange-gateway.exchange-gateway :refer [create-routes-for-exchange]]
[skirmish.middleware.core :refer [create-map-exchange-apis]]
[ring.middleware.cors :refer [wrap-cors]]))
(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 securities-api exchange-apis]
(let [webserver-config (:api-broker config)
api-routes (-> exchange-apis create-map-exchange-apis create-routes-for-exchange)]
(println "Running skirmish.exchange-gateway.core server with bellow config\n" webserver-config)
(run-server (api api-routes) webserver-config)))
+8 -7
View File
@@ -6,7 +6,8 @@
[skirmish.bookkeeper.core]
[skirmish.exchange-gateway.core]
[skirmish.ledger.core]
[skirmish.middleware.core :refer [create-exchange-apis-for]]
[skirmish.middleware.core :refer [create-exchange-apis-for
create-securities-api-for]]
[aero.core :refer (read-config)]
[orchestra.spec.test :as st]
[clojure.core.async :as async])
@@ -20,21 +21,21 @@
:ledger skirmish.ledger.core/-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 securities-api exchange-apis]
(println "running multiple targets. This is okay in dev but horrible in production")
(println (clojure.string/join "\n" target-fns))
(let [threads (map #(async/thread (% config exchange-apis)) target-fns)
(let [threads (map #(async/thread (% config securities-api exchange-apis)) target-fns)
finished-threads (async/merge threads)]
(loop [finished-thread (async/<!! finished-threads)]
(when (some? finished-thread)
(recur (async/<!! finished-threads))))))
(defn run-all-targets [target-fns config]
(let [exchange-apis (create-exchange-apis-for config)]
(let [securities-api (create-securities-api-for config)
exchange-apis (create-exchange-apis-for config securities-api)]
(if (= (count target-fns) 1)
((first target-fns) config exchange-apis)
(run-all-targets-in-separate-threads target-fns config exchange-apis))))
((first target-fns) config securities-api exchange-apis)
(run-all-targets-in-separate-threads target-fns config securities-api exchange-apis))))
(defn -main [& argv]
(let [config (-> "config.edn" clojure.java.io/resource read-config)
+6 -3
View File
@@ -1,5 +1,6 @@
(ns skirmish.exchange.core
(:require [skirmish.util :refer [block-thread]]
[clojure.core.async :as async]
[skirmish.middleware.core :refer [create-exchange-apis-for]]
[skirmish.exchange.exchange :refer [create-exchange-for-security empty-book]]))
@@ -15,7 +16,9 @@
trades-submitter
orders-submitter)))
(defn -main [config exchange-apis]
(doseq [{sec-id :sec-id} (:securities config)]
(create-exchange-for (get exchange-apis sec-id)))
(defn -main [config securities-api exchange-apis-mult-chan]
(let [ch (async/chan)]
(async/tap exchange-apis-mult-chan ch)
(async/go-loop [exchange-api (async/<! ch)]
(create-exchange-for exchange-api)))
(block-thread))
+3 -2
View File
@@ -3,12 +3,13 @@
[compojure.handler :refer [api]]
[skirmish.util :refer [block-thread]]
[skirmish.exchange-gateway.exchange-gateway :refer [create-routes-for-exchange]]
[skirmish.middleware.core :refer [create-map-exchange-apis]]
[ring.middleware.cors :refer [wrap-cors]]))
(defn -main [config exchange-apis]
(defn -main [config securities-api exchange-apis]
(let [webserver-config (:api-server config)
api-routes (create-routes-for-exchange exchange-apis)]
api-routes (-> exchange-apis create-map-exchange-apis create-routes-for-exchange)]
(println "Running skirmish.exchange-gateway.core server with bellow config\n" webserver-config)
(run-server (api api-routes) webserver-config)))
@@ -1,15 +1,12 @@
(ns skirmish.exchange-gateway.exchange-gateway
(:require [skirmish.exchange.exchange :refer [empty-book create-exchange-for-security]]
[org.httpkit.server :refer [run-server with-channel send! close on-close]]
[compojure.route :refer [not-found]]
[compojure.handler :refer [api]]
(:require [compojure.route :refer [not-found]]
[compojure.core :refer :all]
[skirmish.order] ;; for spec side effect
[skirmish.rest.util :refer [build-response build-response-body get-body]]
[skirmish.rest.util :refer [build-response get-body]]
[clojure.spec.alpha :as s]
[kinsky.client]
[slingshot.slingshot :refer [try+ throw+]]
[clojure.core.async :refer [chan go go-loop close! <! >!! sub]]))
[clojure.core.async :refer [>!!]]))
(def order-id (atom -1))
@@ -37,7 +34,7 @@
(fn [{{sec-id-str :sec-id} :route-params body :body :as request}]
(try+
(let [asked-order (get-body body)
exchange-api (get-exchange-api (keyword sec-id-str) exchange-apis)
exchange-api (get-exchange-api (keyword sec-id-str) @exchange-apis)
incoming-order-channel (:new-order-submitter exchange-api)
order (validate-order asked-order (:sec-id exchange-api))]
(>!! incoming-order-channel order)
+2 -4
View File
@@ -80,9 +80,7 @@
(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")))))
(catch [:error-type :5XX] e (build-response request e)))))
(defn get-security [conn]
"Returns the security."
@@ -169,7 +167,7 @@
(not-found "not found")))
(defn -main [config exchange-apis]
(defn -main [config securities-api 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)]
+41 -7
View File
@@ -1,6 +1,7 @@
(ns skirmish.middleware.core
(:require [clojure.core.async :as async]
[kinsky.async]
[clojure.core.async :refer [go-loop <! sub chan]]
[kinsky.client]))
(defn create-chan-based-exchange-api-for [sec-id]
@@ -8,6 +9,7 @@
trades (async/chan)
orders (async/chan)]
{:sec-id sec-id
:disabled false
:new-order-publisher new-order ;; anything published here will be processed as
:new-order-submitter new-order
:trades-publisher (async/pub trades :book.order/type)
@@ -15,12 +17,44 @@
:orders-publisher (async/pub orders :book.order/status)
:orders-submitter orders}))
(def api-creators {})
(defn create-exchange-apis-for [config]
;; todo: spec this function
(let [security-definitions (map :sec-id (:securities config))
create-exchange-api-for (get api-creators (:middleware config) create-chan-based-exchange-api-for)
f (fn [sec] [sec (create-exchange-api-for sec)])
ret (into {} (map f security-definitions))]
(defn create-chan-based-security-api [config]
(let [securities (async/chan)]
{:securities-publisher (async/mult securities)
:securities-submitter securities}))
(def security-api-creators {})
(defn create-securities-api-for [config]
(let [creator (get security-api-creators (:middleware config) create-chan-based-security-api)]
(creator config)))
(defn update-exchange-apis [create-exchange-api-for sec]
(let [sec-id (:sec-id sec)
disabled? (get sec :disabled false)]
(if disabled?
{:sec-id sec-id :disabled true}
(create-exchange-api-for sec-id))))
(def exchange-api-creators {})
(defn create-exchange-apis-for [config securities-api]
(let [ret (async/chan)
securities-ch (async/chan)
securities-publisher (:securities-publisher securities-api)
create-exchange-api-for (get exchange-api-creators (:middleware config) create-chan-based-exchange-api-for)]
(async/tap securities-publisher securities-ch)
(go-loop [sec (<! securities-ch)]
(async/>! ret (update-exchange-apis create-exchange-api-for sec))
(recur (<! securities-ch)))
(async/mult ret)))
(defn create-map-exchange-apis [exchange-apis-mult-chan]
(let [c (async/chan)
ret (atom {})]
(async/tap exchange-apis-mult-chan chan)
(async/go-loop [exchange-api (async/<! c)]
(if (:disabled? exchange-api)
(swap! ret #(dissoc % (:sec-id exchange-api)))
(swap! ret #(assoc % (:sec-id exchange-api) exchange-api))))
ret))
-1
View File
@@ -16,7 +16,6 @@
(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 []]
+1 -1
View File
@@ -3,6 +3,6 @@
[ring.adapter.jetty :refer [run-jetty]])
(:gen-class))
(defn -main [config exchange-apis]
(defn -main [config securities-api exchange-apis]
(let [port 3000]
(run-jetty app {:port port :join? false})))
@@ -7,10 +7,24 @@
"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 login [state]
(let [login-field (atom (get state :user ""))
on-change #(reset! login-field (-> % .-target .-value))]
(fn []
[:div.field
[:div.control
[:label.label "Username"]
[:div.field.is-grouped
[:input.input.is-success {:type "text" :value @login-field :on-change on-change :style {:margin-right "1em"}}]
[:div.control
[:a.button.is-link.has-background-primary {:href (str "/home/" @login-field)} "login"]]]]])))
(defn page [state]
[:div
[:section.section
[:div.container
[:h1.title "lorem ipsum"]
[login state]
(for [sentence lorem-ipsum]
[:p sentence])]]])
+18 -23
View File
@@ -8,26 +8,21 @@
(defn page []
[:div.content
[:ul
[:li "General"
[:ul
[:li "write trivial mock strategy framework."]
[:li "write mock strategies."]
[:li "think of a solution where to store data."]]]
[:li "Web user interface"
[:ul
[:li done "Add simple pages for every section"]
[:li "Securities"
[:ul
[:li "display all the available securities"]
[:li "display the book for selected securities"]]]]]
[:li "Exchange"
[:ul
[:li done "make an exchange that kind of work"]
[:li "spec the exchange"]]]
[:li "Book keeper"
[:ul
[:li "Find a place to store information about securities"]]]
[:li "Brooker"
[:ul
[:li "Write a small broker"]]]]])
[:ol
[:li "Broker"
[:p "the brooker should use the exchange-gateway and ledge as apis. Nothing else."]
[:ol
[:li done "implement basic login in the wui"]
[:li on-it "implement basic web server"]
[:li "implement stupid login."]
[:li "implement account notion. Use ledger for that."]
[:li "implement buy/sell securities."]]]
[:li "Implement Ledger"
[:ol
[:li "Use ledger as source of through in each sub server."]
[:li "Update web ui for integrating securities with ledger."]
[:li "Create a basic broker that supports..."]
[:li "Ledger should compute interest rate."]
[:li "implement transactions in ledger."]
[:li "implement escrow."]]]]])
+22 -1
View File
@@ -10,6 +10,26 @@
"is-active"))
(defn- user-dropdown [state]
(when (:user @state)
(let [icon [:i.fa.fa-sign-out {:aria-hidden true}]]
[:div.navbar-item.has-dropdown.is-hoverable
[:a.navbar-link {} (str "[ " (:user @state) " ]")]
[:div.navbar-dropdown
[:a.navbar-item {:href "/home/"} icon " logout"]]])))
(defn- login [state]
(let [icon [:i.fa.fa-sign-out {:aria-hidden true}]]
[:a.navbar-item.is-tab {:href "/"} icon " login"]))
(defn user-placeholder [state]
(if (:user @state)
[user-dropdown state]
[login state]))
(defn top-bar [state]
[:nav.navbar.is-dark.is-boxed {:style {:margin-bottom "1em"}}
[:div.container
@@ -20,4 +40,5 @@
[:a.navbar-item.is-tab {:href "/portfolio" :class (top-bar-class state :view-portfolio)} "View Portfolio"]
[:a.navbar-item.is-tab {:href "/todo" :class (top-bar-class state :todo)} "Todo"]]
[:div.navbar-end
[:a.navbar-item.is-tab {} "Skirmish Shitty Exchange"]]]])
[:a.navbar-item.is-tab {} "Skirmish Shitty Exchange"]
[user-placeholder state]]]])
+9
View File
@@ -26,6 +26,15 @@
(session/put! :current-page #'pages/home-page)
(swap! state #(into % {:page :home})))
(secretary/defroute "/home/:username" [username]
(session/put! :current-page #'pages/home-page)
(swap! state #(into % {:page :home :user username})))
(secretary/defroute "/home/" []
(session/put! :current-page #'pages/home-page)
(swap! state #(into % {:page :home :user nil})))
(secretary/defroute "/securities" []
(session/put! :current-page #'pages/securities-page)
(swap! state #(into % {:page :securities})))
+8
View File
@@ -8,11 +8,19 @@
@import "../../node_modules/bulma/sass/utilities/_all";
// main color changes
$background: $black;
$body-background-color: $black;
$text: $white-ter;
$link: $turquoise;
// navbar color change
$navbar-dropdown-background-color: $grey-darker;
$navbar-dropdown-border-top: 1px solid $turquoise !default;
$navbar-dropdown-item-hover-color: none;
$navbar-dropdown-item-hover-background-color: $grey-darker;
// table color changes
$table-background-color: $grey-dark;
$table-cell-heading-color: $grey-darker;
$table-row-hover-background-color: $black-ter;
+16 -4
View File
@@ -1,5 +1,6 @@
(ns skirmish.middleware.test-core
(:require [midje.sweet :refer :all]
[clojure.core.async :refer [>!! <!!] :as async]
[skirmish.middleware.core :refer :all]))
(defn all? [xf]
@@ -7,7 +8,18 @@
(facts "we should have a async/chan base middleware for developing."
(fact "It should exchanges for each security in the config"
(let [config {:securities [{:sec-id :molly} {:sec-id :pepper}]}
exchanges (create-exchange-apis-for config)]
(keys exchanges) => [:molly :pepper]
(vals exchanges) => #(every? map? %))))
(let [config {}
securities [{:sec-id :molly :type :equity} {:sec-id :pepper :type :currency} {:sec-id :molly :type :equity :disabled true}]
securities-exchange (create-securities-api-for config)
securities-submitter (:securities-submitter securities-exchange)
exchanges-mult (create-exchange-apis-for config securities-exchange)
exchanges (async/chan)
sec-id-and-disabled (juxt :sec-id :disabled)]
(async/tap exchanges-mult exchanges)
(>!! securities-submitter (first securities))
(-> exchanges <!! sec-id-and-disabled) => [:molly false]
(>!! securities-submitter (second securities))
(-> exchanges <!! sec-id-and-disabled) => [:pepper false]
(>!! securities-submitter (last securities))
(-> exchanges <!! sec-id-and-disabled) => [:molly true])))