Files
goca/currency.go
T

96 lines
2.2 KiB
Go

package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"path/filepath"
"time"
"github.com/shopspring/decimal"
)
var baseCurrency = "EUR"
var ratesAPIURL = "https://open.er-api.com/v6/latest/EUR"
type RatesCache struct {
Timestamp time.Time `json:"timestamp"`
Rates map[string]float64 `json:"rates"`
}
// fetchRates downloads exchange rates from the API or loads them from a local cache
// if they are less than 24 hours old.
func fetchRates() error {
home, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("could not find user home dir for cache: %w", err)
}
cacheFile := filepath.Join(home, ".goca_rates.json")
var data RatesCache
useCache := false
// Try loading from cache
file, err := os.Open(cacheFile)
if err == nil {
if err := json.NewDecoder(file).Decode(&data); err == nil {
if time.Since(data.Timestamp) < 24*time.Hour && len(data.Rates) > 0 {
useCache = true
}
}
file.Close()
}
if !useCache {
// Fetch from API
client := &http.Client{Timeout: 5 * time.Second}
resp, err := client.Get(ratesAPIURL)
if err != nil {
return fmt.Errorf("failed to fetch rates from API: %w", err)
}
defer resp.Body.Close()
var apiResp struct {
Rates map[string]float64 `json:"rates"`
}
if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil {
return fmt.Errorf("failed to decode rates API response: %w", err)
}
data = RatesCache{
Timestamp: time.Now(),
Rates: apiResp.Rates,
}
// Save to cache asynchronously
go func() {
file, err := os.Create(cacheFile)
if err == nil {
json.NewEncoder(file).Encode(data)
file.Close()
}
}()
}
// Populate unit registry with rates
for code, rate := range data.Rates {
// Avoid division by zero
if rate > 0 {
factor := decimal.NewFromFloat(1.0).Div(decimal.NewFromFloat(rate))
unitRegistry[code] = UnitInfo{factor, DimCurrency, code}
}
}
unitRegistry["EUR"] = UnitInfo{decimal.NewFromFloat(1.0), DimCurrency, "EUR"}
return nil
}
// eurRatesLookup returns the conversion factor of the currency to EUR.
func eurRatesLookup(code string) decimal.Decimal {
if u, ok := unitRegistry[code]; ok && u.Dimension == DimCurrency {
return u.Factor
}
return decimal.NewFromFloat(1.0)
}