diff --git a/src/clj/skirmish/ledger/routes.clj b/src/clj/skirmish/ledger/controllers.clj similarity index 98% rename from src/clj/skirmish/ledger/routes.clj rename to src/clj/skirmish/ledger/controllers.clj index e6ecd2b..f8c32f7 100644 --- a/src/clj/skirmish/ledger/routes.clj +++ b/src/clj/skirmish/ledger/controllers.clj @@ -1,4 +1,4 @@ -(ns skirmish.ledger.routes +(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] @@ -178,6 +178,6 @@ (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))) ;; not done - ;(routes/POST :ledger/security [] (emit-securities conn))) ;; done + (routes/PUT :ledger/security [] (update-security conn)) ;; TODO + (routes/POST :ledger/security [] (emit-securities conn))) (not-found "not found"))) \ No newline at end of file diff --git a/src/clj/skirmish/ledger/core.clj b/src/clj/skirmish/ledger/core.clj index 967aa1a..ca77a2f 100644 --- a/src/clj/skirmish/ledger/core.clj +++ b/src/clj/skirmish/ledger/core.clj @@ -1,42 +1,11 @@ (ns skirmish.ledger.core (:require [org.httpkit.server :refer [run-server]] - [skirmish.routes.core :as routes] + [skirmish.ledger.controllers :as controllers] [compojure.handler :refer [api]] - [skirmish.util :refer [block-thread]] - [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 cors-middleware]] - [skirmish.ledger.data-layer :as data-layer] + [skirmish.rest.util :refer [cors-middleware]] [skirmish.ledger.accounts] - [datomic.api :as d] [taoensso.timbre :as timbre] - [compojure.core :as compojure] - [clojure.spec.alpha :as s] - [infix.macros :refer [infix]] - [ring.middleware.cors :refer [wrap-cors]])) - -;; the ledger is the central - -;; an :account-id is the public key of a -;; an :account-secret is an :account-id private key - -(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)))) + [clojure.spec.alpha :as s])) (s/def ::transaction (s/cat :type #{:transfer-immediately} :from-account-id string? @@ -45,12 +14,6 @@ (s/def ::transactions (s/coll-of ::transaction)) -(defn extract-transactions [raw-transactions] - (if (s/valid? ::transactions raw-transactions) - (s/conform ::transactions raw-transactions) - (throw+ {:error-type :4XX :status 400 :body {:error "Invalid transaction format."}}))) - - (s/def ::security (s/cat :sec-id keyword? :interest-rate nat-int? :type #{:equity :currency} @@ -58,172 +21,14 @@ :emitter string? :symbol (s/? string?))) -(defn get-new-security [raw-securities] - (if (s/valid? ::security raw-securities) - raw-securities - (throw+ {:error-type :4XX :status 400 :body {:error "Invalid security format."}}))) - - -(defn 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 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 get-security [conn] - "Returns the security." - (fn [{{sec-id-str :sec-id} :route-params :as request}] - (try+ - (let [sec-id (keyword sec-id-str)] - (build-response request (get-security-from-db conn sec-id))) - (catch [:error-type :4XX] e (build-response request e)) - (catch [:error-type :5XX] e (build-response request e))))) - - -(defn update-security [conn] - "Update a security with new features." - (fn [request] - (try+ - (catch [:error-type :4XX] e (build-response request e)) - (catch [:error-type :5XX] e (build-response request e))))) - - -(defn get-available-securities-body [securities] - (let [sec-id->url #(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))))) - - (s/def ::emit-securities (s/cat :sec-id keyword? :amount nat-int? :account string?)) - -(defn get-emit-securities-request [[amount account] sec-id] - (if (s/valid? ::emit-securities [sec-id amount account]) - [:skirmish.ledger/emit-securities sec-id amount account] - (throw+ {:error-type :4XX :status 400 :body {:error "Invalid emit request."}}))) - - -(defn emit-securities [conn] - "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 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)] - (build-response request (get-available-accounts-body (data-layer/get-all-account-public-key db)))))) - - -(defn get-api-description [request] - (let [body [{:url "ledger/accounts" :name "accounts"} - {:url "ledger/securities" :name "securities"}]] - (build-response request body))) - -(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+ - (let [transactions (-> body get-body extract-transactions) - _ @(d/transact conn [[:skirmish.ledger/create-transactions transactions]])]) - - (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)) ;; done - (routes/POST :ledger/securities [] (create-security conn)) ;; done - (routes/GET :ledger/security [] (get-security conn)) ;; done - (routes/PUT :ledger/security [] (update-security conn)) ;; not done - (routes/POST :ledger/security [] (emit-securities conn))) ;; done - (not-found "not found"))) - - (defn -main [config securities-api exchange-apis] (let [ledger-config (:ledger-server config) conn (skirmish.ledger.accounts/create-connection (-> config :datomic :transactor-url)) - api-routes (create-routes-for-ledger conn)] + 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) diff --git a/test/clj/skirmish/ledger/test_core.clj b/test/clj/skirmish/ledger/test_core.clj index 255c7d9..d91d276 100644 --- a/test/clj/skirmish/ledger/test_core.clj +++ b/test/clj/skirmish/ledger/test_core.clj @@ -1,6 +1,7 @@ (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] @@ -45,7 +46,7 @@ ;; (defn create-some-securities [conn] - (let [cs (create-security 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"}} @@ -58,7 +59,7 @@ (defn create-some-accounts [conn] - (let [es (emit-securities conn) + (let [es (controllers/emit-securities conn) emit-sec-req-1 {:body (edn->stream [100 "1-sec-1"]) :route-params {:sec-id "sec-1"}} emit-sec-req-2 {:body (edn->stream [200 "2-sec-1"]) :route-params {:sec-id "sec-1"}} emit-sec-req-3 {:body (edn->stream [1000 "1-sec-2"]) :route-params {:sec-id "sec-2"}} @@ -78,7 +79,7 @@ (let [conn (create-conn) security [:new-currency 100 :currency "currency for test" "rot13" "NC"] request {:body (edn->stream security) :headers {"accept" "application/edn"}} - resp ((create-security conn) request)] + resp ((controllers/create-security conn) request)] resp => (spec-check ::ok) resp => (location? "/api/1/ledger/securities/new-currency"))) @@ -86,8 +87,8 @@ (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 ((create-security conn) request) - resp-2 ((create-security conn) request)] + 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))) @@ -98,9 +99,9 @@ (let [conn (create-conn) security [:new-currency 100 :currency "currency for test" "rot13" "NC"] create-request {:body (edn->stream security) :headers {"accept" "application/edn"}} - _ ((create-security conn) create-request) + _ ((controllers/create-security conn) create-request) request {:route-params {:sec-id :new-currency} :headers {"accept" "application/edn"}} - resp ((get-security conn) request) + resp ((controllers/get-security conn) request) expt {:sec-id :new-currency :interest-rate 100 :name nil @@ -115,18 +116,18 @@ (fact "we should not be able to get-security if it does not exists" (let [conn (create-conn) request {:route-params {:sec-id :new-currency} :headers {"accept" "application/edn"}} - resp ((get-security conn) request)] + resp ((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 ((get-available-securities conn) request) + 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"}} - _ ((create-security conn) create-security-1) - one-res ((get-available-securities conn) request)] + _ ((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 []} @@ -137,7 +138,7 @@ _ (d/delete-database mem-url) conn (create-connection mem-url true) request {:route-params {} :headers {"accept" "application/edn"}} - resp ((get-available-securities conn) request)] + resp ((controllers/get-available-securities conn) request)] (->> resp body :securities (map :sec-id) sort) => [:apac :apax :apbm :apdr :apmh :dapm]))) @@ -147,27 +148,27 @@ req-1 {:body (edn->stream [10 :not-a-string]) :route-params {:sec-id "sec-1"} :headers {"accept" "application/edn"}} req-2 {:body (edn->stream [-10 "foo"]) :route-params {:sec-id "sec-1"} :headers {"accept" "application/edn"}} req-3 {:body (edn->stream [90 "account-id"]) :route-params {:sec-id "invalid-sec-id"} :headers {"accept" "application/edn"}}] - ((emit-securities conn) req-1) => (spec-check ::status-400) - ((emit-securities conn) req-2) => (spec-check ::status-400) - ((emit-securities conn) req-3) => (spec-check ::status-404))) + ((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"}}] - ((get-account conn) get-account-req) => (spec-check ::status-404) - ((emit-securities conn) emit-sec-req) => (spec-check ::ok) - (body ((get-account conn) get-account-req)) => #(-> % :account :amount (= 90)))) + ((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"}}] - ((get-account conn) get-account-req) => (spec-check ::status-404) - ((emit-securities conn) emit-sec-req-1) => (spec-check ::ok) - ((emit-securities conn) emit-sec-req-2) => (spec-check ::ok) - (body ((get-account conn) get-account-req)) => #(-> % :account :amount (= 190))))) + ((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" @@ -178,28 +179,28 @@ {: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"}]] - ((get-available-accounts conn) req) => (spec-check ::ok) - (-> ((get-available-accounts conn) req) body :accounts sec->hash-set) => (sec->hash-set expt))) + ((controllers/get-available-accounts conn) req) => (spec-check ::ok) + (-> ((controllers/get-available-accounts conn) req) body :accounts sec->hash-set) => (sec->hash-set expt))) (fact "we should be able to get account" (let [conn (create-some-securities-and-accounts (create-conn)) req {:route-params {:account-id "2-sec-1"} :headers {"accept" "application/edn"}}] - ((get-account conn) req) => (spec-check ::ok) - (body ((get-account conn) req)) => {:account {:account-public-key "2-sec-1" :amount 200 :sec-id :sec-1} - :sec-url "/api/1/ledger/securities/sec-1"})) + ((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)) 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/ledger/securities/sec-1"})) + ((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)) req {:route-params {:account-id "2-sec-10"} :headers {"accept" "application/edn"}}] - ((get-account conn) req) => (spec-check ::status-404)))) + ((controllers/get-account conn) req) => (spec-check ::status-404)))) (facts "about interest computation"