Added fallback for currency instant answers
Some checks failed
Run Integration Tests / test (push) Failing after 43s

This commit is contained in:
partisan 2025-07-01 22:06:49 +02:00
parent 43d7068c7a
commit 71dfeaebe9

View file

@ -4,7 +4,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"log"
"net/http" "net/http"
"regexp" "regexp"
"strconv" "strconv"
@ -17,6 +16,7 @@ import (
var ( var (
exchangeRates = make(map[string]float64) exchangeRates = make(map[string]float64)
nextUpdateTime time.Time nextUpdateTime time.Time
lastUpdateTime time.Time
exchangeCacheMutex sync.RWMutex exchangeCacheMutex sync.RWMutex
allCurrencies []string allCurrencies []string
) )
@ -26,47 +26,83 @@ type CurrencyAPIResponse struct {
Rates map[string]float64 `json:"rates"` Rates map[string]float64 `json:"rates"`
} }
// UpdateExchangeRates fetches and caches currency rates var primaryURL = "https://open.er-api.com/v6/latest/USD"
var backupURL = "https://api.frankfurter.app/latest?base=USD"
func fetchRates(url string) ([]byte, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
func UpdateExchangeRates() error { func UpdateExchangeRates() error {
exchangeCacheMutex.Lock() exchangeCacheMutex.Lock()
defer exchangeCacheMutex.Unlock() defer exchangeCacheMutex.Unlock()
// Use a reliable free API with good rate limits var (
resp, err := http.Get("https://open.er-api.com/v6/latest/USD") rates map[string]float64
if err != nil { nextTime time.Time
return err fallback bool
} )
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body) // Try primary API
if err != nil { body, err := fetchRates(primaryURL)
return err if err == nil {
var res struct {
Result string `json:"result"`
Rates map[string]float64 `json:"rates"`
TimeNextUpdateUnix int64 `json:"time_next_update_unix"`
}
if err := json.Unmarshal(body, &res); err == nil && res.Result == "success" && len(res.Rates) > 0 {
rates = res.Rates
nextTime = time.Unix(res.TimeNextUpdateUnix, 0)
} else {
printWarn("Primary API response invalid or empty, falling back to backup")
fallback = true
}
} else {
printWarn("Primary API fetch failed: %v", err)
fallback = true
} }
var apiResponse struct { // Try backup API if needed
Result string `json:"result"` if fallback {
Rates map[string]float64 `json:"rates"` body, err := fetchRates(backupURL)
TimeNextUpdateUnix int64 `json:"time_next_update_unix"` if err != nil {
} return fmt.Errorf("both rate fetches failed: %v", err)
if err := json.Unmarshal(body, &apiResponse); err != nil { }
return err
var res struct {
Base string `json:"base"`
Date string `json:"date"`
Rates map[string]float64 `json:"rates"`
}
if err := json.Unmarshal(body, &res); err != nil {
return fmt.Errorf("backup API response unmarshal failed: %v", err)
}
if len(res.Rates) == 0 {
return fmt.Errorf("backup API returned empty rates")
}
rates = res.Rates
nextTime = time.Now().Add(6 * time.Hour)
} }
if apiResponse.Result != "success" { // Finalize
return fmt.Errorf("API error: %s", apiResponse.Result) exchangeRates = rates
} nextUpdateTime = nextTime
lastUpdateTime = time.Now()
// Cache the rates
exchangeRates = apiResponse.Rates
nextUpdateTime = time.Unix(apiResponse.TimeNextUpdateUnix, 0)
// Update list of all currencies
allCurrencies = make([]string, 0, len(exchangeRates)) allCurrencies = make([]string, 0, len(exchangeRates))
for currency := range exchangeRates { for c := range exchangeRates {
allCurrencies = append(allCurrencies, currency) allCurrencies = append(allCurrencies, c)
} }
printDebug("Updated currency rates: %d currencies cached", len(allCurrencies)) printDebug("Updated currency rates: %d currencies cached", len(allCurrencies))
printDebug("Next currency update at: %s", nextUpdateTime.Format(time.RFC1123))
return nil return nil
} }
@ -76,7 +112,7 @@ func PrecacheAllCurrencyPairs() {
defer exchangeCacheMutex.RUnlock() defer exchangeCacheMutex.RUnlock()
if len(exchangeRates) == 0 { if len(exchangeRates) == 0 {
log.Println("Skipping precache - no rates available") printWarn("Skipping precache: no currency rates available")
return return
} }
@ -99,11 +135,13 @@ func PrecacheAllCurrencyPairs() {
func GetExchangeRate(from, to string) (float64, bool) { func GetExchangeRate(from, to string) (float64, bool) {
// Auto-update cache if expired // Auto-update cache if expired
if time.Now().After(nextUpdateTime) { if time.Now().After(nextUpdateTime) {
err := UpdateExchangeRates() // Avoid excessive updates within 1 min
if err != nil { if time.Since(lastUpdateTime) > time.Minute {
printWarn("Currency update failed: %v", err) err := UpdateExchangeRates()
// Postpone next attempt to avoid hammering API if err != nil {
nextUpdateTime = time.Now().Add(5 * time.Minute) printWarn("Currency update failed: %v", err)
nextUpdateTime = time.Now().Add(5 * time.Minute)
}
} }
} }