X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=kraftakt.go;h=53d1764763e5d81ece73d7dfd4d5ee13f059d453;hb=024f18e56decd3608f0412eb3f613c99140d77b1;hp=8d562996688c99d5fecb12bb23dcbbd6ae8dbd76;hpb=5b245cc42860c980c321c1c1829cdc373d53ac53;p=kraftakt.git diff --git a/kraftakt.go b/kraftakt.go index 8d56299..53d1764 100644 --- a/kraftakt.go +++ b/kraftakt.go @@ -43,6 +43,12 @@ func init() { 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 @@ -50,12 +56,12 @@ func (hndl ContextHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 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 } } @@ -66,7 +72,7 @@ func (hndl AuthenticatedHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques 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 } @@ -74,7 +80,7 @@ func (hndl AuthenticatedHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques 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) @@ -83,12 +89,12 @@ func (hndl AuthenticatedHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques 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 } } @@ -171,21 +177,12 @@ func fitbitDisconnectHandler(ctx context.Context, w http.ResponseWriter, r *http 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)) - continue - } - 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 @@ -261,9 +258,13 @@ func fitbitNotifyHandler(ctx context.Context, w http.ResponseWriter, r *http.Req // 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") - w.WriteHeader(http.StatusNotFound) - return nil + /* + log.Errorf(ctx, "signature mismatch") + w.WriteHeader(http.StatusNotFound) + return nil + */ + } else { + log.Warningf(ctx, "TODO(octo): re-enable signature checking, see https://community.fitbit.com/t5/Web-API-Development/Push-notification-signatures-are-currently-invalid/m-p/2496159") } if err := delayedHandleNotifications.Call(ctx, data); err != nil { @@ -288,22 +289,37 @@ func handleNotifications(ctx context.Context, payload []byte) error { 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 { +func activitiesNotification(ctx context.Context, s *fitbit.Subscription) error { u, err := fitbit.UserFromSubscriberID(ctx, s.SubscriptionID) if err != nil { return err @@ -437,3 +453,74 @@ func handleNotification(ctx context.Context, s *fitbit.Subscription) error { } 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 + } + + 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 = 110 // Deep sleep + case fitbit.SleepLevelLight: + a.Type = 109 // Light sleep + case fitbit.SleepLevelREM: + a.Type = 111 // REM sleep + case fitbit.SleepLevelWake: + a.Type = 112 // Awake (during sleep cycle) + default: + log.Warningf(ctx, "unexpected sleep level %v", stg.Level) + continue + } + } + + wg.Wait() + if gfitErr != nil { + return gfitErr + } + + if err := gfitClient.SetActivities(ctx, activities, tm); err != nil { + return fmt.Errorf("SetActivities() = %v", err) + } + + return nil +}