templates = t
}
+func internalServerError(ctx context.Context, w http.ResponseWriter, err error) {
+ log.Errorf(ctx, "%v", err)
+
+ http.Error(w, "Internal Server Error\n\nReference: "+appengine.RequestID(ctx), http.StatusInternalServerError)
+}
+
// ContextHandler implements http.Handler
type ContextHandler func(context.Context, http.ResponseWriter, *http.Request) error
ctx := appengine.NewContext(r)
if err := app.LoadConfig(ctx); err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
+ internalServerError(ctx, w, fmt.Errorf("LoadConfig() = %v", err))
return
}
if err := hndl(ctx, w, r); err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
+ internalServerError(ctx, w, err)
return
}
}
ctx := appengine.NewContext(r)
if err := app.LoadConfig(ctx); err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
+ internalServerError(ctx, w, fmt.Errorf("LoadConfig() = %v", err))
return
}
if gaeUser == nil {
url, err := user.LoginURL(ctx, r.URL.String())
if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
+ internalServerError(ctx, w, fmt.Errorf("LoginURL() = %v", err))
return
}
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
u, err := app.NewUser(ctx, gaeUser.Email)
if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
+ internalServerError(ctx, w, fmt.Errorf("NewUser(%q) = %v", gaeUser.Email, err))
return
}
if err := hndl(ctx, w, r, u); err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
+ internalServerError(ctx, w, err)
return
}
}
return nil
}
-func fitbitConnectHandler(_ context.Context, w http.ResponseWriter, r *http.Request, _ *app.User) error {
- http.Redirect(w, r, fitbit.AuthURL(), http.StatusTemporaryRedirect)
+func fitbitConnectHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, u *app.User) error {
+ http.Redirect(w, r, fitbit.AuthURL(ctx, u), http.StatusTemporaryRedirect)
return nil
}
return err
}
- var errs appengine.MultiError
-
- for _, collection := range []string{"activities", "sleep"} {
- if err := c.Unsubscribe(ctx, collection); err != nil {
- errs = append(errs, fmt.Errorf("Unsubscribe(%q) = %v", collection, err))
- }
- log.Infof(ctx, "Successfully unsubscribed from %q", collection)
+ if err := c.UnsubscribeAll(ctx); err != nil {
+ return fmt.Errorf("UnsubscribeAll() = %v", err)
}
if err := c.DeleteToken(ctx); err != nil {
- errs = append(errs, fmt.Errorf("DeleteToken() = %v", err))
- }
- if len(errs) != 0 {
- return errs
+ return err
}
redirectURL := r.URL
return nil
}
-func googleConnectHandler(_ context.Context, w http.ResponseWriter, r *http.Request, _ *app.User) error {
- http.Redirect(w, r, gfit.AuthURL(), http.StatusTemporaryRedirect)
+func googleConnectHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, u *app.User) error {
+ http.Redirect(w, r, gfit.AuthURL(ctx, u), http.StatusTemporaryRedirect)
return nil
}
// Fitbit recommendation: "If signature verification fails, you should
// respond with a 404"
if !fitbit.CheckSignature(ctx, data, r.Header.Get("X-Fitbit-Signature")) {
- log.Warningf(ctx, "signature mismatch")
+ log.Errorf(ctx, "signature mismatch")
w.WriteHeader(http.StatusNotFound)
return nil
}
// handleNotifications parses fitbit notifications and requests the individual
// activities from Fitbit. It is executed asynchronously via the delay package.
func handleNotifications(ctx context.Context, payload []byte) error {
+ log.Debugf(ctx, "NOTIFY -> %s", payload)
+
if err := app.LoadConfig(ctx); err != nil {
return err
}
return err
}
+ wg := &sync.WaitGroup{}
+
for _, s := range subscriptions {
- if s.CollectionType != "activities" {
+ switch s.CollectionType {
+ case "activities":
+ wg.Add(1)
+ go func(s fitbit.Subscription) {
+ defer wg.Done()
+ if err := activitiesNotification(ctx, &s); err != nil {
+ log.Warningf(ctx, "activitiesNotification() = %v", err)
+ }
+ }(s) // copies s
+ case "sleep":
+ wg.Add(1)
+ go func(s fitbit.Subscription) {
+ defer wg.Done()
+ if err := sleepNotification(ctx, &s); err != nil {
+ log.Warningf(ctx, "sleepNotification() = %v", err)
+ }
+ }(s) // copies s
+ default:
log.Warningf(ctx, "ignoring collection type %q", s.CollectionType)
- continue
- }
- if err := handleNotification(ctx, &s); err != nil {
- log.Errorf(ctx, "handleNotification() = %v", err)
- continue
}
}
+ wg.Wait()
return nil
}
-func handleNotification(ctx context.Context, s *fitbit.Subscription) error {
- u, err := app.UserByID(ctx, s.SubscriptionID)
+func activitiesNotification(ctx context.Context, s *fitbit.Subscription) error {
+ u, err := fitbit.UserFromSubscriberID(ctx, s.SubscriptionID)
if err != nil {
return err
}
activities = append(activities, gfit.Activity{
Start: startTime,
End: endTime,
- Type: gfit.ParseFitbitActivity(a.Name),
+ Type: a.Name,
})
}
if err := gfitClient.SetActivities(ctx, activities, tm); err != nil {
}
return nil
}
+
+func sleepNotification(ctx context.Context, s *fitbit.Subscription) error {
+ u, err := fitbit.UserFromSubscriberID(ctx, s.SubscriptionID)
+ if err != nil {
+ return err
+ }
+
+ var (
+ wg = &sync.WaitGroup{}
+ gfitClient *gfit.Client
+ gfitErr error
+ )
+
+ wg.Add(1)
+ go func() {
+ gfitClient, gfitErr = gfit.NewClient(ctx, u)
+ wg.Done()
+ }()
+
+ fitbitClient, err := fitbit.NewClient(ctx, s.OwnerID, u)
+ if err != nil {
+ return err
+ }
+
+ profile, err := fitbitClient.Profile(ctx)
+ if err != nil {
+ return err
+ }
+
+ tm, err := time.ParseInLocation("2006-01-02", s.Date, profile.Timezone)
+ if err != nil {
+ return err
+ }
+
+ sleep, err := fitbitClient.Sleep(ctx, tm)
+ if err != nil {
+ return err
+ }
+ log.Debugf(ctx, "fitbitClient.Sleep(%v) returned %d sleep stages", tm, len(sleep.Stages))
+
+ var activities []gfit.Activity
+ for _, stg := range sleep.Stages {
+ a := gfit.Activity{
+ Start: stg.StartTime,
+ End: stg.EndTime,
+ }
+ switch stg.Level {
+ case fitbit.SleepLevelDeep:
+ a.Type = "Deep sleep"
+ case fitbit.SleepLevelLight:
+ a.Type = "Light sleep"
+ case fitbit.SleepLevelREM:
+ a.Type = "REM sleep"
+ case fitbit.SleepLevelWake:
+ a.Type = "Awake (during sleep cycle)"
+ default:
+ log.Warningf(ctx, "unexpected sleep level %v", stg.Level)
+ continue
+ }
+
+ activities = append(activities, a)
+ }
+
+ wg.Wait()
+ if gfitErr != nil {
+ return gfitErr
+ }
+
+ log.Debugf(ctx, "passing %d activities to gfitClient.SetActivities()", len(activities))
+ if err := gfitClient.SetActivities(ctx, activities, tm); err != nil {
+ return fmt.Errorf("SetActivities() = %v", err)
+ }
+
+ return nil
+}