Use html/template to generate HTML output.
authorFlorian Forster <ff@octo.it>
Tue, 30 Jan 2018 08:46:51 +0000 (09:46 +0100)
committerFlorian Forster <ff@octo.it>
Tue, 30 Jan 2018 08:46:51 +0000 (09:46 +0100)
This also removes the authentication requirement.

kraftakt.go

index 1f3ac98..976bbd4 100644 (file)
@@ -4,6 +4,7 @@ import (
        "context"
        "encoding/json"
        "fmt"
+       "html/template"
        "io/ioutil"
        "net/http"
        "sync"
@@ -21,15 +22,24 @@ import (
 
 var delayedHandleNotifications = delay.Func("handleNotifications", handleNotifications)
 
+var templates *template.Template
+
 func init() {
        http.Handle("/fitbit/connect", AuthenticatedHandler(fitbitConnectHandler))
        http.Handle("/fitbit/grant", AuthenticatedHandler(fitbitGrantHandler))
-       http.Handle("/fitbit/notify", ContextHandler(fitbitNotifyHandler))
        http.Handle("/fitbit/disconnect", AuthenticatedHandler(fitbitDisconnectHandler))
        http.Handle("/google/connect", AuthenticatedHandler(googleConnectHandler))
        http.Handle("/google/grant", AuthenticatedHandler(googleGrantHandler))
        http.Handle("/google/disconnect", AuthenticatedHandler(googleDisconnectHandler))
-       http.Handle("/", AuthenticatedHandler(indexHandler))
+       // unauthenticated
+       http.Handle("/fitbit/notify", ContextHandler(fitbitNotifyHandler))
+       http.Handle("/", ContextHandler(indexHandler))
+
+       t, err := template.ParseGlob("templates/*.html")
+       if err != nil {
+               panic(err)
+       }
+       templates = t
 }
 
 // ContextHandler implements http.Handler
@@ -82,47 +92,37 @@ func (hndl AuthenticatedHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
        }
 }
 
-func indexHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, u *app.User) error {
-       _, err := u.Token(ctx, "Fitbit")
-       if err != nil && err != datastore.ErrNoSuchEntity {
-               return err
-       }
-       haveFitbitToken := err == nil
-
-       _, err = u.Token(ctx, "Google")
-       if err != nil && err != datastore.ErrNoSuchEntity {
-               return err
+func indexHandler(ctx context.Context, w http.ResponseWriter, _ *http.Request) error {
+       var templateData struct {
+               HaveFitbit    bool
+               HaveGoogleFit bool
+               *app.User
        }
-       haveGoogleToken := err == nil
-
-       fmt.Fprintln(w, "<html><head><title>Kraftakt</title></head>")
-       fmt.Fprintln(w, "<body><h1>Kraftakt</h1>")
+       templateName := "main.html"
 
-       fmt.Fprintln(w, "<p><strong>Kraftakt</strong> copies your <em>Fitbit</em> data to <em>Google Fit</em>, seconds after you sync.</p>")
+       if gaeUser := user.Current(ctx); gaeUser != nil {
+               templateName = "loggedin.html"
 
-       fmt.Fprintf(w, "<p>Hello %s</p>\n", user.Current(ctx).Email)
-       fmt.Fprintln(w, "<ul>")
+               u, err := app.NewUser(ctx, gaeUser.Email)
+               if err != nil {
+                       return err
+               }
+               templateData.User = u
 
-       fmt.Fprint(w, "<li>Fitbit: ")
-       if haveFitbitToken {
-               fmt.Fprint(w, `<strong style="color: DarkGreen;">Authorized</strong>`)
-       } else {
-               fmt.Fprint(w, `<strong style="color: DarkRed;">Not authorized</strong> (<a href="/fitbit/setup">Authorize</a>)`)
-       }
-       fmt.Fprintln(w, "</li>")
+               _, err = u.Token(ctx, "Fitbit")
+               if err != nil && err != datastore.ErrNoSuchEntity {
+                       return err
+               }
+               templateData.HaveFitbit = (err == nil)
 
-       fmt.Fprint(w, "<li>Google Fit: ")
-       if haveGoogleToken {
-               fmt.Fprint(w, `<strong style="color: DarkGreen;">Authorized</strong>`)
-       } else {
-               fmt.Fprint(w, `<strong style="color: DarkRed;">Not authorized</strong> (<a href="/google/setup">Authorize</a>)`)
+               _, err = u.Token(ctx, "Google")
+               if err != nil && err != datastore.ErrNoSuchEntity {
+                       return err
+               }
+               templateData.HaveGoogleFit = (err == nil)
        }
-       fmt.Fprintln(w, "</li>")
 
-       fmt.Fprintln(w, "</ul>")
-       fmt.Fprintln(w, "</body></html>")
-
-       return nil
+       return templates.ExecuteTemplate(w, templateName, &templateData)
 }
 
 func fitbitConnectHandler(_ context.Context, w http.ResponseWriter, r *http.Request, _ *app.User) error {
@@ -134,7 +134,7 @@ func fitbitGrantHandler(ctx context.Context, w http.ResponseWriter, r *http.Requ
        if err := fitbit.ParseToken(ctx, r, u); err != nil {
                return err
        }
-       c, err := fitbit.NewClient(ctx, "-", u)
+       c, err := fitbit.NewClient(ctx, "", u)
        if err != nil {
                return err
        }
@@ -154,8 +154,35 @@ func fitbitGrantHandler(ctx context.Context, w http.ResponseWriter, r *http.Requ
        return nil
 }
 
-func googleSetupHandler(w http.ResponseWriter, r *http.Request) {
+func fitbitDisconnectHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, u *app.User) error {
+       c, err := fitbit.NewClient(ctx, "", u)
+       if err != nil {
+               return err
+       }
+
+       var errs appengine.MultiError
+       if err := c.Unsubscribe(ctx); err != nil {
+               errs = append(errs, fmt.Errorf("Unsubscribe() = %v", err))
+       }
+
+       if err := c.DeleteToken(ctx); err != nil {
+               errs = append(errs, fmt.Errorf("DeleteToken() = %v", err))
+       }
+       if len(errs) != 0 {
+               return errs
+       }
+
+       redirectURL := r.URL
+       redirectURL.Path = "/"
+       redirectURL.RawQuery = ""
+       redirectURL.Fragment = ""
+       http.Redirect(w, r, redirectURL.String(), http.StatusTemporaryRedirect)
+       return nil
+}
+
+func googleConnectHandler(_ context.Context, w http.ResponseWriter, r *http.Request, _ *app.User) error {
        http.Redirect(w, r, gfit.AuthURL(), http.StatusTemporaryRedirect)
+       return nil
 }
 
 func googleGrantHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, u *app.User) error {
@@ -171,6 +198,24 @@ func googleGrantHandler(ctx context.Context, w http.ResponseWriter, r *http.Requ
        return nil
 }
 
+func googleDisconnectHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, u *app.User) error {
+       c, err := gfit.NewClient(ctx, u)
+       if err != nil {
+               return err
+       }
+
+       if err := c.DeleteToken(ctx); err != nil {
+               return err
+       }
+
+       redirectURL := r.URL
+       redirectURL.Path = "/"
+       redirectURL.RawQuery = ""
+       redirectURL.Fragment = ""
+       http.Redirect(w, r, redirectURL.String(), http.StatusTemporaryRedirect)
+       return nil
+}
+
 // fitbitNotifyHandler is called by Fitbit whenever there are updates to a
 // subscription. It verifies the payload, splits it into individual
 // notifications and adds it to the taskqueue service.