Package fitbit: Fix unsubscribing.
authorFlorian Forster <ff@octo.it>
Wed, 31 Jan 2018 09:53:31 +0000 (10:53 +0100)
committerFlorian Forster <ff@octo.it>
Wed, 31 Jan 2018 09:53:31 +0000 (10:53 +0100)
* Fix the expected JSON format. The example given in the documentation is
  wrong.
* Add a collection argument to the ListSubscribers() call. Listing all
  subscriptions (from all collections) requires all the scopes, which
  we don't (want to) have.
* Add sanity checking to ListSubscribers().

fitbit/fitbit.go

index 52b89d7..8a07fb7 100644 (file)
@@ -211,9 +211,13 @@ func (c *Client) Subscribe(ctx context.Context, collection string) error {
        return nil
 }
 
-func (c *Client) Unsubscribe(ctx context.Context, collection string) error {
+func (c *Client) unsubscribe(ctx context.Context, userID, collection, subscriptionID string) error {
+       if userID == "" {
+               userID = c.fitbitUserID
+       }
+
        url := fmt.Sprintf("https://api.fitbit.com/1/user/%s/%s/apiSubscriptions/%s.json",
-               c.fitbitUserID, collection, c.subscriberID(collection))
+               userID, collection, subscriptionID)
        req, err := http.NewRequest(http.MethodDelete, url, nil)
        if err != nil {
                return err
@@ -238,19 +242,19 @@ func (c *Client) Unsubscribe(ctx context.Context, collection string) error {
 }
 
 func (c *Client) UnsubscribeAll(ctx context.Context) error {
-       subs, err := c.ListSubscriptions(ctx)
-       if err != nil {
-               return err
-       }
-
        var errs appengine.MultiError
-       for _, s := range subs {
-               if s.OwnerType != "user" {
-                       log.Infof(ctx, "unexpected OwnerType: %q", s.OwnerType)
+
+       for _, collection := range []string{"activities", "sleep"} {
+               subs, err := c.ListSubscriptions(ctx, collection)
+               if err != nil {
+                       errs = append(errs, err)
                        continue
                }
-               if err := c.Unsubscribe(ctx, s.CollectionType); err != nil {
-                       errs = append(errs, err)
+
+               for _, sub := range subs {
+                       if err := c.unsubscribe(ctx, sub.OwnerID, sub.CollectionType, sub.SubscriptionID); err != nil {
+                               errs = append(errs, err)
+                       }
                }
        }
        if len(errs) != 0 {
@@ -260,34 +264,62 @@ func (c *Client) UnsubscribeAll(ctx context.Context) error {
        return nil
 }
 
-func (c *Client) ListSubscriptions(ctx context.Context) ([]Subscription, error) {
-       url := fmt.Sprintf("https://api.fitbit.com/1/user/%s/apiSubscriptions.json", c.fitbitUserID)
+func (c *Client) ListSubscriptions(ctx context.Context, collection string) ([]Subscription, error) {
+       url := fmt.Sprintf("https://api.fitbit.com/1/user/%s/%s/apiSubscriptions.json", c.fitbitUserID, collection)
        res, err := c.client.Get(url)
        if err != nil {
                return nil, fmt.Errorf("Get(%q) = %v", url, err)
        }
        defer res.Body.Close()
 
-       if res.StatusCode >= 400 && res.StatusCode != http.StatusNotFound {
-               data, _ := ioutil.ReadAll(res.Body)
-               log.Errorf(ctx, "listing subscriptions failed: status %d %q", res.StatusCode, data)
-               return nil, fmt.Errorf("listing subscriptions failed")
-       }
        if res.StatusCode == http.StatusNotFound {
-               log.Infof(ctx, "listing subscriptions: not found")
+               log.Infof(ctx, "get %q subscription: not found", collection)
                return nil, nil
        }
 
-       var subscriptions []Subscription
-       if err := json.NewDecoder(res.Body).Decode(&subscriptions); err != nil {
+       data, err := ioutil.ReadAll(res.Body)
+       if err != nil {
                return nil, err
        }
+       log.Debugf(ctx, "GET %s -> %s", url, data)
+
+       if res.StatusCode >= 400 {
+               return nil, fmt.Errorf("Get(%q) = %d", url, res.StatusCode)
+       }
+
+       var parsed struct {
+               Subscriptions []Subscription `json:"apiSubscriptions"`
+       }
+       if err := json.Unmarshal(data, &parsed); err != nil {
+               return nil, err
+       }
+
+       var errs appengine.MultiError
+       var ret []Subscription
+       for _, sub := range parsed.Subscriptions {
+               if sub.CollectionType != collection {
+                       errs = append(errs, fmt.Errorf("unexpected collection type: got %q, want %q", sub.CollectionType, collection))
+                       continue
+               }
+               if sub.SubscriptionID == "" {
+                       errs = append(errs, fmt.Errorf("missing subscription ID: %+v", sub))
+                       continue
+               }
+               if sub.OwnerID == "" {
+                       sub.OwnerID = c.fitbitUserID
+               }
+               ret = append(ret, sub)
+       }
+
+       if len(ret) == 0 && len(errs) != 0 {
+               return nil, errs
+       }
 
-       for i, s := range subscriptions {
-               log.Debugf(ctx, "ListSubscriptions() = %d: %s", i, s)
+       for _, err := range errs {
+               log.Warningf(ctx, err)
        }
 
-       return subscriptions, nil
+       return ret, nil
 }
 
 func (c *Client) DeleteToken(ctx context.Context) error {