Move HTML rendering to btcprice, send raw JSON over both WebSockets
Price and Strike WebSockets now emit JSON instead of HTML fragments. Added Strike WebSocket endpoint and wired strike/ticker into API routes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,9 +12,6 @@
|
||||
[reitit.swagger :as swagger])
|
||||
(:import
|
||||
[io.undertow.websockets.core WebSockets WebSocketChannel]
|
||||
[java.time Instant ZoneId]
|
||||
[java.time.format DateTimeFormatter]
|
||||
[java.util Locale]
|
||||
[java.util.concurrent LinkedBlockingQueue TimeUnit]))
|
||||
|
||||
(defn- price->json [{:keys [price prev-price recorded-at]} rates]
|
||||
@@ -25,41 +22,6 @@
|
||||
rates (assoc :price_eur (str (.multiply (bigdec price) (:eur rates)))
|
||||
:price_dkk (str (.multiply (bigdec price) (:dkk rates)))))))
|
||||
|
||||
(def ^:private ts-fmt (DateTimeFormatter/ofPattern "dd-MM HH:mm:ss"))
|
||||
(def ^:private da-locale (Locale. "da" "DK"))
|
||||
|
||||
(defn- da-fmt [fmt val]
|
||||
(String/format da-locale fmt (into-array Object [val])))
|
||||
|
||||
(defn- price->html [{:keys [price prev-price recorded-at]} rates]
|
||||
(let [p (double (bigdec price))
|
||||
pp (when prev-price (double (bigdec prev-price)))
|
||||
color (cond
|
||||
(nil? pp) "text-gray-900"
|
||||
(> p pp) "text-green-600"
|
||||
(< p pp) "text-red-600"
|
||||
:else "text-gray-900")
|
||||
ts (.format (.atZone (Instant/from recorded-at) (ZoneId/systemDefault)) ts-fmt)]
|
||||
(str "<div id=\"price-display\">"
|
||||
"<div class=\"bg-white rounded-2xl shadow-sm border border-gray-200 p-6\">"
|
||||
"<div class=\"text-center\">"
|
||||
"<p class=\"text-4xl font-bold " color "\">$" (da-fmt "%,.2f" p) "</p>"
|
||||
(when rates
|
||||
(let [eur (double (:eur rates))
|
||||
dkk (double (:dkk rates))
|
||||
p-eur (double (.multiply (bigdec price) (:eur rates)))
|
||||
p-dkk (double (.multiply (bigdec price) (:dkk rates)))]
|
||||
(str "<div class=\"flex justify-center gap-4 mt-3\">"
|
||||
"<span class=\"text-2xl font-bold text-gray-600\">\u20AC" (da-fmt "%,.0f" p-eur) "</span>"
|
||||
"<span class=\"text-2xl font-bold text-gray-600\">" (da-fmt "%,.0f" p-dkk) " kr</span>"
|
||||
"</div>"
|
||||
"<div class=\"flex justify-center gap-4 mt-1\">"
|
||||
"<span class=\"text-xs text-gray-400\">EUR/USD " (da-fmt "%.4f" eur) "</span>"
|
||||
"<span class=\"text-xs text-gray-400\">DKK/USD " (da-fmt "%.4f" dkk) "</span>"
|
||||
"</div>")))
|
||||
"<p class=\"text-xs text-gray-400 mt-2\">Updated " ts "</p>"
|
||||
"</div></div></div>")))
|
||||
|
||||
(defn- start-ws-send-loop! [^WebSocketChannel channel price-atom rates-atom]
|
||||
(let [queue (LinkedBlockingQueue.)
|
||||
wkey (keyword (gensym "ws-"))
|
||||
@@ -76,7 +38,7 @@
|
||||
(when (.isOpen channel)
|
||||
(if (.poll queue 15 TimeUnit/SECONDS)
|
||||
(when-let [v @price-atom]
|
||||
(WebSockets/sendTextBlocking (price->html v @rates-atom) channel))
|
||||
(WebSockets/sendTextBlocking (price->json v @rates-atom) channel))
|
||||
(WebSockets/sendTextBlocking "" channel))
|
||||
(recur)))
|
||||
(catch Exception _)
|
||||
@@ -93,6 +55,34 @@
|
||||
{:on-open (fn [{:keys [^WebSocketChannel channel]}]
|
||||
(start-ws-send-loop! channel price-atom rates-atom))}}))
|
||||
|
||||
(defn- start-strike-ws-loop! [^WebSocketChannel channel strike-atom]
|
||||
(let [queue (LinkedBlockingQueue.)
|
||||
wkey (keyword (gensym "ws-s-"))]
|
||||
(add-watch strike-atom wkey
|
||||
(fn [_ _ _ _] (.offer queue :update)))
|
||||
(when @strike-atom
|
||||
(.offer queue :update))
|
||||
(future
|
||||
(try
|
||||
(loop []
|
||||
(when (.isOpen channel)
|
||||
(if (.poll queue 15 TimeUnit/SECONDS)
|
||||
(when-let [rates @strike-atom]
|
||||
(WebSockets/sendTextBlocking (json/write-str rates) channel))
|
||||
(WebSockets/sendTextBlocking "" channel))
|
||||
(recur)))
|
||||
(catch Exception _)
|
||||
(finally
|
||||
(remove-watch strike-atom wkey)
|
||||
(when (.isOpen channel)
|
||||
(try (.close channel) (catch Exception _))))))))
|
||||
|
||||
(defn- ws-strike-handler [{:keys [strike]} _req]
|
||||
(let [strike-atom (:latest-rates strike)]
|
||||
{:undertow/websocket
|
||||
{:on-open (fn [{:keys [^WebSocketChannel channel]}]
|
||||
(start-strike-ws-loop! channel strike-atom))}}))
|
||||
|
||||
(defn- latest-price-handler [{:keys [binance frankfurter]} _req]
|
||||
(let [price-atom (:latest-price binance)
|
||||
rates-atom (:rates frankfurter)
|
||||
@@ -117,7 +107,11 @@
|
||||
:no-doc true
|
||||
:middleware []}]
|
||||
["/price/latest"
|
||||
{:get (fn [req] (latest-price-handler opts req))}]])
|
||||
{:get (fn [req] (latest-price-handler opts req))}]
|
||||
["/strike/ws"
|
||||
{:get (fn [req] (ws-strike-handler opts req))
|
||||
:no-doc true
|
||||
:middleware []}]])
|
||||
|
||||
(defn route-data [opts]
|
||||
(merge
|
||||
|
||||
Reference in New Issue
Block a user