Compare commits

..
25 Commits
Author SHA1 Message Date
didier amyot e3dcba6c37 cleaned up shit. 2020-09-12 11:06:12 -04:00
didier amyot 56fcacc2d8 Fixed some shit. 2020-09-11 19:42:47 -04:00
didier amyot ff1ba2dfde Fixed couple of things. 2020-09-11 09:38:28 -04:00
didier amyot 75a91f7f7b going somewhere. 2020-09-11 07:25:08 -04:00
didier amyot 30d732f708 going somewhere. 2020-09-10 17:58:36 -04:00
didier amyot cfb63cb473 refactored code. 2020-09-10 14:39:26 -04:00
didier amyot ed7f4ef668 started to refactor code. 2020-09-10 13:25:10 -04:00
didier amyot c5d4e83d44 started to implement transactions. 2020-09-10 12:17:42 -04:00
didier amyot 1fa115db79 Implemented interest rates 2020-09-10 10:33:57 -04:00
didier amyot d5c6360f88 going somewhere 2020-09-09 13:10:13 -04:00
didier amyot b7af65b705 Fixed unit tests. 2020-09-05 11:18:09 -04:00
didier amyot 2c53b516d6 made otc wui. no back end 2018-11-12 15:39:39 -05:00
didier amyot 10b054db25 going somewhere 2018-11-12 14:50:56 -05:00
didier amyot b972e6e69f something 2018-11-01 19:40:17 -04:00
didier amyot 440206e924 going somewhere 2018-10-22 09:19:48 -04:00
didier amyot 6e266c787b on the way 2018-10-14 20:04:18 -04:00
didier amyot 82b43ddf90 link the logic of the client-server login logic 2018-10-14 08:46:45 -04:00
didier amyot 1b0d58d1dd implement stupid login on server side. 2018-10-13 21:12:24 -04:00
didier amyot addff66470 implement basic login in the wui 2018-10-13 16:35:19 -04:00
didier amyot d4bc2490ff smaller commit :) 2018-09-09 15:21:54 -04:00
didier amyot bce64fc8f6 implemented so many things... 2018-09-09 12:20:12 -04:00
didier amyot fd843a5425 going in the right direction 2018-08-28 20:26:58 -04:00
didier amyot 33c1d42e63 refactoring... 2018-07-24 18:46:36 -04:00
didier amyot f5e8ffd82d tried (and failed) to add cljs to my test plan 2018-07-21 14:15:59 -04:00
didier amyot 6867f6ffbc Merge branch 'wui' into 'master'
implemented the simplest web user interface possible

See merge request didiercrunch/skirmish!2
2018-07-21 17:52:40 +00:00
69 changed files with 2696 additions and 1410 deletions

No files matched your search

