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"
|
||||
"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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue