diff --git a/src/clj/skirmish/broker/broker.clj b/src/clj/skirmish/broker/broker.clj index a5b2f87..2a9520e 100644 --- a/src/clj/skirmish/broker/broker.clj +++ b/src/clj/skirmish/broker/broker.clj @@ -41,9 +41,28 @@ (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 + (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] (routes (context "/api/1" [] (POST "/users" [] (create-user conn)) - (GET "/users/:email" [email] (get-user conn))) + (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)) diff --git a/src/clj/skirmish/ledger/accounts.clj b/src/clj/skirmish/ledger/accounts.clj index 7ea7fe8..ff9b0b1 100644 --- a/src/clj/skirmish/ledger/accounts.clj +++ b/src/clj/skirmish/ledger/accounts.clj @@ -47,30 +47,36 @@ :db/valueType :db.type/keyword :db/cardinality :db.cardinality/one :db/unique :db.unique/identity - :db/doc "The sec id."} + :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/unique :db.unique/identity :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/unique :db.unique/identity :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/unique :db.unique/identity :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/unique :db.unique/identity :db/doc "The symbol of the security for pretty printing."} { :db/ident :skirmish.security/interest-rate @@ -108,7 +114,7 @@ (defn create-connection [uri] (let [new-db? (d/create-database uri) conn (d/connect uri)] - (when new-db? - @(d/transact conn schema)) + ;(when new-db? + @(d/transact conn schema) conn)) diff --git a/src/clj/skirmish/ledger/core.clj b/src/clj/skirmish/ledger/core.clj index 87dea0a..d5c8e12 100644 --- a/src/clj/skirmish/ledger/core.clj +++ b/src/clj/skirmish/ledger/core.clj @@ -5,7 +5,7 @@ [slingshot.slingshot :refer [try+ throw+]] [compojure.route :refer [not-found]] [skirmish.exchange-gateway.exchange-gateway :refer [create-routes-for-exchange]] - [skirmish.rest.util :refer [build-response get-body]] + [skirmish.rest.util :refer [build-response get-body cors-middleware]] [skirmish.ledger.accounts] [datomic.api :as d] [taoensso.timbre :as timbre] @@ -21,7 +21,7 @@ (defn get-account-body [request account sec] (if (nil? sec) (build-response request {:status 404}) - (let [sec-url (str "/api/1/securities/" (-> sec :skirmish.security/sec-id name)) + (let [sec-url (str "/api/1/ledger/securities/" (-> sec :skirmish.security/sec-id name)) beautify-account { :amount (:skirmish.ledger/amount account) :sec-id (:skirmish.security/sec-id sec) :account-public-key (:skirmish.ledger/owner-public-key account)} @@ -63,21 +63,38 @@ raw-securities (throw+ {:error-type :4XX :status 400 :body {:error "Invalid security format."}}))) -(defn get-security-from-db [conn sec-id] - (let [db (d/db conn) - e (d/pull db '[*] [:skirmish.security/sec-id sec-id]) - {interest-rate :skirmish.security/interest-rate + +(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 - volume (d/q '[:find (sum ?a) . - :where [?e :skirmish.ledger/amount ?a] - [?e :skirmish.ledger/owner-public-key ?pk]] - db) - ret {:sec-id sec-id :interest-rate interest-rate :volume volume :type type_ :description description}] - (if (nil? id) + volume (d/q '[:find (sum ?amount) . + :in $ ?id + :where [?e :skirmish.ledger/sec ?id] + [?e :skirmish.ledger/amount ?amount]] + + db id)] + {:sec-id sec-id + :interest-rate interest-rate + :volume (or volume 0) + :name name + :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])] + (println (str e)) + (if (-> e :db/id nil?) (throw+ {:error-type :4XX :status 404 :body {:error "Security not found."}}) - ret))) + (prettify-security e db)))) (defn create-security [conn] "Create a fresh new security." @@ -156,11 +173,13 @@ (let [securities (d/q '[:find ?pk :where [?e :skirmish.ledger/owner-public-key ?pk]] (d/db conn))] (build-response request (get-available-accounts-body securities))))) + (defn get-api-description [request] (let [body [{:url "ledger/accounts" :name "accounts"} {:url "ledger/securities" :name "securities"}]] (build-response request body))) + (defn create-routes-for-ledger [securities conn] (routes (context "/api/1/ledger" [] @@ -181,4 +200,7 @@ api-routes (create-routes-for-ledger (:securities config) conn)] (timbre/info "Running skirmish.ledger.core server with config: " ledger-config) - (run-server (api api-routes) ledger-config))) + (run-server + (-> api-routes api cors-middleware) + ledger-config))) + diff --git a/src/clj/skirmish/routes.clj b/src/clj/skirmish/routes.clj new file mode 100644 index 0000000..057f4c9 --- /dev/null +++ b/src/clj/skirmish/routes.clj @@ -0,0 +1,47 @@ +(ns skirmish.routes) + +(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))] + (path-for p params)))) + diff --git a/src/cljc/skirmish/wui_util.cljc b/src/cljc/skirmish/wui_util.cljc new file mode 100644 index 0000000..ff75345 --- /dev/null +++ b/src/cljc/skirmish/wui_util.cljc @@ -0,0 +1,3 @@ +(ns skirmish.wui-util) + +(defn pp-number [n]) \ No newline at end of file diff --git a/src/cljs/skirmish/wui/components/face_value.cljs b/src/cljs/skirmish/wui/components/face_value.cljs new file mode 100644 index 0000000..1fd656d --- /dev/null +++ b/src/cljs/skirmish/wui/components/face_value.cljs @@ -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))) diff --git a/src/cljs/skirmish/wui/components/portfolio_page.cljs b/src/cljs/skirmish/wui/components/portfolio_page.cljs index 7186c6c..d4ad55d 100644 --- a/src/cljs/skirmish/wui/components/portfolio_page.cljs +++ b/src/cljs/skirmish/wui/components/portfolio_page.cljs @@ -1,24 +1,35 @@ (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 [ 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 ( sec-info :volume (pp-int (:number-of-decimal 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)]]) diff --git a/src/cljs/skirmish/wui/components/todo_page.cljs b/src/cljs/skirmish/wui/components/todo_page.cljs index 8fad8d7..04e28d2 100644 --- a/src/cljs/skirmish/wui/components/todo_page.cljs +++ b/src/cljs/skirmish/wui/components/todo_page.cljs @@ -19,9 +19,16 @@ [:li done "add the notion of 'accounts' in the server. An user should have many accounts; one for each security he owns"] - [:li on-it "make the " [:a {:href "/portfolio"} "portfolio"] " page representing the use accounts"] + [: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 on-it "add stocks to didier@gmail.com"] + [:li on-it "make something for urls on the backend"] + [:li "add a little bit of professionalism on top of the broker api"] + [:li "make the api support transit and edn"] + [:li "implements interest rate in ledger."] [:li "Make sure the ledger can have accounts"] - [:li "when a new user logs in, he should receive some sort of money"] + [:li "when a new user signs in, he should receive some sort of money"] [:li "\u22EE"] [:li "implement buy/sell securities."]]] @@ -34,7 +41,6 @@ [:ol [:li "Use ledger as source of through in each sub server."] [:li "Update web ui for integrating securities with ledger."] - [:li "Create a basic broker that supports..."] [:li "Ledger should compute interest rate."] [:li "implement transactions in ledger."] [:li "implement escrow."]]]]]) diff --git a/src/cljs/skirmish/wui/util.cljs b/src/cljs/skirmish/wui/util.cljs new file mode 100644 index 0000000..8f775a9 --- /dev/null +++ b/src/cljs/skirmish/wui/util.cljs @@ -0,0 +1,29 @@ +(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)))))) \ No newline at end of file diff --git a/test/clj/skirmish/broker/broker_test.clj b/test/clj/skirmish/broker/broker_test.clj index 24ff752..0bf4d58 100644 --- a/test/clj/skirmish/broker/broker_test.clj +++ b/test/clj/skirmish/broker/broker_test.clj @@ -12,3 +12,4 @@ 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))) + diff --git a/test/clj/skirmish/ledger/test_core.clj b/test/clj/skirmish/ledger/test_core.clj index c104d59..b6737bc 100644 --- a/test/clj/skirmish/ledger/test_core.clj +++ b/test/clj/skirmish/ledger/test_core.clj @@ -99,7 +99,14 @@ request {:route-params {:sec-id :new-currency} :headers {"accept" "application/edn"}} resp ((get-security conn) request)] resp => (spec-check ::ok) - (body resp) => {:sec-id :new-currency :interest-rate 100 :description nil :type nil :volume nil})) + (body resp) => {:sec-id :new-currency + :interest-rate 100 + :name nil + :description nil + :number-of-decimal nil + :symbol nil + :type nil + :volume 0})) (fact "we should not be able to get-security if it does not exists" (let [conn (create-conn) @@ -167,14 +174,14 @@ req {:route-params {:account-id "2-sec-1"} :headers {"accept" "application/edn"}}] ((get-account conn) req) => (spec-check ::ok) (body ((get-account conn) req)) => {:account {:account-public-key "2-sec-1" :amount 200 :sec-id :sec-1} - :sec-url "/api/1/securities/sec-1"})) + :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)) req {:route-params {:account-id "2-sec-1"} :headers {"accept" "application/edn"}}] ((get-account conn) req) => (spec-check ::ok) (body ((get-account conn) req)) => {:account {:account-public-key "2-sec-1" :amount 200 :sec-id :sec-1} - :sec-url "/api/1/securities/sec-1"})) + :sec-url "/api/1/ledger/securities/sec-1"})) (fact "we should not get an account if it does not exists" @@ -185,6 +192,7 @@ ;; TODO + ;; 4. transfer sec from one account to the other. ;; 5. account for interest diff --git a/test/clj/skirmish/test_routes.clj b/test/clj/skirmish/test_routes.clj new file mode 100644 index 0000000..914c053 --- /dev/null +++ b/test/clj/skirmish/test_routes.clj @@ -0,0 +1,28 @@ +(ns skirmish.test-routes + (:require [midje.sweet :refer :all] + [skirmish.routes :refer :all])) + +(def my-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"}]}) + +(def my-all-routes + [my-ledger-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") + + (fact "it should be able to retrieve the contect" + (get-context my-all-routes :ledger) => "/api/1/ledger")) +;; \ No newline at end of file diff --git a/test/cljs/skirmish/wui/components/test_home_page.cljs b/test/cljs/skirmish/wui/components/test_home_page.cljs index e68293f..c84fc97 100644 --- a/test/cljs/skirmish/wui/components/test_home_page.cljs +++ b/test/cljs/skirmish/wui/components/test_home_page.cljs @@ -5,5 +5,5 @@ (deftest test-numbers - (is (= 1 1))) + (is (= 1 2)))