···2323 client *http.Client
2424}
25252626-// NewFetcher returns a Fetcher ready to load data.
2626+// NewBitcoinPrices return a bitcoin prices http loader.
2727func NewBitcoinPrices() *BitcoinPrices {
2828 return &BitcoinPrices{
2929 client: &http.Client{
···3737 }
3838}
39394040+// SetPrices set the prices from upstream and store them safely (mutex lock).
4041func (f *BitcoinPrices) SetPrices(ctx context.Context) error {
4142 prices, err := f.fetchPrices(ctx)
4243 if err != nil {
···5152 return nil
5253}
53545555+// GetPrices get the bitcoin prices from memory.
5456func (f *BitcoinPrices) GetPrices() []types.Price {
5557 f.mu.RLock()
5658 defer f.mu.RUnlock()
5759 return f.prices
5860}
59616262+// fetchPrices implement the upstream fetch of bitcoin prices.
6063func (f *BitcoinPrices) fetchPrices(ctx context.Context) ([]types.Price, error) {
6164 req, err := http.NewRequestWithContext(ctx, http.MethodGet, datasourceURL, nil)
6265 if err != nil {
+6-6
internal/handlers/average.go
···1010 "github.com/eagleusb/proxycon/internal/types"
1111)
12121313-// averageEndpoint is the handler function for `GET /average?start=<timestamp>&end=<timestamp>`
1313+// averageEndpoint is the handler function for `GET /average?start=<timestamp>&end=<timestamp>`.
1414func (h *HttpServer) averageEndpoint(w http.ResponseWriter, r *http.Request) {
1515 startStr := r.URL.Query().Get("start")
1616 endStr := r.URL.Query().Get("end")
17171818 if startStr == "" || endStr == "" {
1919- http.Error(w, "missing query parameters: start and end are required", http.StatusBadRequest)
1919+ writeJSONError(w, "missing query parameters: start and end are required", http.StatusBadRequest)
2020 return
2121 }
22222323 startTime, err := time.Parse(time.RFC3339, startStr)
2424 if err != nil {
2525- http.Error(w, "invalid start timestamp: must be RFC3339 format", http.StatusBadRequest)
2525+ writeJSONError(w, "invalid start timestamp: must be RFC3339 format", http.StatusBadRequest)
2626 return
2727 }
28282929 endTime, err := time.Parse(time.RFC3339, endStr)
3030 if err != nil {
3131- http.Error(w, "invalid end timestamp: must be RFC3339 format", http.StatusBadRequest)
3131+ writeJSONError(w, "invalid end timestamp: must be RFC3339 format", http.StatusBadRequest)
3232 return
3333 }
34343535 if startTime.After(endTime) {
3636- http.Error(w, "start timestamp must be before end timestamp", http.StatusBadRequest)
3636+ writeJSONError(w, "start timestamp must be before end timestamp", http.StatusBadRequest)
3737 return
3838 }
3939···5151 }
52525353 if len(matched) == 0 {
5454- http.Error(w, "no prices found in the given range", http.StatusNotFound)
5454+ writeJSONError(w, "no prices found in the given range", http.StatusNotFound)
5555 return
5656 }
5757
···99 "github.com/eagleusb/proxycon/internal/types"
1010)
11111212-// listEndpoint is the handler function for `GET /list`
1212+// listEndpoint is the handler function for `GET /list`.
1313func (h *HttpServer) listEndpoint(w http.ResponseWriter, r *http.Request) {
1414 cursor := 0
15151616 if c := r.URL.Query().Get("cursor"); c != "" {
1717 parsed, err := strconv.Atoi(c)
1818 if err != nil || parsed < 0 {
1919- http.Error(w, "invalid cursor", http.StatusBadRequest)
1919+ writeJSONError(w, "invalid cursor", http.StatusBadRequest)
2020 return
2121 }
2222 cursor = parsed
···5050 }
5151}
52525353-// buildListResponse format the api response from a slice of bitcoin prices
5353+// buildListResponse format the api response from a slice of bitcoin prices.
5454func buildListResponse(page []types.Price) types.ListResponse {
5555 if len(page) == 0 {
5656 return types.ListResponse{Data: []types.Entry{}}
+4-4
internal/handlers/price.go
···99 "github.com/eagleusb/proxycon/internal/types"
1010)
11111212-// priceEndpoint is the handler function for `GET /price?at=<timestamp>`
1212+// priceEndpoint is the handler function for `GET /price?at=<timestamp>`.
1313func (h *HttpServer) priceEndpoint(w http.ResponseWriter, r *http.Request) {
1414 at := r.URL.Query().Get("at")
1515 if at == "" {
1616- http.Error(w, "missing query parameter: at", http.StatusBadRequest)
1616+ writeJSONError(w, "missing query parameter: at", http.StatusBadRequest)
1717 return
1818 }
19192020 if _, err := time.Parse(time.RFC3339, at); err != nil {
2121- http.Error(w, "invalid timestamp: must be RFC3339 format", http.StatusBadRequest)
2121+ writeJSONError(w, "invalid timestamp: must be RFC3339 format", http.StatusBadRequest)
2222 return
2323 }
2424···3838 }
3939 }
40404141- http.Error(w, "no price found for the given timestamp", http.StatusNotFound)
4141+ writeJSONError(w, "no price found for the given timestamp", http.StatusNotFound)
4242}
+3-1
internal/handlers/server.go
···1414 serveMux *http.ServeMux
1515}
16161717-// NewHttpServer returns the http api
1717+// NewHttpServer returns the http api.
1818func NewHttpServer(p *data.BitcoinPrices, pageSize int) *HttpServer {
1919 return &HttpServer{
2020 datasource: p,
···2323 }
2424}
25252626+// Listen starts the http server with http2 support.
2627func (h *HttpServer) Listen(addr string) error {
2728 h.registerHandler()
2829···4546 return nil
4647}
47484949+// registerHandler registers all api handlers.
4850func (h *HttpServer) registerHandler() {
4951 h.serveMux.HandleFunc("GET /list", h.listEndpoint)
5052 h.serveMux.HandleFunc("GET /price", h.priceEndpoint)