diff --git a/client.go b/client.go index 231552f..0135854 100644 --- a/client.go +++ b/client.go @@ -1,59 +1,23 @@ package dexcomClient -import ( - "context" - "fmt" - "net/http" - "os/exec" +import "fmt" - "github.com/gorilla/mux" -) - -type DexcomClient struct { - AuthCode string - DexcomToken string - config *Config - oAuthToken *Token - logger +type Client struct { + token string + sandbox bool } -func NewClient(config *Config) *DexcomClient { - dc := &DexcomClient{ - config: config, - logger: &defaultLogger{config: config}, - } - - if config.IsDev { - fmt.Println("Dev server starting on :8000") - url := config.getBaseUrl() + "/v1/oauth2/login?client_id=" + config.ClientId + "&redirect_uri=" + config.RedirectURI + "&response_type=code&scope=offline_access" - fmt.Println(url) - defer dc.startDevServer(url) +func (c *Client) getURL(path string) string { + base := baseUrl + if c.sandbox { + base = sandboxUrl } - return dc + return fmt.Sprintf("%s%s", base, path) } -func NewClientWithToken(config *Config, token *Token) *DexcomClient { - return &DexcomClient{ - config: config, - oAuthToken: token, - logger: &defaultLogger{config: config}, +func NewClient(token string) *Client { + dc := &Client{ + token: token, } -} - -func (client *DexcomClient) startDevServer(url string) { - server := &http.Server{Addr: ":8000"} - - router := mux.NewRouter() - router.Path("/oauth").Queries("code", "{code}").HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - client.AuthCode = req.FormValue("code") - _, err := client.GetOauthToken() - if err != nil { - panic(err) - } - server.Shutdown(context.Background()) - }) - - server.Handler = router - exec.Command("open", url) - server.ListenAndServe() + return dc } diff --git a/config.go b/config.go index 37412a6..b4ac379 100644 --- a/config.go +++ b/config.go @@ -1,105 +1,6 @@ package dexcomClient -import ( - "encoding/json" - "errors" - "io/ioutil" - "net/http" - "strings" - "time" -) - const ( - baseUrl = "https://api.dexcom.com" - devUrl = "https://sandbox-api.dexcom.com" - authUrl = "/v1/oauth2/token" + baseUrl = "https://api.dexcom.com" + sandboxUrl = "https://sandboxUrl-api.dexcom.com" ) - -type Config struct { - ClientId string - ClientSecret string - RedirectURI string - Sandbox bool - IsDev bool - IsDebug bool - Logging bool -} - -type Token struct { - AccessToken string `json:"access_token"` - ExpiresIn int64 `json:"expires_in"` - TokenType string `json:"token_type"` - RefreshToken string `json:"refresh_token"` - TimeRefreshed int64 -} - -func (client *DexcomClient) GetOauthToken() (*Token, error) { - - if client.oAuthToken != nil { - expired := client.oAuthToken.TimeRefreshed + client.oAuthToken.ExpiresIn <= time.Now().Unix() - if expired { - goto REQUEST - } - return client.oAuthToken, nil - } - - REQUEST: - token, err := client.authenticate() - if err != nil { - return nil, err - } - client.oAuthToken = token - token.TimeRefreshed = time.Now().Unix() - return token, err -} - -func (client *DexcomClient) GetOAuthTokenWithAuth(authorization string) (*Token, error) { - client.oAuthToken = &Token{} - token, err := client.authenticate() - if err != nil { - return nil, err - } - client.oAuthToken = token - token.TimeRefreshed = time.Now().Unix() - return token, err -} - -func (client *DexcomClient) SetOAuthToken(token *Token) { - client.oAuthToken = token -} - -func (c *Config) getBaseUrl() string { - if c.Sandbox { - return devUrl - } - return baseUrl -} - -func (client *DexcomClient) authenticate() (*Token, error) { - req, _ := http.NewRequest("POST", client.config.getBaseUrl()+authUrl, client.getAuthPayload()) - req.Header.Add("content-type", "application/x-www-form-urlencoded") - req.Header.Add("cache", "no-cache") - resp, err := http.DefaultClient.Do(req) - if err != nil { - return nil, err - } - - body, _ := ioutil.ReadAll(resp.Body) - var token Token - json.Unmarshal(body, &token) - - if token.RefreshToken == "" { - return nil, errors.New("auth error") - } - - return &token, nil -} - -func (client *DexcomClient) getAuthPayload() *strings.Reader { - clientSecret := "client_secret=" + client.config.ClientSecret + "&" - clientId := "client_id=" + client.config.ClientId + "&" - code := "code=" + client.AuthCode + "&" - redirectUri := "redirect_uri=" + client.config.RedirectURI - b := clientSecret + clientId + code + "grant_type=authorization_code&" + redirectUri - return strings.NewReader(b) -} diff --git a/devices.go b/devices.go index 8eb0b2b..d368761 100644 --- a/devices.go +++ b/devices.go @@ -2,48 +2,40 @@ package dexcomClient import ( "encoding/json" - "io/ioutil" + "fmt" "net/http" + + "github.com/ryan-berger/dexcomClient/model" ) -type Device struct { - Model string - LastUploadDate string - AlertSettings []AlertSetting -} -type AlertSetting struct { - AlertName string - Value int - Unit string - Snooze int - Delay int - Enabled bool - SystemTime string - DisplayTime string +type deviceResponse struct { + Devices []model.Device `json:"devices"` } -func (client *DexcomClient) GetDevices() ([]*Device, error) { - url := client.config.getBaseUrl() + "/v1/users/self/devices" - req, _ := http.NewRequest("GET", url, nil) +func (c *Client) GetDevices() ([]model.Device, error) { + url := c.getURL("/v2/users/self/devices") + req, err := http.NewRequest("GET", url, nil) - req.Header.Add("authorization", "Bearer ") - resp, err := http.DefaultClient.Do(req) + // TODO: if err != nil { return nil, err } - body, err := ioutil.ReadAll(resp.Body) + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.token)) + resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } - var devices []*Device + var devResp deviceResponse + err = json. + NewDecoder(resp.Body). + Decode(&devResp) - err = json.Unmarshal(body, &devices) if err != nil { return nil, err } - return devices, err + return devResp.Devices, nil } diff --git a/egvs.go b/egvs.go index 65be199..3ba09cd 100644 --- a/egvs.go +++ b/egvs.go @@ -2,13 +2,8 @@ package dexcomClient import ( "encoding/json" - "errors" - "fmt" - "io/ioutil" - "math" "net/http" "sort" - "strconv" "time" ) @@ -31,13 +26,16 @@ type EGV struct { TrendRate float64 } -type Range struct { +type queryParam struct { StartDate string EndDate string } -func (client *DexcomClient) GetEGVs(startDate string, endDate string) ([]*EGVResponse, error) { - ranges := getEGVRanges(startDate, endDate) +func (c *Client) GetEGVs(startDate string, endDate string) ([]EGVResponse, error) { + ranges, err := getEGVRanges(startDate, endDate) + if err != nil { + return nil, err + } // egv channel to allow for concurrency in the requests egvChan := make(chan *EGVResponse) @@ -45,10 +43,10 @@ func (client *DexcomClient) GetEGVs(startDate string, endDate string) ([]*EGVRes errChan := make(chan error) for i, r := range ranges { - go client.getRange(rangeRequest{Range: r, index: i}, egvChan, errChan) + go c.getRange(rangeRequest{queryParam: r, index: i}, egvChan, errChan) } - var responses []*EGVResponse + var responses []EGVResponse // catch all of the requests. There should only be // len(ranges) so we can just wait for all of those @@ -56,7 +54,7 @@ func (client *DexcomClient) GetEGVs(startDate string, endDate string) ([]*EGVRes for i, j := 0, len(ranges); i < j; i++ { select { case response := <-egvChan: - responses = append(responses, response) + responses = append(responses, *response) case reqErr := <-errChan: return nil, reqErr } @@ -66,7 +64,7 @@ func (client *DexcomClient) GetEGVs(startDate string, endDate string) ([]*EGVRes return responses[i].index < responses[j].index }) - var egvResponses []*EGVResponse + var egvResponses []EGVResponse for _, response := range responses { egvResponses = append(egvResponses, response) @@ -75,49 +73,33 @@ func (client *DexcomClient) GetEGVs(startDate string, endDate string) ([]*EGVRes return egvResponses, nil } -func (client *DexcomClient) getRange(request rangeRequest, resultChan chan *EGVResponse, errChan chan error) { +func (c *Client) getRange(request rangeRequest, resultChan chan *EGVResponse, errChan chan error) { // Make request with url date range - req, _ := http.NewRequest("GET", - urlWithDateRange(client.config, egvUrl, request.StartDate, request.EndDate), nil) - - client.Debug("URL: " + urlWithDateRange(client.config, egvUrl, request.StartDate, request.EndDate)) - - token, err := client.GetOauthToken() - - if err != nil { - errChan <- err - return - } + req, _ := http.NewRequest("GET", "", nil) - req.Header.Add("Authorization", "Bearer "+token.AccessToken) resp, err := http.DefaultClient.Do(req) if err != nil { errChan <- err - panic(err) return } - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != 200 { - fmt.Println(string(body)) - panic(errors.New("Code " + strconv.FormatInt(int64(resp.StatusCode), 10))) - } var jsonResponse EGVResponse - jsonErr := json.Unmarshal(body, &jsonResponse) + jsonErr := json. + NewDecoder(resp.Body). + Decode(&jsonResponse) // deal with json parse error if jsonErr != nil { errChan <- jsonErr - panic(jsonErr) return } + jsonResponse.index = request.index resultChan <- &jsonResponse } type rangeRequest struct { - *Range + queryParam index int } @@ -126,41 +108,56 @@ func daysDuration(days int) time.Duration { return time.Duration(days) * 24 * time.Hour } +func offset(t time.Time, days int) string { + return t. + Add(daysDuration(days)). + Format(dateTimeString) +} + // Get ranges inclusively given a start and end date -func getEGVRanges(start string, end string) []*Range { +func getEGVRanges(start string, end string) ([]queryParam, error) { // Parse dates - startDate, _ := time.Parse(dateTimeString, start) - endDate, _ := time.Parse(dateTimeString, end) + var startDate, endDate time.Time + var err error + + startDate, err = time.Parse(dateTimeString, start) + if err != nil { + return nil, err + } + + endDate, err = time.Parse(dateTimeString, end) + if err != nil { + return nil, err + } endUTC := endDate.UTC() // Get diff between the dates in days - diff := endUTC.Sub(startDate) - daysDiff := diff.Hours() / 24 - - // Figure out how many months we have to request - // and subtract one because it may not be completely even - monthDistance := int(math.Floor(daysDiff / 90)) - - var ranges []*Range - - for i := 0; i <= monthDistance; i++ { - var requestEndDay time.Time - - // Start date is 90 * 2i - requestStartDay := startDate.Add(daysDuration(90*i + i)) - if i == monthDistance { - // If we are at the end of the list, - // don't use the diff formula because then we could - // be going over the end date - requestEndDay = endDate - } else { - // End date is (90 * 2i) + 90 - // so that we are 90 days ahead of the start date - requestEndDay = startDate.Add(daysDuration((90*i + i) + 90)) - } - ranges = append(ranges, - &Range{StartDate: requestStartDay.Format(dateTimeString), EndDate: requestEndDay.Format(dateTimeString)}) + diff := endUTC.Sub(startDate.UTC()) + days := int(diff.Hours() / 24) + + var ranges []queryParam + + for i := 0; i < days / 90; i++ { + ranges = append( + ranges, + queryParam{ + StartDate: offset(startDate, i * 90), + EndDate: offset(startDate, (i + 1) * 90), + }, + ) } - return ranges + + if leftover := days % 90; leftover != 0 { + startOffset := days - leftover + ranges = append( + ranges, + queryParam{ + StartDate: offset(startDate, startOffset), + EndDate: offset(startDate, days), + }, + ) + } + + return ranges, nil } diff --git a/events.go b/events.go index 9534276..47ee06b 100644 --- a/events.go +++ b/events.go @@ -2,47 +2,39 @@ package dexcomClient import ( "encoding/json" - "io/ioutil" + "fmt" "net/http" -) - -const eventsUrl = "v1/users/self/events" -type Event struct { - SystemTime string `json:"systemTime"` - DisplayTime string `json:"displayTime"` - EventType string `json:"eventType"` - EventSubType string `json:"eventSubType"` - Value int `json:"value"` - Unit string `json:"unit"` -} + "github.com/ryan-berger/dexcomClient/model" +) -type EventResponse struct { - Events []Event `json:"events"` +type eventResponse struct { + Events []model.Event `json:"events"` } -func (client *DexcomClient) GetEvents(startDate, endDate string) ([]Event, error) { - req, _ := http.NewRequest("GET", - urlWithDateRange(client.config, eventsUrl, startDate, endDate), nil) - - token, err := client.GetOauthToken() +func (c *Client) GetEvents(startDate, endDate string) ([]model.Event, error) { + path := fmt.Sprintf("/v2/users/self/egvs?startDate=%s&endDate=%s", startDate, endDate) + req, err := http.NewRequest("GET", c.getURL(path), nil) if err != nil { return nil, err } - req.Header.Add("authorization", "Bearer " + token.AccessToken) + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.token)) resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } - body, _ := ioutil.ReadAll(resp.Body) - defer resp.Body.Close() + var response eventResponse + err = json. + NewDecoder(resp.Body). + Decode(&resp) - var response EventResponse - json.Unmarshal(body, &response) + if err != nil { + return nil, err + } return response.Events, nil } diff --git a/evgs_test.go b/evgs_test.go new file mode 100644 index 0000000..01969e1 --- /dev/null +++ b/evgs_test.go @@ -0,0 +1,32 @@ +package dexcomClient + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRanges(t *testing.T) { + tests := []struct{ + start string + end string + expected []queryParam + isErr bool + } { + // empty + {isErr: true}, + {start: "2020-01-02T15:04:05", isErr: true}, + {start: "2020-01-02T15:04:05", end: "2020-04-01T15:04:05"}, + {start: "2020-01-02T15:04:05", end: "2020-04-02T15:04:05"}, + } + + for _, test := range tests { + ranges, err := getEGVRanges(test.start, test.end) + if test.isErr { + assert.Error(t, err) + continue + } + assert.NoError(t, err) + assert.Equal(t, test.expected, ranges) + } +} \ No newline at end of file diff --git a/go.mod b/go.mod index e90aa5f..ba16db6 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,9 @@ module github.com/ryan-berger/dexcomClient -go 1.14 +go 1.16 -require github.com/gorilla/mux v1.7.4 +require ( + github.com/kr/pretty v0.1.0 // indirect + github.com/stretchr/testify v1.4.0 + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect +) diff --git a/go.sum b/go.sum index abb0613..f17d286 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,18 @@ -github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= -github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/logger.go b/logger.go deleted file mode 100644 index a712a67..0000000 --- a/logger.go +++ /dev/null @@ -1,31 +0,0 @@ -package dexcomClient - -import "fmt" - -type logger interface { - Debug(string) - Dev(string) - Log(string) -} - -type defaultLogger struct { - config *Config -} - -func (logger *defaultLogger) Log(string string) { - if logger.config.Logging { - fmt.Println("Log: ", string) - } -} - -func (logger *defaultLogger) Debug(string string) { - if logger.config.IsDebug { - fmt.Println("Debug: ", string) - } -} - -func (logger *defaultLogger) Dev(string string) { - if logger.config.IsDev { - fmt.Println("Dev: ", string) - } -} diff --git a/model/calibrations.go b/model/calibrations.go new file mode 100644 index 0000000..394030c --- /dev/null +++ b/model/calibrations.go @@ -0,0 +1,35 @@ +package model + +import "time" + + +// CalibrationResponse is a struct that holds information about +// user calibration data. It can be retrieved from the +// /calibrations endpoint. From the docs, the JSON looks like: +// { +// "calibrations": [ +// { +// "systemTime": "2017-06-17T03:59:11", +// "displayTime": "2017-06-16T19:59:11", +// "unit": "mg/dL", +// "value": 124 +// }, +// { +// "systemTime": "2017-06-16T16:03:28", +// "displayTime": "2017-06-16T08:03:28", +// "unit": "mg/dL", +// "value": 86 +// } +// ] +//} +type CalibrationResponse struct { + Calibrations []Calibration `json:"calibrations"` +} + +// Calibration represents a single calibration event for a Dexcom sensor +type Calibration struct { + SystemTime time.Time `json:"systemTime"` + DisplayTime time.Time `json:"displayTime"` + Unit string `json:"unit"` + Value int `json:"value"` +} diff --git a/model/data_range.go b/model/data_range.go new file mode 100644 index 0000000..7d52137 --- /dev/null +++ b/model/data_range.go @@ -0,0 +1,25 @@ +package model + +import "time" + + +// DataRange is an object which can inform further requests and +// valid date ranges for requesting user data +type DataRange struct { + Calibrations Range + EVGs Range + Events []Event +} + +// Time is a struct that handles both systemTime and displayTime which +// is used frequently throughout the Dexcom API +type Time struct { + SystemTime time.Time `json:"systemTime"` + DisplayTime time.Time `json:"displayTime"` +} + +// Range is a struct that holds a start and end Time +type Range struct { + Start Time + End Time +} \ No newline at end of file diff --git a/model/devices.go b/model/devices.go new file mode 100644 index 0000000..245f624 --- /dev/null +++ b/model/devices.go @@ -0,0 +1,21 @@ +package model + +type Device struct { + Model string `json:"model"` + LastUploadDate string `json:"lastUploadDate"` + AlertSettings []AlertSetting +} + +type AlertSetting struct { + AlertName string `json:"alertName"` + Value int `json:"value"` + Unit string `json:"unit"` + Snooze int `json:"snooze"` + Delay int `json:"delay"` + Enabled bool `json:"enabled"` + Time +} + +type DeviceResponse struct { + Devices []Device `json:"devices"` +} diff --git a/model/egvs.go b/model/egvs.go new file mode 100644 index 0000000..e7284c8 --- /dev/null +++ b/model/egvs.go @@ -0,0 +1,66 @@ +package model + +// This file contains models for the /egv endpoint. The /evg endpoint +// has a single handler. A sample request/response would look like: +// GET /egv HTTP/1.1 +// { +// "events": [ +// { +// "systemTime": "2018-02-29T20:59:33", +// "displayTime": "2018-02-29T12:59:33", +// "eventType": "carbs", +// "eventSubType": null, +// "value": 35, +// "unit": "grams", +// "eventId": "601620ec-8caf-4244-81ff-cd946669806e", +// "eventStatus": "created" +// }, +// { +// "systemTime": "2018-02-29T20:59:04", +// "displayTime": "2018-02-29T12:59:04", +// "eventType": "insulin", +// "eventSubType": "longActing", +// "value": 17, +// "unit": "units", +// "eventId": "4b790d7c-542e-4673-b1c4-92e4d8d4550a", +// "eventStatus": "created" +// }, +// { +// "systemTime": "2018-02-29T20:58:53", +// "displayTime": "2018-02-29T12:58:53", +// "eventType": "insulin", +// "eventSubType": "fastActing", +// "value": 2.5, +// "unit": "units", +// "eventId": "a46b5c18-5fbd-4bb4-bcfc-5c9582eede9c", +// "eventStatus": "created" +// } +// { +// "systemTime": "2018-02-29T20:58:37", +// "displayTime": "2018-02-29T12:58:37", +// "eventType": "insulin", +// "eventSubType": "fastActing", +// "value": 2, +// "unit": "units", +// "eventId": "8705ff4c-e0d4-46a2-ad20-0839eafdfa32", +// "eventStatus": "deleted" +// } +// ] +//} + +// EGVResponse is a struct to hold a response from the /egv endpoint +type EGVResponse struct { + Unit string `json:"unit"` + RateUnit string `json:"rateUnit"` + EGVs []EGV `json:"egvs"` +} + +// EGV is an Estimated Glucose Value object that contains information +// about a sensor's measurements +type EGV struct { + RealTimeValue int `json:"realTimeValue"` + SmoothedValue int `json:"smoothedValue"` + Trend string `json:"trend"` + TrendRate float64 `json:"trendRate"` + Time +} diff --git a/model/event.go b/model/event.go new file mode 100644 index 0000000..bed6ae0 --- /dev/null +++ b/model/event.go @@ -0,0 +1,67 @@ +package model + +// This file holds all models for the /events endpoint, which has one handler. +// / +// The response represents the JSON object: +// { +// "events": [ +// { +// "systemTime": "2018-02-29T20:59:33", +// "displayTime": "2018-02-29T12:59:33", +// "eventType": "carbs", +// "eventSubType": null, +// "value": 35, +// "unit": "grams", +// "eventId": "601620ec-8caf-4244-81ff-cd946669806e", +// "eventStatus": "created" +// }, +// { +// "systemTime": "2018-02-29T20:59:04", +// "displayTime": "2018-02-29T12:59:04", +// "eventType": "insulin", +// "eventSubType": "longActing", +// "value": 17, +// "unit": "units", +// "eventId": "4b790d7c-542e-4673-b1c4-92e4d8d4550a", +// "eventStatus": "created" +// }, +// { +// "systemTime": "2018-02-29T20:58:53", +// "displayTime": "2018-02-29T12:58:53", +// "eventType": "insulin", +// "eventSubType": "fastActing", +// "value": 2.5, +// "unit": "units", +// "eventId": "a46b5c18-5fbd-4bb4-bcfc-5c9582eede9c", +// "eventStatus": "created" +// } +// { +// "systemTime": "2018-02-29T20:58:37", +// "displayTime": "2018-02-29T12:58:37", +// "eventType": "insulin", +// "eventSubType": "fastActing", +// "value": 2, +// "unit": "units", +// "eventId": "8705ff4c-e0d4-46a2-ad20-0839eafdfa32", +// "eventStatus": "deleted" +// } +// ] +// } + +// EventResponse holds an array of events an can be fetched +// via the /events +type EventResponse struct { + Events []Event `json:"events"` +} + +// Event is a struct to hold arbitrary Dexcom user events such as +// exercise events, +type Event struct { + ID string `json:"id"` + Type string `json:"eventType"` + SubType *string `json:"eventSubType"` + Value int `json:"value"` + Unit string `json:"unit"` + Status string `json:"eventStatus"` + Time +} diff --git a/model/statistics.go b/model/statistics.go new file mode 100644 index 0000000..02446ae --- /dev/null +++ b/model/statistics.go @@ -0,0 +1,47 @@ +package model + +import "time" + +// StatsRequest is a json struct for all statistical parameters. +type StatsRequest struct { + Name string `json:"name"` + StartTime time.Time `json:"startTime"` + EndTime time.Time `json:"endTime"` + EGVRanges []EGVRange `json:"egvRanges"` +} + +// RangeName is a typedef to create an enum of the form: +// enum RangeName { Low, High, UrgentLow } +type RangeName string + +const ( + Low RangeName = "low" + High RangeName = "high" + UrgentLow RangeName = "urgentLow" +) + +type EGVRange struct { + Name RangeName + Bound int +} + +type StatsResponse struct { + HypoglycemiaRisk string `json:"hypoglycemiaRisk"` + Min int `json:"min"` + Max int `json:"max"` + Mean float64 `json:"mean"` + Median int `json:"median"` + Variance float64 `json:"variance"` + StdDev float64 `json:"stdDev"` + Sum int `json:"sum"` + Q1 int `json:"q1"` + Q2 int `json:"q2"` + Q3 int `json:"q3"` + UtilizationPercent float64 `json:"utilizationPercent"` + MeanDailyCalibrations float64 `json:"meanDailyCalibrations"` + NDays int `json:"nDays"` + NValues int `json:"nValues"` + NUrgentLow int `json:"nUrgentLow"` + NBelowRange int `json:"nBelowRange"` + NWithinRange int `json:"nWithinRange"` +} diff --git a/url.go b/url.go deleted file mode 100644 index 1ad7ad9..0000000 --- a/url.go +++ /dev/null @@ -1,6 +0,0 @@ -package dexcomClient - -func urlWithDateRange(config *Config, endpoint string, start string, end string) string { - url := config.getBaseUrl() + endpoint + "?startDate=" + start + "&endDate=" + end - return url -} diff --git a/user.go b/user.go deleted file mode 100644 index 820261b..0000000 --- a/user.go +++ /dev/null @@ -1,84 +0,0 @@ -package dexcomClient - -import ( - "bytes" - "encoding/json" - "errors" - "io/ioutil" - "net/http" - "strings" -) - -const ( - applicationId = "d89443d2-327c-4a6f-89e5-496bbb0317db" - agent = "Dexcom Share/3.0.2.11 CFNetwork/711.2.23 Darwin/14.0.0" - loginUrl = "https://share1.dexcom.com/ShareWebServices/Services/General/LoginPublisherAccountByName" - latestGlucoseUrl = "https://share1.dexcom.com/ShareWebServices/Services/Publisher/ReadPublisherLatestGlucoseValues" -) - -type RealTimeData struct { - DeviceTime string `json:"DT"` - ServerTime string `json:"ST"` - Trend int - Value float64 -} - -func (client *DexcomClient) getLatestGlucoseUrl() string { - return latestGlucoseUrl + "?sessionID=" + client.DexcomToken + "&minutes=1440&maxCount=1" -} - -func (client *DexcomClient) GetSessionID(username, password string) error { - payload := map[string]string{ - "accountName": username, - "password": password, - "applicationId": applicationId, - } - payloadBytes, _ := json.Marshal(&payload) - payloadReader := bytes.NewReader(payloadBytes) - req, _ := http.NewRequest("POST", loginUrl, payloadReader) - req.Header.Add("user-agent", agent) - req.Header.Add("content-type", "application/json") - req.Header.Add("accept", "application/json") - resp, err := http.DefaultClient.Do(req) - - if err != nil { - return err - } - - sessionId, _ := ioutil.ReadAll(resp.Body) - - var id []byte - // Strip quotations from the response - for _, b := range sessionId { - if b != 34 { - id = append(id, b) - } - } - - client.DexcomToken = string(id) - return nil -} - -func (client *DexcomClient) GetRealTimeData() (*RealTimeData, error) { - url := client.getLatestGlucoseUrl() - - req, _ := http.NewRequest("POST", url, strings.NewReader("")) - req.Header.Add("user-agent", agent) - req.Header.Add("content-type", "application/json") - req.Header.Add("accept", "application/json") - resp, err := http.DefaultClient.Do(req) - - if err != nil { - return nil, err - } - - body, _ := ioutil.ReadAll(resp.Body) - - var realTimeData []RealTimeData - json.Unmarshal(body, &realTimeData) - - if len(realTimeData) == 0 { - return nil, errors.New("no real time data returned") - } - return &realTimeData[0], nil -}