this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(datasource) add bitcoin datasource fetcher

eagleusb 7ac2bc7c 15fb8d85

+113
+83
internal/data/bitcoin.go
··· 1 + package data 2 + 3 + import ( 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 + ) 15 + 16 + const ( 17 + datasourceURL = "https://europe-west1-bitstack-test.cloudfunctions.net/coding-challenge-v2" 18 + ) 19 + 20 + type BitcoinPrices struct { 21 + mu sync.RWMutex 22 + prices []types.Price 23 + client *http.Client 24 + } 25 + 26 + // NewFetcher returns a Fetcher ready to load data. 27 + func NewBitcoinPrices() *BitcoinPrices { 28 + return &BitcoinPrices{ 29 + client: &http.Client{ 30 + Timeout: 60 * time.Second, 31 + Transport: &http.Transport{ 32 + MaxIdleConns: 10, 33 + MaxIdleConnsPerHost: 10, 34 + IdleConnTimeout: 300 * time.Second, 35 + }, 36 + }, 37 + } 38 + } 39 + 40 + func (f *BitcoinPrices) SetPrices(ctx context.Context) error { 41 + prices, err := f.fetchPrices(ctx) 42 + if err != nil { 43 + return err 44 + } 45 + 46 + f.mu.Lock() 47 + f.prices = prices 48 + f.mu.Unlock() 49 + 50 + slog.Info("loaded bitcoin prices from datasource", "count", len(prices)) 51 + return nil 52 + } 53 + 54 + func (f *BitcoinPrices) GetPrices() []types.Price { 55 + f.mu.RLock() 56 + defer f.mu.RUnlock() 57 + return f.prices 58 + } 59 + 60 + func (f *BitcoinPrices) fetchPrices(ctx context.Context) ([]types.Price, error) { 61 + req, err := http.NewRequestWithContext(ctx, http.MethodGet, datasourceURL, nil) 62 + if err != nil { 63 + return nil, fmt.Errorf("create request: %w", err) 64 + } 65 + 66 + resp, err := f.client.Do(req) 67 + if err != nil { 68 + return nil, fmt.Errorf("fetch datasource: %w", err) 69 + } 70 + defer resp.Body.Close() 71 + 72 + if resp.StatusCode != http.StatusOK { 73 + body, _ := io.ReadAll(io.LimitReader(resp.Body, 1024)) 74 + return nil, fmt.Errorf("datasource returned %d: %s", resp.StatusCode, string(body)) 75 + } 76 + 77 + var datasourceResp types.DatasourceResponse 78 + if err := json.NewDecoder(resp.Body).Decode(&datasourceResp); err != nil { 79 + return nil, fmt.Errorf("decode datasource response: %w", err) 80 + } 81 + 82 + return datasourceResp.Data, nil 83 + }
+30
internal/types/model.go
··· 1 + package types 2 + 3 + // upstream bitcoin api format 4 + type Price struct { 5 + Price string `json:"price"` 6 + Timestamp string `json:"timestamp"` 7 + } 8 + 9 + type DatasourceResponse struct { 10 + Data []Price `json:"data"` 11 + } 12 + 13 + // downstream api (proxycon) api format 14 + type Entry struct { 15 + Amount string `json:"amount"` 16 + Timestamp string `json:"timestamp"` 17 + } 18 + 19 + type ListResponse struct { 20 + Start string `json:"start"` 21 + End string `json:"end"` 22 + Min string `json:"min"` 23 + Max string `json:"max"` 24 + NextCursor *int `json:"next_cursor,omitempty"` 25 + Data []Entry `json:"data"` 26 + } 27 + 28 + type PriceResponse struct { 29 + Data []Entry `json:"data"` 30 + }