From addff664707d3aecd208bc9c7478d7bc39c2a69b Mon Sep 17 00:00:00 2001 From: didier amyot Date: Sat, 13 Oct 2018 16:35:19 -0400 Subject: [PATCH] implement basic login in the wui --- README.md | 34 +++++++++++-- resources/config.edn | 7 +++ resources/public/css/site.css | 14 +++--- src/clj/skirmish/bookkeeper/bookkeeper.clj | 8 +-- src/clj/skirmish/bookkeeper/core.clj | 4 +- src/clj/skirmish/broker/core.clj | 19 ++++--- src/clj/skirmish/core.clj | 15 +++--- src/clj/skirmish/exchange/core.clj | 9 ++-- src/clj/skirmish/exchange_gateway/core.clj | 5 +- .../exchange_gateway/exchange_gateway.clj | 11 ++-- src/clj/skirmish/ledger/core.clj | 6 +-- src/clj/skirmish/middleware/core.clj | 50 ++++++++++++++++--- src/clj/skirmish/util.clj | 1 - src/clj/skirmish/wui/server.clj | 2 +- .../skirmish/wui/components/home_page.cljs | 14 ++++++ .../skirmish/wui/components/todo_page.cljs | 41 +++++++-------- src/cljs/skirmish/wui/components/topbar.cljs | 23 ++++++++- src/cljs/skirmish/wui/core.cljs | 9 ++++ src/sass/site.scss | 8 +++ test/clj/skirmish/middleware/test_core.clj | 20 ++++++-- 20 files changed, 217 insertions(+), 83 deletions(-) diff --git a/README.md b/README.md index 169c9ec..d195359 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/resources/config.edn b/resources/config.edn index 0be01c2..6df0942 100644 --- a/resources/config.edn +++ b/resources/config.edn @@ -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]} diff --git a/resources/public/css/site.css b/resources/public/css/site.css index e82899a..a39965c 100644 --- a/resources/public/css/site.css +++ b/resources/public/css/site.css @@ -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; } diff --git a/src/clj/skirmish/bookkeeper/bookkeeper.clj b/src/clj/skirmish/bookkeeper/bookkeeper.clj index 3a11522..86170b0 100644 --- a/src/clj/skirmish/bookkeeper/bookkeeper.clj +++ b/src/clj/skirmish/bookkeeper/bookkeeper.clj @@ -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) diff --git a/src/clj/skirmish/bookkeeper/core.clj b/src/clj/skirmish/bookkeeper/core.clj index ef195df..0f0e4fe 100644 --- a/src/clj/skirmish/bookkeeper/core.clj +++ b/src/clj/skirmish/bookkeeper/core.clj @@ -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) diff --git a/src/clj/skirmish/broker/core.clj b/src/clj/skirmish/broker/core.clj index 9ec1691..4256c33 100644 --- a/src/clj/skirmish/broker/core.clj +++ b/src/clj/skirmish/broker/core.clj @@ -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]) \ No newline at end of file +(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))) diff --git a/src/clj/skirmish/core.clj b/src/clj/skirmish/core.clj index f4e8edd..64113ec 100644 --- a/src/clj/skirmish/core.clj +++ b/src/clj/skirmish/core.clj @@ -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/ "config.edn" clojure.java.io/resource read-config) diff --git a/src/clj/skirmish/exchange/core.clj b/src/clj/skirmish/exchange/core.clj index 914efee..8972d25 100644 --- a/src/clj/skirmish/exchange/core.clj +++ b/src/clj/skirmish/exchange/core.clj @@ -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/ 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))) diff --git a/src/clj/skirmish/exchange_gateway/exchange_gateway.clj b/src/clj/skirmish/exchange_gateway/exchange_gateway.clj index d9d6cb9..c9b988b 100644 --- a/src/clj/skirmish/exchange_gateway/exchange_gateway.clj +++ b/src/clj/skirmish/exchange_gateway/exchange_gateway.clj @@ -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) diff --git a/src/clj/skirmish/ledger/core.clj b/src/clj/skirmish/ledger/core.clj index 8ca8877..a3b43b2 100644 --- a/src/clj/skirmish/ledger/core.clj +++ b/src/clj/skirmish/ledger/core.clj @@ -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)] diff --git a/src/clj/skirmish/middleware/core.clj b/src/clj/skirmish/middleware/core.clj index d9bacd0..d7e5d74 100644 --- a/src/clj/skirmish/middleware/core.clj +++ b/src/clj/skirmish/middleware/core.clj @@ -1,6 +1,7 @@ (ns skirmish.middleware.core (:require [clojure.core.async :as async] [kinsky.async] + [clojure.core.async :refer [go-loop ! ret (update-exchange-apis create-exchange-api-for sec)) + (recur ( % .-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])]]]) \ No newline at end of file diff --git a/src/cljs/skirmish/wui/components/todo_page.cljs b/src/cljs/skirmish/wui/components/todo_page.cljs index f3b3746..665a244 100644 --- a/src/cljs/skirmish/wui/components/todo_page.cljs +++ b/src/cljs/skirmish/wui/components/todo_page.cljs @@ -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."]]]]]) diff --git a/src/cljs/skirmish/wui/components/topbar.cljs b/src/cljs/skirmish/wui/components/topbar.cljs index 71049bb..21683bf 100644 --- a/src/cljs/skirmish/wui/components/topbar.cljs +++ b/src/cljs/skirmish/wui/components/topbar.cljs @@ -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]]]]) diff --git a/src/cljs/skirmish/wui/core.cljs b/src/cljs/skirmish/wui/core.cljs index 122ceee..127bd9e 100644 --- a/src/cljs/skirmish/wui/core.cljs +++ b/src/cljs/skirmish/wui/core.cljs @@ -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}))) diff --git a/src/sass/site.scss b/src/sass/site.scss index ae5324e..c3de32b 100644 --- a/src/sass/site.scss +++ b/src/sass/site.scss @@ -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; diff --git a/test/clj/skirmish/middleware/test_core.clj b/test/clj/skirmish/middleware/test_core.clj index a484f48..ec67453 100644 --- a/test/clj/skirmish/middleware/test_core.clj +++ b/test/clj/skirmish/middleware/test_core.clj @@ -1,5 +1,6 @@ (ns skirmish.middleware.test-core (:require [midje.sweet :refer :all] + [clojure.core.async :refer [>!! [:molly :pepper] - (vals exchanges) => #(every? map? %)))) \ No newline at end of file + (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 [:molly false] + (>!! securities-submitter (second securities)) + (-> exchanges [:pepper false] + (>!! securities-submitter (last securities)) + (-> exchanges [:molly true]))) \ No newline at end of file