diff --git a/apps/api/internal/service/cdi.go b/apps/api/internal/service/cdi.go index c8db2ae..4e5f87d 100644 --- a/apps/api/internal/service/cdi.go +++ b/apps/api/internal/service/cdi.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "net/http" + "sync" "time" "financeiro-carvalho/internal/model" @@ -13,10 +14,58 @@ import ( const bcbCDIURL = "https://api.bcb.gov.br/dados/serie/bcdata.sgs.12/dados?formato=json&dataInicial=%s&dataFinal=%s" +// cdiRateCache guarda as taxas BCB em memória até as 23:59:59 do dia do fetch. +type cdiRateCache struct { + mu sync.RWMutex + rates map[string]float64 // YYYY-MM-DD → taxa diária + minDate string + maxDate string + expiresAt time.Time +} + +func (c *cdiRateCache) valid() bool { + return !c.expiresAt.IsZero() && time.Now().Before(c.expiresAt) +} + +func (c *cdiRateCache) covers(startDate, endDate string) bool { + return c.valid() && c.minDate != "" && startDate >= c.minDate && endDate <= c.maxDate +} + +func (c *cdiRateCache) get(startDate, endDate string) []bcbEntry { + start, _ := time.Parse("2006-01-02", startDate) + end, _ := time.Parse("2006-01-02", endDate) + var out []bcbEntry + for d := start; !d.After(end); d = d.AddDate(0, 0, 1) { + key := d.Format("2006-01-02") + if r, ok := c.rates[key]; ok { + out = append(out, bcbEntry{date: key, value: r}) + } + } + return out +} + +func (c *cdiRateCache) put(entries []bcbEntry, startDate, endDate string) { + if c.rates == nil { + c.rates = make(map[string]float64, len(entries)) + } + for _, e := range entries { + c.rates[e.date] = e.value + } + if c.minDate == "" || startDate < c.minDate { + c.minDate = startDate + } + if c.maxDate == "" || endDate > c.maxDate { + c.maxDate = endDate + } + now := time.Now() + c.expiresAt = time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, now.Location()) +} + type CDIYieldService struct { accountRepo repository.AccountRepoWithYield txRepo repository.ManualTransactionRepository client *http.Client + cache cdiRateCache } func NewCDIYieldService(accountRepo repository.AccountRepoWithYield, txRepo repository.ManualTransactionRepository) *CDIYieldService { @@ -109,7 +158,21 @@ type bcbEntry struct { } func (s *CDIYieldService) fetchCDIRates(startDate, endDate string) ([]bcbEntry, error) { - // BCB API uses DD/MM/YYYY format + s.cache.mu.RLock() + if s.cache.covers(startDate, endDate) { + entries := s.cache.get(startDate, endDate) + s.cache.mu.RUnlock() + return entries, nil + } + s.cache.mu.RUnlock() + + s.cache.mu.Lock() + defer s.cache.mu.Unlock() + // double-check after acquiring write lock + if s.cache.covers(startDate, endDate) { + return s.cache.get(startDate, endDate), nil + } + start, _ := time.Parse("2006-01-02", startDate) end, _ := time.Parse("2006-01-02", endDate) url := fmt.Sprintf(bcbCDIURL, start.Format("02/01/2006"), end.Format("02/01/2006")) @@ -128,16 +191,17 @@ func (s *CDIYieldService) fetchCDIRates(startDate, endDate string) ([]bcbEntry, return nil, err } - var out []bcbEntry + var entries []bcbEntry for _, r := range raw { - // BCB date format: DD/MM/YYYY → convert to YYYY-MM-DD t, err := time.Parse("02/01/2006", r.Data) if err != nil { continue } var v float64 fmt.Sscanf(r.Valor, "%f", &v) - out = append(out, bcbEntry{date: t.Format("2006-01-02"), value: v}) + entries = append(entries, bcbEntry{date: t.Format("2006-01-02"), value: v}) } - return out, nil + + s.cache.put(entries, startDate, endDate) + return entries, nil }