From fc071af99096a4cc25d74acb58b7c0d4a23ab1a9 Mon Sep 17 00:00:00 2001 From: Per Magnus Petersen Date: Tue, 8 Sep 2026 15:55:35 +0200 Subject: [PATCH] Fix local price fetching --- ...op-day-ahead-time-dk-unique-index.down.sql | 2 + ...drop-day-ahead-time-dk-unique-index.up.sql | 1 + resources/queries.sql | 9 +-- run-local.sh | 68 +++++++++++++++++++ .../elprice/web/controllers/prices.clj | 10 +++ src/clj/pmagnus/elprice/web/routes/ui.clj | 4 +- 6 files changed, 88 insertions(+), 6 deletions(-) create mode 100644 resources/migrations/20260908154500-drop-day-ahead-time-dk-unique-index.down.sql create mode 100644 resources/migrations/20260908154500-drop-day-ahead-time-dk-unique-index.up.sql create mode 100755 run-local.sh diff --git a/resources/migrations/20260908154500-drop-day-ahead-time-dk-unique-index.down.sql b/resources/migrations/20260908154500-drop-day-ahead-time-dk-unique-index.down.sql new file mode 100644 index 0000000..4a4ac2f --- /dev/null +++ b/resources/migrations/20260908154500-drop-day-ahead-time-dk-unique-index.down.sql @@ -0,0 +1,2 @@ +CREATE UNIQUE INDEX IF NOT EXISTS idx_day_ahead_prices_time_dk_area + ON day_ahead_prices (time_dk, price_area); diff --git a/resources/migrations/20260908154500-drop-day-ahead-time-dk-unique-index.up.sql b/resources/migrations/20260908154500-drop-day-ahead-time-dk-unique-index.up.sql new file mode 100644 index 0000000..7462dc0 --- /dev/null +++ b/resources/migrations/20260908154500-drop-day-ahead-time-dk-unique-index.up.sql @@ -0,0 +1 @@ +DROP INDEX IF EXISTS idx_day_ahead_prices_time_dk_area; diff --git a/resources/queries.sql b/resources/queries.sql index 29a3ef0..7f1aa3e 100644 --- a/resources/queries.sql +++ b/resources/queries.sql @@ -1,10 +1,11 @@ -- :name insert-price! :! :n --- :doc Insert a day-ahead price record, update DKK if it was null +-- :doc Insert/update a day-ahead price record INSERT INTO day_ahead_prices (time_utc, time_dk, price_area, price_dkk, price_eur) VALUES (:time-utc, :time-dk, :price-area, :price-dkk, :price-eur) -ON CONFLICT (time_dk, price_area) DO UPDATE SET - price_dkk = COALESCE(day_ahead_prices.price_dkk, EXCLUDED.price_dkk), - price_eur = COALESCE(day_ahead_prices.price_eur, EXCLUDED.price_eur); +ON CONFLICT (time_utc, price_area) DO UPDATE SET + time_dk = EXCLUDED.time_dk, + price_dkk = EXCLUDED.price_dkk, + price_eur = EXCLUDED.price_eur; -- :name get-prices-for-date :? :* -- :doc Get all prices for a given date (time_dk local date) and price area diff --git a/run-local.sh b/run-local.sh new file mode 100755 index 0000000..3bf42fc --- /dev/null +++ b/run-local.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Run the app locally without Docker. +# Loads environment variables from .env and the Eloverblik refresh token from .token. + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$ROOT_DIR" + +load_env_file() { + local file="$1" + + [[ -f "$file" ]] || return 0 + + while IFS= read -r line || [[ -n "$line" ]]; do + # Trim CR from CRLF files. + line="${line%$'\r'}" + + # Skip blank lines and full-line comments. + [[ -z "${line//[[:space:]]/}" || "$line" =~ ^[[:space:]]*# ]] && continue + + # Allow lines prefixed with "export ". + line="${line#export }" + + local key="${line%%=*}" + local value="${line#*=}" + + # Trim whitespace around the key only; preserve value contents (e.g. JDBC_URL query strings). + key="$(printf '%s' "$key" | xargs)" + + if [[ ! "$key" =~ ^[A-Za-z_][A-Za-z0-9_]*$ || "$line" != *=* ]]; then + echo "Skipping invalid env line in $file: $line" >&2 + continue + fi + + # Remove one matching pair of surrounding quotes, if present. + if [[ "$value" =~ ^\".*\"$ || "$value" =~ ^\'.*\'$ ]]; then + value="${value:1:${#value}-2}" + fi + + export "$key=$value" + done < "$file" +} + +load_env_file ".env" + +if [[ -f ".token" && -z "${ELOVERBLIK_TOKEN:-}" ]]; then + ELOVERBLIK_TOKEN="$(tr -d '\r\n' < .token)" + export ELOVERBLIK_TOKEN +fi + +: "${PORT:=3000}" +export PORT + +# Energi Data Service currently serves only the leaf certificate. Allow the JVM +# to fetch missing intermediate CA certificates from the certificate AIA URL. +export JAVA_TOOL_OPTIONS="${JAVA_TOOL_OPTIONS:-} -Dcom.sun.security.enableAIAcaIssuers=true" + +echo "Starting elprice locally on port $PORT (no Docker)..." + +# `clj` requires rlwrap for REPL command editing. Fall back to `clojure` +# when rlwrap is not installed so the app can still start. +if command -v rlwrap >/dev/null 2>&1; then + exec clj -M:dev -e "(dev-prep!) (go)" -r +else + echo "rlwrap not found; using clojure instead of clj (REPL editing disabled)." >&2 + exec clojure -M:dev -e "(dev-prep!) (go)" -r +fi diff --git a/src/clj/pmagnus/elprice/web/controllers/prices.clj b/src/clj/pmagnus/elprice/web/controllers/prices.clj index d93fecf..4e3bed0 100644 --- a/src/clj/pmagnus/elprice/web/controllers/prices.clj +++ b/src/clj/pmagnus/elprice/web/controllers/prices.clj @@ -123,3 +123,13 @@ (defn get-tomorrow-prices [query-fn] (get-prices-for-date query-fn (.plusDays (LocalDate/now dk-zone) 1) "DK1")) + + +(defn ensure-current-prices! + "Fetch prices only when today's DK1 prices are missing from the database." + [query-fn] + (if (seq (get-today-prices query-fn)) + (do + (log/info "Day-ahead prices already exist; skipping API fetch") + nil) + (fetch-and-save! query-fn))) diff --git a/src/clj/pmagnus/elprice/web/routes/ui.clj b/src/clj/pmagnus/elprice/web/routes/ui.clj index fe9b66e..b232aed 100644 --- a/src/clj/pmagnus/elprice/web/routes/ui.clj +++ b/src/clj/pmagnus/elprice/web/routes/ui.clj @@ -184,7 +184,7 @@ (defn home [query-fn request] - (prices/fetch-and-save! query-fn) + (prices/ensure-current-prices! query-fn) (page {:lang "en"} [:h1 {:class "text-2xl font-bold text-gray-900 cursor-pointer hover:text-blue-600 transition-colors" :hx-post "/prices/fetch" @@ -211,7 +211,7 @@ (defn fetch-prices [query-fn request] - (prices/fetch-and-save! query-fn) + (prices/ensure-current-prices! query-fn) (fragment (price-content :today (prices/get-today-prices query-fn) (has-tomorrow? query-fn))))