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) }