this repo has no description
1package data
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "io"
8 "log/slog"
9 "net/http"
10 "sync"
11 "time"
12
13 "github.com/eagleusb/proxycon/internal/types"
14 "github.com/eagleusb/proxycon/internal/utility"
15)
16
17const (
18 datasourceURL = "https://europe-west1-bitstack-test.cloudfunctions.net/coding-challenge-v2"
19)
20
21type BitcoinPrices struct {
22 mu sync.RWMutex
23 prices []types.Price
24 client *http.Client
25}
26
27// NewBitcoinPrices return a bitcoin prices http loader.
28func NewBitcoinPrices() *BitcoinPrices {
29 return &BitcoinPrices{
30 client: &http.Client{
31 Timeout: 60 * time.Second,
32 Transport: utility.NewLoggingRoundTripper(&http.Transport{
33 MaxIdleConns: 10,
34 MaxIdleConnsPerHost: 10,
35 IdleConnTimeout: 300 * time.Second,
36 }),
37 },
38 }
39}
40
41// SetPrices set the prices from upstream and store them safely (mutex lock).
42func (f *BitcoinPrices) SetPrices(ctx context.Context) error {
43 prices, err := f.fetchPrices(ctx)
44 if err != nil {
45 return err
46 }
47
48 f.mu.Lock()
49 f.prices = prices
50 f.mu.Unlock()
51
52 slog.Info("loaded bitcoin prices from datasource", "count", len(prices))
53 return nil
54}
55
56// GetPrices get the bitcoin prices from memory.
57func (f *BitcoinPrices) GetPrices() []types.Price {
58 f.mu.RLock()
59 defer f.mu.RUnlock()
60 return f.prices
61}
62
63// fetchPrices implement the upstream fetch of bitcoin prices.
64func (f *BitcoinPrices) fetchPrices(ctx context.Context) ([]types.Price, error) {
65 req, err := http.NewRequestWithContext(ctx, http.MethodGet, datasourceURL, nil)
66 if err != nil {
67 return nil, fmt.Errorf("create request: %w", err)
68 }
69
70 resp, err := f.client.Do(req)
71 if err != nil {
72 return nil, fmt.Errorf("fetch datasource: %w", err)
73 }
74 defer resp.Body.Close()
75
76 if resp.StatusCode != http.StatusOK {
77 body, _ := io.ReadAll(io.LimitReader(resp.Body, 1024))
78 return nil, fmt.Errorf("datasource returned %d: %s", resp.StatusCode, string(body))
79 }
80
81 var datasourceResp types.DatasourceResponse
82 if err := json.NewDecoder(resp.Body).Decode(&datasourceResp); err != nil {
83 return nil, fmt.Errorf("decode datasource response: %w", err)
84 }
85
86 return datasourceResp.Data, nil
87}