initial commit [141.14.140.180,mike]
This commit is contained in:
+95
@@ -0,0 +1,95 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user