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