Format codebase with cljstyle
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,8 +2,10 @@
|
|||||||
(:require
|
(:require
|
||||||
[kit.config :as config]))
|
[kit.config :as config]))
|
||||||
|
|
||||||
|
|
||||||
(def ^:const system-filename "system.edn")
|
(def ^:const system-filename "system.edn")
|
||||||
|
|
||||||
|
|
||||||
(defn system-config
|
(defn system-config
|
||||||
[options]
|
[options]
|
||||||
(config/read-config system-filename options))
|
(config/read-config system-filename options))
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
(ns pmagnus.elprice.core
|
(ns pmagnus.elprice.core
|
||||||
|
(:gen-class)
|
||||||
(:require
|
(:require
|
||||||
[clojure.tools.logging :as log]
|
[clojure.tools.logging :as log]
|
||||||
[integrant.core :as ig]
|
[integrant.core :as ig]
|
||||||
[pmagnus.elprice.config :as config]
|
[kit.edge.db.postgres]
|
||||||
[pmagnus.elprice.env :refer [defaults]]
|
|
||||||
|
|
||||||
;; Edges
|
;; Edges
|
||||||
[kit.edge.db.sql.conman]
|
[kit.edge.db.sql.conman]
|
||||||
[kit.edge.db.sql.migratus]
|
[kit.edge.db.sql.migratus]
|
||||||
[kit.edge.db.postgres]
|
|
||||||
[kit.edge.server.undertow]
|
[kit.edge.server.undertow]
|
||||||
|
[pmagnus.elprice.config :as config]
|
||||||
|
[pmagnus.elprice.env :refer [defaults]]
|
||||||
[pmagnus.elprice.web.handler]
|
[pmagnus.elprice.web.handler]
|
||||||
|
|
||||||
;; Routes
|
;; Routes
|
||||||
[pmagnus.elprice.web.routes.api]
|
[pmagnus.elprice.web.routes.api]
|
||||||
[pmagnus.elprice.web.routes.ui])
|
[pmagnus.elprice.web.routes.ui]))
|
||||||
(:gen-class))
|
|
||||||
|
|
||||||
;; log uncaught exceptions in threads
|
;; log uncaught exceptions in threads
|
||||||
(Thread/setDefaultUncaughtExceptionHandler
|
(Thread/setDefaultUncaughtExceptionHandler
|
||||||
@@ -24,19 +23,26 @@
|
|||||||
:exception ex
|
:exception ex
|
||||||
:where (str "Uncaught exception on" (.getName thread))})))
|
:where (str "Uncaught exception on" (.getName thread))})))
|
||||||
|
|
||||||
|
|
||||||
(defonce system (atom nil))
|
(defonce system (atom nil))
|
||||||
|
|
||||||
(defn stop-app []
|
|
||||||
|
(defn stop-app
|
||||||
|
[]
|
||||||
((or (:stop defaults) (fn [])))
|
((or (:stop defaults) (fn [])))
|
||||||
(some-> (deref system) (ig/halt!)))
|
(some-> (deref system) (ig/halt!)))
|
||||||
|
|
||||||
(defn start-app [& [params]]
|
|
||||||
|
(defn start-app
|
||||||
|
[& [params]]
|
||||||
((or (:start params) (:start defaults) (fn [])))
|
((or (:start params) (:start defaults) (fn [])))
|
||||||
(->> (config/system-config (or (:opts params) (:opts defaults) {}))
|
(->> (config/system-config (or (:opts params) (:opts defaults) {}))
|
||||||
(ig/expand)
|
(ig/expand)
|
||||||
(ig/init)
|
(ig/init)
|
||||||
(reset! system)))
|
(reset! system)))
|
||||||
|
|
||||||
(defn -main [& _]
|
|
||||||
|
(defn -main
|
||||||
|
[& _]
|
||||||
(start-app)
|
(start-app)
|
||||||
(.addShutdownHook (Runtime/getRuntime) (Thread. (fn [] (stop-app) (shutdown-agents)))))
|
(.addShutdownHook (Runtime/getRuntime) (Thread. (fn [] (stop-app) (shutdown-agents)))))
|
||||||
|
|||||||
@@ -4,21 +4,36 @@
|
|||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
[clojure.tools.logging :as log])
|
[clojure.tools.logging :as log])
|
||||||
(:import
|
(:import
|
||||||
[java.net URI]
|
(java.net
|
||||||
[java.net.http HttpClient HttpRequest HttpRequest$BodyPublishers HttpResponse$BodyHandlers]
|
URI)
|
||||||
[java.time LocalDate LocalDateTime ZoneId]
|
(java.net.http
|
||||||
[java.time.format DateTimeFormatter]
|
HttpClient
|
||||||
[java.sql Timestamp Date]))
|
HttpRequest
|
||||||
|
HttpRequest$BodyPublishers
|
||||||
|
HttpResponse$BodyHandlers)
|
||||||
|
(java.sql
|
||||||
|
Date
|
||||||
|
Timestamp)
|
||||||
|
(java.time
|
||||||
|
LocalDate
|
||||||
|
LocalDateTime
|
||||||
|
ZoneId)
|
||||||
|
(java.time.format
|
||||||
|
DateTimeFormatter)))
|
||||||
|
|
||||||
|
|
||||||
(def ^:private base-url "https://api.eloverblik.dk/customerapi")
|
(def ^:private base-url "https://api.eloverblik.dk/customerapi")
|
||||||
(def ^:private dk-zone (ZoneId/of "Europe/Copenhagen"))
|
(def ^:private dk-zone (ZoneId/of "Europe/Copenhagen"))
|
||||||
(def ^:private date-fmt (DateTimeFormatter/ofPattern "yyyy-MM-dd"))
|
(def ^:private date-fmt (DateTimeFormatter/ofPattern "yyyy-MM-dd"))
|
||||||
|
|
||||||
|
|
||||||
;; --- Auth ---
|
;; --- Auth ---
|
||||||
|
|
||||||
(defn- read-refresh-token []
|
(defn- read-refresh-token
|
||||||
|
[]
|
||||||
(str/trim (slurp ".token")))
|
(str/trim (slurp ".token")))
|
||||||
|
|
||||||
|
|
||||||
(defn- get-access-token
|
(defn- get-access-token
|
||||||
"Exchange refresh token for a short-lived access token."
|
"Exchange refresh token for a short-lived access token."
|
||||||
[refresh-token]
|
[refresh-token]
|
||||||
@@ -34,6 +49,7 @@
|
|||||||
(let [body (json/read-str (.body response) :key-fn keyword)]
|
(let [body (json/read-str (.body response) :key-fn keyword)]
|
||||||
(:result body)))))
|
(:result body)))))
|
||||||
|
|
||||||
|
|
||||||
;; --- HTTP helpers ---
|
;; --- HTTP helpers ---
|
||||||
|
|
||||||
(defn- api-get
|
(defn- api-get
|
||||||
@@ -51,6 +67,7 @@
|
|||||||
(when (= 200 (.statusCode response))
|
(when (= 200 (.statusCode response))
|
||||||
(json/read-str (.body response) :key-fn keyword))))
|
(json/read-str (.body response) :key-fn keyword))))
|
||||||
|
|
||||||
|
|
||||||
(defn- api-post
|
(defn- api-post
|
||||||
"POST with Bearer access token + JSON body, returns parsed JSON."
|
"POST with Bearer access token + JSON body, returns parsed JSON."
|
||||||
[access-token path body]
|
[access-token path body]
|
||||||
@@ -68,17 +85,23 @@
|
|||||||
(when (= 200 (.statusCode response))
|
(when (= 200 (.statusCode response))
|
||||||
(json/read-str (.body response) :key-fn keyword))))
|
(json/read-str (.body response) :key-fn keyword))))
|
||||||
|
|
||||||
|
|
||||||
;; --- Metering Points ---
|
;; --- Metering Points ---
|
||||||
|
|
||||||
(defn- fetch-metering-points [access-token]
|
(defn- fetch-metering-points
|
||||||
|
[access-token]
|
||||||
(let [data (api-get access-token "/api/meteringpoints/meteringpoints?includeAll=true")]
|
(let [data (api-get access-token "/api/meteringpoints/meteringpoints?includeAll=true")]
|
||||||
(:result data)))
|
(:result data)))
|
||||||
|
|
||||||
(defn- parse-date [s]
|
|
||||||
|
(defn- parse-date
|
||||||
|
[s]
|
||||||
(when (and s (not (str/blank? s)))
|
(when (and s (not (str/blank? s)))
|
||||||
(Date/valueOf (LocalDate/parse (subs s 0 10)))))
|
(Date/valueOf (LocalDate/parse (subs s 0 10)))))
|
||||||
|
|
||||||
(defn- upsert-mp! [query-fn mp]
|
|
||||||
|
(defn- upsert-mp!
|
||||||
|
[query-fn mp]
|
||||||
(query-fn :upsert-metering-point!
|
(query-fn :upsert-metering-point!
|
||||||
{:metering-point-id (:meteringPointId mp)
|
{:metering-point-id (:meteringPointId mp)
|
||||||
:type-of-mp (:typeOfMP mp)
|
:type-of-mp (:typeOfMP mp)
|
||||||
@@ -93,7 +116,9 @@
|
|||||||
:consumer-start-date (parse-date (:consumerStartDate mp))
|
:consumer-start-date (parse-date (:consumerStartDate mp))
|
||||||
:first-consumer-name (:firstConsumerPartyName mp)}))
|
:first-consumer-name (:firstConsumerPartyName mp)}))
|
||||||
|
|
||||||
(defn- save-metering-points! [query-fn metering-points]
|
|
||||||
|
(defn- save-metering-points!
|
||||||
|
[query-fn metering-points]
|
||||||
(let [all-mps (mapcat (fn [mp]
|
(let [all-mps (mapcat (fn [mp]
|
||||||
(cons mp (:childMeteringPoints mp)))
|
(cons mp (:childMeteringPoints mp)))
|
||||||
metering-points)]
|
metering-points)]
|
||||||
@@ -101,15 +126,18 @@
|
|||||||
(upsert-mp! query-fn mp))
|
(upsert-mp! query-fn mp))
|
||||||
(log/info "Saved" (count all-mps) "metering points")))
|
(log/info "Saved" (count all-mps) "metering points")))
|
||||||
|
|
||||||
|
|
||||||
;; --- Time Series ---
|
;; --- Time Series ---
|
||||||
|
|
||||||
(defn- fetch-time-series [access-token metering-point-ids from to]
|
(defn- fetch-time-series
|
||||||
|
[access-token metering-point-ids from to]
|
||||||
(api-post access-token
|
(api-post access-token
|
||||||
(str "/api/meterdata/gettimeseries/"
|
(str "/api/meterdata/gettimeseries/"
|
||||||
(.format from date-fmt) "/"
|
(.format from date-fmt) "/"
|
||||||
(.format to date-fmt) "/Hour")
|
(.format to date-fmt) "/Hour")
|
||||||
{:meteringPoints {:meteringPoint metering-point-ids}}))
|
{:meteringPoints {:meteringPoint metering-point-ids}}))
|
||||||
|
|
||||||
|
|
||||||
(defn- parse-time-series
|
(defn- parse-time-series
|
||||||
"Parse the nested time series response into flat reading maps."
|
"Parse the nested time series response into flat reading maps."
|
||||||
[response metering-point-ids]
|
[response metering-point-ids]
|
||||||
@@ -145,9 +173,11 @@
|
|||||||
results
|
results
|
||||||
metering-point-ids)))
|
metering-point-ids)))
|
||||||
|
|
||||||
|
|
||||||
(def ^:private production-mp-id "571313113163368491")
|
(def ^:private production-mp-id "571313113163368491")
|
||||||
(def ^:private consumption-mp-id "571313113163368507")
|
(def ^:private consumption-mp-id "571313113163368507")
|
||||||
|
|
||||||
|
|
||||||
(defn- utc-start-for-date
|
(defn- utc-start-for-date
|
||||||
"Compute the UTC start of a Danish date (midnight DK -> UTC)."
|
"Compute the UTC start of a Danish date (midnight DK -> UTC)."
|
||||||
[^LocalDate date]
|
[^LocalDate date]
|
||||||
@@ -156,7 +186,9 @@
|
|||||||
(.toInstant)
|
(.toInstant)
|
||||||
(Timestamp/from)))
|
(Timestamp/from)))
|
||||||
|
|
||||||
(defn- save-time-series! [query-fn readings from]
|
|
||||||
|
(defn- save-time-series!
|
||||||
|
[query-fn readings from]
|
||||||
(doseq [r readings]
|
(doseq [r readings]
|
||||||
(query-fn :insert-meter-reading! r))
|
(query-fn :insert-meter-reading! r))
|
||||||
(let [time-start (utc-start-for-date from)]
|
(let [time-start (utc-start-for-date from)]
|
||||||
@@ -170,14 +202,18 @@
|
|||||||
{:time-start time-start :hour hour :kwh (:quantity-kwh r)}))))
|
{:time-start time-start :hour hour :kwh (:quantity-kwh r)}))))
|
||||||
(log/info "Saved" (count readings) "meter readings + production/consumption"))
|
(log/info "Saved" (count readings) "meter readings + production/consumption"))
|
||||||
|
|
||||||
|
|
||||||
;; --- Charges ---
|
;; --- Charges ---
|
||||||
|
|
||||||
(defn- fetch-charges [access-token metering-point-ids]
|
(defn- fetch-charges
|
||||||
|
[access-token metering-point-ids]
|
||||||
(api-post access-token
|
(api-post access-token
|
||||||
"/api/meteringpoints/meteringpoint/getcharges"
|
"/api/meteringpoints/meteringpoint/getcharges"
|
||||||
{:meteringPoints {:meteringPoint metering-point-ids}}))
|
{:meteringPoints {:meteringPoint metering-point-ids}}))
|
||||||
|
|
||||||
(defn- save-charges! [query-fn charges-response]
|
|
||||||
|
(defn- save-charges!
|
||||||
|
[query-fn charges-response]
|
||||||
(let [results (:result charges-response)]
|
(let [results (:result charges-response)]
|
||||||
(doseq [result results]
|
(doseq [result results]
|
||||||
(let [mp-id (:id result)
|
(let [mp-id (:id result)
|
||||||
@@ -240,6 +276,7 @@
|
|||||||
:quantity (:quantity fee)
|
:quantity (:quantity fee)
|
||||||
:position 0}))))))
|
:position 0}))))))
|
||||||
|
|
||||||
|
|
||||||
;; --- Orchestrator ---
|
;; --- Orchestrator ---
|
||||||
|
|
||||||
(defn fetch-and-save-all!
|
(defn fetch-and-save-all!
|
||||||
@@ -286,4 +323,3 @@
|
|||||||
{:metering-points (count all-ids)
|
{:metering-points (count all-ids)
|
||||||
:from (.format from date-fmt)
|
:from (.format from date-fmt)
|
||||||
:to (.format to date-fmt)})))))))
|
:to (.format to date-fmt)})))))))
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
(:require
|
(:require
|
||||||
[ring.util.http-response :as http-response])
|
[ring.util.http-response :as http-response])
|
||||||
(:import
|
(:import
|
||||||
[java.util Date]))
|
(java.util
|
||||||
|
Date)))
|
||||||
|
|
||||||
|
|
||||||
(defn healthcheck!
|
(defn healthcheck!
|
||||||
[req]
|
[req]
|
||||||
|
|||||||
@@ -3,16 +3,27 @@
|
|||||||
[clojure.data.json :as json]
|
[clojure.data.json :as json]
|
||||||
[clojure.tools.logging :as log])
|
[clojure.tools.logging :as log])
|
||||||
(:import
|
(:import
|
||||||
[java.net URI URLEncoder]
|
(java.net
|
||||||
[java.net.http HttpClient HttpRequest HttpResponse$BodyHandlers]
|
URI
|
||||||
[java.time LocalDate ZoneId]
|
URLEncoder)
|
||||||
[java.time.format DateTimeFormatter]
|
(java.net.http
|
||||||
[java.sql Timestamp]))
|
HttpClient
|
||||||
|
HttpRequest
|
||||||
|
HttpResponse$BodyHandlers)
|
||||||
|
(java.sql
|
||||||
|
Timestamp)
|
||||||
|
(java.time
|
||||||
|
LocalDate
|
||||||
|
ZoneId)
|
||||||
|
(java.time.format
|
||||||
|
DateTimeFormatter)))
|
||||||
|
|
||||||
|
|
||||||
(def ^:private dk-zone (ZoneId/of "Europe/Copenhagen"))
|
(def ^:private dk-zone (ZoneId/of "Europe/Copenhagen"))
|
||||||
|
|
||||||
(def ^:private date-fmt (DateTimeFormatter/ofPattern "yyyy-MM-dd"))
|
(def ^:private date-fmt (DateTimeFormatter/ofPattern "yyyy-MM-dd"))
|
||||||
|
|
||||||
|
|
||||||
(defn- build-url
|
(defn- build-url
|
||||||
"Build the Energi Data Service API URL for day-ahead prices."
|
"Build the Energi Data Service API URL for day-ahead prices."
|
||||||
[start-date end-date price-area]
|
[start-date end-date price-area]
|
||||||
@@ -23,6 +34,7 @@
|
|||||||
"&sort=TimeDK%20asc"
|
"&sort=TimeDK%20asc"
|
||||||
"&limit=200"))
|
"&limit=200"))
|
||||||
|
|
||||||
|
|
||||||
(defn- fetch-json
|
(defn- fetch-json
|
||||||
"Fetch JSON from a URL using JDK HttpClient."
|
"Fetch JSON from a URL using JDK HttpClient."
|
||||||
[url]
|
[url]
|
||||||
@@ -36,6 +48,7 @@
|
|||||||
(when (= 200 (.statusCode response))
|
(when (= 200 (.statusCode response))
|
||||||
(json/read-str (.body response) :key-fn keyword))))
|
(json/read-str (.body response) :key-fn keyword))))
|
||||||
|
|
||||||
|
|
||||||
(defn- fetch-area
|
(defn- fetch-area
|
||||||
"Fetch today's and tomorrow's prices for a single price area."
|
"Fetch today's and tomorrow's prices for a single price area."
|
||||||
[start end price-area]
|
[start end price-area]
|
||||||
@@ -44,6 +57,7 @@
|
|||||||
(when-let [data (fetch-json url)]
|
(when-let [data (fetch-json url)]
|
||||||
(:records data))))
|
(:records data))))
|
||||||
|
|
||||||
|
|
||||||
(defn fetch-prices-from-api
|
(defn fetch-prices-from-api
|
||||||
"Fetch today's and tomorrow's prices from Energi Data Service for DK1 and DK2."
|
"Fetch today's and tomorrow's prices from Energi Data Service for DK1 and DK2."
|
||||||
[]
|
[]
|
||||||
@@ -53,6 +67,7 @@
|
|||||||
(concat (fetch-area start end "DK1")
|
(concat (fetch-area start end "DK1")
|
||||||
(fetch-area start end "DK2"))))
|
(fetch-area start end "DK2"))))
|
||||||
|
|
||||||
|
|
||||||
(defn- parse-timestamp
|
(defn- parse-timestamp
|
||||||
"Parse an ISO timestamp string to java.sql.Timestamp."
|
"Parse an ISO timestamp string to java.sql.Timestamp."
|
||||||
[s]
|
[s]
|
||||||
@@ -60,6 +75,7 @@
|
|||||||
(Timestamp/valueOf
|
(Timestamp/valueOf
|
||||||
(.replace ^String s "T" " "))))
|
(.replace ^String s "T" " "))))
|
||||||
|
|
||||||
|
|
||||||
(defn save-records!
|
(defn save-records!
|
||||||
"Insert price records into the database, skipping existing ones."
|
"Insert price records into the database, skipping existing ones."
|
||||||
[query-fn records]
|
[query-fn records]
|
||||||
@@ -71,6 +87,7 @@
|
|||||||
:price-dkk (:DayAheadPriceDKK rec)
|
:price-dkk (:DayAheadPriceDKK rec)
|
||||||
:price-eur (:DayAheadPriceEUR rec)})))
|
:price-eur (:DayAheadPriceEUR rec)})))
|
||||||
|
|
||||||
|
|
||||||
(defn fetch-and-save!
|
(defn fetch-and-save!
|
||||||
"Fetch prices from API and save to database."
|
"Fetch prices from API and save to database."
|
||||||
[query-fn]
|
[query-fn]
|
||||||
@@ -79,6 +96,7 @@
|
|||||||
(save-records! query-fn records)
|
(save-records! query-fn records)
|
||||||
(count records)))
|
(count records)))
|
||||||
|
|
||||||
|
|
||||||
(defn get-prices-for-date
|
(defn get-prices-for-date
|
||||||
"Get prices for a given LocalDate and price area from the database."
|
"Get prices for a given LocalDate and price area from the database."
|
||||||
[query-fn date price-area]
|
[query-fn date price-area]
|
||||||
@@ -86,10 +104,12 @@
|
|||||||
{:date (java.sql.Date/valueOf date)
|
{:date (java.sql.Date/valueOf date)
|
||||||
:price-area price-area}))
|
:price-area price-area}))
|
||||||
|
|
||||||
|
|
||||||
(defn get-today-prices
|
(defn get-today-prices
|
||||||
[query-fn]
|
[query-fn]
|
||||||
(get-prices-for-date query-fn (LocalDate/now dk-zone) "DK1"))
|
(get-prices-for-date query-fn (LocalDate/now dk-zone) "DK1"))
|
||||||
|
|
||||||
|
|
||||||
(defn get-tomorrow-prices
|
(defn get-tomorrow-prices
|
||||||
[query-fn]
|
[query-fn]
|
||||||
(get-prices-for-date query-fn (.plusDays (LocalDate/now dk-zone) 1) "DK1"))
|
(get-prices-for-date query-fn (.plusDays (LocalDate/now dk-zone) 1) "DK1"))
|
||||||
|
|||||||
@@ -1,22 +1,26 @@
|
|||||||
(ns pmagnus.elprice.web.controllers.tariffs
|
(ns pmagnus.elprice.web.controllers.tariffs
|
||||||
(:import
|
(:import
|
||||||
[java.time LocalDateTime]))
|
(java.time
|
||||||
|
LocalDateTime)))
|
||||||
|
|
||||||
|
|
||||||
(def n1-tariffs
|
(def n1-tariffs
|
||||||
"N1 network tariffs in øre/kWh (incl. VAT), effective 2026-01-01.
|
"N1 network tariffs in øre/kWh (incl. VAT), effective 2026-01-01.
|
||||||
Source: https://n1.dk/elnetkunder/gaeldende-priser"
|
Source: https://n1.dk/elnetkunder/gaeldende-priser"
|
||||||
{:summer {:low 10.98 ;; 00:00-06:00
|
{:summer {:low 10.98 ; 00:00-06:00
|
||||||
:high 16.47 ;; 06:00-17:00, 21:00-24:00
|
:high 16.47 ; 06:00-17:00, 21:00-24:00
|
||||||
:peak 42.83} ;; 17:00-21:00
|
:peak 42.83} ; 17:00-21:00
|
||||||
:winter {:low 10.98
|
:winter {:low 10.98
|
||||||
:high 32.95
|
:high 32.95
|
||||||
:peak 98.84}})
|
:peak 98.84}})
|
||||||
|
|
||||||
|
|
||||||
(defn season
|
(defn season
|
||||||
"Returns :summer (Apr-Sep) or :winter (Oct-Mar) for a given month (1-12)."
|
"Returns :summer (Apr-Sep) or :winter (Oct-Mar) for a given month (1-12)."
|
||||||
[month]
|
[month]
|
||||||
(if (<= 4 month 9) :summer :winter))
|
(if (<= 4 month 9) :summer :winter))
|
||||||
|
|
||||||
|
|
||||||
(defn load-level
|
(defn load-level
|
||||||
"Returns :low, :high, or :peak for a given hour (0-23).
|
"Returns :low, :high, or :peak for a given hour (0-23).
|
||||||
Low: 00:00-06:00
|
Low: 00:00-06:00
|
||||||
@@ -29,6 +33,7 @@
|
|||||||
(and (<= 17 hour) (< hour 21)) :peak
|
(and (<= 17 hour) (< hour 21)) :peak
|
||||||
:else :high))
|
:else :high))
|
||||||
|
|
||||||
|
|
||||||
(defn tariff-for-hour
|
(defn tariff-for-hour
|
||||||
"Returns the applicable N1 tariff in øre/kWh.
|
"Returns the applicable N1 tariff in øre/kWh.
|
||||||
Can be called with (month, hour) or a LocalDateTime."
|
Can be called with (month, hour) or a LocalDateTime."
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
(ns pmagnus.elprice.web.handler
|
(ns pmagnus.elprice.web.handler
|
||||||
(:require
|
(:require
|
||||||
[pmagnus.elprice.web.middleware.core :as middleware]
|
|
||||||
[integrant.core :as ig]
|
[integrant.core :as ig]
|
||||||
[ring.util.http-response :as http-response]
|
[pmagnus.elprice.web.middleware.core :as middleware]
|
||||||
[reitit.ring :as ring]
|
[reitit.ring :as ring]
|
||||||
[reitit.swagger-ui :as swagger-ui]))
|
[reitit.swagger-ui :as swagger-ui]
|
||||||
|
[ring.util.http-response :as http-response]))
|
||||||
|
|
||||||
|
|
||||||
(defmethod ig/init-key :handler/ring
|
(defmethod ig/init-key :handler/ring
|
||||||
[_ {:keys [router api-path] :as opts}]
|
[_ {:keys [router api-path] :as opts}]
|
||||||
@@ -30,6 +31,7 @@
|
|||||||
(http-response/content-type "text/plain")))}))
|
(http-response/content-type "text/plain")))}))
|
||||||
{:middleware [(middleware/wrap-base opts)]}))
|
{:middleware [(middleware/wrap-base opts)]}))
|
||||||
|
|
||||||
|
|
||||||
(defmethod ig/init-key :router/routes
|
(defmethod ig/init-key :router/routes
|
||||||
[_ {:keys [routes]}]
|
[_ {:keys [routes]}]
|
||||||
(mapv (fn [route]
|
(mapv (fn [route]
|
||||||
@@ -38,6 +40,7 @@
|
|||||||
route))
|
route))
|
||||||
routes))
|
routes))
|
||||||
|
|
||||||
|
|
||||||
(defmethod ig/init-key :router/core
|
(defmethod ig/init-key :router/core
|
||||||
[_ {:keys [routes env] :as opts}]
|
[_ {:keys [routes env] :as opts}]
|
||||||
(if (= env :dev)
|
(if (= env :dev)
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
(ns pmagnus.elprice.web.htmx
|
(ns pmagnus.elprice.web.htmx
|
||||||
(:require
|
(:require
|
||||||
[ring.util.http-response :as http-response]
|
|
||||||
[hiccup.core :as h]
|
[hiccup.core :as h]
|
||||||
[hiccup.page :as p]))
|
[hiccup.page :as p]
|
||||||
|
[ring.util.http-response :as http-response]))
|
||||||
|
|
||||||
(defmacro page [opts & content]
|
|
||||||
|
(defmacro page
|
||||||
|
[opts & content]
|
||||||
`(-> (p/html5 ~opts
|
`(-> (p/html5 ~opts
|
||||||
[:head
|
[:head
|
||||||
[:meta {:charset "UTF-8"}]
|
[:meta {:charset "UTF-8"}]
|
||||||
@@ -19,7 +21,9 @@
|
|||||||
http-response/ok
|
http-response/ok
|
||||||
(http-response/content-type "text/html")))
|
(http-response/content-type "text/html")))
|
||||||
|
|
||||||
(defmacro fragment [opts & content]
|
|
||||||
|
(defmacro fragment
|
||||||
|
[opts & content]
|
||||||
`(-> (str (h/html ~opts ~@content))
|
`(-> (str (h/html ~opts ~@content))
|
||||||
http-response/ok
|
http-response/ok
|
||||||
(http-response/content-type "text/html")))
|
(http-response/content-type "text/html")))
|
||||||
|
|||||||
@@ -4,11 +4,11 @@
|
|||||||
[ring.middleware.defaults :as defaults]
|
[ring.middleware.defaults :as defaults]
|
||||||
[ring.middleware.session.cookie :as cookie]))
|
[ring.middleware.session.cookie :as cookie]))
|
||||||
|
|
||||||
|
|
||||||
(defn wrap-base
|
(defn wrap-base
|
||||||
[{:keys [metrics site-defaults-config cookie-secret] :as opts}]
|
[{:keys [metrics site-defaults-config cookie-secret] :as opts}]
|
||||||
(let [cookie-store (cookie/cookie-store {:key (.getBytes ^String cookie-secret)})]
|
(let [cookie-store (cookie/cookie-store {:key (.getBytes ^String cookie-secret)})]
|
||||||
(fn [handler]
|
(fn [handler]
|
||||||
(cond-> ((:middleware env/defaults) handler opts)
|
(cond-> ((:middleware env/defaults) handler opts)
|
||||||
true (defaults/wrap-defaults
|
true (defaults/wrap-defaults
|
||||||
(assoc-in site-defaults-config [:session :store] cookie-store))
|
(assoc-in site-defaults-config [:session :store] cookie-store))))))
|
||||||
))))
|
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
[clojure.tools.logging :as log]
|
[clojure.tools.logging :as log]
|
||||||
[reitit.ring.middleware.exception :as exception]))
|
[reitit.ring.middleware.exception :as exception]))
|
||||||
|
|
||||||
(defn handler [message status exception request]
|
|
||||||
|
(defn handler
|
||||||
|
[message status exception request]
|
||||||
(when (>= status 500)
|
(when (>= status 500)
|
||||||
;; You can optionally use this to report error to an external service
|
;; You can optionally use this to report error to an external service
|
||||||
(log/error exception))
|
(log/error exception))
|
||||||
@@ -13,6 +15,7 @@
|
|||||||
:data (ex-data exception)
|
:data (ex-data exception)
|
||||||
:uri (:uri request)}})
|
:uri (:uri request)}})
|
||||||
|
|
||||||
|
|
||||||
(def wrap-exception
|
(def wrap-exception
|
||||||
(exception/create-exception-middleware
|
(exception/create-exception-middleware
|
||||||
(merge
|
(merge
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
[luminus-transit.time :as time]
|
[luminus-transit.time :as time]
|
||||||
[muuntaja.core :as m]))
|
[muuntaja.core :as m]))
|
||||||
|
|
||||||
|
|
||||||
(def instance
|
(def instance
|
||||||
(m/create
|
(m/create
|
||||||
(-> m/default-options
|
(-> m/default-options
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
(ns pmagnus.elprice.web.routes.api
|
(ns pmagnus.elprice.web.routes.api
|
||||||
(:require
|
(:require
|
||||||
|
[integrant.core :as ig]
|
||||||
[pmagnus.elprice.web.controllers.health :as health]
|
[pmagnus.elprice.web.controllers.health :as health]
|
||||||
[pmagnus.elprice.web.middleware.exception :as exception]
|
[pmagnus.elprice.web.middleware.exception :as exception]
|
||||||
[pmagnus.elprice.web.middleware.formats :as formats]
|
[pmagnus.elprice.web.middleware.formats :as formats]
|
||||||
[integrant.core :as ig]
|
|
||||||
[reitit.coercion.malli :as malli]
|
[reitit.coercion.malli :as malli]
|
||||||
[reitit.ring.coercion :as coercion]
|
[reitit.ring.coercion :as coercion]
|
||||||
[reitit.ring.middleware.muuntaja :as muuntaja]
|
[reitit.ring.middleware.muuntaja :as muuntaja]
|
||||||
[reitit.ring.middleware.parameters :as parameters]
|
[reitit.ring.middleware.parameters :as parameters]
|
||||||
[reitit.swagger :as swagger]))
|
[reitit.swagger :as swagger]))
|
||||||
|
|
||||||
|
|
||||||
(def route-data
|
(def route-data
|
||||||
{:coercion malli/coercion
|
{:coercion malli/coercion
|
||||||
:muuntaja formats/instance
|
:muuntaja formats/instance
|
||||||
@@ -31,8 +32,10 @@
|
|||||||
;; exception handling
|
;; exception handling
|
||||||
exception/wrap-exception]})
|
exception/wrap-exception]})
|
||||||
|
|
||||||
|
|
||||||
;; Routes
|
;; Routes
|
||||||
(defn api-routes [_opts]
|
(defn api-routes
|
||||||
|
[_opts]
|
||||||
[["/swagger.json"
|
[["/swagger.json"
|
||||||
{:get {:no-doc true
|
{:get {:no-doc true
|
||||||
:swagger {:info {:title "pmagnus.elprice API"}}
|
:swagger {:info {:title "pmagnus.elprice API"}}
|
||||||
@@ -43,8 +46,10 @@
|
|||||||
;; restarting the system
|
;; restarting the system
|
||||||
{:get #'health/healthcheck!}]])
|
{:get #'health/healthcheck!}]])
|
||||||
|
|
||||||
|
|
||||||
(derive :reitit.routes/api :reitit/routes)
|
(derive :reitit.routes/api :reitit/routes)
|
||||||
|
|
||||||
|
|
||||||
(defmethod ig/init-key :reitit.routes/api
|
(defmethod ig/init-key :reitit.routes/api
|
||||||
[_ {:keys [base-path]
|
[_ {:keys [base-path]
|
||||||
:or {base-path ""}
|
:or {base-path ""}
|
||||||
|
|||||||
@@ -1,39 +1,49 @@
|
|||||||
(ns pmagnus.elprice.web.routes.ui
|
(ns pmagnus.elprice.web.routes.ui
|
||||||
(:require
|
(:require
|
||||||
|
[integrant.core :as ig]
|
||||||
|
[pmagnus.elprice.web.controllers.eloverblik :as eloverblik]
|
||||||
|
[pmagnus.elprice.web.controllers.prices :as prices]
|
||||||
|
[pmagnus.elprice.web.controllers.tariffs :as tariffs]
|
||||||
|
[pmagnus.elprice.web.htmx :refer [page fragment]]
|
||||||
[pmagnus.elprice.web.middleware.exception :as exception]
|
[pmagnus.elprice.web.middleware.exception :as exception]
|
||||||
[pmagnus.elprice.web.middleware.formats :as formats]
|
[pmagnus.elprice.web.middleware.formats :as formats]
|
||||||
[pmagnus.elprice.web.routes.utils :as utils]
|
[pmagnus.elprice.web.routes.utils :as utils]
|
||||||
[pmagnus.elprice.web.htmx :refer [page fragment]]
|
|
||||||
[pmagnus.elprice.web.controllers.prices :as prices]
|
|
||||||
[pmagnus.elprice.web.controllers.tariffs :as tariffs]
|
|
||||||
[pmagnus.elprice.web.controllers.eloverblik :as eloverblik]
|
|
||||||
[integrant.core :as ig]
|
|
||||||
[reitit.ring.middleware.muuntaja :as muuntaja]
|
[reitit.ring.middleware.muuntaja :as muuntaja]
|
||||||
[reitit.ring.middleware.parameters :as parameters])
|
[reitit.ring.middleware.parameters :as parameters])
|
||||||
(:import
|
(:import
|
||||||
[java.time LocalDateTime ZoneId]
|
(java.time
|
||||||
[java.time.format DateTimeFormatter]))
|
LocalDateTime
|
||||||
|
ZoneId)
|
||||||
|
(java.time.format
|
||||||
|
DateTimeFormatter)))
|
||||||
|
|
||||||
|
|
||||||
(def ^:private dk-zone (ZoneId/of "Europe/Copenhagen"))
|
(def ^:private dk-zone (ZoneId/of "Europe/Copenhagen"))
|
||||||
(def ^:private hour-fmt (DateTimeFormatter/ofPattern "HH:00"))
|
(def ^:private hour-fmt (DateTimeFormatter/ofPattern "HH:00"))
|
||||||
(def ^:private date-fmt (DateTimeFormatter/ofPattern "yyyy-MM-dd"))
|
(def ^:private date-fmt (DateTimeFormatter/ofPattern "yyyy-MM-dd"))
|
||||||
|
|
||||||
(defn- ->ldt [time-dk]
|
|
||||||
|
(defn- ->ldt
|
||||||
|
[time-dk]
|
||||||
(cond
|
(cond
|
||||||
(instance? LocalDateTime time-dk) time-dk
|
(instance? LocalDateTime time-dk) time-dk
|
||||||
(instance? java.sql.Timestamp time-dk)
|
(instance? java.sql.Timestamp time-dk)
|
||||||
(.toLocalDateTime ^java.sql.Timestamp time-dk)
|
(.toLocalDateTime ^java.sql.Timestamp time-dk)
|
||||||
:else (LocalDateTime/parse (str time-dk))))
|
:else (LocalDateTime/parse (str time-dk))))
|
||||||
|
|
||||||
(defn- format-hour [time-dk]
|
|
||||||
|
(defn- format-hour
|
||||||
|
[time-dk]
|
||||||
(.format (->ldt time-dk) hour-fmt))
|
(.format (->ldt time-dk) hour-fmt))
|
||||||
|
|
||||||
|
|
||||||
(defn- dkk-mwh->ore-kwh
|
(defn- dkk-mwh->ore-kwh
|
||||||
"Convert DKK/MWh to øre/kWh (divide by 10)."
|
"Convert DKK/MWh to øre/kWh (divide by 10)."
|
||||||
[price-dkk]
|
[price-dkk]
|
||||||
(when price-dkk
|
(when price-dkk
|
||||||
(/ (double price-dkk) 10.0)))
|
(/ (double price-dkk) 10.0)))
|
||||||
|
|
||||||
|
|
||||||
(defn- format-ore
|
(defn- format-ore
|
||||||
"Format price as total(net) where total = net + tariff, or just net if no tariff."
|
"Format price as total(net) where total = net + tariff, or just net if no tariff."
|
||||||
([price-dkk]
|
([price-dkk]
|
||||||
@@ -45,31 +55,41 @@
|
|||||||
(format "%.1f(%.1f)" (+ ore tariff) ore)
|
(format "%.1f(%.1f)" (+ ore tariff) ore)
|
||||||
"-")))
|
"-")))
|
||||||
|
|
||||||
(defn- group-by-hour [prices]
|
|
||||||
|
(defn- group-by-hour
|
||||||
|
[prices]
|
||||||
(->> prices
|
(->> prices
|
||||||
(group-by #(.getHour (->ldt (:time_dk %))))
|
(group-by #(.getHour (->ldt (:time_dk %))))
|
||||||
(sort-by key)))
|
(sort-by key)))
|
||||||
|
|
||||||
(defn- avg-ore [quarters]
|
|
||||||
|
(defn- avg-ore
|
||||||
|
[quarters]
|
||||||
(let [vals (keep :price_dkk quarters)]
|
(let [vals (keep :price_dkk quarters)]
|
||||||
(when (seq vals)
|
(when (seq vals)
|
||||||
(/ (reduce + 0.0 (map double vals)) (count vals) 10.0))))
|
(/ (reduce + 0.0 (map double vals)) (count vals) 10.0))))
|
||||||
|
|
||||||
|
|
||||||
(def ^:private red-gradient
|
(def ^:private red-gradient
|
||||||
["bg-red-50" "bg-red-100" "bg-red-200" "bg-red-300"
|
["bg-red-50" "bg-red-100" "bg-red-200" "bg-red-300"
|
||||||
"bg-red-400" "bg-red-500" "bg-red-600" "bg-red-700"])
|
"bg-red-400" "bg-red-500" "bg-red-600" "bg-red-700"])
|
||||||
|
|
||||||
|
|
||||||
(def ^:private green-gradient
|
(def ^:private green-gradient
|
||||||
["bg-green-700" "bg-green-600" "bg-green-500" "bg-green-400"
|
["bg-green-700" "bg-green-600" "bg-green-500" "bg-green-400"
|
||||||
"bg-green-300" "bg-green-200" "bg-green-100" "bg-green-50"])
|
"bg-green-300" "bg-green-200" "bg-green-100" "bg-green-50"])
|
||||||
|
|
||||||
(defn- rank-bg [rank total]
|
|
||||||
|
(defn- rank-bg
|
||||||
|
[rank total]
|
||||||
(cond
|
(cond
|
||||||
(<= rank 8) (get green-gradient (dec rank))
|
(<= rank 8) (get green-gradient (dec rank))
|
||||||
(> rank (- total 8)) (get red-gradient (- rank (- total 7)))
|
(> rank (- total 8)) (get red-gradient (- rank (- total 7)))
|
||||||
:else nil))
|
:else nil))
|
||||||
|
|
||||||
(defn- hour-block [hour quarters rank total-hours]
|
|
||||||
|
(defn- hour-block
|
||||||
|
[hour quarters rank total-hours]
|
||||||
(let [sorted (sort-by #(.getMinute (->ldt (:time_dk %))) quarters)
|
(let [sorted (sort-by #(.getMinute (->ldt (:time_dk %))) quarters)
|
||||||
avg (avg-ore quarters)
|
avg (avg-ore quarters)
|
||||||
month (.getMonthValue (->ldt (:time_dk (first quarters))))
|
month (.getMonthValue (->ldt (:time_dk (first quarters))))
|
||||||
@@ -90,6 +110,7 @@
|
|||||||
[:span (format "%02d:%02d" hour (.getMinute (->ldt (:time_dk q))))]
|
[:span (format "%02d:%02d" hour (.getMinute (->ldt (:time_dk q))))]
|
||||||
[:span {:class "font-mono"} (format-ore (:price_dkk q) tariff)]])]]))
|
[:span {:class "font-mono"} (format-ore (:price_dkk q) tariff)]])]]))
|
||||||
|
|
||||||
|
|
||||||
(defn- hour-ranks
|
(defn- hour-ranks
|
||||||
"Compute a map of hour -> rank (1 = cheapest) based on total price (avg + tariff)."
|
"Compute a map of hour -> rank (1 = cheapest) based on total price (avg + tariff)."
|
||||||
[grouped-hours]
|
[grouped-hours]
|
||||||
@@ -103,7 +124,9 @@
|
|||||||
(map-indexed (fn [i [hour _]] [hour (inc i)]))
|
(map-indexed (fn [i [hour _]] [hour (inc i)]))
|
||||||
(into {}))))
|
(into {}))))
|
||||||
|
|
||||||
(defn- price-table [prices]
|
|
||||||
|
(defn- price-table
|
||||||
|
[prices]
|
||||||
[:div
|
[:div
|
||||||
(if (seq prices)
|
(if (seq prices)
|
||||||
(let [grouped (group-by-hour prices)
|
(let [grouped (group-by-hour prices)
|
||||||
@@ -114,16 +137,21 @@
|
|||||||
(hour-block hour quarters (get ranks hour) total-hours))])
|
(hour-block hour quarters (get ranks hour) total-hours))])
|
||||||
[:p {:class "mt-2 text-sm text-gray-400"} "Not available yet"])])
|
[:p {:class "mt-2 text-sm text-gray-400"} "Not available yet"])])
|
||||||
|
|
||||||
|
|
||||||
(def ^:private tab-base
|
(def ^:private tab-base
|
||||||
"px-4 py-2 font-medium text-sm rounded-t-lg border-2 border-b-0 ")
|
"px-4 py-2 font-medium text-sm rounded-t-lg border-2 border-b-0 ")
|
||||||
|
|
||||||
|
|
||||||
(def ^:private tab-active
|
(def ^:private tab-active
|
||||||
(str tab-base "border-blue-400 bg-white text-gray-900"))
|
(str tab-base "border-blue-400 bg-white text-gray-900"))
|
||||||
|
|
||||||
|
|
||||||
(def ^:private tab-inactive
|
(def ^:private tab-inactive
|
||||||
(str tab-base "border-gray-200 bg-gray-100 text-gray-500 hover:text-gray-700 cursor-pointer"))
|
(str tab-base "border-gray-200 bg-gray-100 text-gray-500 hover:text-gray-700 cursor-pointer"))
|
||||||
|
|
||||||
(defn- tabs [active-tab has-tomorrow?]
|
|
||||||
|
(defn- tabs
|
||||||
|
[active-tab has-tomorrow?]
|
||||||
(let [today (java.time.LocalDate/now dk-zone)
|
(let [today (java.time.LocalDate/now dk-zone)
|
||||||
tomorrow (.plusDays today 1)]
|
tomorrow (.plusDays today 1)]
|
||||||
[:div {:class "flex gap-1 mt-4"}
|
[:div {:class "flex gap-1 mt-4"}
|
||||||
@@ -139,17 +167,23 @@
|
|||||||
:hx-swap "innerHTML"}
|
:hx-swap "innerHTML"}
|
||||||
(str "Tomorrow " (.format tomorrow date-fmt))])]))
|
(str "Tomorrow " (.format tomorrow date-fmt))])]))
|
||||||
|
|
||||||
(defn- price-content [tab prices has-tomorrow?]
|
|
||||||
|
(defn- price-content
|
||||||
|
[tab prices has-tomorrow?]
|
||||||
[:div
|
[:div
|
||||||
(tabs tab has-tomorrow?)
|
(tabs tab has-tomorrow?)
|
||||||
[:div {:id "price-content"
|
[:div {:id "price-content"
|
||||||
:class "border-2 border-blue-400 rounded-b-lg rounded-tr-lg p-4 bg-white"}
|
:class "border-2 border-blue-400 rounded-b-lg rounded-tr-lg p-4 bg-white"}
|
||||||
(price-table prices)]])
|
(price-table prices)]])
|
||||||
|
|
||||||
(defn- has-tomorrow? [query-fn]
|
|
||||||
|
(defn- has-tomorrow?
|
||||||
|
[query-fn]
|
||||||
(seq (prices/get-tomorrow-prices query-fn)))
|
(seq (prices/get-tomorrow-prices query-fn)))
|
||||||
|
|
||||||
(defn home [query-fn request]
|
|
||||||
|
(defn home
|
||||||
|
[query-fn request]
|
||||||
(prices/fetch-and-save! query-fn)
|
(prices/fetch-and-save! query-fn)
|
||||||
(page {:lang "en"}
|
(page {:lang "en"}
|
||||||
[:h1 {:class "text-2xl font-bold text-gray-900 cursor-pointer hover:text-blue-600 transition-colors"
|
[:h1 {:class "text-2xl font-bold text-gray-900 cursor-pointer hover:text-blue-600 transition-colors"
|
||||||
@@ -162,20 +196,28 @@
|
|||||||
[:div {:id "price-panel"}
|
[:div {:id "price-panel"}
|
||||||
(price-content :today (prices/get-today-prices query-fn) (has-tomorrow? query-fn))]))
|
(price-content :today (prices/get-today-prices query-fn) (has-tomorrow? query-fn))]))
|
||||||
|
|
||||||
(defn prices-today [query-fn request]
|
|
||||||
|
(defn prices-today
|
||||||
|
[query-fn request]
|
||||||
(fragment
|
(fragment
|
||||||
(price-content :today (prices/get-today-prices query-fn) (has-tomorrow? query-fn))))
|
(price-content :today (prices/get-today-prices query-fn) (has-tomorrow? query-fn))))
|
||||||
|
|
||||||
(defn prices-tomorrow [query-fn request]
|
|
||||||
|
(defn prices-tomorrow
|
||||||
|
[query-fn request]
|
||||||
(fragment
|
(fragment
|
||||||
(price-content :tomorrow (prices/get-tomorrow-prices query-fn) (has-tomorrow? query-fn))))
|
(price-content :tomorrow (prices/get-tomorrow-prices query-fn) (has-tomorrow? query-fn))))
|
||||||
|
|
||||||
(defn fetch-prices [query-fn request]
|
|
||||||
|
(defn fetch-prices
|
||||||
|
[query-fn request]
|
||||||
(prices/fetch-and-save! query-fn)
|
(prices/fetch-and-save! query-fn)
|
||||||
(fragment
|
(fragment
|
||||||
(price-content :today (prices/get-today-prices query-fn) (has-tomorrow? query-fn))))
|
(price-content :today (prices/get-today-prices query-fn) (has-tomorrow? query-fn))))
|
||||||
|
|
||||||
(defn sync-eloverblik [query-fn request]
|
|
||||||
|
(defn sync-eloverblik
|
||||||
|
[query-fn request]
|
||||||
(let [date-str (get-in request [:params :date])
|
(let [date-str (get-in request [:params :date])
|
||||||
date (if date-str
|
date (if date-str
|
||||||
(java.time.LocalDate/parse date-str)
|
(java.time.LocalDate/parse date-str)
|
||||||
@@ -194,14 +236,17 @@
|
|||||||
(str "Synced " (:metering-points result)
|
(str "Synced " (:metering-points result)
|
||||||
" metering points (" (:from result) " to " (:to result) ")")]))))
|
" metering points (" (:from result) " to " (:to result) ")")]))))
|
||||||
|
|
||||||
|
|
||||||
;; Routes
|
;; Routes
|
||||||
(defn ui-routes [{:keys [query-fn]}]
|
(defn ui-routes
|
||||||
|
[{:keys [query-fn]}]
|
||||||
[["/" {:get (partial home query-fn)}]
|
[["/" {:get (partial home query-fn)}]
|
||||||
["/prices/today" {:get (partial prices-today query-fn)}]
|
["/prices/today" {:get (partial prices-today query-fn)}]
|
||||||
["/prices/tomorrow" {:get (partial prices-tomorrow query-fn)}]
|
["/prices/tomorrow" {:get (partial prices-tomorrow query-fn)}]
|
||||||
["/prices/fetch" {:post (partial fetch-prices query-fn)}]
|
["/prices/fetch" {:post (partial fetch-prices query-fn)}]
|
||||||
["/eloverblik/sync" {:post (partial sync-eloverblik query-fn)}]])
|
["/eloverblik/sync" {:post (partial sync-eloverblik query-fn)}]])
|
||||||
|
|
||||||
|
|
||||||
(def route-data
|
(def route-data
|
||||||
{:muuntaja formats/instance
|
{:muuntaja formats/instance
|
||||||
:middleware
|
:middleware
|
||||||
@@ -213,8 +258,10 @@
|
|||||||
;; exception handling
|
;; exception handling
|
||||||
exception/wrap-exception]})
|
exception/wrap-exception]})
|
||||||
|
|
||||||
|
|
||||||
(derive :reitit.routes/ui :reitit/routes)
|
(derive :reitit.routes/ui :reitit/routes)
|
||||||
|
|
||||||
|
|
||||||
(defmethod ig/init-key :reitit.routes/ui
|
(defmethod ig/init-key :reitit.routes/ui
|
||||||
[_ {:keys [base-path]
|
[_ {:keys [base-path]
|
||||||
:or {base-path ""}
|
:or {base-path ""}
|
||||||
|
|||||||
@@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
(def route-data-path [:reitit.core/match :data])
|
(def route-data-path [:reitit.core/match :data])
|
||||||
|
|
||||||
|
|
||||||
(defn route-data
|
(defn route-data
|
||||||
[req]
|
[req]
|
||||||
(get-in req route-data-path))
|
(get-in req route-data-path))
|
||||||
|
|
||||||
|
|
||||||
(defn route-data-key
|
(defn route-data-key
|
||||||
[req k]
|
[req k]
|
||||||
(get-in req (conj route-data-path k)))
|
(get-in req (conj route-data-path k)))
|
||||||
|
|||||||
Reference in New Issue
Block a user