+2
View File
@@ -15,3 +15,5 @@ skirmish.iml
full-stack
fixtures
node_modules
figwheel_server.log
.rebel_readline_history
+1
View File
@@ -1,4 +1,5 @@
FROM clojure
MAINTAINER Didier Amyot <didier.amyot@gmail.com>
# Set the working directory to /app
WORKDIR /app
+34 -4
View File
@@ -1,9 +1,9 @@
# Skrimish
![logo](doc/logo.svg "Skrimish")
![logo](https://erpscan.com/wp-content/themes/erpscancom/img/hq__amsterdam.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,22 +26,52 @@ 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
* [datomic-free](https://www.datomic.com/) for transaction needs
* [clickhouse](https://clickhouse.yandex) to store the market data
#### 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. 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
Copyright © 2018 didier amyot
Copyright © 2020 didier amyot
Distributed under the Eclipse Public License either version 1.0 or (at
your option) any later version.
+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
+39 -21
View File
@@ -4,7 +4,7 @@ version: '2'
services:
zoo1:
image: zookeeper:3.4.9
restart: unless-stopped
restart: "unless-stopped"
hostname: zoo1
ports:
- "2181:2181"
@@ -150,26 +150,44 @@ services:
depends_on:
- zoo1
# skirmish
skirmish-api:
image: skirmish
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"
# datomic
datomic-transactors:
build: "datomic/"
restart: "unless-stopped"
ports:
- "1602:1602"
- "4334:4334"
- "4335:4335"
- "4336:4336"
skirmish-exchange-core:
image: skirmish
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"
clickhouse:
image: "yandex/clickhouse-server:18.14"
restart: "unless-stopped"
ports:
- "1602:1602"
- "8123:8123"
- "9000:9000"
# skirmish itself
# skirmish-api:
# build: .
# 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:
# - "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"
+8 -4
View File
@@ -1,10 +1,14 @@
(ns skirmish.wui.middleware
(:require [ring.middleware.defaults :refer [site-defaults wrap-defaults]]
[prone.middleware :refer [wrap-exceptions]]
[ring.middleware.reload :refer [wrap-reload]]))
[prone.middleware :refer [wrap-exceptions]]))
(defn custom-dev-middleware [handler]
(fn [request]
(let [response (handler request)]
response)))
(defn wrap-middleware [handler]
(-> handler
(wrap-defaults site-defaults)
wrap-exceptions
wrap-reload))
custom-dev-middleware
wrap-exceptions))
-33
View File
@@ -1,33 +0,0 @@
(ns skirmish.wui.repl
(:use skirmish.wui.handler
figwheel-sidecar.repl-api
ring.server.standalone
[ring.middleware file-info file]))
(defonce server (atom nil))
(defn get-handler []
;; #'app expands to (var app) so that when we reload our code,
;; the server is forced to re-resolve the symbol in the var
;; rather than having its own copy. When the root binding
;; changes, the server picks it up without having to restart.
(-> #'app
; Makes static assets in $PROJECT_DIR/resources/public/ available.
(wrap-file "resources")
; Content-Type, Content-Length, and Last Modified headers for files in body
(wrap-file-info)))
(defn start-server
"used for starting the server in development mode from REPL"
[& [port]]
(let [port (if port (Integer/parseInt port) 3000)]
(reset! server
(serve (get-handler)
{:port port
:auto-reload? true
:join? false}))
(println (str "You can view the site at http://localhost:" port))))
(defn stop-server []
(.stop @server)
(reset! server nil))
-867
View File
@@ -1,867 +0,0 @@
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.611 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/goog/deps.js
notifying browser that file changed: target/cljsbuild/public/js/out/cljs_deps.js
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/topbar.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.552 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/topbar.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.471 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/topbar.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.464 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/topbar.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.447 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/topbar.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
---- Compiler Warning on src/cljs/skirmish/core.cljs line:37 column:1 ----
current-page at line 24 is being replaced
33 ;; -------------------------
34 ;; Initialize app
35 
36 
37 (defn current-page []
  ^--- current-page at line 24 is being replaced
38  [:div
39  (dase.topbar/top-bar state)
40  [:div
41  [:div.columns
42  [:div.column.is-8.is-offset-2
---- Compiler Warning : Please see src/cljs/skirmish/core.cljs ----
---- Compiler Warning on src/cljs/skirmish/core.cljs line:39 column:5 ----
No such namespace: dase.topbar, could not locate dase/topbar.cljs, dase/topbar.cljc, or JavaScript source providing "dase.topbar"
37 (defn current-page []
38  [:div
39  (dase.topbar/top-bar state)
   ^--- No such namespace: dase.topbar, could not locate dase/topbar.cljs, dase/topbar.cljc, or JavaScript source providing "dase.topbar"
40  [:div
41  [:div.columns
42  [:div.column.is-8.is-offset-2
43  [(session/get :current-page)]]]]])
---- Compiler Warning : Please see src/cljs/skirmish/core.cljs ----
---- Compiler Warning on src/cljs/skirmish/core.cljs line:39 column:5 ----
Use of undeclared Var dase.topbar/top-bar
37 (defn current-page []
38  [:div
39  (dase.topbar/top-bar state)
   ^--- Use of undeclared Var dase.topbar/top-bar
40  [:div
41  [:div.columns
42  [:div.column.is-8.is-offset-2
43  [(session/get :current-page)]]]]])
---- Compiler Warning : Please see src/cljs/skirmish/core.cljs ----
---- Compiler Warning on src/cljs/skirmish/core.cljs line:39 column:25 ----
Use of undeclared Var skirmish.core/state
37 (defn current-page []
38  [:div
39  (dase.topbar/top-bar state)
   ^--- Use of undeclared Var skirmish.core/state
40  [:div
41  [:div.columns
42  [:div.column.is-8.is-offset-2
43  [(session/get :current-page)]]]]])
---- Compiler Warning : Please see src/cljs/skirmish/core.cljs ----
---- Compiler Warning on src/cljs/skirmish/core.cljs line:43 column:9 ----
No such namespace: session, could not locate session.cljs, session.cljc, or JavaScript source providing "session"
39  (dase.topbar/top-bar state)
40  [:div
41  [:div.columns
42  [:div.column.is-8.is-offset-2
43  [(session/get :current-page)]]]]])
   ^--- No such namespace: session, could not locate session.cljs, session.cljc, or JavaScript source providing "session"
44 
45 
46 (defn mount-root []
47  (reagent/render [current-page] (.getElementById js/document "app")))
---- Compiler Warning : Please see src/cljs/skirmish/core.cljs ----
---- Compiler Warning on src/cljs/skirmish/core.cljs line:43 column:9 ----
Use of undeclared Var session/get
39  (dase.topbar/top-bar state)
40  [:div
41  [:div.columns
42  [:div.column.is-8.is-offset-2
43  [(session/get :current-page)]]]]])
   ^--- Use of undeclared Var session/get
44 
45 
46 (defn mount-root []
47  (reagent/render [current-page] (.getElementById js/document "app")))
---- Compiler Warning : Please see src/cljs/skirmish/core.cljs ----
---- Compiler Warning on src/cljs/skirmish/core.cljs line:51 column:1 ----
mount-root at line 46 is being replaced
47  (reagent/render [current-page] (.getElementById js/document "app")))
48 
49 
50 
51 (defn mount-root []
  ^--- mount-root at line 46 is being replaced
52  (reagent/render [current-page] (.getElementById js/document "app")))
53 
54 (defn init! []
55  (accountant/configure-navigation!
56  {:nav-handler
---- Compiler Warning : Please see src/cljs/skirmish/core.cljs ----
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.575 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/cljs_deps.js
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
---- Compiler Warning on src/cljs/skirmish/core.cljs line:37 column:1 ----
current-page at line 24 is being replaced
33 ;; -------------------------
34 ;; Initialize app
35 
36 
37 (defn current-page []
  ^--- current-page at line 24 is being replaced
38  [:div
39  (top-bar state)
40  [:div
41  [:div.columns
42  [:div.column.is-8.is-offset-2
---- Compiler Warning : Please see src/cljs/skirmish/core.cljs ----
---- Compiler Warning on src/cljs/skirmish/core.cljs line:39 column:13 ----
Use of undeclared Var skirmish.core/state
37 (defn current-page []
38  [:div
39  (top-bar state)
   ^--- Use of undeclared Var skirmish.core/state
40  [:div
41  [:div.columns
42  [:div.column.is-8.is-offset-2
43  [(session/get :current-page)]]]]])
---- Compiler Warning : Please see src/cljs/skirmish/core.cljs ----
---- Compiler Warning on src/cljs/skirmish/core.cljs line:43 column:9 ----
No such namespace: session, could not locate session.cljs, session.cljc, or JavaScript source providing "session"
39  (top-bar state)
40  [:div
41  [:div.columns
42  [:div.column.is-8.is-offset-2
43  [(session/get :current-page)]]]]])
   ^--- No such namespace: session, could not locate session.cljs, session.cljc, or JavaScript source providing "session"
44 
45 
46 (defn mount-root []
47  (reagent/render [current-page] (.getElementById js/document "app")))
---- Compiler Warning : Please see src/cljs/skirmish/core.cljs ----
---- Compiler Warning on src/cljs/skirmish/core.cljs line:43 column:9 ----
Use of undeclared Var session/get
39  (top-bar state)
40  [:div
41  [:div.columns
42  [:div.column.is-8.is-offset-2
43  [(session/get :current-page)]]]]])
   ^--- Use of undeclared Var session/get
44 
45 
46 (defn mount-root []
47  (reagent/render [current-page] (.getElementById js/document "app")))
---- Compiler Warning : Please see src/cljs/skirmish/core.cljs ----
---- Compiler Warning on src/cljs/skirmish/core.cljs line:51 column:1 ----
mount-root at line 46 is being replaced
47  (reagent/render [current-page] (.getElementById js/document "app")))
48 
49 
50 
51 (defn mount-root []
  ^--- mount-root at line 46 is being replaced
52  (reagent/render [current-page] (.getElementById js/document "app")))
53 
54 (defn init! []
55  (accountant/configure-navigation!
56  {:nav-handler
---- Compiler Warning : Please see src/cljs/skirmish/core.cljs ----
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.517 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
---- Compiler Warning on src/cljs/skirmish/core.cljs line:40 column:1 ----
current-page at line 27 is being replaced
36 ;; -------------------------
37 ;; Initialize app
38 
39 
40 (defn current-page []
  ^--- current-page at line 27 is being replaced
41  [:div
42  (top-bar state)
43  [:div
44  [:div.columns
45  [:div.column.is-8.is-offset-2
---- Compiler Warning : Please see src/cljs/skirmish/core.cljs ----
---- Compiler Warning on src/cljs/skirmish/core.cljs line:46 column:9 ----
No such namespace: session, could not locate session.cljs, session.cljc, or JavaScript source providing "session"
42  (top-bar state)
43  [:div
44  [:div.columns
45  [:div.column.is-8.is-offset-2
46  [(session/get :current-page)]]]]])
   ^--- No such namespace: session, could not locate session.cljs, session.cljc, or JavaScript source providing "session"
47 
48 
49 (defn mount-root []
50  (reagent/render [current-page] (.getElementById js/document "app")))
---- Compiler Warning : Please see src/cljs/skirmish/core.cljs ----
---- Compiler Warning on src/cljs/skirmish/core.cljs line:46 column:9 ----
Use of undeclared Var session/get
42  (top-bar state)
43  [:div
44  [:div.columns
45  [:div.column.is-8.is-offset-2
46  [(session/get :current-page)]]]]])
   ^--- Use of undeclared Var session/get
47 
48 
49 (defn mount-root []
50  (reagent/render [current-page] (.getElementById js/document "app")))
---- Compiler Warning : Please see src/cljs/skirmish/core.cljs ----
---- Compiler Warning on src/cljs/skirmish/core.cljs line:54 column:1 ----
mount-root at line 49 is being replaced
50  (reagent/render [current-page] (.getElementById js/document "app")))
51 
52 
53 
54 (defn mount-root []
  ^--- mount-root at line 49 is being replaced
55  (reagent/render [current-page] (.getElementById js/document "app")))
56 
57 (defn init! []
58  (accountant/configure-navigation!
59  {:nav-handler
---- Compiler Warning : Please see src/cljs/skirmish/core.cljs ----
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.489 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
---- Compiler Warning on src/cljs/skirmish/core.cljs line:41 column:1 ----
current-page at line 28 is being replaced
37 ;; -------------------------
38 ;; Initialize app
39 
40 
41 (defn current-page []
  ^--- current-page at line 28 is being replaced
42  [:div
43  (top-bar state)
44  [:div
45  [:div.columns
46  [:div.column.is-8.is-offset-2
---- Compiler Warning : Please see src/cljs/skirmish/core.cljs ----
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.553 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/cljs_deps.js
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.436 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.447 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Notifying browser that CSS file changed: resources/public/css/site.css
Notifying browser that CSS file changed: resources/public/css/site.css
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.649 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/goog/deps.js
notifying browser that file changed: target/cljsbuild/public/js/out/cljs_deps.js
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/todo.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.561 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/todo.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.521 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/todo.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.477 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/cljs_deps.js
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/home_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.531 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/cljs_deps.js
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/todo_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.464 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/home_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 1.474 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/topbar.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.496 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/topbar.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.479 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/cljs_deps.js
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.44 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.472 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.432 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.449 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.468 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.466 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
---- Compiler Warning on src/cljs/skirmish/core.cljs line:26 column:1 ----
page already refers to: skirmish.wui.components.todo-page/page being replaced by: skirmish.core/page
23 ;; -------------------------
24 ;; Routes
25 
26 (defonce page (atom #'home-page))
  ^--- page already refers to: skirmish.wui.components.todo-page/page being replaced by: skirmish.core/page
27 
28 (secretary/defroute "/todo" []
29  (session/put! :current-page 'page)
30  (swap! state #(assoc % :page :todo)))
---- Compiler Warning : Please see src/cljs/skirmish/core.cljs ----
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.479 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
---- Compiler Warning on src/cljs/skirmish/core.cljs line:26 column:1 ----
page already refers to: skirmish.wui.components.todo-page/page being replaced by: skirmish.core/page
23 ;; -------------------------
24 ;; Routes
25 
26 (defonce page (atom #'home-page))
  ^--- page already refers to: skirmish.wui.components.todo-page/page being replaced by: skirmish.core/page
27 
28 (secretary/defroute "/todo" []
29  (session/put! :current-page #'page)
30  (swap! state #(assoc % :page :todo)))
---- Compiler Warning : Please see src/cljs/skirmish/core.cljs ----
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.473 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.458 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.435 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.474 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
---- Compiler Warning on src/cljs/skirmish/core.cljs line:34 column:21 ----
No such namespace: pages, could not locate pages.cljs, pages.cljc, or JavaScript source providing "pages"
30  (session/put! :current-page #'page)
31  (swap! state #(assoc % :page :todo)))
32 
33 (secretary/defroute "/" []
34  (session/put! :current-page #'pages/homepage)
   ^--- No such namespace: pages, could not locate pages.cljs, pages.cljc, or JavaScript source providing "pages"
35  (swap! state #(assoc % :page :home)))
36 ;(secretary/defroute "/" []
37 ; (reset! page #'home-page))
38 ;
39 ;(secretary/defroute "/about" []
---- Compiler Warning : Please see src/cljs/skirmish/core.cljs ----
Failed to compile build :app from ["src/cljs" "src/cljc" "env/dev/cljs"] in 0.234 seconds.
---- Could not Analyze src/cljs/skirmish/core.cljs line:34 column:21 ----
Unable to resolve var: homepage in this context at line 34 src/cljs/skirmish/core.cljs
33 (secretary/defroute "/" []
34  (session/put! :current-page #'pages/homepage)
   ^--- Unable to resolve var: homepage in this context at line 34 src/cljs/skirmish/core.cljs
35  (swap! state #(assoc % :page :home)))
36 ;(secretary/defroute "/" []
37 ; (reset! page #'home-page))
---- Analysis Error : Please see src/cljs/skirmish/core.cljs ----
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
---- Compiler Warning on src/cljs/skirmish/core.cljs line:35 column:21 ----
No such namespace: pages, could not locate pages.cljs, pages.cljc, or JavaScript source providing "pages"
31  (session/put! :current-page #'page)
32  (swap! state #(assoc % :page :todo)))
33 
34 (secretary/defroute "/" []
35  (session/put! :current-page #'pages/homepage)
   ^--- No such namespace: pages, could not locate pages.cljs, pages.cljc, or JavaScript source providing "pages"
36  (swap! state #(assoc % :page :home)))
37 ;(secretary/defroute "/" []
38 ; (reset! page #'home-page))
39 ;
40 ;(secretary/defroute "/about" []
---- Compiler Warning : Please see src/cljs/skirmish/core.cljs ----
Failed to compile build :app from ["src/cljs" "src/cljc" "env/dev/cljs"] in 0.286 seconds.
---- Could not Analyze src/cljs/skirmish/core.cljs line:35 column:21 ----
Unable to resolve var: homepage in this context at line 35 /home/didier/gitlab/skirmish/skirmish/src/cljs/skirmish/core.cljs
34 (secretary/defroute "/" []
35  (session/put! :current-page #'pages/homepage)
   ^--- Unable to resolve var: homepage in this context at line 35 /home/didier/gitlab/skirmish/skirmish/src/cljs/skirmish/core.cljs
36  (swap! state #(assoc % :page :home)))
37 ;(secretary/defroute "/" []
38 ; (reset! page #'home-page))
---- Analysis Error : Please see src/cljs/skirmish/core.cljs ----
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.443 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/cljs_deps.js
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.444 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/home_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.437 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/cljs_deps.js
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/pages.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.394 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/cljs_deps.js
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/pages.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.409 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.409 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.384 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/pages.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Failed to compile build :app from ["src/cljs" "src/cljc" "env/dev/cljs"] in 0.245 seconds.
---- Could not Analyze src/cljs/skirmish/core.cljs ----
Invalid :refer, var skirmish.pages/pages does not exist 
---- Analysis Error : Please see src/cljs/skirmish/core.cljs ----
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Failed to compile build :app from ["src/cljs" "src/cljc" "env/dev/cljs"] in 0.248 seconds.
---- Could not Analyze src/cljs/skirmish/core.cljs ----
Invalid :refer, var skirmish.pages/pages does not exist 
---- Analysis Error : Please see src/cljs/skirmish/core.cljs ----
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Failed to compile build :app from ["src/cljs" "src/cljc" "env/dev/cljs"] in 0.254 seconds.
---- Could not Analyze src/cljs/skirmish/core.cljs ----
Invalid :refer, var skirmish.pages/pages does not exist 
---- Analysis Error : Please see src/cljs/skirmish/core.cljs ----
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
---- Compiler Warning on src/cljs/skirmish/core.cljs line:49 column:5 ----
Use of undeclared Var skirmish.core/top-bar
47 (defn current-page []
48  [:div
49  (top-bar state)
   ^--- Use of undeclared Var skirmish.core/top-bar
50  [:div
51  [:div.columns
52  [:div.column.is-8.is-offset-2
53  [(session/get :current-page)]]]]])
---- Compiler Warning : Please see src/cljs/skirmish/core.cljs ----
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.434 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/cljs_deps.js
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.409 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/cljs_deps.js
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.395 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.439 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/home_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.39 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.398 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/topbar.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.433 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/topbar.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.424 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/topbar.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.422 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/topbar.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.413 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/topbar.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.391 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/topbar.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.389 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/cljs_deps.js
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.402 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Notifying browser that CSS file changed: resources/public/css/site.css
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 1.82 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/goog/deps.js
notifying browser that file changed: target/cljsbuild/public/js/out/cljs_deps.js
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/topbar.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.616 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/topbar.js
Notifying browser that CSS file changed: resources/public/css/site.css
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.515 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/topbar.js
Notifying browser that CSS file changed: resources/public/css/site.css
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.586 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/topbar.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.481 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/topbar.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.521 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/topbar.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.496 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/topbar.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.457 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/topbar.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.531 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/topbar.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.448 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/topbar.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.442 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/topbar.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.461 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/topbar.js
Notifying browser that CSS file changed: resources/public/css/site.css
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.437 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/topbar.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.419 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.438 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.436 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.414 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.453 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.435 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.412 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.407 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.433 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.497 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.504 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.384 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.373 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.39 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.367 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.444 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/cljs_deps.js
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/pages.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Failed to compile build :app from ["src/cljs" "src/cljc" "env/dev/cljs"] in 0.278 seconds.
---- Could not Read src/cljs/skirmish/core.cljs line:68 ----
/home/didier/gitlab/skirmish/skirmish/src/cljs/skirmish/core.cljs [line 68, col 1] Unexpected EOF while reading item 8 of list, starting at line 37 and column 1.
---- Reader Error : Please see src/cljs/skirmish/core.cljs ----
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.427 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.441 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.455 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.432 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.418 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.421 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.445 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.407 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.398 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.41 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.427 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.402 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.427 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.393 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.392 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.4 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.385 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.392 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.423 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.386 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.399 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.393 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Notifying browser that CSS file changed: resources/public/css/site.css
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.393 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Notifying browser that CSS file changed: resources/public/css/site.css
Notifying browser that CSS file changed: resources/public/css/site.css
Notifying browser that CSS file changed: resources/public/css/site.css
Notifying browser that CSS file changed: resources/public/css/site.css
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.404 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.388 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.431 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/cljs_deps.js
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/security_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.457 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/security_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.417 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/cljs_deps.js
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/pages.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.414 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/pages.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.45 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.452 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.445 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.402 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.397 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.452 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.44 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.423 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Notifying browser that CSS file changed: resources/public/css/site.css
Notifying browser that CSS file changed: resources/public/css/site.css
Notifying browser that CSS file changed: resources/public/css/site.css
Notifying browser that CSS file changed: resources/public/css/site.css
Notifying browser that CSS file changed: resources/public/css/site.css
Notifying browser that CSS file changed: resources/public/css/site.css
Notifying browser that CSS file changed: resources/public/css/site.css
Notifying browser that CSS file changed: resources/public/css/site.css
Notifying browser that CSS file changed: resources/public/css/site.css
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.446 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Notifying browser that CSS file changed: resources/public/css/site.css
Notifying browser that CSS file changed: resources/public/css/site.css
Notifying browser that CSS file changed: resources/public/css/site.css
Notifying browser that CSS file changed: resources/public/css/site.css
Notifying browser that CSS file changed: resources/public/css/site.css
Notifying browser that CSS file changed: resources/public/css/site.css
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.402 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/security_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.385 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/security_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.396 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/core.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.418 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.401 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/home_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.39 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/security_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.392 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/components/security_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Failed to compile build :app from ["src/cljs" "src/cljc" "env/dev/cljs"] in 0.387 seconds.
---- Could not Analyze src/cljs/skirmish/wui/pages.cljs ----
No such namespace: skirmish.components.todo-page, could not locate skirmish/components/todo_page.cljs, skirmish/components/todo_page.cljc, or JavaScript source providing "skirmish.components.todo-page" (Please check that namespaces with dashes use underscores in the ClojureScript file name) 
---- Analysis Error : Please see src/cljs/skirmish/wui/pages.cljs ----
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Failed to compile build :app from ["src/cljs" "src/cljc" "env/dev/cljs"] in 0.3 seconds.
---- Could not Analyze src/cljs/skirmish/wui/pages.cljs ----
No such namespace: skirmish.components.todo-page, could not locate skirmish/components/todo_page.cljs, skirmish/components/todo_page.cljc, or JavaScript source providing "skirmish.components.todo-page" (Please check that namespaces with dashes use underscores in the ClojureScript file name) 
---- Analysis Error : Please see src/cljs/skirmish/wui/pages.cljs ----
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Failed to compile build :app from ["src/cljs" "src/cljc" "env/dev/cljs"] in 0.307 seconds.
---- Could not Analyze src/cljs/skirmish/wui/pages.cljs ----
No such namespace: skirmish.components.todo-page, could not locate skirmish/components/todo_page.cljs, skirmish/components/todo_page.cljc, or JavaScript source providing "skirmish.components.todo-page" (Please check that namespaces with dashes use underscores in the ClojureScript file name) 
---- Analysis Error : Please see src/cljs/skirmish/wui/pages.cljs ----
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 2.041 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/goog/deps.js
notifying browser that file changed: target/cljsbuild/public/js/out/cljs_deps.js
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/wui/pages.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.814 seconds.
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.603 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/goog/deps.js
notifying browser that file changed: target/cljsbuild/public/js/out/cljs_deps.js
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/order.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.746 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/goog/deps.js
notifying browser that file changed: target/cljsbuild/public/js/out/cljs_deps.js
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/wui/components/securities_page.js
Compiling build :app to "target/cljsbuild/public/js/app.js" from ["src/cljs" "src/cljc" "env/dev/cljs"]...
Successfully compiled build :app to "target/cljsbuild/public/js/app.js" in 0.533 seconds.
notifying browser that file changed: target/cljsbuild/public/js/out/skirmish/wui/components/securities_page.js
+3 -3
View File
@@ -5,9 +5,9 @@
"requires": true,
"dependencies": {
"bulma": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/bulma/-/bulma-0.7.1.tgz",
"integrity": "sha512-wRSO2LXB+qI9Pyz2id+uZr4quz5aftSN7Ay1ysr1+krzVp3utD+Ci4CeKuZdrYGc800t65b7heXBL6qw2Wo/lQ=="
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/bulma/-/bulma-0.7.2.tgz",
"integrity": "sha512-6JHEu8U/1xsyOst/El5ImLcZIiE2JFXgvrz8GGWbnDLwTNRPJzdAM0aoUM1Ns0avALcVb6KZz9NhzmU53dGDcQ=="
},
"font-awesome": {
"version": "4.7.0",
+35 -29
View File
@@ -4,49 +4,57 @@
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.9.0"]
[org.clojure/core.async "0.4.474"]
[org.clojure/math.combinatorics "0.1.4"]
:resource-paths ["resources"
"target/cljsbuild"]
:dependencies [[org.clojure/clojure "1.10.1"]
[org.clojure/core.async "1.3.610"]
[org.clojure/math.combinatorics "0.1.6"]
[com.datomic/datomic-free "0.9.5697"]
[com.google.guava/guava "22.0"]
;[javax.xml.bind/jaxb-api "2.2.11"]
;; server
[aero "1.1.3"]
[http-kit "2.3.0"]
[compojure "1.6.1"]
[com.taoensso/nippy "2.14.0"]
[ring-cors "0.1.13"]
[ring/ring-defaults "0.3.2"]
;[ring/ring-jetty-adapter "1.8.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"]
[spootnik/kinsky "0.1.22"]
[aero "1.1.3"]
[com.taoensso/timbre "4.10.0"]
[org.clojure/tools.reader "1.3.3"]
[http-kit "2.4.0"]
[compojure "1.6.2"]
;; backends
[spootnik/kinsky "0.1.25"]
;;utils
[slingshot "0.12.2"]
[orchestra "2017.11.12-1"]
[cheshire "5.8.0"]
[orchestra "2020.07.12-1"]
[cheshire "5.10.0"]
[com.stuartsierra/frequencies "0.1.0"]
[rm-hull/infix "0.3.3"]
;; wui
[ring-server "0.5.0"]
[reagent "0.8.1"]
[reagent-utils "0.3.1"]
[ring "1.6.3"]
[ring/ring-defaults "0.3.1"]
[compojure "1.6.1"]
[hiccup "1.0.5"]
;; cljs
[org.clojure/clojurescript "1.10.339" :scope "provided"]
[org.clojure/clojurescript "1.10.773" :scope "provided"]
[cljs-http "0.1.46"]
[secretary "1.2.3"]
[venantius/accountant "0.2.4" :exclusions [org.clojure/tools.reader]]]
[venantius/accountant "0.2.2" :exclusions [org.clojure/tools.reader]]]
:plugins [[lein-midje "3.2.1"]
[lein-npm "0.6.2"]
[lein-environ "1.1.0"] ;; maybe to remove
[lein-cljsbuild "1.1.7"]
[lein-asset-minifier "0.2.7" :exclusions [org.clojure/clojure]]]
[lein-cljsbuild "1.1.8"]
[lein-asset-minifier "0.4.6" :exclusions [org.clojure/clojure]]]
:java-source-paths ["src/java"]
:npm {:dependencies [[bulma "0.7.1"]
:npm {:dependencies [[bulma "0.7.2"]
[font-awesome "4.7.0"]]}
:ring {:handler skirmish.wui.handler/app
@@ -54,15 +62,13 @@
:min-lein-version "2.5.0"
:uberjar-name "skirmish.jar"
:main skirmish.wui.core
:jvm-opts ["--add-modules" "java.xml.bind"]
:main skirmish.core
:clean-targets ^{:protect false}
[:target-path
[:cljsbuild :builds :app :compiler :output-dir]
[:cljsbuild :builds :app :compiler :output-to]]
:source-paths ["src/clj" "src/cljc"]
:resource-paths ["resources" "target/cljsbuild"]
:minify-assets
{:assets
@@ -92,7 +98,7 @@
:devcards
{:source-paths ["src/cljs" "src/cljc" "env/dev/cljs"]
:figwheel {:devcards true}
:figwheel {:devcards false}
:compiler {:main "skirmish.wui.cards"
:asset-path "js/devcards_out"
:output-to "target/cljsbuild/public/js/app_devcards.js"
@@ -112,14 +118,14 @@
:css-dirs ["resources/public/css"]
:ring-handler skirmish.wui.handler/app}
:test-paths ["test/clj" "test/cljs"]
:sass {:source-paths ["src/sass"]
:target-path "resources/public/css"}
:profiles {:dev {:repl-options {:init-ns skirmish.wui.repl
:nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]}
:dependencies [[midje "1.9.1"]
:profiles {:dev {
:dependencies [[midje "1.9.9"]
[stylefruits/gniazdo "1.0.1"] ;; only used in benchmark
[clj-http "3.9.0"] ;; only used in benchmark
+19 -14
View File
@@ -1,10 +1,13 @@
{
;; Specify which middleware to use, it will default to :chan.
:middleware :kafka
:middleware :chan
;; If set to true, orchestra will instrument all the spec'ed functions
:instrument-spec true
:datomic
{:transactor-url "datomic:free://localhost:4334/skirmish.ledger?password=datomicpassword"}
;; all the kafka configuration goes bellow.
:kafka
{:bootstrap.servers #or [#env KAFKA_BOOTSTRAP_SERVERS "localhost:9092"]
@@ -24,19 +27,21 @@
:max-body 524288 ;; 512kb, small but enough
: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}
: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
:default-sec-id :dapm
:datomic {:transactor-url "datomic:free://localhost:4334/skirmish.broker?password=datomicpassword"}}
:wui
{:port #long #or [#env WUI_PORT 3000]}
;; list of tradable securities
:securities
[{:sec-id :foo}
{:sec-id :bar}
{:sec-id :toto}
{:sec-id :tata}]
;; stupid parameters about benchmarking tools
:benchmark {
:number-of-requests-per-securities 30}}
{:port #long #or [#env WUI_PORT 3000]}}
+412 -85
View File
@@ -14,7 +14,7 @@
-ms-user-select: none;
user-select: none; }
.select:not(.is-multiple):not(.is-loading)::after, .navbar-link::after {
.select:not(.is-multiple):not(.is-loading)::after, .navbar-link:not(.is-arrowless)::after {
border: 3px solid transparent;
border-radius: 2px;
border-right: 0;
@@ -31,7 +31,7 @@
width: 0.625em; }
.box:not(:last-child), .content:not(:last-child), .notification:not(:last-child), .progress:not(:last-child), .table:not(:last-child), .table-container:not(:last-child), .title:not(:last-child),
.subtitle:not(:last-child), .block:not(:last-child), .highlight:not(:last-child), .breadcrumb:not(:last-child), .level:not(:last-child), .message:not(:last-child), .tabs:not(:last-child) {
.subtitle:not(:last-child), .block:not(:last-child), .highlight:not(:last-child), .breadcrumb:not(:last-child), .level:not(:last-child), .list:not(:last-child), .message:not(:last-child), .tabs:not(:last-child) {
margin-bottom: 1.5rem; }
.delete, .modal-close {
@@ -41,6 +41,7 @@
border: none;
border-radius: 290486px;
cursor: pointer;
pointer-events: auto;
display: inline-block;
flex-grow: 0;
flex-shrink: 0;
@@ -993,6 +994,16 @@ a.has-text-danger:hover, a.has-text-danger:focus {
.is-hidden {
display: none !important; }
.is-sr-only {
border: none !important;
clip: rect(0, 0, 0, 0) !important;
height: 0.01em !important;
overflow: hidden !important;
padding: 0 !important;
position: absolute !important;
white-space: nowrap !important;
width: 0.01em !important; }
@media screen and (max-width: 768px) {
.is-hidden-mobile {
display: none !important; } }
@@ -1081,10 +1092,10 @@ a.has-text-danger:hover, a.has-text-danger:focus {
box-shadow: none !important; }
.box {
background-color: white;
background-color: #7a7a7a;
border-radius: 6px;
box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1);
color: whitesmoke;
box-shadow: 0 2px 3px #b5b5b5, 0 0 0 1px #b5b5b5;
color: white;
display: block;
padding: 1.25rem; }
@@ -1788,7 +1799,7 @@ a.box:active {
justify-content: flex-start; }
.buttons .button {
margin-bottom: 0.5rem; }
.buttons .button:not(:last-child) {
.buttons .button:not(:last-child):not(.is-fullwidth) {
margin-right: 0.5rem; }
.buttons:last-child {
margin-bottom: -0.5rem; }
@@ -1899,13 +1910,23 @@ a.box:active {
.content blockquote {
background-color: #0a0a0a;
border-left: 5px solid #ffdd57;
border-left: 5px solid #dbdbdb;
padding: 1.25em 1.5em; }
.content ol {
list-style: decimal outside;
list-style-position: outside;
margin-left: 2em;
margin-top: 1em; }
.content ol:not([type]) {
list-style-type: decimal; }
.content ol:not([type]).is-lower-alpha {
list-style-type: lower-alpha; }
.content ol:not([type]).is-lower-roman {
list-style-type: lower-roman; }
.content ol:not([type]).is-upper-alpha {
list-style-type: upper-alpha; }
.content ol:not([type]).is-upper-roman {
list-style-type: upper-roman; }
.content ul {
list-style: disc outside;
@@ -1948,7 +1969,7 @@ a.box:active {
width: 100%; }
.content table td,
.content table th {
border: 1px solid #ffdd57;
border: 1px solid #dbdbdb;
border-width: 0 0 1px;
padding: 0.5em 0.75em;
vertical-align: top; }
@@ -2239,7 +2260,7 @@ a.box:active {
.select select:not([multiple]) {
padding-right: 2.5em; }
.select select[multiple] {
height: initial;
height: auto;
padding: 0; }
.select select[multiple] option {
padding: 0.5em 1em; }
@@ -2587,24 +2608,25 @@ a.box:active {
background-color: #eeeeee;
color: #363636; }
.file-label:hover .file-name {
border-color: #ffdb4a; }
border-color: #d5d5d5; }
.file-label:active .file-cta {
background-color: #e8e8e8;
color: #363636; }
.file-label:active .file-name {
border-color: #ffd83d; }
border-color: #cfcfcf; }
.file-input {
height: 0.01em;
height: 100%;
left: 0;
opacity: 0;
outline: none;
position: absolute;
top: 0;
width: 0.01em; }
width: 100%; }
.file-cta,
.file-name {
border-color: #ffdd57;
border-color: #dbdbdb;
border-radius: 4px;
font-size: 1em;
padding-left: 1em;
@@ -2616,7 +2638,7 @@ a.box:active {
color: #4a4a4a; }
.file-name {
border-color: #ffdd57;
border-color: #dbdbdb;
border-style: solid;
border-width: 1px 1px 1px 0;
display: block;
@@ -2696,31 +2718,31 @@ a.box:active {
.field.has-addons .control:last-child .select select {
border-bottom-left-radius: 0;
border-top-left-radius: 0; }
.field.has-addons .control .button:hover, .field.has-addons .control .button.is-hovered,
.field.has-addons .control .input:hover,
.field.has-addons .control .input.is-hovered,
.field.has-addons .control .select select:hover,
.field.has-addons .control .select select.is-hovered {
.field.has-addons .control .button:not([disabled]):hover, .field.has-addons .control .button:not([disabled]).is-hovered,
.field.has-addons .control .input:not([disabled]):hover,
.field.has-addons .control .input:not([disabled]).is-hovered,
.field.has-addons .control .select select:not([disabled]):hover,
.field.has-addons .control .select select:not([disabled]).is-hovered {
z-index: 2; }
.field.has-addons .control .button:focus, .field.has-addons .control .button.is-focused, .field.has-addons .control .button:active, .field.has-addons .control .button.is-active,
.field.has-addons .control .input:focus,
.field.has-addons .control .input.is-focused,
.field.has-addons .control .input:active,
.field.has-addons .control .input.is-active,
.field.has-addons .control .select select:focus,
.field.has-addons .control .select select.is-focused,
.field.has-addons .control .select select:active,
.field.has-addons .control .select select.is-active {
.field.has-addons .control .button:not([disabled]):focus, .field.has-addons .control .button:not([disabled]).is-focused, .field.has-addons .control .button:not([disabled]):active, .field.has-addons .control .button:not([disabled]).is-active,
.field.has-addons .control .input:not([disabled]):focus,
.field.has-addons .control .input:not([disabled]).is-focused,
.field.has-addons .control .input:not([disabled]):active,
.field.has-addons .control .input:not([disabled]).is-active,
.field.has-addons .control .select select:not([disabled]):focus,
.field.has-addons .control .select select:not([disabled]).is-focused,
.field.has-addons .control .select select:not([disabled]):active,
.field.has-addons .control .select select:not([disabled]).is-active {
z-index: 3; }
.field.has-addons .control .button:focus:hover, .field.has-addons .control .button.is-focused:hover, .field.has-addons .control .button:active:hover, .field.has-addons .control .button.is-active:hover,
.field.has-addons .control .input:focus:hover,
.field.has-addons .control .input.is-focused:hover,
.field.has-addons .control .input:active:hover,
.field.has-addons .control .input.is-active:hover,
.field.has-addons .control .select select:focus:hover,
.field.has-addons .control .select select.is-focused:hover,
.field.has-addons .control .select select:active:hover,
.field.has-addons .control .select select.is-active:hover {
.field.has-addons .control .button:not([disabled]):focus:hover, .field.has-addons .control .button:not([disabled]).is-focused:hover, .field.has-addons .control .button:not([disabled]):active:hover, .field.has-addons .control .button:not([disabled]).is-active:hover,
.field.has-addons .control .input:not([disabled]):focus:hover,
.field.has-addons .control .input:not([disabled]).is-focused:hover,
.field.has-addons .control .input:not([disabled]):active:hover,
.field.has-addons .control .input:not([disabled]).is-active:hover,
.field.has-addons .control .select select:not([disabled]):focus:hover,
.field.has-addons .control .select select:not([disabled]).is-focused:hover,
.field.has-addons .control .select select:not([disabled]):active:hover,
.field.has-addons .control .select select:not([disabled]).is-active:hover {
z-index: 4; }
.field.has-addons .control.is-expanded {
flex-grow: 1; }
@@ -2805,6 +2827,7 @@ a.box:active {
margin-right: 0.75rem; } }
.control {
clear: both;
font-size: 1rem;
position: relative;
text-align: left; }
@@ -2963,7 +2986,7 @@ a.box:active {
border-radius: 4px;
padding: 1.25rem 2.5rem 1.25rem 1.5rem;
position: relative; }
.notification a:not(.button) {
.notification a:not(.button):not(.dropdown-item) {
color: currentColor;
text-decoration: underline; }
.notification strong {
@@ -3023,7 +3046,7 @@ a.box:active {
padding: 0;
width: 100%; }
.progress::-webkit-progress-bar {
background-color: #ffdd57; }
background-color: #dbdbdb; }
.progress::-webkit-progress-value {
background-color: whitesmoke; }
.progress::-moz-progress-bar {
@@ -3203,8 +3226,10 @@ a.box:active {
.table.is-fullwidth {
width: 100%; }
.table.is-hoverable tbody tr:not(.is-selected):hover {
background-color: #fafafa; }
background-color: #242424; }
.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover {
background-color: #242424; }
.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even) {
background-color: whitesmoke; }
.table.is-narrow td,
.table.is-narrow th {
@@ -3502,7 +3527,7 @@ a.tag:hover {
position: relative; }
.card-header {
background-color: none;
background-color: transparent;
align-items: stretch;
box-shadow: 0 1px 2px rgba(10, 10, 10, 0.1);
display: flex; }
@@ -3529,12 +3554,12 @@ a.tag:hover {
position: relative; }
.card-content {
background-color: none;
background-color: transparent;
padding: 1.5rem; }
.card-footer {
background-color: none;
border-top: 1px solid #ffdd57;
background-color: transparent;
border-top: 1px solid #dbdbdb;
align-items: stretch;
display: flex; }
@@ -3547,7 +3572,7 @@ a.tag:hover {
justify-content: center;
padding: 0.75rem; }
.card-footer-item:not(:last-child) {
border-right: 1px solid #ffdd57; }
border-right: 1px solid #dbdbdb; }
.card .media:not(:last-child) {
margin-bottom: 0.75rem; }
@@ -3591,18 +3616,23 @@ a.tag:hover {
padding: 0.375rem 1rem;
position: relative; }
a.dropdown-item {
a.dropdown-item,
button.dropdown-item {
padding-right: 3rem;
white-space: nowrap; }
a.dropdown-item:hover {
text-align: left;
white-space: nowrap;
width: 100%; }
a.dropdown-item:hover,
button.dropdown-item:hover {
background-color: #0a0a0a;
color: #0a0a0a; }
a.dropdown-item.is-active {
a.dropdown-item.is-active,
button.dropdown-item.is-active {
background-color: #00d1b2;
color: #fff; }
.dropdown-divider {
background-color: #ffdd57;
background-color: #dbdbdb;
border: none;
display: block;
height: 1px;
@@ -3623,10 +3653,9 @@ a.dropdown-item {
display: flex; }
.level.is-mobile .level-left + .level-right {
margin-top: 0; }
.level.is-mobile .level-item {
margin-right: 0.75rem; }
.level.is-mobile .level-item:not(:last-child) {
margin-bottom: 0; }
margin-bottom: 0;
margin-right: 0.75rem; }
.level.is-mobile .level-item:not(.is-narrow) {
flex-grow: 1; }
@media screen and (min-width: 769px), print {
@@ -3679,6 +3708,32 @@ a.dropdown-item {
.level-right {
display: flex; } }
.list {
background-color: white;
border-radius: 4px;
box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1); }
.list-item {
display: block;
padding: 0.5em 1em; }
.list-item:not(a) {
color: whitesmoke; }
.list-item:first-child {
border-top-left-radius: 4px;
border-top-right-radius: 4px; }
.list-item:last-child {
border-top-left-radius: 4px;
border-top-right-radius: 4px; }
.list-item:not(:last-child) {
border-bottom: 1px solid #dbdbdb; }
.list-item.is-active {
background-color: #00d1b2;
color: #fff; }
a.list-item {
background-color: #0a0a0a;
cursor: pointer; }
.media {
align-items: flex-start;
display: flex;
@@ -3686,7 +3741,7 @@ a.dropdown-item {
.media .content:not(:last-child) {
margin-bottom: 0.75rem; }
.media .media {
border-top: 1px solid rgba(255, 221, 87, 0.5);
border-top: 1px solid rgba(219, 219, 219, 0.5);
display: flex;
padding-top: 0.75rem; }
.media .media .content:not(:last-child),
@@ -3697,7 +3752,7 @@ a.dropdown-item {
.media .media .media + .media {
margin-top: 0.5rem; }
.media + .media {
border-top: 1px solid rgba(255, 221, 87, 0.5);
border-top: 1px solid rgba(219, 219, 219, 0.5);
margin-top: 1rem;
padding-top: 1rem; }
.media.is-large + .media {
@@ -3722,6 +3777,10 @@ a.dropdown-item {
flex-shrink: 1;
text-align: left; }
@media screen and (max-width: 768px) {
.media-content {
overflow-x: auto; } }
.menu {
font-size: 1rem; }
.menu.is-small {
@@ -3745,7 +3804,7 @@ a.dropdown-item {
background-color: #00d1b2;
color: #fff; }
.menu-list li ul {
border-left: 1px solid #ffdd57;
border-left: 1px solid #dbdbdb;
margin: 0.75em;
padding-left: 0.75em; }
@@ -3876,7 +3935,7 @@ a.dropdown-item {
border-top-right-radius: 0; }
.message-body {
border-color: #ffdd57;
border-color: #dbdbdb;
border-radius: 4px;
border-style: solid;
border-width: 0 0 0 4px;
@@ -3891,6 +3950,7 @@ a.dropdown-item {
.modal {
align-items: center;
display: none;
flex-direction: column;
justify-content: center;
overflow: hidden;
position: fixed;
@@ -3927,7 +3987,8 @@ a.dropdown-item {
display: flex;
flex-direction: column;
max-height: calc(100vh - 40px);
overflow: hidden; }
overflow: hidden;
-ms-overflow-y: visible; }
.modal-card-head,
.modal-card-foot {
@@ -3940,7 +4001,7 @@ a.dropdown-item {
position: relative; }
.modal-card-head {
border-bottom: 1px solid #ffdd57;
border-bottom: 1px solid #dbdbdb;
border-top-left-radius: 6px;
border-top-right-radius: 6px; }
@@ -3954,7 +4015,7 @@ a.dropdown-item {
.modal-card-foot {
border-bottom-left-radius: 6px;
border-bottom-right-radius: 6px;
border-top: 1px solid #ffdd57; }
border-top: 1px solid #dbdbdb; }
.modal-card-foot .button:not(:last-child) {
margin-right: 10px; }
@@ -3984,6 +4045,8 @@ a.dropdown-item {
color: #0a0a0a; }
.navbar.is-white .navbar-brand .navbar-link::after {
border-color: #0a0a0a; }
.navbar.is-white .navbar-burger {
color: #0a0a0a; }
@media screen and (min-width: 1088px) {
.navbar.is-white .navbar-start > .navbar-item,
.navbar.is-white .navbar-start .navbar-link,
@@ -4022,6 +4085,8 @@ a.dropdown-item {
color: white; }
.navbar.is-black .navbar-brand .navbar-link::after {
border-color: white; }
.navbar.is-black .navbar-burger {
color: white; }
@media screen and (min-width: 1088px) {
.navbar.is-black .navbar-start > .navbar-item,
.navbar.is-black .navbar-start .navbar-link,
@@ -4060,6 +4125,8 @@ a.dropdown-item {
color: #363636; }
.navbar.is-light .navbar-brand .navbar-link::after {
border-color: #363636; }
.navbar.is-light .navbar-burger {
color: #363636; }
@media screen and (min-width: 1088px) {
.navbar.is-light .navbar-start > .navbar-item,
.navbar.is-light .navbar-start .navbar-link,
@@ -4098,6 +4165,8 @@ a.dropdown-item {
color: whitesmoke; }
.navbar.is-dark .navbar-brand .navbar-link::after {
border-color: whitesmoke; }
.navbar.is-dark .navbar-burger {
color: whitesmoke; }
@media screen and (min-width: 1088px) {
.navbar.is-dark .navbar-start > .navbar-item,
.navbar.is-dark .navbar-start .navbar-link,
@@ -4136,6 +4205,8 @@ a.dropdown-item {
color: #fff; }
.navbar.is-primary .navbar-brand .navbar-link::after {
border-color: #fff; }
.navbar.is-primary .navbar-burger {
color: #fff; }
@media screen and (min-width: 1088px) {
.navbar.is-primary .navbar-start > .navbar-item,
.navbar.is-primary .navbar-start .navbar-link,
@@ -4174,6 +4245,8 @@ a.dropdown-item {
color: #fff; }
.navbar.is-link .navbar-brand .navbar-link::after {
border-color: #fff; }
.navbar.is-link .navbar-burger {
color: #fff; }
@media screen and (min-width: 1088px) {
.navbar.is-link .navbar-start > .navbar-item,
.navbar.is-link .navbar-start .navbar-link,
@@ -4212,6 +4285,8 @@ a.dropdown-item {
color: #fff; }
.navbar.is-info .navbar-brand .navbar-link::after {
border-color: #fff; }
.navbar.is-info .navbar-burger {
color: #fff; }
@media screen and (min-width: 1088px) {
.navbar.is-info .navbar-start > .navbar-item,
.navbar.is-info .navbar-start .navbar-link,
@@ -4250,6 +4325,8 @@ a.dropdown-item {
color: #fff; }
.navbar.is-success .navbar-brand .navbar-link::after {
border-color: #fff; }
.navbar.is-success .navbar-burger {
color: #fff; }
@media screen and (min-width: 1088px) {
.navbar.is-success .navbar-start > .navbar-item,
.navbar.is-success .navbar-start .navbar-link,
@@ -4288,6 +4365,8 @@ a.dropdown-item {
color: rgba(0, 0, 0, 0.7); }
.navbar.is-warning .navbar-brand .navbar-link::after {
border-color: rgba(0, 0, 0, 0.7); }
.navbar.is-warning .navbar-burger {
color: rgba(0, 0, 0, 0.7); }
@media screen and (min-width: 1088px) {
.navbar.is-warning .navbar-start > .navbar-item,
.navbar.is-warning .navbar-start .navbar-link,
@@ -4326,6 +4405,8 @@ a.dropdown-item {
color: #fff; }
.navbar.is-danger .navbar-brand .navbar-link::after {
border-color: #fff; }
.navbar.is-danger .navbar-burger {
color: #fff; }
@media screen and (min-width: 1088px) {
.navbar.is-danger .navbar-start > .navbar-item,
.navbar.is-danger .navbar-start .navbar-link,
@@ -4395,6 +4476,7 @@ body.has-navbar-fixed-bottom {
overflow-y: hidden; }
.navbar-burger {
color: #4a4a4a;
cursor: pointer;
display: block;
height: 3.25rem;
@@ -4481,9 +4563,9 @@ a.navbar-item,
flex-grow: 1;
flex-shrink: 1; }
.navbar-link {
.navbar-link:not(.is-arrowless) {
padding-right: 2.5em; }
.navbar-link::after {
.navbar-link:not(.is-arrowless)::after {
border-color: #00d1b2;
margin-top: -0.375em;
right: 1.125em; }
@@ -4564,8 +4646,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 +4664,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 #ffdd57;
border-bottom: 1px solid #00d1b2;
border-radius: 6px 6px 0 0;
border-top: none;
bottom: 100%;
@@ -4604,10 +4686,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 #ffdd57;
border-top: 1px solid #00d1b2;
box-shadow: 0 8px 8px rgba(10, 10, 10, 0.1);
display: none;
font-size: 0.875rem;
@@ -4622,8 +4704,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; }
@@ -4645,10 +4727,10 @@ a.navbar-item,
display: block; }
.navbar > .container .navbar-brand,
.container > .navbar .navbar-brand {
margin-left: -1rem; }
margin-left: -.75rem; }
.navbar > .container .navbar-menu,
.container > .navbar .navbar-menu {
margin-right: -1rem; }
margin-right: -.75rem; }
.navbar.is-fixed-bottom-desktop, .navbar.is-fixed-top-desktop {
left: 0;
position: fixed;
@@ -4808,13 +4890,13 @@ a.navbar-item,
.panel-heading,
.panel-tabs,
.panel-block {
border-bottom: 1px solid #ffdd57;
border-left: 1px solid #ffdd57;
border-right: 1px solid #ffdd57; }
border-bottom: 1px solid #dbdbdb;
border-left: 1px solid #dbdbdb;
border-right: 1px solid #dbdbdb; }
.panel-heading:first-child,
.panel-tabs:first-child,
.panel-block:first-child {
border-top: 1px solid #ffdd57; }
border-top: 1px solid #dbdbdb; }
.panel-heading {
background-color: #0a0a0a;
@@ -4831,7 +4913,7 @@ a.navbar-item,
font-size: 0.875em;
justify-content: center; }
.panel-tabs a {
border-bottom: 1px solid #ffdd57;
border-bottom: 1px solid #dbdbdb;
margin-bottom: -1px;
padding: 0.5em; }
.panel-tabs a.is-active {
@@ -4895,7 +4977,7 @@ label.panel-block {
white-space: nowrap; }
.tabs a {
align-items: center;
border-bottom-color: #ffdd57;
border-bottom-color: #dbdbdb;
border-bottom-style: solid;
border-bottom-width: 1px;
color: whitesmoke;
@@ -4914,7 +4996,7 @@ label.panel-block {
color: #00d1b2; }
.tabs ul {
align-items: center;
border-bottom-color: #ffdd57;
border-bottom-color: #dbdbdb;
border-bottom-style: solid;
border-bottom-width: 1px;
display: flex;
@@ -4944,16 +5026,16 @@ label.panel-block {
border-radius: 4px 4px 0 0; }
.tabs.is-boxed a:hover {
background-color: #0a0a0a;
border-bottom-color: #ffdd57; }
border-bottom-color: #dbdbdb; }
.tabs.is-boxed li.is-active a {
background-color: white;
border-color: #ffdd57;
border-color: #dbdbdb;
border-bottom-color: transparent !important; }
.tabs.is-fullwidth li {
flex-grow: 1;
flex-shrink: 0; }
.tabs.is-toggle a {
border-color: #ffdd57;
border-color: #dbdbdb;
border-style: solid;
border-width: 1px;
margin-bottom: 0;
@@ -5816,22 +5898,265 @@ label.panel-block {
padding-right: var(--columnGap); }
.columns.is-variable.is-0 {
--columnGap: 0rem; }
@media screen and (max-width: 768px) {
.columns.is-variable.is-0-mobile {
--columnGap: 0rem; } }
@media screen and (min-width: 769px), print {
.columns.is-variable.is-0-tablet {
--columnGap: 0rem; } }
@media screen and (min-width: 769px) and (max-width: 1087px) {
.columns.is-variable.is-0-tablet-only {
--columnGap: 0rem; } }
@media screen and (max-width: 1087px) {
.columns.is-variable.is-0-touch {
--columnGap: 0rem; } }
@media screen and (min-width: 1088px) {
.columns.is-variable.is-0-desktop {
--columnGap: 0rem; } }
@media screen and (min-width: 1088px) and (max-width: 1279px) {
.columns.is-variable.is-0-desktop-only {
--columnGap: 0rem; } }
@media screen and (min-width: 1280px) {
.columns.is-variable.is-0-widescreen {
--columnGap: 0rem; } }
@media screen and (min-width: 1280px) and (max-width: 1471px) {
.columns.is-variable.is-0-widescreen-only {
--columnGap: 0rem; } }
@media screen and (min-width: 1472px) {
.columns.is-variable.is-0-fullhd {
--columnGap: 0rem; } }
.columns.is-variable.is-1 {
--columnGap: 0.25rem; }
@media screen and (max-width: 768px) {
.columns.is-variable.is-1-mobile {
--columnGap: 0.25rem; } }
@media screen and (min-width: 769px), print {
.columns.is-variable.is-1-tablet {
--columnGap: 0.25rem; } }
@media screen and (min-width: 769px) and (max-width: 1087px) {
.columns.is-variable.is-1-tablet-only {
--columnGap: 0.25rem; } }
@media screen and (max-width: 1087px) {
.columns.is-variable.is-1-touch {
--columnGap: 0.25rem; } }
@media screen and (min-width: 1088px) {
.columns.is-variable.is-1-desktop {
--columnGap: 0.25rem; } }
@media screen and (min-width: 1088px) and (max-width: 1279px) {
.columns.is-variable.is-1-desktop-only {
--columnGap: 0.25rem; } }
@media screen and (min-width: 1280px) {
.columns.is-variable.is-1-widescreen {
--columnGap: 0.25rem; } }
@media screen and (min-width: 1280px) and (max-width: 1471px) {
.columns.is-variable.is-1-widescreen-only {
--columnGap: 0.25rem; } }
@media screen and (min-width: 1472px) {
.columns.is-variable.is-1-fullhd {
--columnGap: 0.25rem; } }
.columns.is-variable.is-2 {
--columnGap: 0.5rem; }
@media screen and (max-width: 768px) {
.columns.is-variable.is-2-mobile {
--columnGap: 0.5rem; } }
@media screen and (min-width: 769px), print {
.columns.is-variable.is-2-tablet {
--columnGap: 0.5rem; } }
@media screen and (min-width: 769px) and (max-width: 1087px) {
.columns.is-variable.is-2-tablet-only {
--columnGap: 0.5rem; } }
@media screen and (max-width: 1087px) {
.columns.is-variable.is-2-touch {
--columnGap: 0.5rem; } }
@media screen and (min-width: 1088px) {
.columns.is-variable.is-2-desktop {
--columnGap: 0.5rem; } }
@media screen and (min-width: 1088px) and (max-width: 1279px) {
.columns.is-variable.is-2-desktop-only {
--columnGap: 0.5rem; } }
@media screen and (min-width: 1280px) {
.columns.is-variable.is-2-widescreen {
--columnGap: 0.5rem; } }
@media screen and (min-width: 1280px) and (max-width: 1471px) {
.columns.is-variable.is-2-widescreen-only {
--columnGap: 0.5rem; } }
@media screen and (min-width: 1472px) {
.columns.is-variable.is-2-fullhd {
--columnGap: 0.5rem; } }
.columns.is-variable.is-3 {
--columnGap: 0.75rem; }
@media screen and (max-width: 768px) {
.columns.is-variable.is-3-mobile {
--columnGap: 0.75rem; } }
@media screen and (min-width: 769px), print {
.columns.is-variable.is-3-tablet {
--columnGap: 0.75rem; } }
@media screen and (min-width: 769px) and (max-width: 1087px) {
.columns.is-variable.is-3-tablet-only {
--columnGap: 0.75rem; } }
@media screen and (max-width: 1087px) {
.columns.is-variable.is-3-touch {
--columnGap: 0.75rem; } }
@media screen and (min-width: 1088px) {
.columns.is-variable.is-3-desktop {
--columnGap: 0.75rem; } }
@media screen and (min-width: 1088px) and (max-width: 1279px) {
.columns.is-variable.is-3-desktop-only {
--columnGap: 0.75rem; } }
@media screen and (min-width: 1280px) {
.columns.is-variable.is-3-widescreen {
--columnGap: 0.75rem; } }
@media screen and (min-width: 1280px) and (max-width: 1471px) {
.columns.is-variable.is-3-widescreen-only {
--columnGap: 0.75rem; } }
@media screen and (min-width: 1472px) {
.columns.is-variable.is-3-fullhd {
--columnGap: 0.75rem; } }
.columns.is-variable.is-4 {
--columnGap: 1rem; }
@media screen and (max-width: 768px) {
.columns.is-variable.is-4-mobile {
--columnGap: 1rem; } }
@media screen and (min-width: 769px), print {
.columns.is-variable.is-4-tablet {
--columnGap: 1rem; } }
@media screen and (min-width: 769px) and (max-width: 1087px) {
.columns.is-variable.is-4-tablet-only {
--columnGap: 1rem; } }
@media screen and (max-width: 1087px) {
.columns.is-variable.is-4-touch {
--columnGap: 1rem; } }
@media screen and (min-width: 1088px) {
.columns.is-variable.is-4-desktop {
--columnGap: 1rem; } }
@media screen and (min-width: 1088px) and (max-width: 1279px) {
.columns.is-variable.is-4-desktop-only {
--columnGap: 1rem; } }
@media screen and (min-width: 1280px) {
.columns.is-variable.is-4-widescreen {
--columnGap: 1rem; } }
@media screen and (min-width: 1280px) and (max-width: 1471px) {
.columns.is-variable.is-4-widescreen-only {
--columnGap: 1rem; } }
@media screen and (min-width: 1472px) {
.columns.is-variable.is-4-fullhd {
--columnGap: 1rem; } }
.columns.is-variable.is-5 {
--columnGap: 1.25rem; }
@media screen and (max-width: 768px) {
.columns.is-variable.is-5-mobile {
--columnGap: 1.25rem; } }
@media screen and (min-width: 769px), print {
.columns.is-variable.is-5-tablet {
--columnGap: 1.25rem; } }
@media screen and (min-width: 769px) and (max-width: 1087px) {
.columns.is-variable.is-5-tablet-only {
--columnGap: 1.25rem; } }
@media screen and (max-width: 1087px) {
.columns.is-variable.is-5-touch {
--columnGap: 1.25rem; } }
@media screen and (min-width: 1088px) {
.columns.is-variable.is-5-desktop {
--columnGap: 1.25rem; } }
@media screen and (min-width: 1088px) and (max-width: 1279px) {
.columns.is-variable.is-5-desktop-only {
--columnGap: 1.25rem; } }
@media screen and (min-width: 1280px) {
.columns.is-variable.is-5-widescreen {
--columnGap: 1.25rem; } }
@media screen and (min-width: 1280px) and (max-width: 1471px) {
.columns.is-variable.is-5-widescreen-only {
--columnGap: 1.25rem; } }
@media screen and (min-width: 1472px) {
.columns.is-variable.is-5-fullhd {
--columnGap: 1.25rem; } }
.columns.is-variable.is-6 {
--columnGap: 1.5rem; }
@media screen and (max-width: 768px) {
.columns.is-variable.is-6-mobile {
--columnGap: 1.5rem; } }
@media screen and (min-width: 769px), print {
.columns.is-variable.is-6-tablet {
--columnGap: 1.5rem; } }
@media screen and (min-width: 769px) and (max-width: 1087px) {
.columns.is-variable.is-6-tablet-only {
--columnGap: 1.5rem; } }
@media screen and (max-width: 1087px) {
.columns.is-variable.is-6-touch {
--columnGap: 1.5rem; } }
@media screen and (min-width: 1088px) {
.columns.is-variable.is-6-desktop {
--columnGap: 1.5rem; } }
@media screen and (min-width: 1088px) and (max-width: 1279px) {
.columns.is-variable.is-6-desktop-only {
--columnGap: 1.5rem; } }
@media screen and (min-width: 1280px) {
.columns.is-variable.is-6-widescreen {
--columnGap: 1.5rem; } }
@media screen and (min-width: 1280px) and (max-width: 1471px) {
.columns.is-variable.is-6-widescreen-only {
--columnGap: 1.5rem; } }
@media screen and (min-width: 1472px) {
.columns.is-variable.is-6-fullhd {
--columnGap: 1.5rem; } }
.columns.is-variable.is-7 {
--columnGap: 1.75rem; }
@media screen and (max-width: 768px) {
.columns.is-variable.is-7-mobile {
--columnGap: 1.75rem; } }
@media screen and (min-width: 769px), print {
.columns.is-variable.is-7-tablet {
--columnGap: 1.75rem; } }
@media screen and (min-width: 769px) and (max-width: 1087px) {
.columns.is-variable.is-7-tablet-only {
--columnGap: 1.75rem; } }
@media screen and (max-width: 1087px) {
.columns.is-variable.is-7-touch {
--columnGap: 1.75rem; } }
@media screen and (min-width: 1088px) {
.columns.is-variable.is-7-desktop {
--columnGap: 1.75rem; } }
@media screen and (min-width: 1088px) and (max-width: 1279px) {
.columns.is-variable.is-7-desktop-only {
--columnGap: 1.75rem; } }
@media screen and (min-width: 1280px) {
.columns.is-variable.is-7-widescreen {
--columnGap: 1.75rem; } }
@media screen and (min-width: 1280px) and (max-width: 1471px) {
.columns.is-variable.is-7-widescreen-only {
--columnGap: 1.75rem; } }
@media screen and (min-width: 1472px) {
.columns.is-variable.is-7-fullhd {
--columnGap: 1.75rem; } }
.columns.is-variable.is-8 {
--columnGap: 2rem; }
@media screen and (max-width: 768px) {
.columns.is-variable.is-8-mobile {
--columnGap: 2rem; } }
@media screen and (min-width: 769px), print {
.columns.is-variable.is-8-tablet {
--columnGap: 2rem; } }
@media screen and (min-width: 769px) and (max-width: 1087px) {
.columns.is-variable.is-8-tablet-only {
--columnGap: 2rem; } }
@media screen and (max-width: 1087px) {
.columns.is-variable.is-8-touch {
--columnGap: 2rem; } }
@media screen and (min-width: 1088px) {
.columns.is-variable.is-8-desktop {
--columnGap: 2rem; } }
@media screen and (min-width: 1088px) and (max-width: 1279px) {
.columns.is-variable.is-8-desktop-only {
--columnGap: 2rem; } }
@media screen and (min-width: 1280px) {
.columns.is-variable.is-8-widescreen {
--columnGap: 2rem; } }
@media screen and (min-width: 1280px) and (max-width: 1471px) {
.columns.is-variable.is-8-widescreen-only {
--columnGap: 2rem; } }
@media screen and (min-width: 1472px) {
.columns.is-variable.is-8-fullhd {
--columnGap: 2rem; } }
.tile {
align-items: stretch;
@@ -6356,16 +6681,18 @@ label.panel-block {
.hero.is-large .hero-body {
padding-bottom: 18rem;
padding-top: 18rem; } }
.hero.is-halfheight .hero-body, .hero.is-fullheight .hero-body {
.hero.is-halfheight .hero-body, .hero.is-fullheight .hero-body, .hero.is-fullheight-with-navbar .hero-body {
align-items: center;
display: flex; }
.hero.is-halfheight .hero-body > .container, .hero.is-fullheight .hero-body > .container {
.hero.is-halfheight .hero-body > .container, .hero.is-fullheight .hero-body > .container, .hero.is-fullheight-with-navbar .hero-body > .container {
flex-grow: 1;
flex-shrink: 1; }
.hero.is-halfheight {
min-height: 50vh; }
.hero.is-fullheight {
min-height: 100vh; }
.hero.is-fullheight-with-navbar {
min-height: calc(100vh - 3.25rem); }
.hero-video {
overflow: hidden; }
+55
View File
@@ -0,0 +1,55 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" id="&#x421;&#x43B;&#x43E;&#x439;_1" data-name="&#x421;&#x43B;&#x43E;&#x439; 1" viewBox="0 0 320 76.71">
<defs>
<style>.cls-1{fill:#fff;}.cls-2{fill:#fffcef;}.cls-3{fill:#333;}</style>
</defs>
<title>hq__amsterdam</title>
<rect class="cls-1" x="209.68" y="41.71" width="15.45" height="29.85"/>
<rect class="cls-2" x="56.42" y="11.54" width="2.1" height="7.59"/>
<ellipse class="cls-3" cx="8.25" cy="52.07" rx="0.57" ry="0.82"/>
<path class="cls-3" d="M8.25,53.38a1.34,1.34,0,0,1,0-2.61,1.34,1.34,0,0,1,0,2.61Zm0-1.6a.53.53,0,0,0-.08.29.47.47,0,0,0,.1.33s0-.14,0-.33A.53.53,0,0,0,8.25,51.78Z"/>
<ellipse class="cls-3" cx="13.72" cy="52.07" rx="0.57" ry="0.82"/>
<path class="cls-3" d="M13.72,53.38a1.34,1.34,0,0,1,0-2.61,1.34,1.34,0,0,1,0,2.61Zm0-1.6a.53.53,0,0,0-.08.29.47.47,0,0,0,.1.33s0-.14,0-.33A.53.53,0,0,0,13.72,51.78Z"/>
<ellipse class="cls-3" cx="19.15" cy="52.07" rx="0.57" ry="0.82"/>
<path class="cls-3" d="M19.15,53.38a1.34,1.34,0,0,1,0-2.61,1.34,1.34,0,0,1,0,2.61Zm0-1.6a.53.53,0,0,0-.08.29.47.47,0,0,0,.1.33s0-.14,0-.33A.53.53,0,0,0,19.15,51.78Z"/>
<ellipse class="cls-3" cx="24.59" cy="52.07" rx="0.57" ry="0.82"/>
<path class="cls-3" d="M24.59,53.38a1.34,1.34,0,0,1,0-2.61,1.34,1.34,0,0,1,0,2.61Zm0-1.6a.54.54,0,0,0-.08.29.47.47,0,0,0,.1.33s0-.14,0-.33A.53.53,0,0,0,24.59,51.78Z"/>
<ellipse class="cls-3" cx="30.05" cy="52.07" rx="0.57" ry="0.82"/>
<path class="cls-3" d="M30.05,53.38a1.34,1.34,0,1,1,1.06-1.31A1.19,1.19,0,0,1,30.05,53.38Zm0-1.6a.53.53,0,0,0-.08.29.47.47,0,0,0,.1.33s0-.14,0-.33A.53.53,0,0,0,30.05,51.78Z"/>
<ellipse class="cls-3" cx="35.49" cy="52.07" rx="0.57" ry="0.82"/>
<path class="cls-3" d="M35.49,53.38a1.34,1.34,0,1,1,1.06-1.31A1.19,1.19,0,0,1,35.49,53.38Zm0-1.6a.53.53,0,0,0-.08.29.47.47,0,0,0,.1.33s0-.14,0-.33A.54.54,0,0,0,35.49,51.78Z"/>
<ellipse class="cls-3" cx="41" cy="52.07" rx="0.57" ry="0.82"/>
<path class="cls-3" d="M41,53.38a1.34,1.34,0,1,1,1.06-1.31A1.19,1.19,0,0,1,41,53.38Zm0-1.6a.53.53,0,0,0-.08.29.47.47,0,0,0,.1.33s0-.14,0-.33A.53.53,0,0,0,41,51.78Z"/>
<ellipse class="cls-3" cx="46.21" cy="52.07" rx="0.57" ry="0.82"/>
<path class="cls-3" d="M46.21,53.38a1.34,1.34,0,1,1,1.06-1.31A1.19,1.19,0,0,1,46.21,53.38Zm0-1.6a.53.53,0,0,0-.08.29.47.47,0,0,0,.1.33s0-.14,0-.33A.53.53,0,0,0,46.21,51.78Z"/>
<circle class="cls-3" cx="73.17" cy="26.91" r="1.75"/>
<path class="cls-3" d="M73.17,28.91a2,2,0,1,1,2-2A2,2,0,0,1,73.17,28.91Zm0-3.51a1.51,1.51,0,1,0,1.51,1.51A1.51,1.51,0,0,0,73.17,25.4Z"/>
<path class="cls-3" d="M226.19,24.76a.49.49,0,0,0-.44-.49h-17a.5.5,0,0,0,0,1h17A.49.49,0,0,0,226.19,24.76Z"/>
<path class="cls-3" d="M316,75.2l0-5.78-5,.07V64.55h-6.5L288.34,46.06l15.12,3,.4-2-29-5.73-.4,2,1.66.32-1.36,19,.32,0-.07.1.07,0-1.33-.12-.17,1.68,1.56.12h-5l1.56-.12-.17-1.71-1.11.12,0,0,0-.1.32,0-1.38-19.08,1.71-.35-.4-2L241.54,47l.4,2,15.15-3L240.55,64.94,235,64.6c0-.15,1-3.51-1.21-5.49h-1s-7.17-9.39-7.17-18H226V38.6h-.49V28.72h2.84l-2.13,4.42.67.49,2.59-5.41h-4v-2h1.61a1.39,1.39,0,0,0,.77-1.36,1.47,1.47,0,0,0-.77-1.36h-1.58l-7.73-8.4h-1l-7.73,8.4h-1.58a1.43,1.43,0,0,0-.77,1.36,1.39,1.39,0,0,0,.77,1.36H209v2h-3.68l2.94,6.15L209,34v4.62h-.49V41H209c0,6.92-4.57,14.33-6.43,17.05H161V36.55h0V33.39h0v-4h0V26.27h0V24a1.57,1.57,0,0,0,1.26-.89,1.53,1.53,0,0,0-.77-2.05,1.43,1.43,0,0,0-.52-.15V19.55h-.49V21a2,2,0,0,0-1.09.86,1.53,1.53,0,0,0,.72,2.05,3.07,3.07,0,0,1,.37.12V58.1h-4.2V33.63h1V32.4h-2.87l-3.19-12-1.66-6.3h-.47V9.93h-2.2v4.2h-.52l-1.66,6.3-3.16,12h-3.46c-5.78-4-5.86-14.33-5.86-14.33h-8.72c-.22,9.64-4.62,13.59-6.18,14.33h-.84V25h-1.24V20.56h-2V17.35h-2V13.89H109.1V7H105.4v6.92h-2.47v3.46h-2v3.21H99.22V25H98V19.08H95.29l-2.52-4.52L90,9.71l-2.74,4.87-2.52,4.5H81.4V30.94h-2V30h-.59l-2.57-8.77,1.68-6.3H68l1.68,6.25L67.12,30H66.06v1H63.83v-10a2.9,2.9,0,0,0,1.24.15V19.84s-3.46.17-3.46-5V6H53.23v8.87c0,5.16-3.71,5-3.71,5v1.24a3.34,3.34,0,0,0,1-.07V57.38H47.05V53.82a.75.75,0,0,0-1.48,0v3.56h-4V53.82c0-.25,0-.47-.25-.57V34.67h.84c.17,0,.15-.27.15-.42v-.17c0-.17,0-.15-.15-.15h-.35a1.39,1.39,0,0,0,0-.35V32a.93.93,0,0,0-.49-.72V29.75a2,2,0,0,1,4,0v5c-.74.22-1.14,1.21-1.36,2.2h-.17a.37.37,0,0,0,0,.74h.49a1.69,1.69,0,0,0,1.46,1.19,1.72,1.72,0,0,0,1.46-1.19h.47a.37.37,0,0,0,0-.74h-.12c-.22-1-.74-2.05-1.73-2.22V29.75a2.32,2.32,0,0,0-2.35-2.47h0a2.69,2.69,0,0,0-2.64,2,2.78,2.78,0,0,0-2.64-2.1h0a2.62,2.62,0,0,0-2.47,2.57v5a3.44,3.44,0,0,0-1.26,2.2h-.17a.37.37,0,0,0,0,.74h.49a1.69,1.69,0,0,0,1.46,1.19,1.72,1.72,0,0,0,1.46-1.19h.47a.37.37,0,1,0,0-.74H38c-.22-1-.84-2.05-1.58-2.22V29.75a1.85,1.85,0,1,1,3.71,0v1.51a1,1,0,0,0-.49.77V34h0c-.2,0-.52,0-.52.15v.17c0,.17.17.42.59.42h.4V57.43h-4V53.87a.62.62,0,1,0-1.24,0v3.56h-4.2V53.87c0-.37,0-.4-.4-.4h0c-.37,0-.82,0-.82.4v3.56h-4.2V53.87a.62.62,0,1,0-1.24,0v3.56h-4.2V53.87a.75.75,0,0,0-1.48,0v3.56h-4.2V53.87a.62.62,0,1,0-1.24,0v3.56H9V53.87c0-.37,0-.4-.4-.4h0c-.37,0-1.09,0-1.09.4v3.56h-1v1h1v1.24H7V69.79H3.53v5.68H0v1.24H161.2V75.47h-1V72s5.16-8.48,19.13-8.2,18.19,8.35,18.19,8.35v3.26h-1.24v1.24l11.19,0H320V75.22l-4,0ZM161,21.33a.85.85,0,0,1,.37.1,1.19,1.19,0,0,1,.57,1.58,1.07,1.07,0,0,1-.94.67Zm-141.11,37h4.2v1.24h-4.2Zm-5.68,0h4.2v1.24h-4.2ZM9,58.37h4v1.24H9ZM24.34,69.74H11.74v-.86c0-2.89,2.72-4.82,5.58-4.82h2.15a4.61,4.61,0,0,1,4.87,4.82v.86Zm6.43-11.37H35v1.24h-4.2Zm-5.44,0h4.2v1.24h-4.2ZM39.91,69.74H27.31v-.86c0-2.89,2.74-5.07,5.63-5.07h2.15a4.77,4.77,0,0,1,4.82,5.07Zm.25-10.13h-4V58.37h4Zm1.48-1.24h4v1.24h-4Zm8.9,11.37H42.88v-.86c0-2.89,2.77-4.82,5.66-4.82h2Zm0-10.13H47.08V58.37h3.46Zm3.71,7.17H52.76V58.86h1.48Zm0-9.14H52.76V50h1.48Zm0-8.9H52.76V40.58h1.48Zm0-9.14H52.76V31.68h1.48Zm0-8.9H52.76V22.54h1.48Zm5.68,36.08h-4.7v-8.4h4.7Zm0-9.14h-4.7V49.72h4.7Zm0-8.9h-4.7V40.58h4.7Zm0-9.14h-4.7V31.68h4.7Zm0-8.9h-4.7V22.54h4.7ZM57.21,15.37v3.46h-.49V15.37Zm-.49-.25V11.91h.49v3.21Zm1.48.25v3.46H57.7V15.37Zm-.49-.25V11.91h.49v3.21Zm4.2,51.65H60.67V58.86H61.9Zm0-9.14H60.67V50H61.9Zm0-8.9H60.67V40.58H61.9Zm0-9.14H60.67V31.68H61.9Zm0-8.9H60.67V22.54H61.9Zm-1.73-9.88V19.08H58.69V11.42H56.22v7.66H55v1.73H51.48C54.27,19.57,54.24,15,54.24,15V7.22h6.4V15s.07,4.6,2.84,5.83Zm10.6.49.1-.42-.07-.27-1.31-4.77h7.12L75.3,20.61l-.07.25.1.52L77.79,30H68.33Zm1.26,23V35.54A1.14,1.14,0,0,1,73.3,34.4h.2a1.14,1.14,0,0,1,1.26,1.16v8.72Zm2.72,4.84v8.75H72V49.1A1.12,1.12,0,0,1,73.3,48h.2A1.14,1.14,0,0,1,74.75,49.13Zm-5.44,8.75H66.6V49.1c0-.72.72-1.11,1.43-1.11h.2a1,1,0,0,1,1.09,1.14v8.75Zm0-13.59H66.6V35.54A1.29,1.29,0,0,1,68,34.4h.2a1,1,0,0,1,1.09,1.16v8.72ZM75.5,70.73H70.8V62.35a2.27,2.27,0,0,1,2.37-2h.32a2,2,0,0,1,2,2.05v8.33Zm4.45-12.85H77.23V49.1A1.18,1.18,0,0,1,78.56,48h.2a1.07,1.07,0,0,1,1.19,1.14v8.75Zm0-13.59H77.23V35.54a1.2,1.2,0,0,1,1.33-1.14h.2a1.08,1.08,0,0,1,1.19,1.16v8.72Zm7.91-28.17h4.2v4.7h-4.2Zm-3.46,6.18H96v1H84.39Zm9.64,22V38.6h2v5.68ZM96,51v5.93H94V51ZM94,31.68V26h2v5.68ZM92.79,28v4.94H87.36V28Zm-5.44-.49v-.84A2.44,2.44,0,0,1,90,24.27h.27a2.42,2.42,0,0,1,2.57,2.37v.84Zm2.57,9.14h.27a2.52,2.52,0,0,1,2.57,2.5v1H87.33v-1A2.56,2.56,0,0,1,89.93,36.62Zm2.87,4v4.94H87.36V40.58Zm-2.87,8.65h.27a2.43,2.43,0,0,1,2.57,2.4v.82H87.33v-.82A2.48,2.48,0,0,1,89.93,49.23Zm2.87,3.71v4.94H87.36V52.93Zm0,7.66v6.92H87.36V60.59ZM94,68v1.48H86.12V68ZM84.39,26h2v5.68h-2Zm0,12.6h2v5.68h-2Zm0,12.36h2v5.93h-2Zm0,19.28H95.76v1.48H84.39Zm13.34,4.45H82.17v-2H97.74ZM103.42,66H101.2V62.57h2.22Zm0-4H101.2V58.37h2.22Zm0-9.39H101.2V49.23h2.22Zm0-4H101.2V45h2.22Zm0-5.19H101.2V39.84h2.22Zm0-4.2H101.2V35.88h2.22Zm0-4.94H101.2V30.69h2.22Zm0-4H101.2V26.74h2.22ZM105.89,66h-1.73V62.57h1.73Zm0-4h-1.73V58.37h1.73Zm0-9.39h-1.73V49.23h1.73Zm0-4h-1.73V45h1.73Zm0-5.19h-1.73V39.84h1.73Zm0-4.2h-1.73V35.88h1.73Zm0-4.94h-1.73V30.69h1.73ZLine truncated
<path class="cls-3" d="M215.62,33v4.82h3.46V33a1.75,1.75,0,0,0-3.46,0Zm3,.4v3.44h-2.47V33.44a1.26,1.26,0,0,1,1.24-1.31A1.27,1.27,0,0,1,218.58,33.44Z"/>
<rect class="cls-3" x="55.73" y="45.03" width="3.71" height="3.21"/>
<rect class="cls-3" x="55.73" y="35.88" width="3.71" height="3.21"/>
<rect class="cls-3" x="55.73" y="32.18" width="3.71" height="3.21"/>
<rect class="cls-3" x="55.73" y="23.28" width="3.71" height="2.97"/>
<rect class="cls-3" x="55.73" y="41.07" width="3.71" height="3.21"/>
<rect class="cls-3" x="55.73" y="59.11" width="3.71" height="3.21"/>
<path class="cls-3" d="M128.18,70.68a1.28,1.28,0,0,1,.35-.52.82.82,0,0,0,0-.27l-.37-1.43h0v0h0l0,0h0a3.08,3.08,0,0,0-1.68,2.45c.37,0,.77,0,1.11,0A2.41,2.41,0,0,0,128.18,70.68Z"/>
<path class="cls-3" d="M129.86,71.1a.47.47,0,0,0-.47-.47h-.07a.81.81,0,0,1-.37.62.49.49,0,0,0,.44.3A.45.45,0,0,0,129.86,71.1Z"/>
<rect class="cls-3" x="89.83" y="17.1" width="1.73" height="1.24"/>
<path class="cls-3" d="M127.86,67.51l-.15-.59a5.54,5.54,0,0,0-.86.59L126,68.68l-1.53,2.13c.22,0,.57,0,1,0A3.89,3.89,0,0,1,127.86,67.51Z"/>
<rect class="cls-3" x="88.35" y="18.83" width="1.24" height="1.48"/>
<rect class="cls-3" x="88.35" y="17.1" width="1.24" height="1.24"/>
<rect class="cls-3" x="89.83" y="18.83" width="1.73" height="1.48"/>
<path class="cls-3" d="M118.17,70.5a.82.82,0,0,1,.27.64.86.86,0,0,1-1.73,0,.85.85,0,0,1,.69-.84l.67-2.15a3.35,3.35,0,0,0-.49,0,3,3,0,1,0,3,3,3,3,0,0,0-1.78-2.74Z"/>
<path class="cls-3" d="M129.37,68.11a4.8,4.8,0,0,0-.59,0l.35,1.31a1.6,1.6,0,0,1,.1.32l.17,0a1.31,1.31,0,1,1-1.26,1.66l-1.8,0a3,3,0,0,0,6-.27A3,3,0,0,0,129.37,68.11Z"/>
<polygon class="cls-3" points="127.05 66.03 127.05 66.03 123.56 66.03 120.08 66.03 121.71 68.28 123.56 70.85 125.44 68.28 127.05 66.03"/>
<rect class="cls-3" x="55.73" y="26.74" width="3.71" height="3.46"/>
<rect class="cls-3" x="55.73" y="54.17" width="3.71" height="2.97"/>
<rect class="cls-3" x="55.73" y="50.22" width="3.71" height="2.97"/>
<rect class="cls-3" x="55.73" y="63.07" width="3.71" height="2.97"/>
<path class="cls-3" d="M214.5,17.32,197.16,0,195.1,2.05l0,0-1.41,1.38L211,20.73l1.41-1.41,0,0ZM197.16,1.53l.52.52-.52.52-.52-.52Zm-.64.4L196,1.41l.52-.52.52.52Zm15.91,15.89-.52-.52.52-.52.52.52Zm.64-.4.52.52-.52.52-.52-.52Zm-1.8.52.52-.52.52.52-.52.52Zm-.15-1.43-.57-.57.52-.52.57.57Zm.64-.4.52.52-.52.52-.52-.52Zm-1.33-.3-.52-.52.52-.52.52.52Zm-.64-.64-.52-.52.52-.52.52.52Zm-.67-.67L208.6,14l.52-.52.52.52Zm-.64-.64-.52-.52.52-.52.52.52Zm-.67-.67-.57-.57.52-.52.57.57Zm-.69-.69L206.6,12l.52-.52.52.52Zm-.64-.64-.52-.52.52-.52.52.52Zm-.67-.67-.52-.52.52-.52.52.52Zm-.64-.64-.57-.57.52-.52.57.57Zm-.69-.69L204,9.34l.52-.52.52.52Zm-.67-.67-.52-.52.52-.52.52.52Zm-.67-.64L202.62,8l.52-.52.52.52Zm-.64-.67L202,7.36l.52-.52.52.52Zm-.67-.64-.57-.57.52-.52.57.57Zm-.69-.69L200.61,6l.52-.52.52.52Zm-.64-.67L200,5.36l.52-.52.52.52Zm-.67-.64-.52-.52.52-.52.52.52Zm-.64-.67-.52-.52.52-.52.52.52Zm-.67-.64-.57-.57.52-.52.57.57Zm-.69-.69-.52-.52.52-.52.52.52Zm-.12.12-.52.52-.52-.52.52-.52Zm.12.12.57.57-.52.52L197.3,4Zm.69.69.52.52-.52.52L198,4.7Zm.67.64.52.52-.52.52-.52-.52Zm.64.67.52.52-.52.52L199.3,6Zm.67.64.52.52-.52.52L200,6.65Zm.64.67.57.57-.52.52-.57-.57Zm.69.69.52.52-.52.52L201.31,8Zm.67.64.52.52-.52.52L202,8.65Zm.64.67.52.52-.52.52-.52-.52Zm.67.64.52.52-.52.52-.52-.52Zm.67.67.57.57-.52.52-.57-.57Zm.69.69.52.52-.52.52-.52-.52Zm.64.64.52.52-.52.52-.52-.52Zm.67.67.52.52-.52.52-.52-.52Zm.64.64.57.57-.52.52-.57-.57Zm.69.69.52.52-.52.52-.52-.52Zm.67.67.52.52-.52.52-.52-.52Zm.64.64.52.52-.52.52-.52-.52Zm.67.67.52.52-.52.52-.52-.52Zm.64.64.57.57-.52.52-.57-.57Zm.69.69.52.52-.52.52-.52-.52Zm1.43-.1.52-.52.52.52-.52.52Zm-.12-.12-.52-.52.52-.52.52.52Zm-.67-.67-.57-.57.52-.52.57.57Zm-.69-.69-.52-.52.52-.52.52.52Zm-.64-.64-.52-.52.52-.52.52.52Zm-.67-.67-.52-.52.52-.52.52.52Zm-.64-.64-.52-.52.52-.52.52.52Zm-.67-.67-.57-.57.52-.52L209,12Zm-.69-.69-.52-.52.52-.52.52.52Zm-.64-.64-.52-.52.49-.54.52.52Zm-.67-.67-.52-.52.52-.52L207,10Zm-.64-.64-.57-.57.52-.52.57.57Zm-.69-.69-.52-.52.52-.52.52.52Zm-.67-.67L203.93,8l.52-.52L205,8Zm-.64-.64-.52-.52.52-.52.52.52Zm-.67-.67-.52-.52.52-.52.52.52Zm-.67-.64L201.9,6l.52-.52.57.57Zm-.67-.69-.52-.52.52-.52.52.52Zm-.67-.67-.52-.52.52-.52.52.52Zm-.67-.64-.52-.52.52-.52.52.52Zm-.64-.67-.52-.52.52-.52.52.52Zm-.67-.64-.57-.57.52-.52.57.57Zm-.69-.69-.52-.52.52-.52.52.52Zm-.64-.67-.52-.52.52-.52.52.52Zm-.77.77-.52.52L196,2.69l.52-.52Zm14.85,15.89.52-.52.52.52-.52.52Zm1.83-.74-.52-.52.52-.52.52.52ZM197.16.22l.52.52-.52.52-.52-.52Zm-1.29,1.31.52.52-.52.52-.52-.52Z"/>
<path class="cls-3" d="M211.24,30.27l-2.05-2.05L191.87,45.57l2.05,2.05,0,0L195.38,49l17.25-17.25-1.41-1.41Zm-1.56,0-.52.52-.52-.52.52-.52Zm-.4-.67.52-.52.52.52-.52.52ZM194.56,46.71l-.52-.52.52-.52.52.52Zm.67-.64-.52-.52.52-.52.52.52Zm13.15-14.46-.57.57-.52-.52.57-.57ZM208,31l.52-.52L209,31l-.52.52Zm-.3,1.33-.52.52-.52-.52.52-.52Zm-.64.64-.52.52-.52-.52.52-.52Zm-.67.67-.52.52-.52-.52.52-.52Zm-.64.64-.52.52-.52-.52.52-.52Zm-.67.67-.57.57L204,35l.57-.57Zm-.69.69-.52.52-.52-.52.52-.52Zm-.64.64-.52.52-.52-.52.52-.52Zm-.67.67-.52.52-.52-.52.52-.52Zm-.64.64-.57.57-.52-.52.57-.57Zm-.69.69-.52.52-.52-.52.52-.52Zm-.67.67-.52.52-.52-.52.52-.52Zm-.64.64-.52.52-.52-.52.52-.52Zm-.67.67-.52.52-.52-.52.52-.52Zm-.64.64-.57.57-.52-.52.57-.57Zm-.69.69-.52.52-.52-.52.52-.52Zm-.67.67-.52.52-.52-.52.52-.52Zm-.64.64-.52.52-.52-.52.52-.52Zm-.67.67-.52.52-.52-.52.52-.52Zm-.64.64-.57.57-.52-.52.57-.57Zm-.69.69-.52.52-.52-.52.52-.52Zm.77.52-.52-.52.57-.57.52.52Zm.69-.69-.52-.52.52-.52.52.52Zm.67-.64-.52-.52.52-.52.52.52Zm.64-.67-.52-.52.52-.52.52.52Zm.67-.64-.52-.52.52-.52.52.52Zm.64-.67-.52-.52.57-.57.52.52Zm.69-.69-.52-.52.52-.52.52.52Zm.64-.64-.52-.52.52-.52.52.52Zm.67-.67-.52-.52.52-.52.52.52Zm.64-.64-.52-.52.52-.52.52.52Zm.67-.67-.52-.52.57-.57.52.52Zm.69-.69-.52-.52.52-.52.52.52Zm.64-.64-.52-.52.52-.52.52.52Zm.67-.67-.52-.52.52-.52.52.52Zm.64-.64-.52-.52.57-.57.52.52Zm.69-.69-.52-.52.52-.52.52.52Zm.67-.67-.52-.52.52-.52.52.52Zm.64-.64-.52-.52.52-.52.52.52Zm.67-.67-.52-.52.52-.52.52.52Zm.64-.64-.52-.52.57-.57.52.52Zm.69-.69-.52-.52.52-.52.52.52Zm.67-.67-.52-.52.52-.52.52.52Zm-1.29-1.29-.52-.52.52-.52.52.52Zm-.12.12-.52.52-.52-.52.52-.52Zm-.67.67-.57.57-.52-.52.57-.57Zm-.69.69-.52.52-.52-.52.52-.52Zm-.64.64-.52.52-.52-.52.52-.52Zm-.67.67-.52.52-.52-.52.52-.52Zm-.64.64-.52.52-.52-.52.52-.52Zm-.67.67-.57.57-.52-.52.57-.57Zm-.69.69-.52.52-.52-.52.52-.52Zm-.64.64-.52.52-.52-.52.52-.52Zm-.67.67-.52.52-.52-.52.52-.52Zm-.64.64-.57.57-.52-.52.57-.57Zm-.69.69-.52.52-.52-.52.52-.52Zm-.67.67-.52.52-.52-.52.52-.52Zm-.64.64-.52.52-.52-.52.52-.52Zm-.67.67-.52.52-.52-.52.52-.52Zm-.64.64-.57.57-.52-.52.57-.57Zm-.69.69-.52.52-.52-.52.52-.52Zm-.67.67-.52.52-.52-.52.52-.52Zm-.64.64-.52.52-.52-.52.52-.52Zm-.67.67-.52.52-.52-.52.52-.52Zm-.64.64-.57.57-.52-.52.57-.57Zm-.69.69-.52.52-.52-.52.52-.52Zm-.67.67-.52.52-.52-.52.52-.52Zm.12.12.52.52-.52.52-.52-.52Zm-.12,1.16-.54.54-.52-.52.52-.52Zm.12.15.52.52-.52.52-.52-.52Zm16.53-15.52-.52-.52.52-.52.52.52Zm-1.26-2.35.52.52-.52.52-.52-.52ZM192.63,45l.52.52-.52.52-.52-.52Z"/>
<path class="cls-3" d="M223.05,20l2.05,2.05L242.43,4.74l-2.05-2.05,0,0-1.41-1.41L221.67,18.58,223.08,20Zm1.56,0,.52-.52.52.52-.52.52Zm.4.64-.52.52-.52-.52.52-.52ZM239.73,3.61l.52.52-.52.52-.52-.52Zm-.67.64.52.52-.52.52-.52-.52ZM225.92,18.73l.57-.57.52.52-.57.57Zm.4.64-.52.52-.52-.52.52-.52Zm.3-1.33.52-.52.52.52-.52.52Zm.64-.67.52-.52.52.52-.52.52Zm.67-.64.52-.52.52.52-.52.52Zm.64-.67.52-.52.52.52-.52.52Zm.67-.67.57-.57.52.52-.57.57Zm.69-.69.52-.52.52.52-.52.52Zm.64-.64.52-.52.52.52-.52.52Zm.67-.67.52-.52.52.52-.52.52Zm.64-.64.57-.57.52.52-.57.57Zm.69-.69.52-.52.52.52-.52.52Zm.64-.67.52-.52.54.52-.52.52Zm.67-.64.52-.52.52.52-.52.52Zm.64-.67.52-.52.52.52-.52.52Zm.67-.64.57-.57.52.52-.57.57Zm.69-.69.52-.52.52.52-.52.52Zm.64-.67.52-.52.52.52L237,8.6Zm.67-.64.52-.52.52.52-.52.52Zm.64-.67.52-.52.52.52-.52.52Zm.67-.64.57-.57.52.52-.57.57Zm.69-.69.52-.52.52.52-.52.52Zm-.77-.52.52.52-.57.57-.52-.52Zm-.69.69.52.52-.52.52-.52-.52Zm-.67.64.52.52-.52.52-.52-.52Zm-.64.67.52.52-.52.52-.52-.52Zm-.67.64.52.52-.52.52-.52-.52Zm-.64.67.52.52-.57.57-.52-.52Zm-.69.69.52.52-.52.52-.52-.52Zm-.67.64.52.52-.52.52-.52-.52Zm-.64.67.52.52-.52.52-.52-.52Zm-.67.64.52.52-.52.52-.52-.52Zm-.64.67.52.52-.57.57-.52-.52Zm-.69.69.52.52-.52.52-.52-.52Zm-.67.64.52.52-.52.52-.52-.52Zm-.64.67.52.52-.52.52-.52-.52Zm-.67.67.52.52-.57.57-.52-.52Zm-.69.69.52.52-.52.52-.52-.52Zm-.64.64.52.52-.52.52-.52-.52Zm-.67.67.52.52-.52.52-.52-.52Zm-.64.64.52.52-.52.52-.52-.52Zm-.67.67.52.52-.57.57-.52-.52Zm-.69.69.52.52-.52.52-.52-.52Zm-.64.64.52.52-.52.52-.52-.52Zm1.29,1.29.52.52-.52.52-.52-.52Zm.12-.12.52-.52.52.52-.52.52Zm.67-.64.57-.57.52.52-.57.57Zm.69-.69.52-.52.52.52-.52.52Zm.64-.67.52-.52.52.52-.52.52Zm.67-.64.52-.54.52.52-.52.52Zm.64-.67.52-.52.52.52-.52.52Zm.67-.64.57-.57L231,16l-.57.57Zm.69-.69.52-.52.52.52-.52.52Zm.64-.67.52-.52.52.52-.52.52Zm.67-.64.52-.52.52.52-.52.52Zm.64-.67.57-.57.52.52-.57.57Zm.69-.69.52-.52.52.52-.52.52Zm.67-.67.52-.52.52.52-.52.52Zm.64-.64.52-.52.52.52-.52.52Zm.67-.67.52-.52.52.52-.52.52Zm.64-.64.57-.57.52.52-.57.57Zm.69-.69.52-.52.52.52-.52.52Zm.67-.67.52-.52.52.52-.52.52Zm.64-.64.52-.52.52.52-.52.52Zm.67-.67.52-.52.52.52-.52.52Zm.64-.64.57-.57.52.52-.57.57Zm.69-.69.52-.52.52.52-.52.52Zm.64-.67.52-.52.52.52-.52.52Zm-.12-.12-.52-.52.52-.52.52.52Zm.15-1.16.52-.52.52.52-.52.52ZM240.38,4l-.52-.52.52-.52.52.52ZM223.82,19.52l.52.52-.52.52L223.3,20Zm1.29,2.32-.52-.52.52-.52.52.52ZM241.66,5.29l-.52-.52.52-.52.52.52Z"/>
<path class="cls-3" d="M223.89,30.22l-1.41,1.41,0,0-2.05,2.05L237.73,51l2.05-2.05,0,0,1.41-1.41Zm13.81,19.23-.52-.52.52-.52.52.52Zm.64-.4.52.52-.52.52-.52-.52Zm-14.58-14.6.57.57-.52.52-.57-.57Zm-.64.4-.52-.52.52-.52.52.52Zm1.33.3.52.52-.52.52-.52-.52Zm.64.67.52.52-.49.52-.52-.52Zm.67.64.52.52-.52.52-.52-.52Zm.64.67.52.52-.52.52-.52-.52Zm.67.64.57.57-.52.52-.57-.57Zm.69.69.52.52-.52.52-.52-.52Zm.64.67.52.52-.52.52-.52-.52Zm.67.64.52.52-.52.52-.52-.52Zm.64.67.57.57-.52.52-.57-.57Zm.69.69.52.52-.52.52-.52-.52Zm.67.64.52.52-.52.52-.52-.52Zm.64.67.52.52-.52.52-.52-.52Zm.67.64.52.52-.52.52-.52-.52Zm.64.67.57.57-.52.52-.57-.57Zm.69.69.52.52-.52.52-.52-.52Zm.67.64.52.52-.52.52-.52-.52Zm.64.67.52.52-.52.52-.52-.52Zm.67.64.52.52-.52.52-.52-.52Zm.64.67.57.57-.52.52-.57-.57Zm.69.69.52.52-.52.52-.52-.52Zm.12-.12.52-.52.52.52-.52.52Zm-.12-.15-.57-.57.52-.52.57.57Zm-.69-.67-.52-.52.52-.52.52.52Zm-.64-.67-.52-.52.52-.52.52.52ZM235,45.5l-.52-.52.52-.52.52.52Zm-.64-.67-.52-.52.52-.52.52.52Zm-.67-.64-.57-.57.52-.52.57.57Zm-.69-.69-.52-.52.52-.52.52.52Zm-.64-.67-.52-.52.52-.52.52.52Zm-.67-.64-.52-.52.52-.52.52.52Zm-.64-.67-.52-.52.52-.52.52.52Zm-.67-.64-.57-.57.52-.52.57.57Zm-.69-.69-.52-.52.52-.52.52.52Zm-.64-.67-.52-.52.52-.52.52.52Zm-.67-.64-.52-.52.52-.52.52.52Zm-.64-.67-.57-.57.52-.52.57.57Zm-.69-.69-.52-.52.52-.52.52.52Zm-.67-.64-.52-.52.52-.52.52.52Zm-.64-.67-.52-.52.52-.52.52.52Zm-.67-.64-.52-.52.52-.52.52.52Zm-.64-.67-.57-.57.52-.52.57.57Zm-.69-.69-.52-.52.52-.52.52.52Zm-.67-.64-.52-.52.52-.52.52.52Zm-.64-.67-.52-.52.52-.52.52.52Zm-.15.12-.52.52-.52-.52.52-.52Zm.15.12.52.52-.52.52-.52-.52Zm-.12,1.19-.52.52-.52-.52.52-.52Zm.12.12L223,35l-.52.52-.52-.52Zm.67.64.57.57-.52.52-.57-.57Zm.69.69.52.52-.52.52-.52-.52Zm.64.67L225,37l-.52.52-.52-.52Zm.67.64.52.52-.52.52-.52-.52Zm.64.67.52.52-.52.52-.52-.52Zm.67.64L227,39l-.52.52-.57-.57Zm.69.69.52.52-.52.52-.52-.52Zm.64.67.52.52-.52.52-.52-.52Zm.67.64.52.52-.52.52-.52-.52Zm.64.67.57.57-.52.52-.57-.57Zm.69.69.52.52-.52.52-.52-.52Zm.67.64.52.52-.52.52-.52-.52Zm.64.67.52.52-.52.52-.52-.52Zm.67.64.52.52-.52.52-.52-.52Zm.64.67L233,45l-.52.52-.57-.57Zm.69.69.52.52-.52.52-.52-.52Zm.67.64.52.52-.52.52-.52-.52Zm.64.67.52.52-.52.52-.52-.52Zm.67.64.52.52-.52.52-.52-.52Zm.64.67.57.57-.52.52-.57-.57Zm.69.69.52.52-.52.52-.52-.52Zm.67.64.52.52-.52.52-.52-.52Zm.77-.77.52-.52.52.52-.52.52ZM221.15,33.14l.52.52-.52.52-.52-.52Zm16.58,17.6-.52-.52.52-.52.52.52ZM239,49.45l-.52-.52.52-.52.52.52Z"/>
<path class="cls-3" d="M128.87,22.17h-1.36s0,4.28-2.22,6.38v2.77H131V28.54C129,26.47,128.87,22.17,128.87,22.17Z"/>
</svg>

After

Width:  |  Height:  |  Size: 24 KiB

@@ -1,5 +1,5 @@
(ns skirmish.rest.bookkeeper
(:require [skirmish.exchange.exchange :refer [empty-book create-exchange-for-security]]
(ns skirmish.bookkeeper.bookkeeper
(: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]]
@@ -7,17 +7,20 @@
[skirmish.order] ;; for spec side effect
[skirmish.rest.util :refer [build-response build-response-body]]
[clojure.spec.alpha :as s]
[kinsky.client]
[slingshot.slingshot :refer [try+ throw+]]
[clojure.core.async :refer [chan go-loop close! <! sub]]))
(def order-id (atom -1))
(defn endpoint [name description path]
{:name name :description description :url path})
(def endpoints [(endpoint "securities" "list all securities." "/api/1/securities")])
(defn api-description [req]
(let [body {"/api/1/securities" "list all securities."
"/api/1/book" "For all information about the book."}]
(let [body endpoints]
(build-response req body)))
(defn subscribe-to-orders-publisher [exchange-api status-to-listen-on]
@@ -42,9 +45,9 @@
ret))
(defn securities [exchange-apis]
(defn securities [exchange-apis config]
(fn [request]
(build-response request {:securities (keys exchange-apis)})))
(build-response request {:securities (:securities config)})))
(defn get-security [exchange-apis]
@@ -70,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)))))
@@ -87,7 +90,6 @@
(go-loop [order (<! new-book-order-channel)]
(let [order-id (:book.order/id order)
to-remove (contains? #{:withdrawn :totally-fulfilled} (:book.order/status order))]
(println ret)
(swap! ret (fn [book]
(if to-remove
(dissoc book order-id)
@@ -98,21 +100,20 @@
(defn map-values [f m]
(into {} (for [[k v] m] [k (f v)])))
(defn get-books [exchange-apis]
(let [books (map-values #(create-book %) exchange-apis)]
(defn get-books [exchange-apis books]
(fn [{{sec-id-str :sec-id} :route-params body :body :as request}]
(let [sec-id (keyword sec-id-str)]
(build-response request @(get books sec-id))))))
(build-response request @(get books sec-id)))))
(defn create-routes-for-bookkeeper [exchange-apis]
(let [get-book (get-books exchange-apis)]
(defn create-routes-for-bookkeeper [exchange-apis config]
(let [books (map-values #(create-book %) @exchange-apis)]
(routes
(context "/api/1" []
(GET "/" [] api-description)
(GET "/securities" [] (securities exchange-apis))
(GET "/securities" [] (securities exchange-apis config))
(GET "/securities/:sec-id" [] (get-security exchange-apis))
(GET "/securities/:sec-id/trades" [] (listen-to-trades exchange-apis))
(GET "/securities/:sec-id/orders" [] (listen-to-orders exchange-apis))
(GET "/securities/:sec-id/book" [] get-book))
(GET "/securities/:sec-id/book" [] (get-books exchange-apis books)))
(not-found "not found"))))
+20
View File
@@ -0,0 +1,20 @@
; This is the service that broadcast market data to the
; world. The users need to expect websocket and REST api.
(ns skirmish.bookkeeper.core
(:require [org.httpkit.server :refer [run-server]]
[compojure.handler :refer [api]]
[taoensso.timbre :as timbre]
[skirmish.middleware.core :refer [create-map-exchange-apis]]
[skirmish.bookkeeper.bookkeeper :refer [create-routes-for-bookkeeper]]
[skirmish.rest.util :refer [cors-middleware]]))
(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)]
(timbre/info "Running skirmish.bookkeeper.core server with config: " bookkeeper-config)
(run-server
(-> api-routes api cors-middleware)
bookkeeper-config)))
+102
View File
@@ -0,0 +1,102 @@
(ns skirmish.broker.broker
(:require [compojure.core :refer :all]
[compojure.route :refer [not-found]]
[datomic.api :as d]
[slingshot.slingshot :refer [try+ throw+]]
[skirmish.rest.util :refer [build-response not-found-resp get-body]]
[taoensso.timbre :as timbre]
[skirmish.routes.core :as routes]
[clj-http.client :as http-client]))
(defn generate-amount-for-new-user [seed]
(let [random (new java.util.Random seed)
random-fn #(. random nextInt 20000)]
(* 100 (reduce + (take 100 (repeatedly random-fn))))))
(defn create-account [account-id sec-id]
#:skirmish.broker.account{:id account-id
:sec-id sec-id
:private-key (str account-id)})
(defn create-user-account [seed default-sec-id]
(let [url (routes/get-path-for :ledger/security {:sec-id (name default-sec-id)})
account-id (java.util.UUID/randomUUID)
amount (generate-amount-for-new-user seed)
payload [amount (str account-id)]]
(timbre/info "Creating new account " account-id " with ammount " amount " of " default-sec-id)
(timbre/info url)
(http-client/post url {:body (str payload)})
(create-account account-id default-sec-id)))
(defn create-user-seed-from-email [email]
(let [hasher (java.security.MessageDigest/getInstance "SHA-256")
salt (. "this should be private and set in config" getBytes "utf-8")
msg (. email getBytes "utf-8")
_ (doto hasher (.update salt) (.update msg))
hash (. hasher digest)]
(int (mod (new BigInteger 1 hash) (biginteger Integer/MAX_VALUE)))))
(defn create-user [conn default-sec-id]
(fn [{body :body :as request}]
(try+
(let [email (-> body get-body :email)
_ (timbre/info "creating new user" email)
seed (create-user-seed-from-email email)
account (create-user-account seed default-sec-id)]
@(d/transact conn [[:skirmish.broker.user/create-user email [account]]])
(build-response request {:status 201 :headers {"location" (str "/api/1/users/" email)}}))
(catch [:error-type :4XX] e (build-response request e))
(catch [:error-type :5XX] e (build-response request e)))))
(defn add-account-urls [user]
(let [accounts (:skirmish.broker.user/accounts user)
url #(->> % :skirmish.broker.account/id (str "http://localhost:1604/api/1/ledger/accounts/"))
ret (map #(assoc % :skirmish.broker.account/url (url %)) accounts)]
(assoc user :skirmish.broker.user/accounts ret)))
(defn get-user [conn]
(fn [{{email :email} :route-params :as request}]
(try+
(timbre/info "getting user with email" email)
(let [db (d/db conn)
users (d/q '[:find (pull ?e [:skirmish.broker.user/email
{:skirmish.broker.user/accounts [:skirmish.broker.account/id
:skirmish.broker.account/sec-id]}]) .
:in $ ?email
:where [?e :skirmish.broker.user/email ?email]] db email)]
(if (nil? users)
(build-response request {:status 404 :body {:error (str "cannot find user with email " email)}})
(build-response request (add-account-urls users))))
(catch [:error-type :4XX] e (build-response request e))
(catch [:error-type :5XX] e (build-response request e)))))
(defn get-all-users [conn]
(fn [{{email :email} :route-params :as request}]
(try+
(timbre/info "getting user with email" email)
(let [db (d/db conn)
emails (d/q '[:find ?email
:where [_ :skirmish.broker.user/email ?email]] db)
xf (comp cat (map #(hash-map :email % :url (str "/api/1/users/" %))))]
(build-response request
{:users (doall (sequence xf emails))}))
(catch [:error-type :4XX] e (build-response request e))
(catch [:error-type :5XX] e (build-response request e)))))
(defn get-account [conn]
(fn [email account]))
(defn create-routes [conn config]
(routes
(context "/api/1" []
(POST "/users" [] (create-user conn (:default-sec-id config)))
(GET "/users" [] (get-all-users conn))
(GET "/users/:email" [email] (get-user conn))
(GET "/users/:email/accounts/:account" [email account] (get-account conn)))
not-found-resp))
+20
View File
@@ -0,0 +1,20 @@
; The backend of the skirmish web user interface.
(ns skirmish.broker.core
(:require [org.httpkit.server :refer [run-server]]
[compojure.handler :refer [api]]
[skirmish.broker.broker :refer [create-routes]]
[taoensso.timbre :as timbre]
[skirmish.middleware.core :refer [create-map-exchange-apis]]
[skirmish.broker.db :refer [create-connection]]
[skirmish.rest.util :refer [cors-middleware]]))
(defn -main [config securities-api exchange-apis]
(let [webserver-config (:api-broker config)
conn (create-connection (-> webserver-config :datomic :transactor-url))
api-routes (create-routes conn webserver-config)]
(timbre/info "Running skirmish.broker.core server with config: " webserver-config)
(run-server
(-> api-routes api cors-middleware)
webserver-config)))
+61
View File
@@ -0,0 +1,61 @@
(ns skirmish.broker.db
(:require [datomic.api :as d]
[slingshot.slingshot :refer [throw+]]))
(def create-user
[
{:db/ident :skirmish.broker.user/create-user
:db/doc "Create a new user in the system."
:db/fn (d/function {:lang "clojure"
:requires ['[slingshot.slingshot]]
:params '[db email accounts]
:code '(let [existing-user (d/pull db '[:db/id] [:skirmish.broker.user/email email])]
(when (-> existing-user :db/id some?)
(slingshot.slingshot/throw+ {:error-type :4XX :status 400 :body {:error "A user with this email already exists."}}))
[#:skirmish.broker.user{:email email :accounts accounts}])})}])
(def accounts-schema [
{
:db/ident :skirmish.broker.account/id
:db/unique :db.unique/identity
:db/valueType :db.type/uuid
:db/cardinality :db.cardinality/one
:db/doc "The id of an account."}
{
:db/ident :skirmish.broker.account/sec-id
:db/valueType :db.type/keyword
:db/cardinality :db.cardinality/one
:db/doc "The security id of the account."}
{
:db/ident :skirmish.broker.account/private-key
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db/doc "The private key of the account."}])
(def user-schema [
{
:db/ident :skirmish.broker.user/email
:db/unique :db.unique/identity
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db/doc "The email of the user; used as id."}
{
:db/ident :skirmish.broker.user/accounts
:db/valueType :db.type/ref
:db/cardinality :db.cardinality/many
:db/doc "The list of the accounts own by the user."}])
(def schema (-> []
(concat user-schema)
(concat create-user)
(concat accounts-schema)))
(defn create-connection [uri]
(let [_ (d/create-database uri)
conn (d/connect uri)]
@(d/transact conn schema)
conn))
-104
View File
@@ -1,104 +0,0 @@
(ns skirmish.cli.benchmark
(:require [clojure.math.combinatorics :refer [cartesian-product]]
[clojure.core.async :refer [go chan <! >! close! to-chan close!] :as async]
[gniazdo.core :as ws]
[clj-http.client :as client]
[com.stuartsierra.frequencies :as freq]
[clojure.edn :as edn]))
(def ids (atom 0))
(defn web-socket-to-channel [url output-ch]
(let [on-receive #(go (>! output-ch %))
headers {"accept" "application/edn"}]
(ws/connect url :headers headers :on-receive on-receive)))
(defn create-orders-channel [securities]
(let [output (chan 1 (map edn/read-string))
url #(format "ws://localhost:%s/api/1/securities/%s/orders" 8080 %)
xf (comp (map :sec-id)
(map name)
(map url)
(map #(web-socket-to-channel % output)))]
(doall (sequence xf securities))
(async/pub output :book.order/id)))
(defn create-requests-payload-for-security [i security id]
(let [rd (new java.util.Random i)
rand-bool (= (mod (. rd nextInt) 2) 0) ;; java random number generator REALLY sucks
type (if rand-bool :sell :buy)
price (+ (* 3 (. rd nextGaussian)) 30)
int-price (int (* price 100))
quantity (reduce + 1 (map #(. rd nextInt (inc %)) (range 100)))]
{:type type :quantity quantity :price int-price :security security :benchmark/id id}))
(defn create-requests-for-security [[i security]]
(let [req-id (swap! ids inc)
payload (create-requests-payload-for-security i (str (:sec-id security)) req-id)
sec-id-str (-> security :sec-id name)
params {:throw-exceptions false :content-type :edn :headers {"accept" "application/edn"}}
url (format "http://localhost:%s/api/1/securities/%s" 8080 sec-id-str)]
(fn []
(let [now (System/nanoTime)]
(client/post url (assoc params :form-params (assoc payload :benchmark/request-time now)))))))
(defn make-requests [n securities]
(map create-requests-for-security (cartesian-product (range n) securities)))
(defn no-more-then-n-simultaneously [f input-chan n]
(let [buffer (chan)]
(go (async/pipeline n buffer (map f) input-chan))
buffer))
(defn decode-response [response]
(let [now (System/nanoTime)
ret {}
ret (assoc ret :status (:status response))
ret (assoc ret :order (edn/read-string (:body response)))
ret (assoc ret :duration (/ (- now (get-in ret [:order :benchmark/request-time])) (Math/pow 10 9)))]
ret))
(defn benchmark [requests orders]
(let [decoded-responses (chan 1 (map decode-response))
responses (no-more-then-n-simultaneously #(%) (to-chan requests) 10)]
(go (async/pipe responses decoded-responses))
decoded-responses))
(defn compute-result [results new-result]
(let [status-code (:status new-result)
duration (:duration new-result)
old-status-code-count (get-in results [:status status-code] 0)
ret results
ret (assoc-in ret [:status status-code] (inc old-status-code-count))
ret (assoc ret :durations (conj (:durations ret) duration))]
ret))
(defn pp-durations [durations]
(let [freq-map (frequencies durations)]
(print (freq/stats freq-map))))
(defn display-results [results]
(println results)
(println "number of request: " (:n results))
(println "status map: " (:status results))
(pp-durations (:durations results)))
(defn -main [config exchange-apis]
(let [benchmark-config (:benchmark config)
requests (-> benchmark-config :number-of-requests-per-securities (make-requests (:securities config)))
orders (create-orders-channel (:securities config))
results (benchmark requests orders)]
(display-results (async/<!! (async/reduce compute-result {} results)))))
-1
View File
@@ -1 +0,0 @@
(ns skirmish.cli.core)
-56
View File
@@ -1,56 +0,0 @@
(ns skirmish.cli.exchange
(:require [skirmish.exchange.match :refer [sell?]]
[skirmish.util :refer :all]
[skirmish.exchange.exchange :refer [empty-book create-exchange-for-security]]
[skirmish.order] ;; for spec side effect
[slingshot.slingshot :refer [try+ throw+]]
[clojure.edn :as edn]
[clojure.core.async :refer [chan go >!! <!!]]
[clojure.spec.alpha :as s]))
(def incoming-order-channel (chan))
;(def trades-channel (chan))
;
;(def new-book-order-channel (chan))
(defn print-chan [prefix c]
(loop [shit (<!! c)]
(println prefix shit)
(recur (<!! c))))
;(def master-book {:sell (create-sell-book)
; :buy (create-buy-book)})
;;; repl things.
(defn read-order []
(try
(print "$ ")
(let [order (edn/read-string (read-line))
valid? (s/valid? :book.order/order order)]
(if valid? order nil))
(catch Exception e nil)))
(def order-ids (atom -1))
(defn add-id-to-order [order]
(assoc order :book.order/id (swap! order-ids inc)))
(defn -main [& args]
(let [initial-book empty-book
incoming-order-channel (chan)
[trades-channel new-book-order-channel] (create-exchange-for-security initial-book incoming-order-channel)]
(go (print-chan "trade > " trades-channel))
(go (print-chan "order > " new-book-order-channel))
(loop [order (read-order)]
(if (some? order)
(do
(let [order-with-id (add-id-to-order order)]
(>!! incoming-order-channel order-with-id)
(println "\n\norder accepted with id: " (:book.order/id order-with-id))))
(println "invalid order"))
(recur (read-order)))))
+26 -20
View File
@@ -1,37 +1,43 @@
(ns skirmish.core
(:require [skirmish.cli.core]
[skirmish.rest.core]
[skirmish.cli.benchmark]
[skirmish.exchange.core]
(:require [skirmish.exchange.core]
[skirmish.wui.server]
[skirmish.middleware.core :refer [create-exchange-apis-for]]
[aero.core :refer (read-config)]
[skirmish.bookkeeper.core]
[skirmish.exchange-gateway.core]
[skirmish.ledger.core]
[taoensso.timbre :as timbre]
[skirmish.broker.core]
[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])
(:gen-class))
(def targets ;; a target is a function that blocks the thread and takes two
;; arguments: the config map and the "exchange-api"
{:api-server skirmish.rest.core/api-main
{:api-server skirmish.exchange-gateway.core/-main
:exchange-core skirmish.exchange.core/-main ;; should be skirmish.exchange.core/api-main
:api-bookkeeper skirmish.rest.core/bookkeeper-main
:api-bookkeeper skirmish.bookkeeper.core/-main
:ledger skirmish.ledger.core/-main
:broker skirmish.broker.core/-main
:wui skirmish.wui.server/-main})
(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 target-fns)
(let [threads (map #(async/thread (% config exchange-apis)) target-fns)
(defn run-all-targets-in-separate-threads [target-fns config securities-api exchange-apis]
(timbre/warn "running multiple targets. This is okay in dev but horrible in production")
(timbre/info (clojure.string/join "\n" 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))
(skirmish.util/block-thread)))
(defn -main [& argv]
(let [config (-> "config.edn" clojure.java.io/resource read-config)
@@ -42,7 +48,7 @@
(if (seq target-fns)
(run-all-targets target-fns config)
(do
(println argv)
(println "target" target-fns "not found")
(println "all known targets are...")
(println (keys targets))))))
(timbre/warn argv)
(timbre/warn "target" target-fns "not found")
(timbre/warn "all known targets are...")
(timbre/warn (keys targets))))))
+9 -6
View File
@@ -1,9 +1,9 @@
(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]]
[kinsky.async]
[kinsky.client]))
[skirmish.exchange.exchange :refer [create-exchange-for-security empty-book]]))
(defn create-exchange-for [exchange-api]
(let [initial-book empty-book
@@ -16,7 +16,10 @@
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))
+1 -4
View File
@@ -82,7 +82,7 @@
(defn get-processed-orders [order leftover-order trades book]
"Computes all the intermediary orders that took place during
a the trade.
The interal logic is very complicated. Probably too complicated."
The internal logic is very complicated. Probably too complicated."
(let [ret [(assoc order :book.order/status :received)]
ret (into ret (get-processed-orders-in-trades order book trades))
ret (if (seq leftover-order)
@@ -106,6 +106,3 @@
(<! orders-copy)
(<! trades-copy)
(recur (<! new-order-publisher) new-book)))))
@@ -0,0 +1,17 @@
(ns skirmish.exchange-gateway.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]]
[taoensso.timbre :as timbre]
[ring.middleware.cors :refer [wrap-cors]]))
(defn -main [config securities-api exchange-apis]
(let [webserver-config (:api-server config)
api-routes (-> exchange-apis create-map-exchange-apis create-routes-for-exchange)]
(timbre/info "Running skirmish.exchange-gateway.core server with config:" webserver-config)
(run-server (api api-routes) webserver-config)))
@@ -1,16 +1,12 @@
(ns skirmish.rest.exchange
(: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]]
(ns skirmish.exchange-gateway.exchange-gateway
(: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]]
[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.edn :as edn]))
[clojure.core.async :refer [>!!]]))
(def order-id (atom -1))
@@ -28,13 +24,6 @@
: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]
(if (contains? exchange-apis sec-id)
(get exchange-apis sec-id)
@@ -45,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)
+183
View File
@@ -0,0 +1,183 @@
(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)]])})}])
(def create-transactions
[{:db/ident :skirmish.ledger/create-transactions
:db/doc "Move money from one account to the other."
:db/fn (d/function {:lang "clojure"
:requires ['[slingshot.slingshot]]
:params '[db transactions]
:code '(let [from-account-id (map :from-account-id transactions)
from-accounts (d/pull-many db '[*] (for [] [:skirmish.ledger/owner-public-key "2-sec-2"]))]
[])})}])
;; 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 security id."}
{
:db/ident :skirmish.security/name
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db/doc "The usual name of the security."}
{
:db/ident :skirmish.security/type
:db/valueType :db.type/keyword
:db/cardinality :db.cardinality/one
:db/doc "The type of security. For instance :equity or :currency."}
{
:db/ident :skirmish.security/description
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db/doc "The description of the currency. Please be verbose."}
{
:db/ident :skirmish.security/emitter
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db/doc "The public key of the emmiter. This is not yet supported."}
{
:db/ident :skirmish.security/number-of-decimal
:db/valueType :db.type/long
:db/cardinality :db.cardinality/one
:db/doc "The number of decimal to show in the pretty printing."}
{
:db/ident :skirmish.security/symbol
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db/doc "The symbol of the security for pretty printing."}
{
:db/ident :skirmish.security/interest-rate
:db/valueType :db.type/long
:db/cardinality :db.cardinality/one
:db/doc "The daily interest-rate payed by the security as a multiple of 0.01% (100 is 1% interest rate)."}
{
:db/ident :skirmish.security/inflation
:db/valueType :db.type/long
:db/cardinality :db.cardinality/one
:db/doc "The daily inflation as a multiple of 0.01% (100 is 1%)."}])
(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)
(concat create-transactions)))
(defn add-default-currencies [conn]
(let [dapm #:skirmish.security{:sec-id :dapm
:name "didier amyot paper money"
:type :currency
:description "The official currency of the 'Skirmish Shitty Exchange'."
:emitter "dapm private key"
:number-of-decimal 2
:symbol "რ"
:interest-rate (* 100 10)
:inflation (* 100 9)}
apbm #:skirmish.security{:sec-id :apbm
:name "apache beam"
:type :equity
:description "Unified programming model to define and execute data processing pipelines, including ETL, batch and stream (continuous) processing."
:emitter "apbm private key"
:number-of-decimal 0}
apac #:skirmish.security{:sec-id :apac
:name "apache accumulo"
:type :equity
:description "A sorted, distributed key/value store that provides robust, scalable data storage and retrieval."
:emitter "apac private key"
:number-of-decimal 0}
apax #:skirmish.security{:sec-id :apax
:name "apache apex"
:type :equity
:description "Enterprise-grade unified stream and batch processing engine."
:emitter ":apax private key"
:number-of-decimal 0}
apdr #:skirmish.security{:sec-id :apdr
:name "apache drill"
:type :equity
:description "Schema-free SQL Query Engine for Hadoop, NoSQL and Cloud Storage."
:emitter ":apdr private key"
:number-of-decimal 0}
apmh #:skirmish.security{:sec-id :apmh
:name "apache mahout"
:type :equity
:description "For Creating Scalable Performant Machine Learning Applications."
:emitter ":apmh private key"
:number-of-decimal 0}]
@(d/transact conn [dapm apbm apac apax apdr apmh])))
(defn create-connection
([uri init-when-new?]
(let [new-db? (d/create-database uri)
conn (d/connect uri)]
@(d/transact conn schema)
(if (and init-when-new? new-db?)
(add-default-currencies conn))
conn))
([uri]
(create-connection uri true)))
+183
View File
@@ -0,0 +1,183 @@
(ns skirmish.ledger.controllers
(:require [skirmish.rest.util :refer [build-response get-body cors-middleware]]
[skirmish.ledger.data-layer :as data-layer]
[skirmish.routes.core :as routes]
[compojure.core :as compojure]
[compojure.route :refer [not-found]]
[clojure.spec.alpha :as s]
[datomic.api :as d]
[slingshot.slingshot :refer [try+ throw+]]))
(defn get-api-description [request]
(let [body [{:url "ledger/accounts" :name "accounts"}
{:url "ledger/securities" :name "securities"}]]
(build-response request body)))
(defn get-available-accounts-body [accounts]
(let [sec-id->url #(routes/get-path-for :ledger/account {:account-id (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 [db (d/db conn)
resp (get-available-accounts-body (data-layer/get-all-account-public-key db))]
(build-response request resp))))
(defn get-account-body [request {sec-id :sec-id amount :amount account-public-key :account-public-key}]
(if (nil? sec-id)
(build-response request {:status 404})
(let [sec-url (str "/api/1/ledger/securities/" (name sec-id))
beautify-account { :amount amount
:sec-id sec-id
:account-public-key account-public-key}
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-info (data-layer/get-amount-in-account db account-id)]
(get-account-body request account-info))))
(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-info (data-layer/get-amount-in-account db account-id)]
(get-account-body request account-info))))
(defn get-transactions [conn]
(fn [request]
(let [all-pending-transactions []]
(build-response request all-pending-transactions))))
(defn create-transactions [conn]
(fn [{body :body :as request}]
(try+
(build-response request {:message "merci beaucoup"})
(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 #(routes/get-path-for :ledger/security {:sec-id (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 [db (d/db conn)
securities (data-layer/get-all-securities-id db)]
(build-response request (get-available-securities-body securities)))))
(defn get-new-security [raw-securities]
(if (s/valid? :skirmish.ledger.core/security raw-securities)
raw-securities
(throw+ {:error-type :4XX :status 400 :body {:error "Invalid security format."}})))
(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)
new-sec @(data-layer/create-security conn 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)))))
(defn prettify-security [e db]
(let [{interest-rate :skirmish.security/interest-rate
description :skirmish.security/description
id :db/id
sec-id :skirmish.security/sec-id
symbol :skirmish.security/symbol
name :skirmish.security/name
number-of-decimal :skirmish.security/number-of-decimal
type_ :skirmish.security/type} e
interest-rate-history (data-layer/get-interest-rate-history db id)
volumes (d/q '[:find ?amount ?date
:in $ ?id
:where [?e :skirmish.ledger/sec ?id]
[?e :skirmish.ledger/amount ?amount ?tx]
[?tx :db/txInstant ?date]]
db id)
now (new java.util.Date)
volume (reduce + 0 (map #(data-layer/compute-face-value (first %) interest-rate-history (second %) now) volumes))]
{:sec-id sec-id
:interest-rate interest-rate
:volume (or volume 0)
:name name
:interest-rate-history interest-rate-history
:symbol symbol
:number-of-decimal number-of-decimal
:type type_
:description description}))
(defn get-security-from-db [conn sec-id]
(let [db (d/db conn)
e (d/pull db '[*] [:skirmish.security/sec-id sec-id])]
(if (-> e :db/id nil?)
(throw+ {:error-type :4XX :status 404 :body {:error "Security not found."}})
(prettify-security e db))))
(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-emit-securities-request [[amount account] sec-id]
(if (s/valid? :skirmish.ledger.core/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]
"Emit securities in the specified wallet. This is the only way to
create securities.
payload
[:amount :account]"
(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 create-routes-for-ledger [conn]
(compojure/routes
(routes/context :ledger []
(routes/GET :ledger/home [] get-api-description)
(routes/GET :ledger/accounts [] (get-available-accounts conn))
(routes/GET :ledger/account [] (get-account conn))
(routes/GET :ledger/transactions [] (get-transactions conn))
(routes/POST :ledger/transactions [] (create-transactions conn))
(routes/GET :ledger/securities [] (get-available-securities conn))
(routes/POST :ledger/securities [] (create-security conn))
(routes/GET :ledger/security [] (get-security conn))
(routes/PUT :ledger/security [] (update-security conn)) ;; TODO
(routes/POST :ledger/security [] (emit-securities conn)))
(not-found "not found")))
+36
View File
@@ -0,0 +1,36 @@
(ns skirmish.ledger.core
(:require [org.httpkit.server :refer [run-server]]
[skirmish.ledger.controllers :as controllers]
[compojure.handler :refer [api]]
[skirmish.rest.util :refer [cors-middleware]]
[skirmish.ledger.accounts]
[taoensso.timbre :as timbre]
[clojure.spec.alpha :as s]))
(s/def ::transaction (s/cat :type #{:transfer-immediately}
:from-account-id string?
:to-account-id string?
:amount int?))
(s/def ::transactions (s/coll-of ::transaction))
(s/def ::security (s/cat :sec-id keyword?
:interest-rate nat-int?
:type #{:equity :currency}
:description string?
:emitter string?
:symbol (s/? string?)))
(s/def ::emit-securities (s/cat :sec-id keyword?
:amount nat-int?
:account string?))
(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 (controllers/create-routes-for-ledger conn)]
(timbre/info "Running skirmish.ledger.core server with config: " ledger-config)
(run-server
(-> api-routes api cors-middleware)
ledger-config)))
+154
View File
@@ -0,0 +1,154 @@
(ns skirmish.ledger.data-layer
(:require [datomic.api :as d]
[infix.macros :refer [infix]]
[slingshot.slingshot :refer [throw+]]))
(defn get-all-account-public-key [db]
(d/q '[:find ?pk :where [?e :skirmish.ledger/owner-public-key ?pk]] db))
(defn compute-simple-face-value [amount daily-interest-rate deposit-date value-date]
(let [min-in-milliseconds (infix 60 * 1000)
minute-interest-rate (infix daily-interest-rate / (24 * 60. * 10000.))
ite (quot (- (. value-date getTime) (. deposit-date getTime)) min-in-milliseconds)
base (infix 1 + minute-interest-rate)]
(infix amount * base ** ite)))
(defn- in-interval? [[_ start end] x]
(let [x-time (. x getTime)]
(and (<= (. start getTime) x-time)
(< x-time (. end getTime)))))
(defn- larger-then-interval? [[ _ start end] x]
(and (<= (. x getTime) (. end getTime))
(<= (. x getTime) (. start getTime))))
(defn- split-interest-rate-intervals [interest-rates-intervals deposit-date value-date]
(let [xf (comp (drop-while #((comp not in-interval?) % deposit-date))
(take-while #((comp not larger-then-interval?) % value-date)))
ret (into [] xf interest-rates-intervals)
ret (assoc-in ret [0 1] deposit-date)
ret (assoc-in ret [(-> ret count dec) 2] value-date)]
ret))
(defn- find-interest-rate-for [interest-rates-intervals d]
(if (in-interval? (first interest-rates-intervals) d)
(ffirst interest-rates-intervals)
(recur (rest interest-rates-intervals) d)))
(defn split-interest-rates [interest-rates deposit-date value-date]
(let [interest-rates (into [[0 (new java.util.Date 0)]] interest-rates)
interest-rates (conj interest-rates [0 (new java.util.Date Long/MAX_VALUE)])
interest-rates-intervals (partition 2 1 interest-rates)
interest-rates-intervals (map (juxt ffirst (comp second first) (comp second second)) interest-rates-intervals)]
(if (= deposit-date value-date)
[[(find-interest-rate-for interest-rates-intervals deposit-date) deposit-date value-date]]
(split-interest-rate-intervals interest-rates-intervals deposit-date value-date))))
(defn compute-face-value [amount interest-rates deposit-date value-date]
(loop [interest-rates (split-interest-rates interest-rates deposit-date value-date)
amount amount]
(let [[interest-rate start end] (first interest-rates)
amount (compute-simple-face-value amount interest-rate start end)]
(if (empty? (rest interest-rates))
(long amount)
(recur (rest interest-rates) amount)))))
(defn get-interest-rate-history [db sec-id]
(let [history (d/history db)
interest-rates (d/q '[:find ?interest-rate ?v
:in $ ?eid
:where [?e :skirmish.security/interest-rate ?interest-rate ?tx]
[?e :skirmish.security/sec-id ?sec-id]
[?tx :db/txInstant ?v]]
history sec-id)]
(sort-by second interest-rates)))
(defn compute-face-value-from-db [db amount sec-id date]
(compute-face-value amount
(get-interest-rate-history db sec-id)
date
(new java.util.Date)))
(defn get-amount-in-account [db account-id]
(let [[amount sec-id pk date] (d/q '[:find [?amount ?sec-id ?pk ?v]
:in $ ?account-id
:where [?e :skirmish.ledger/owner-public-key ?account-id]
[?e :skirmish.ledger/amount ?amount ?tx]
[?e :skirmish.ledger/sec ?sec]
[?sec :skirmish.security/sec-id ?sec-id]
[?e :skirmish.ledger/owner-public-key ?pk]
[?tx :db/txInstant ?v]]
db account-id)]
{:amount (when (some? sec-id) (compute-face-value-from-db db amount sec-id date))
:sec-id sec-id
:account-public-key pk}))
(defn get-all-securities-id [db]
(d/q '[:find ?s :where [?e :skirmish.security/sec-id ?s]] db))
(defn create-security [conn sec-id interest-rate]
(d/transact conn [[:skirmish.ledger/create-security sec-id interest-rate]]))
(defn get-account [db account-id]
(d/pull db '[*] [:skirmish.ledger/owner-public-key account-id]))
(defn assoc-accounts [db transaction]
(let [from-account (get-account db (:from-account-id transaction))
to-account (get-account db (:to-account-id transaction))
ret (assoc transaction :from-account from-account)
ret (assoc ret :to-account to-account)]
ret))
(defn get-new-from-account-value [{amount :amount from-account :from-account} old-change]
(if (some? old-change)
(assoc old-change 3 (- (get old-change 3) amount))
[:db/add
(:db/id from-account)
:skirmish.ledger/amount
(- (:skirmish.ledger/amount from-account) amount)]))
(defn get-new-to-account-value [{amount :amount to-account :to-account} old-change]
(if (some? old-change)
(assoc old-change 3 (+ (get old-change 3) amount))
[:db/add
(:db/id to-account)
:skirmish.ledger/amount
(+ (:skirmish.ledger/amount to-account) amount)]))
(defn compute-transactions [res transaction]
(let [res-from-account (get res (:from-account-id transaction))
from-account (get-new-from-account-value transaction res-from-account)
res (assoc res (:from-account-id transaction) from-account)
res-to-account (get res (:to-account-id transaction))
to-account (get-new-to-account-value transaction res-to-account)
res (assoc res (:to-account-id transaction) to-account)]
res))
(defn validate-transactions [transactions]
(loop [transactions transactions]
(when (seq transactions)
(let [transaction (first transactions)]
(when (not= (-> transaction :from-account :skirmish.ledger/sec)
(-> transaction :to-account :skirmish.ledger/sec))
(throw+ {:error-type :4XX
:status 400
:body {:error "Part of the transaction includes a transaction between two wallets of different underlying securities."}}))
(recur (rest transactions)))))
transactions)
(defn validate-resulting-transactions [transactions]
(loop [transactions transactions]
(when (seq transactions)
(let [transaction (first transactions)]
(when (neg? (get transaction 3))
(throw+ {:error-type :4XX
:status 400
:body {:error "Transaction would result in a negative amount in a wallet."}})))
(recur (rest transactions))))
transactions)
(defn create-transactions [db transactions]
(let [transactions (map #(assoc-accounts db %) transactions)
transactions (validate-transactions transactions)
ret (vals (reduce compute-transactions {} transactions))]
(validate-resulting-transactions ret)))
+41 -9
View File
@@ -1,7 +1,6 @@
(ns skirmish.middleware.core
(:require [clojure.core.async :as async]
[skirmish.exchange.exchange :refer [empty-book create-exchange-for-security]] ;; remove this import.
[kinsky.async]
[clojure.core.async :refer [go-loop <! sub chan]]
[kinsky.client]))
(defn create-chan-based-exchange-api-for [sec-id]
@@ -9,6 +8,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)
@@ -16,12 +16,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))
-23
View File
@@ -1,23 +0,0 @@
(ns skirmish.rest.core
(:require [org.httpkit.server :refer [run-server]]
[compojure.handler :refer [api]]
[skirmish.util :refer [block-thread]]
[skirmish.middleware.core :refer [create-exchange-apis-for]]
[skirmish.rest.bookkeeper :refer [create-routes-for-bookkeeper]]
[skirmish.rest.exchange :refer [create-routes-for-exchange]]))
(defn api-main [config exchange-apis]
(let [webserver-config (:api-server config)
api-routes (create-routes-for-exchange exchange-apis)]
(println "Running server with bellow config")
(println webserver-config)
(run-server (api api-routes) webserver-config)))
(defn bookkeeper-main [config exchange-apis]
(let [webserver-config (:api-bookkeeper config)
api-routes (create-routes-for-bookkeeper exchange-apis)]
(println "Running server with bellow config")
(println webserver-config)
(run-server (api api-routes) webserver-config)))
+31 -4
View File
@@ -1,13 +1,21 @@
(ns skirmish.rest.util
(:require [cheshire.core :refer [generate-string]]
[slingshot.slingshot :refer [try+ throw+]]
[clojure.edn :as edn]
[ring.middleware.cors :refer [wrap-cors]]
[clojure.string :refer [split starts-with? ends-with?]]))
;; Access-Control-Allow-Origin
(def mime-types
[["application/json" generate-string]
["application/edn" str]])
(defn cors-middleware [handler]
(wrap-cors handler
:access-control-allow-origin #"http://localhost:3449"
:access-control-allow-credentials true
:access-control-allow-methods [:get :put :post :delete]))
(defn create-mime-type-predicate [accept]
@@ -40,8 +48,27 @@
(let [[_ serializer] (get-best-serializer request)]
(serializer body)))
(defn build-response [request {status :status body :body :as response}]
(let [[content-type serializer] (get-best-serializer request)]
(defn build-response [request {status :status body :body headers :headers :as response}]
(let [[content-type serializer] (get-best-serializer request)
ret-headers (if (some? headers) headers {})]
{: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)}))
(defn not-found-resp [request]
(build-response request {:status 404 :body {:error "not found"}}))
(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))
+46
View File
@@ -0,0 +1,46 @@
(ns skirmish.routes.api)
(defn get-app-for [all-routes k]
"An app consists on a name, a prefix and some routes."
(let [app-name (-> k namespace keyword)
app (first (filter #(= app-name (:name %)) all-routes))]
app))
(defn- get-route-for [all-routes k]
(let [app (get-app-for all-routes k)
route-name (-> k name keyword)
route (first (filter #(= route-name (:name %)) (:routes app)))]
route))
(defn get-compojure-path [all-routes k]
"Returns what compojure expects to have."
(->> k (get-route-for all-routes) :path))
(defn- replace-part [part params]
(if (clojure.string/starts-with? part ":")
(-> part (subs 1) keyword params)
part))
(defn- path-for [p params]
(let [parts (clojure.string/split p #"/")]
(clojure.string/join "/" (map #(replace-part % params) parts))))
(defn get-context [all-routes c]
(let [get-app #(get-app-for all-routes %)]
(-> c
name
(keyword "some-name")
get-app
:path)))
(defn get-path-for
"Returns a link to a location by name."
([all-routes k]
(get-path-for all-routes k {}))
([all-routes k params]
(let [app (get-app-for all-routes k)
route (get-route-for all-routes k)
p (str (:path app) (:path route))
domain (get app :domain "")]
(str domain (path-for p params)))))
+45
View File
@@ -0,0 +1,45 @@
(ns skirmish.routes.core
(:require [skirmish.routes.api :as routes-api]
[compojure.core :as compojure]))
(def ledger-routes {
:name :ledger
:domain "http://localhost:1604" ;; should somehow be in some config
:path "/api/1/ledger"
:routes [{:name :home :path "/"}
{:name :accounts :path "/accounts"}
{:name :account :path "/accounts/:account-id"}
{:name :securities :path "/securities"}
{:name :security :path "/securities/:sec-id"}
{:name :transactions :path "/transactions"}]})
(def all-routes [ledger-routes])
(def get-context (partial routes-api/get-context all-routes))
(def get-compojure-path (partial routes-api/get-compojure-path all-routes))
(def get-path-for (partial routes-api/get-path-for all-routes))
(defmacro GET [path args & body]
(compojure/compile-route :get
(get-compojure-path path)
args
body))
(defmacro POST [path args & body]
(compojure/compile-route :post
(get-compojure-path path)
args
body))
(defmacro PUT [path args & body]
(compojure/compile-route :put
(get-compojure-path path)
args
body))
(defmacro context [path args & routes]
(let [resolved-path (get-context path)]
(concat `(compojure/context ~resolved-path ~args) routes)))
+39
View File
@@ -0,0 +1,39 @@
(ns skirmish.strategies.core
(:require [clojure.core.async :as async :refer [>! <!]]))
(defn strategy-one [])
(defprotocol SimpleStrategy
"Implement this protocol if you wish to make a strategy"
(get-urls-to-fetch [strategy msecs]
"returns a list of all the url to fetch.")
(get-wished-trades [strategy msec data]
""))
(defrecord TheBananaStrategy [sec-id look-back-in-days period])
(extend-type TheBananaStrategy
SimpleStrategy
(get-urls-to-fetch [strat msecs]
(let [sec-id (-> strat :sec-id name)
one-day (* 24 3600 1000)
from (- msecs (-> strat :look-back-in-days (* one-day)))
period (:period strat)
to msecs]
(format "/api/v1/securities/%s/l1?from=%s&to=%s&period=period" sec-id from to period))))
(defn get-data-for-strategy [strategy])
(defn make-trades [wanted-trades])
(defn framework [strategy msecs]
(async/go-loop [_ (<! (async/timeout msecs))]
(let [data-for-strategy (<! (get-data-for-strategy strategy))
wanted-trades (strategy data-for-strategy)
results (<! (make-trades wanted-trades))])
(recur (<! (async/timeout msecs)))))
+3 -3
View File
@@ -1,12 +1,12 @@
(ns skirmish.util
(:require [clojure.core.async :as async])
(:import [skirmish.PersistentSortedList]))
(:import [ skirmish PersistentSortedList]))
(defn sorted-list [& keys]
(skirmish.PersistentSortedList/create (seq keys)))
(PersistentSortedList/create (seq keys)))
(defn sorted-list-by [comparator & keys]
(skirmish.PersistentSortedList/create comparator (seq keys)))
(PersistentSortedList/create comparator (seq keys)))
(defn block-thread []
"Block the current thread until chuck norris gets weaker."
+5 -2
View File
@@ -2,7 +2,8 @@
(:require [compojure.core :refer [GET defroutes]]
[compojure.route :refer [not-found resources]]
[hiccup.page :refer [include-js include-css html5]]
[skirmish.wui.middleware :refer [wrap-middleware]]))
[skirmish.wui.middleware :refer [wrap-middleware]])
(:gen-class))
(def mount-target
[:div#app
@@ -14,6 +15,7 @@
(defn head []
[:head
[:meta {:charset "utf-8"}]
[:title "Skirmish"]
[:meta {:name "viewport"
:content "width=device-width, initial-scale=1"}]
(include-css "/css/site.css"); use "/css/site.min.css" in production
@@ -25,7 +27,8 @@
(head)
[:body.body-container
mount-target
(include-js "/js/app.js")]))
(include-js "/js/app.js")
(include-js "https://sidecar.gitter.im/dist/sidecar.v1.js")]))
(defn loading-page []
(html5
+3 -3
View File
@@ -1,8 +1,8 @@
(ns skirmish.wui.server
(:require [skirmish.wui.handler :refer [app]]
[ring.adapter.jetty :refer [run-jetty]])
[org.httpkit.server :refer [run-server]])
(:gen-class))
(defn -main [config exchange-apis]
(defn -main [config securities-api exchange-apis]
(let [port 3000]
(run-jetty app {:port port :join? false})))
(run-server app {:port port :join? false})))
+3
View File
@@ -0,0 +1,3 @@
(ns skirmish.wui-util)
(defn pp-number [n])
@@ -0,0 +1,50 @@
(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 []
(let [order (atom {})]
(fn []
[:div
[:div.columns
[:div.column.content
[:div.columns.is-centered
[:div
[:div.field.has-addons
(select-type order)
(select-securities order)
(select-quantities order)
action-button]]]]]
[:div.columns
[:div.column.content
[:pre (str @order)]]]])))
@@ -0,0 +1,6 @@
(ns skirmish.wui.components.face-value
(:require [skirmish.wui.components.secunit :refer [secunit]]))
(defn face-value [amount sec-id]
(when (= sec-id :dapm)
(secunit amount sec-id)))
@@ -1,4 +1,45 @@
(ns skirmish.wui.components.home-page)
(ns skirmish.wui.components.home-page
(:require [cljs-http.client :as http]
[reagent.core :refer [atom]]
[skirmish.wui.util :refer [email?]]
[cljs.core.async :refer [<!] :refer-macros [go]]
[secretary.core :as secretary]))
(defn create-user [email]
(go
(let [url "http://localhost:1605/api/1/users"
resp (<! (http/post url {:edn-params {:email email}}))]
(secretary/dispatch! (str "/home/" email)))))
(defn login [state]
(let [login-field (atom (get-in state [:user :skirmish.broker.user/email] ""))
on-change #(reset! login-field (-> % .-target .-value))]
(fn []
(let [disabled? (-> @login-field email? not)
href (if (not disabled?) (str "/home/" @login-field) nil)]
[:div.field
[:div.control
[:label.label "Email"]
[:div.field.is-grouped
[:input.input.is-success {:type "email"
:value @login-field
:on-change on-change
:placeholder "boris.johnson@redneck.co.uk"
:style {:margin-right "1em"}}]
[:div.control
[:a.button.is-link.has-background-primary {:href href
:disabled disabled?} "login"]]
[:div.control
[:button.button.is-link.has-background-primary {:disabled disabled?
:on-click #(create-user @login-field)} "create"]]]]]))))
(defn page [state]
[:h1 "Hello world sdjjksd dsklsd"])
[:div
[:section.section
[:div.columns
[:div.container
[:img {:src "/images/amsterdam.svg"}]]]
[:div.columns.is-centered
[login state]]]])
@@ -0,0 +1,67 @@
(ns skirmish.wui.components.otc-page
(:require [reagent.core :refer [atom]]
[skirmish.wui.components.securities-page :refer [get-securities]]
[skirmish.wui.util :refer [email?]]))
(defn set-amount [state event]
(let [inserted-value (-> event .-target .-value)
new-value (first (re-find #"^\d+(\.\d*)?" inserted-value))]
(swap! state #(assoc % :value new-value))))
(defn set-email [state event]
(let [new-email (-> event .-target .-value)]
(swap! state #(assoc % :email new-email))))
(defn set-sec-id [state event]
(let [sec-id (-> event .-target .-value)]
(swap! state #(assoc % :sec-id sec-id))))
(defn ready-to-transfer? [state]
(and (-> state :value js/parseInt pos?)
(-> state :email email?)))
(defn select-securities-for [securities on-change]
[:select {:on-change on-change}
(for [security securities]
[:option {:value (:sec-id security) :key (:sec-id security)} (:sec-id security)])])
(defn select-security [on-change]
(let [securities (atom [])]
(get-securities securities)
#(select-securities-for @securities on-change)))
(defn direct-send []
(let [state (atom {})]
(fn []
[:div
[:div.content
[:p "Send securities to another person."]]
[:form.form
[:div.field.has-addons.has-addons-centered
[:div.control
[:span.select
[select-security (partial set-sec-id state)]]]
[:div.control
[:input.input {:type "text"
:value (:value @state)
:on-change (partial set-amount state)
:placeholder "110.90"}]]
[:div.control
[:input.input {:type "text"
:value (:email @state)
:on-change (partial set-email state)
:placeholder "boris.johnson@redneck.co.uk"}]]
[:a.button.is-primary {:style {:marginLeft "1em"}
:disabled (-> @state ready-to-transfer? not)} "transfer"]]]])))
(defn page [state]
[:div
[:div.content
[:h5 "Do transaction without passing by the exchange."]]
[direct-send]])
@@ -0,0 +1,36 @@
(ns skirmish.wui.components.portfolio-page
(:require [reagent.core :refer [atom]]
[cljs-http.client :as http]
[skirmish.wui.components.face-value :refer [face-value]]
[cljs.core.async :refer [<!] :refer-macros [go] :as async]
[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 :account} portfolio]
[:tr {:key (:account-public-key sec-summary)}
[:td (:sec-id sec-summary)]
[:td [secunit (:amount sec-summary) (-> sec-summary :sec-id keyword)]]
[:td [face-value (:amount sec-summary) (-> sec-summary :sec-id keyword)]]])]])
(defn get-accounts-information [urls a-accounts]
(go
(let [ar (async/merge (map #(http/get % {:with-credentials? false}) urls))
accounts (map :body (<! (async/into [] ar)))]
(reset! a-accounts accounts))))
(defn page-wrapper [accounts]
[:div.content
[:h5 "View all the assets you own."]
[make-portfolio-summary-table accounts]])
(defn page [{{accounts :skirmish.broker.user/accounts} :user}]
(let [urls (map :skirmish.broker.account/url accounts)
a-accounts (atom [])]
(get-accounts-information urls a-accounts)
#(page-wrapper @a-accounts)))
@@ -0,0 +1,25 @@
(ns skirmish.wui.components.secunit
(:require [reagent.core :refer [atom]]
[skirmish.wui.util :refer [pp-int]]
[cljs-http.client :as http]
[cljs.core.async :refer [<!] :refer-macros [go] :as async]))
(defn get-security-definition [sec sec-id]
(go
(let [url (str "http://localhost:1604/api/1/ledger/securities/" (name sec-id))
resp (<! (http/get url {:with-credentials? false}))]
(reset! sec (:body resp)))))
(defn- pp-amount [amount sec]
(when (seq sec)
(let [{sec-id :sec-id number-of-decimal :number-of-decimal symbol :symbol} sec
href (str "/securities/" sec-id)]
[:a {:href href} (pp-int amount (or number-of-decimal 0)) " "
(or symbol "")])))
(defn secunit [amount sec-id]
(let [sec (atom {})]
(get-security-definition sec sec-id)
#(pp-amount amount @sec)))
@@ -1,4 +1,8 @@
(ns skirmish.wui.components.securities-page)
(ns skirmish.wui.components.securities-page
(:require [cljs-http.client :as http]
[cljs.core.async :refer [<!] :as async]
[reagent.core :refer [atom]])
(:require-macros [cljs.core.async.macros :refer [go]]))
(def table-head
[:thead.has-background-grey-dark
@@ -14,9 +18,71 @@
[:td (:name sec)]
[:td (:description sec)]]))
(defn page [state]
(let [securities [{:sec-id :foo} {:sec-id :bar} {:sec-id :toto}]]
(defn get-all-securities-urls []
(let [ret (async/chan)]
(go (let [url "http://localhost:1604/api/1/ledger/securities"
response (<! (http/get url {:with-credentials? false}))
urls (map :url (get-in response [:body :securities]))]
(async/onto-chan ret urls)))
ret))
(defn get-all-securities []
(let [ret (async/chan)
urls (get-all-securities-urls)]
(async/go-loop [url (async/<! urls)]
(when-let [url (async/<! urls)]
(async/go
(let [sec (:body (<! (http/get url {:with-credentials? false})))]
(println sec)
(async/>! ret sec)))
(recur url)))
ret))
(defn get-securities [a-securities]
(let [securities (get-all-securities)]
(async/go-loop [security (async/<! securities)]
(when (some? security)
(swap! a-securities #(conj % security))
(recur (async/<! securities))))))
(defn create-currencies-table [securities]
[:div
[:div.content
[:h3 "Currencies"]
[:p "A " [:em "currency"] " is currency that pays interest rate in
its own nomination."]]
[:table.table.is-hoverable.is-fullwidth
table-head
[:tbody
(for [sec securities] (create-row sec))]]))
(for [sec securities :when (= (:type sec) "currency")] (create-row sec))]]])
(defn create-equities-table [securities]
[:div
[:div.content
[:h3 "Equities"]
[:p "An " [:em "equity"] " is a type of security that pays dividends. In
real life, an equity (or stock) is a share in a company ownership.
When the company make profits, it shares the its profits between its stock
holder in a pro rata fashion."]]
[:table.table.is-hoverable.is-fullwidth
table-head
[:tbody
(for [sec securities :when (= (:type sec) "equity")] (create-row sec))]]])
(defn create-securities-table [securities]
[:div
[create-currencies-table securities]
[:br]
[create-equities-table securities]])
(defn security-table []
(let [securities (atom [])]
(get-securities securities)
#(create-securities-table @securities)))
(defn page [state]
[:div
[:div.content
[:h5 "View the available securities for trading."]]
[security-table]])
@@ -1,4 +1,42 @@
(ns skirmish.wui.components.security-page)
(ns skirmish.wui.components.security-page
(:require [skirmish.wui.util :refer [pp-int]]
[reagent.core :refer [atom]]
[cljs-http.client :as http]
[cljs.core.async :refer [<!] :refer-macros [go]]))
(defn get-ledget-information [sec-info sec-id]
(go
(let [url (str "http://localhost:1604/api/1/ledger/securities/" (name sec-id))
resp (<! (http/get url {:with-credentials? false}))]
(reset! sec-info (:body resp)))))
(defn create-ledger-table [sec-info]
[:div
[:h1 (:name sec-info)]
[:cite (:description sec-info)]
[:table
[:tbody
[:tr
[:td "Security ID"] [:td (:sec-id sec-info)]]
[:tr
[:td "Type"] [:td (:type sec-info)]]
(when-let [symbol (:symbol sec-info)]
[:tr
[:td "Symbol"] [:td symbol]])
[:tr
[:td "Interest Rate"] [:td (-> sec-info :interest-rate (pp-int 2) (str " %"))]]
[:tr
[:td "Total Volume"] [:td (-> sec-info :volume (pp-int (:number-of-decimal sec-info)) (str " " (:symbol sec-info)))]]]]])
(defn ledger-table [sec-id]
(let [sec-info (atom {})]
(get-ledget-information sec-info sec-id)
#(create-ledger-table @sec-info)))
(defn page [state]
[:h1 (str "this is a security page for " (:sec-id state))])
[:div.content
[ledger-table (:sec-id state)]])
+48 -22
View File
@@ -6,27 +6,53 @@
(def done {:style {:text-decoration "line-through"}})
(defn filter-out-done [n]
(let [state (volatile! n)]
(fn [item]
(when (= (second item) done)
(vswap! state dec))
(or (not= (second item) done) (neg? @state)))))
(defn todo-list [& items]
(let [filter-fn (filter-out-done (- (count items) 20))
item-to-show (filter filter-fn items)]
[:ol
(for [item item-to-show]
item)]))
(defn page []
[:div.content
[:ul
[:li "shits"
[:ul
[:li "add a list of bogus securities"]]]
[:li "kafka"
[:ul
[:li done "setup kafka"]
[:li "try kafka streams"]
[:li done "pass every order in kafka"]]]
[:li "Logic"
[:ul
[:li done "les orders doivent inclurent les bornes"]
[:li done "simplifier le spec des ordres en enlevant " [:code ":order/"]]
[:li "finish insertion logic and test with spec"]
[:li "link insertion logic to application"]]]
[:li "User interface"
[:ul
[:li "Add logo"]
[:li "Faire un ui pour sell limit order"]
[:li "Faire un ui pour buy limit order"]
[:li "Faire un ui pour sell market order"]
[:li "Faire un ui pour buy market order"]]]]])
[:h5 "This still to do in this project. This is a jira for the poor (or for the geek)."]
[:ol
[:li "Broker"
[:p "the brooker should use the exchange-gateway and ledge as apis. Nothing else."]
[todo-list
[:li done "implement basic login in the wui"]
[:li done "implement basic web server"]
[:li done "implement stupid login on server side."]
[:li done "link the logic of the client-server login logic"]
[:li done "add the notion of 'accounts' in the server. An
user should have many accounts; one for each security
he owns"]
[:li done "make the " [:a {:href "/portfolio"} "portfolio"] " page representing the use accounts"]
[:li done "make a somewhat pretty " [:a {:href "/securities/dapm"} "security"] " page"]
[:li done "create two stocks"]
[:li done "add stocks to didier@gmail.com"]
[:li done "make something for urls on the backend"]
[:li done "add a wui for OTC"]
[:li "fix the stupid chat. This is an important feature for OTC users."]
[:li "make something that allows to send securities from one user to another"]
[:li "move 'routes' in 'rest'"]
[:li "add a little bit of professionalism on top of the broker api"]
[:li "make the api support transit and edn"]
[:li done "implements interest rate in ledger."]
[:li done "Make sure the ledger can have accounts"]
[:li done "when a new user signs in, he should receive some sort of money"]
[:li "implement send securities to friends"]
[:li "implement buy/sell securities."]
[:li "Use ledger as source of through in each sub server."]
[:li "Update web ui for integrating securities with ledger."]
[:li "implement transactions in ledger."]
[:li "implement escrow."]]]]])
+27 -3
View File
@@ -10,14 +10,38 @@
"is-active"))
(defn- user-dropdown [state]
(when (:user @state)
(let [icon [:i.fa.fa-sign-out {:aria-hidden true}]
email (get-in @state [:user :skirmish.broker.user/email])]
[:div.navbar-item.has-dropdown.is-hoverable
[:a.navbar-link {} (str "[ " email " ]")]
[: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
[:div.navbar-start
[:a.navbar-item.is-tab {:href "/" :class (top-bar-class state :home)} "Home"]
[:a.navbar-item.is-tab {:href "/securities" :class (top-bar-class state :securities)} "Securities"]
[:a.navbar-item.is-tab {:href "/create-order" :class (top-bar-class state :create-order)} "Create order"]
[:a.navbar-item.is-tab {:href "/portfolio" :class (top-bar-class state :view-portfolio)} "View Portfolio"]
[:a.navbar-item.is-tab {:href "/create-order" :class (top-bar-class state :create-order)} "Create Order"]
[:a.navbar-item.is-tab {:href "/otc" :class (top-bar-class state :otc)} "Over The Counter"]
[:a.navbar-item.is-tab {:href "/portfolio" :class (top-bar-class state :view-portfolio)} "Portfolio"]
[:a.navbar-item.is-tab {:href "/users" :class (top-bar-class state :users)} "Users"]
[: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 {} "Skirmish Shitty Exchange"]
[user-placeholder state]]]])
@@ -0,0 +1,41 @@
(ns skirmish.wui.components.users-page
(:require [cljs-http.client :as http]
[cljs.core.async :refer [<!] :as async]
[reagent.core :refer [atom]])
(:require-macros [cljs.core.async.macros :refer [go]]))
(def users-table-headers
[:thead.has-background-grey-dark
[:tr
[:th.has-text-white-bis "User email"]
[:th.has-text-white-bis "Public wallet id"]]])
(defn create-row [user]
[:tr.has-text-white-bis {:key (:email user)}
[:td (:email user)]
[:td "key"]])
(defn create-users-table [users]
[:table.table.is-hoverable.is-fullwidth
users-table-headers
[:tbody
(map create-row users)]])
(defn get-users [users-a]
(go
(let [url "http://localhost:1605/api/1/users"
resp (<! (http/get url))]
(reset! users-a (-> resp :body :users)))))
(defn users-table []
(let [users (atom [])]
(get-users users)
#(create-users-table @users)))
(defn page [state]
[:div
[:div.content
[:h5 "Users"]
[users-table]]])
+41 -2
View File
@@ -3,6 +3,8 @@
[skirmish.wui.pages :as pages]
[skirmish.wui.components.topbar :refer [top-bar]]
[reagent.session :as session]
[cljs-http.client :as http]
[cljs.core.async :refer [<!] :refer-macros [go]]
[secretary.core :as secretary :include-macros true]
[accountant.core :as accountant]))
@@ -12,7 +14,16 @@
(defonce state (atom {}))
(defn login [email]
(go (let [resp (<! (http/get (str "http://localhost:1605/api/1/users/" email)))]
(if (= (:status resp) 200)
(do
(println "Logging in as:" (-> resp :body type))
(session/put! :current-page #'pages/home-page)
(swap! state #(into % {:page :home :user (:body resp)})))
(do
(println "Cannot log in as" email)
(swap! state #(dissoc % :user)))))))
;; -------------------------
;; Routes
@@ -26,10 +37,37 @@
(session/put! :current-page #'pages/home-page)
(swap! state #(into % {:page :home})))
(secretary/defroute "/home/:email" [email]
(login email))
(secretary/defroute "/home/" []
(session/put! :current-page #'pages/home-page)
(swap! state #(into % {:page :home :user nil})))
(secretary/defroute "/users" []
(session/put! :current-page #'pages/users-page)
(swap! state #(into % {:page :users})))
(secretary/defroute "/securities" []
(session/put! :current-page #'pages/securities-page)
(swap! state #(into % {:page :securities})))
(secretary/defroute "/portfolio" []
(session/put! :current-page #'pages/portfolio-page)
(swap! state #(into % {:page :portfolio})))
(secretary/defroute "/otc" []
(session/put! :current-page #'pages/otc-page)
(swap! state #(into % {:page :otc})))
(secretary/defroute "/create-order" []
(session/put! :current-page #'pages/create-order-page)
(swap! state #(into % {:page :create-order})))
(secretary/defroute "/securities/:sec-id" [sec-id]
(session/put! :current-page #'pages/security-page)
(swap! state #(into % {:page :securities :sec-id (keyword sec-id)})))
@@ -41,13 +79,14 @@
(defn current-page []
"This is the basic page setup."
[:div
(top-bar state)
[top-bar state]
[:div
[:div.columns
[:div.column.is-8.is-offset-2
[(session/get :current-page) @state]]]]])
(defn mount-root []
(reagent/render [current-page] (.getElementById js/document "app")))
+13 -1
View File
@@ -1,8 +1,12 @@
(ns skirmish.wui.pages
(:require [skirmish.wui.components.todo-page]
[skirmish.wui.components.home-page]
[skirmish.wui.components.portfolio-page]
[skirmish.wui.components.create-order-page]
[skirmish.wui.components.securities-page]
[skirmish.wui.components.security-page]))
[skirmish.wui.components.security-page]
[skirmish.wui.components.otc-page]
[skirmish.wui.components.users-page]))
(def home-page skirmish.wui.components.home-page/page)
@@ -10,4 +14,12 @@
(def securities-page skirmish.wui.components.securities-page/page)
(def create-order-page skirmish.wui.components.create-order-page/page)
(def portfolio-page skirmish.wui.components.portfolio-page/page)
(def security-page skirmish.wui.components.security-page/page)
(def otc-page skirmish.wui.components.otc-page/page)
(def users-page skirmish.wui.components.users-page/page)
+34
View File
@@ -0,0 +1,34 @@
(ns skirmish.wui.util)
(defn right-pad [s o n]
"right pad s with o until length is greater or
equal to n."
(if (<= n (count s))
s
(recur (str s o) o n)))
(defn left-pad [s o n]
"left pad s with o until length is greater or
equal to n."
(if (<= n (count s))
s
(recur (str o s) o n)))
(defn pp-int
"returns the string representing n with the spaces at
the proper place."
([n]
(cond
(neg? n) (str "-" (pp-int (- n)))
(< n 1000) (str n)
:else (str (pp-int (quot n 1000)) " " (left-pad (str (mod n 1000)) "0" 3))))
([n dec]
(if (<= dec 0)
(pp-int n)
(let [i (int (Math/pow 10 dec))]
(str (pp-int (quot n i)) "," (right-pad (str (mod n i)) "0" dec))))))
(defn email? [s]
(let [email-pattern #"^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$"]
(->> s str (re-matches email-pattern) some?)))
+13 -1
View File
@@ -8,15 +8,27 @@
@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;
$border: $yellow;
$table-row-hover-background-color: $black-ter;
// box
$box-background-color: $grey;
$box-color: $white;
$box-shadow: 0 2px 3px $grey-light, 0 0 0 1px $grey-light;// $red;
@import "../../node_modules/bulma/sass/base/_all";
+33
View File
@@ -0,0 +1,33 @@
(ns skirmish.broker.broker-test
(:require [midje.sweet :refer :all]
[orchestra.spec.test :as st]
[skirmish.broker.broker :refer [add-account-urls
create-user-seed-from-email
generate-amount-for-new-user]]))
(st/instrument)
(facts "the broker util functions"
(fact "it should be able to add url to accounts"
(let [user {:skirmish.broker.user/accounts [{:skirmish.broker.account/id "foo"}]}
ret {:skirmish.broker.user/accounts [{:skirmish.broker.account/id "foo"
:skirmish.broker.account/url "http://localhost:1604/api/1/ledger/accounts/foo"}]}]
(add-account-urls user) => ret)))
(facts "about the new users"
(fact "we should add some random amount of damp in their account"
(let [random-values (map generate-amount-for-new-user (range 10000))
sum (reduce + random-values)
mean (/ sum (count random-values))
min-data (apply min random-values)
max-data (apply max random-values)]
(int mean) => 100027427
min-data => 78422400
max-data => 120128000))
(fact "we should generate the random seed by the email"
(create-user-seed-from-email "foo@gmail.com") => 399122929
(create-user-seed-from-email "boo@gmail.com") => 1433675645))
+25
View File
@@ -0,0 +1,25 @@
(ns skirmish.broker.db-test
(:require [midje.sweet :refer :all]
[orchestra.spec.test :as st]
[datomic.api :as d]
[skirmish.broker.db :refer [create-connection]]
[skirmish.broker.broker :refer [create-account]]))
(st/instrument)
(defn create-conn []
(let [mem-url "datomic:mem://skirmish"]
(d/delete-database mem-url)
(create-connection mem-url)))
(facts "about the db functions."
(fact "should be able to create new users"
(let [conn (create-conn)
email "foo@gmail.com"
account-id #uuid"dc0d1123-e659-4e8a-b38b-ece520f4c4d0"
account (create-account account-id :dapm)
_ @(d/transact conn [[:skirmish.broker.user/create-user email account]])
db (d/db conn)
user (d/pull db '[* {:skirmish.broker.user/accounts [*]}] [:skirmish.broker.user/email email])]
(:skirmish.broker.user/email user) => email
(-> user :skirmish.broker.user/accounts first :skirmish.broker.account/id) => account-id)))
@@ -1,7 +1,8 @@
(ns skirmish.exchange.exchange-test
(:import [skirmish.PersistentSortedList])
(:import (skirmish PersistentSortedList))
(:require [midje.sweet :refer :all]
[orchestra.spec.test :as st]
[skirmish.order]
[skirmish.exchange.exchange :refer :all]
[skirmish.exchange.match :refer [create-buy-book
create-sell-book
@@ -9,7 +10,7 @@
(st/instrument)
(def PersistentSortedList? (comp #(= % skirmish.PersistentSortedList) type))
(def PersistentSortedList? (comp #(= % PersistentSortedList) type))
(facts "the exchange as an whole"
(fact "process-sell-order should work when book empty"
+316
View File
@@ -0,0 +1,316 @@
(ns skirmish.ledger.test-core
(:require [midje.sweet :refer :all]
[skirmish.ledger.data-layer :as data-layer]
[skirmish.ledger.controllers :as controllers]
[skirmish.ledger.accounts :refer [create-connection schema]]
[skirmish.rest.util :refer [build-response]]
[skirmish.ledger.core :refer :all]
[skirmish.rest.util :refer [edn->stream]]
[datomic.api :as d]
[clojure.edn]
[clojure.spec.alpha :as s]
[infix.macros :refer [infix]]))
(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 false)))
(defn location? [path]
#(-> % :headers (get "location") (clojure.string/ends-with? path)))
(def sec->hash-set #(apply hash-set %))
(def mock-securities [[:sec-1 100 :equity "first equity" "rot13"]
[:sec-2 200 :equity "second equity" "rot13"]
[:sec-3 300 :equity "third equity" "rot13"]])
(def mock-currencies [[:zimbabwean-dollar 1000000 :currency "zimbabwean dollar" "rot13" "zd"]])
;;
(defn create-some-securities [conn]
(let [cs (controllers/create-security conn)
create-security-1 {:body (edn->stream (get mock-securities 0)) :headers {"accept" "application/edn"}}
create-security-2 {:body (edn->stream (get mock-securities 1)) :headers {"accept" "application/edn"}}
create-security-3 {:body (edn->stream (get mock-securities 2)) :headers {"accept" "application/edn"}}
create-security-4 {:body (edn->stream (first mock-currencies)) :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 n]
(let [create-emit-req (fn [amount account-id sec-id] {:body (edn->stream [amount account-id]) :route-params {:sec-id sec-id}})
sec-1-reqs (for [i (range 1 (inc n))] (create-emit-req (* i 100) (str i "-sec-1") "sec-1"))
sec-2-reqs (for [i (range 1 (inc n))] (create-emit-req (* i 100) (str i "-sec-2") "sec-2"))
reqs (concat sec-1-reqs sec-2-reqs)]
(loop [reqs reqs]
(when (seq reqs)
(let [es (controllers/emit-securities conn)
req (first reqs)]
(->> req es (s/assert ::ok))
(recur (rest reqs)))))
conn))
(defn create-some-securities-and-accounts [conn n-accounts-by-currency]
(-> conn create-some-securities (create-some-accounts n-accounts-by-currency)))
(facts "about the securities"
(fact "We should be able to create a security"
(let [conn (create-conn)
security [:new-currency 100 :currency "currency for test" "rot13" "NC"]
request {:body (edn->stream security) :headers {"accept" "application/edn"}}
resp ((controllers/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 :currency "currency for test" "rot13" "NC"]
request {:body (edn->stream security) :headers {"accept" "application/edn"}}
resp-1 ((controllers/create-security conn) request)
resp-2 ((controllers/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 :currency "currency for test" "rot13" "NC"]
create-request {:body (edn->stream security) :headers {"accept" "application/edn"}}
_ ((controllers/create-security conn) create-request)
request {:route-params {:sec-id :new-currency} :headers {"accept" "application/edn"}}
resp ((controllers/get-security conn) request)
expt {:sec-id :new-currency
:interest-rate 100
:name nil
:description nil
:number-of-decimal nil
:symbol nil
:type nil
:volume 0}]
resp => (spec-check ::ok)
(body resp) => (contains expt)))
(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 ((controllers/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 ((controllers/get-available-securities conn) request)
new-currency [:new-currency 100 :currency "currency for test" "rot13" "NC"]
create-security-1 {:body (edn->stream new-currency) :headers {"accept" "application/edn"}}
_ ((controllers/create-security conn) create-security-1)
one-res ((controllers/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 "http://localhost:1604/api/1/ledger/securities/new-currency"}]}))
(fact "it should add default currencies when creating new db"
(let [mem-url "datomic:mem://skirmish-in-test"
_ (d/delete-database mem-url)
conn (create-connection mem-url true)
request {:route-params {} :headers {"accept" "application/edn"}}
resp ((controllers/get-available-securities conn) request)]
(->> resp body :securities (map :sec-id) sort) => [:apac :apax :apbm :apdr :apmh :dapm])))
(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"}}]
((controllers/emit-securities conn) req-1) => (spec-check ::status-400)
((controllers/emit-securities conn) req-2) => (spec-check ::status-400)
((controllers/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"}}]
((controllers/get-account conn) get-account-req) => (spec-check ::status-404)
((controllers/emit-securities conn) emit-sec-req) => (spec-check ::ok)
(body ((controllers/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"}}]
((controllers/get-account conn) get-account-req) => (spec-check ::status-404)
((controllers/emit-securities conn) emit-sec-req-1) => (spec-check ::ok)
((controllers/emit-securities conn) emit-sec-req-2) => (spec-check ::ok)
(body ((controllers/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) 2)
req {:route-params {} :headers {"accept" "application/edn"}}
expt #{{:account-public-key "1-sec-1" :url "http://localhost:1604/api/1/ledger/accounts/1-sec-1"}
{:account-public-key "1-sec-2" :url "http://localhost:1604/api/1/ledger/accounts/1-sec-2"}
{:account-public-key "2-sec-1" :url "http://localhost:1604/api/1/ledger/accounts/2-sec-1"}
{:account-public-key "2-sec-2" :url "http://localhost:1604/api/1/ledger/accounts/2-sec-2"}}]
((controllers/get-available-accounts conn) req) => (spec-check ::ok)
(-> ((controllers/get-available-accounts conn) req) body :accounts) => (just expt)))
(fact "we should be able to get account"
(let [conn (create-some-securities-and-accounts (create-conn) 10)
req {:route-params {:account-id "2-sec-1"} :headers {"accept" "application/edn"}}]
((controllers/get-account conn) req) => (spec-check ::ok)
(body ((controllers/get-account conn) req)) => {:account {:account-public-key "2-sec-1" :amount 200 :sec-id :sec-1}
:sec-url "/api/1/ledger/securities/sec-1"}))
(fact "the interest should be payed when getting the account"
(let [conn (create-some-securities-and-accounts (create-conn) 10)
req {:route-params {:account-id "2-sec-1"} :headers {"accept" "application/edn"}}]
((controllers/get-account conn) req) => (spec-check ::ok)
(body ((controllers/get-account conn) req)) => {:account {:account-public-key "2-sec-1" :amount 200 :sec-id :sec-1}
:sec-url "/api/1/ledger/securities/sec-1"}))
(fact "we should not get an account if it does not exists"
(let [conn (create-some-securities-and-accounts (create-conn) 10)
req {:route-params {:account-id "2-sec-10"} :headers {"accept" "application/edn"}}]
((controllers/get-account conn) req) => (spec-check ::status-404))))
(facts "about interest computation"
(fact "about very basic interest rates"
(let [amount 1000
interest-rate (* 10 100) ;; daily 10% interest rates
negative-interest-rate (- interest-rate)
start #inst"2020-09-05T00:00:00.000-00:00"
start+1d #inst"2020-09-06T00:00:00.000-00:00"
start+2d #inst"2020-09-07T00:00:00.000-00:00"]
(data-layer/compute-simple-face-value amount interest-rate start start) => (roughly amount)
(data-layer/compute-simple-face-value amount interest-rate start start+1d) => (roughly 1105.1670808)
(data-layer/compute-simple-face-value amount interest-rate start start+2d) => (roughly 1221.3942766)
(data-layer/compute-simple-face-value amount negative-interest-rate start start+2d) => (roughly 818.7250672)
(data-layer/compute-simple-face-value amount 0 start start+2d) => (roughly amount)))
(fact "split-interest-rates"
(let [d-1d #inst"2020-09-04T00:00:00.000-00:00"
d-½d #inst"2020-09-04T12:00:00.000-00:00"
d #inst"2020-09-05T00:00:00.000-00:00"
d+½d #inst"2020-09-05T12:00:00.000-00:00"
d+1d #inst"2020-09-06T00:00:00.000-00:00"
d+1½d #inst"2020-09-06T12:00:00.000-00:00"
d+2d #inst"2020-09-07T00:00:00.000-00:00"
d+2½d #inst"2020-09-07T12:00:00.000-00:00"
d+3d #inst"2020-09-08T00:00:00.000-00:00"
d+3½d #inst"2020-09-08T12:00:00.000-00:00" ;;
interest-rates [[10 d] [15 d+1d] [20 d+2d]]]
(data-layer/split-interest-rates interest-rates d+½d d+3d) => [[10 d+½d d+1d] [15 d+1d d+2d] [20 d+2d d+3d]]
(data-layer/split-interest-rates interest-rates d+3d d+3½d) => [[20 d+3d d+3½d]]
(data-layer/split-interest-rates interest-rates d-1d d-½d) => [[0 d-1d d-½d]]
(data-layer/split-interest-rates interest-rates d d+½d) => [[10 d d+½d]]
(data-layer/split-interest-rates interest-rates d+2½d d+2½d) => [[20 d+2½d d+2½d]]))
(fact "compute-face-value"
(let [d-1d #inst"2020-09-04T00:00:00.000-00:00"
d-½d #inst"2020-09-04T12:00:00.000-00:00"
d #inst"2020-09-05T00:00:00.000-00:00"
d+½d #inst"2020-09-05T12:00:00.000-00:00"
d+1d #inst"2020-09-06T00:00:00.000-00:00"
d+1½d #inst"2020-09-06T12:00:00.000-00:00"
d+2d #inst"2020-09-07T00:00:00.000-00:00"
d+2½d #inst"2020-09-07T12:00:00.000-00:00"
d+3d #inst"2020-09-08T00:00:00.000-00:00"
d+3½d #inst"2020-09-08T12:00:00.000-00:00"
amount 10000000
interest-rates [[10 d] [15 d+1d] [20 d+2d]]]
(data-layer/compute-face-value amount interest-rates d+½d d+½d) => amount
(data-layer/compute-face-value amount interest-rates d-1d d-½d) => amount
(data-layer/compute-face-value amount interest-rates d-1d d+3d) => 10045101))
(fact "we had bugs... we need to fix them"
(let [deposit-date #inst"2020-09-09T18:49:47.205-00:00"
value-date #inst"2020-09-09T18:52:56.760-00:00"
amount 105402100
interest-rates (list [1000 #inst "2020-09-09T18:47:34.152-00:00"])]
(data-layer/compute-face-value amount interest-rates deposit-date value-date) => 105424060)))
;; 4. transfer sec from one account to the other.
(defn get-account-eids [db & owner-public-keys]
(let [q (map #(vector :skirmish.ledger/owner-public-key %) owner-public-keys)
ret (d/pull-many db '[:db/id] q)]
(map :db/id ret)))
(facts "about transactions"
(fact "about transferring an amount from one wallet to another"
(let [conn (create-some-securities-and-accounts (create-conn) 10)
transaction {:from-account-id "1-sec-1"
:to-account-id "2-sec-1"
:amount 50}
db (d/db conn)
[eid1 eid2] (get-account-eids db "1-sec-1" "2-sec-1")
expt #{[:db/add eid1 :skirmish.ledger/amount 50]
[:db/add eid2 :skirmish.ledger/amount 250]}]
(data-layer/create-transactions db [transaction]) => (just expt)))
(fact "about transferring an amount from one wallet to another"
(let [conn (create-some-securities-and-accounts (create-conn) 10)
transaction {:from-account-id "1-sec-1"
:to-account-id "1-sec-1"
:amount 45}
db (d/db conn)
[eid1] (get-account-eids db "1-sec-1")
expt #{[:db/add eid1 :skirmish.ledger/amount 100]}]
(data-layer/create-transactions db [transaction]) => (just expt)))
(fact "about transferring amounts between three wallets"
(let [conn (create-some-securities-and-accounts (create-conn) 10)
transaction-1 {:from-account-id "1-sec-1"
:to-account-id "2-sec-1"
:amount 50}
transaction-2 {:from-account-id "2-sec-1"
:to-account-id "3-sec-1"
:amount 250}
db (d/db conn)
[eid1 eid2 eid3] (get-account-eids db "1-sec-1" "2-sec-1" "3-sec-1")
expt #{[:db/add eid1 :skirmish.ledger/amount 50]
[:db/add eid2 :skirmish.ledger/amount 0]
[:db/add eid3 :skirmish.ledger/amount 550]}]
(data-layer/create-transactions db [transaction-1 transaction-2]) => (just expt)))
(fact "about transaction between different assets types"
(let [conn (create-some-securities-and-accounts (create-conn) 10)
transaction {:from-account-id "1-sec-1"
:to-account-id "1-sec-2"
:amount 50}
db (d/db conn)]
(data-layer/create-transactions db [transaction]) => (throws #"different underlying securities")))
(fact "about transaction with negative results"
(let [conn (create-some-securities-and-accounts (create-conn) 10)
transaction {:from-account-id "1-sec-1"
:to-account-id "2-sec-1"
:amount 500}
db (d/db conn)]
(data-layer/create-transactions db [transaction]) => (throws #"negative amount in a wallet"))))
@@ -0,0 +1,25 @@
(ns skirmish.middleware.test-core
(:require [midje.sweet :refer :all]
[clojure.core.async :refer [>!! <!!] :as async]
[skirmish.middleware.core :refer :all]))
(defn all? [xf]
#(every? true? (sequence xf %)))
(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 :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])))
File renamed without changes.
+30
View File
@@ -0,0 +1,30 @@
(ns skirmish.routes.test-api
(:require [midje.sweet :refer :all]
[skirmish.routes.api :refer :all]))
(def my-ledger-routes {
:name :ledger
:path "/api/1/ledger"
:routes [{:name :home :path "/"}
{:name :accounts :path "/accounts"}
{:name :account :path "/accounts/:account-id"}]})
(def wui-routes {
:name :wui
:domain "http://localhost:1534"
:path "/api/1/wui"
:routes [{:name :home :path "/home"}]})
(def my-all-routes [my-ledger-routes wui-routes])
(facts "About routes"
(fact "it should be easy to get routes"
(get-compojure-path my-all-routes :ledger/accounts) => "/accounts")
(fact "it should be able to retrieve routes"
(get-path-for my-all-routes :ledger/accounts) => "/api/1/ledger/accounts"
(get-path-for my-all-routes :ledger/account {:account-id "one-two"}) => "/api/1/ledger/accounts/one-two"
(get-path-for my-all-routes :wui/home) => "http://localhost:1534/api/1/wui/home")
(fact "it should be able to retrieve the contect"
(get-context my-all-routes :ledger) => "/api/1/ledger"))
+6
View File
@@ -0,0 +1,6 @@
(ns skirmish.test-util
(:require [midje.sweet :refer :all]
[skirmish.util :refer :all]))
;; midje testing
@@ -0,0 +1,9 @@
(ns skirmish.wui.components.test-home-page
(:require [cljs.test :refer-macros [deftest is testing run-tests]]))
(deftest test-numbers
(is (= 1 2)))
-30
View File
@@ -1,30 +0,0 @@
(ns skirmish.cli.benchmark-test
(:require [skirmish.cli.benchmark :refer :all]
[midje.sweet :refer :all]
[clojure.core.async :as async]))
(facts "creating simple orders"
(fact "buy order"
(let [buy-order (create-requests-payload-for-security 666 (str :equity/ibm) 1)
expt {:benchmark/id 1 :price 2922 :quantity 2476 :security ":equity/ibm" :type :buy}]
buy-order => expt));;
(fact "sell order"
(let [buy-order (create-requests-payload-for-security 667 (str :equity/ibm) 2)
expt {:benchmark/id 2 :price 2740 :quantity 2516 :security ":equity/ibm" :type :sell}]
buy-order => expt)))
(facts "the utility functions"
(fact "no-more-then-n-simultaneously"
(let [simultatious (atom [])
input-chan (async/to-chan (range 10))
sleep-time #(+ (mod (+ (* 5142 %) 8765) 200) 50)
f (fn [i]
(swap! simultatious #(conj % {:id i :status :start :value 1}))
(Thread/sleep (sleep-time i))
(swap! simultatious #(conj % {:id i :status :end :value -1}))
i)
res (async/<!! (async/into [] (no-more-then-n-simultaneously f input-chan 3)))
simultatious-over-time (reductions #(+ %1 (:value %2)) 0 @simultatious)]
(apply max simultatious-over-time) => 3
(first simultatious-over-time) => 0
(last simultatious-over-time) => 0)))
-13
View File
@@ -1,13 +0,0 @@
(ns skirmish.middleware.test-core
(:require [midje.sweet :refer :all]
[skirmish.middleware.core :refer :all]))
(defn all? [xf]
#(every? true? (sequence xf %)))
(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? %))))