Added fallback for currency instant answers
Some checks failed
Run Integration Tests / test (push) Failing after 43s
Some checks failed
Run Integration Tests / test (push) Failing after 43s
This commit is contained in:
parent
43d7068c7a
commit
71dfeaebe9
1 changed files with 72 additions and 34 deletions
106
ia-currency.go
106
ia-currency.go
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue