diff --git a/TODO b/TODO index 073af08..a9efd28 100644 --- a/TODO +++ b/TODO @@ -1,7 +1,7 @@ TODO - Break up indexHandler - Flash message when sending a single email -- Loop over registered accounts and send them email +- Use task queue for sending out of digests, for better parallelism and retry behavior - Flash message and sign out when OAuth token has expired/is invalid - Handle pagination for user repository list - Handle pagination for user organization list diff --git a/app/account.go b/app/account.go index d99895c..87c9a99 100644 --- a/app/account.go +++ b/app/account.go @@ -30,6 +30,22 @@ func getAccount(c appengine.Context, gitHubUserId int) (*Account, error) { return account, err } +func getAllAccounts(c appengine.Context, accounts *[]Account) error { + q := datastore.NewQuery("Account") + _, err := q.GetAll(c, accounts) + if err != nil { + return err + } + for i, _ := range *accounts { + r := bytes.NewBuffer((*accounts)[i].OAuthTokenSerialized) + err = gob.NewDecoder(r).Decode(&(*accounts)[i].OAuthToken) + if err != nil { + return err + } + } + return nil +} + func (account *Account) put(c appengine.Context) error { w := new(bytes.Buffer) err := gob.NewEncoder(w).Encode(&account.OAuthToken) diff --git a/app/app.yaml b/app/app.yaml index 5bc3861..408c588 100644 --- a/app/app.yaml +++ b/app/app.yaml @@ -6,6 +6,9 @@ api_version: go1 handlers: - url: /static static_dir: static +- url: /digest/cron + script: _go_app + login: admin - url: /.* script: _go_app secure: always diff --git a/app/digest.go b/app/digest.go index 3a9b5bb..8797903 100644 --- a/app/digest.go +++ b/app/digest.go @@ -56,7 +56,7 @@ func newDigest(githubClient *github.Client) (*Digest, error) { now := time.Now() digestStartTime := time.Date(now.Year()-1, now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) - digestEndTime := digestStartTime.AddDate(0, 0, 7) + digestEndTime := digestStartTime.AddDate(0, 0, 1) // Only look at repos that may have activity in the digest interval. var digestRepos []github.Repository diff --git a/app/githop.go b/app/githop.go index 50176dc..1de5a91 100644 --- a/app/githop.go +++ b/app/githop.go @@ -3,6 +3,8 @@ package githop import ( "bytes" "encoding/json" + "errors" + "fmt" "html/template" "io/ioutil" "log" @@ -49,6 +51,7 @@ func init() { router = mux.NewRouter() router.HandleFunc("/", indexHandler).Name("index") router.HandleFunc("/digest/send", sendDigestHandler).Name("send-digest").Methods("POST") + router.HandleFunc("/digest/cron", digestCronHandler) router.HandleFunc("/session/sign-in", signInHandler).Name("sign-in") router.HandleFunc("/session/sign-out", signOutHandler).Name("sign-out") router.HandleFunc("/github/callback", githubOAuthCallbackHandler) @@ -82,7 +85,8 @@ func indexHandler(w http.ResponseWriter, r *http.Request) { } return } - account, err := getAccount(appengine.NewContext(r), userId) + c := appengine.NewContext(r) + account, err := getAccount(c, userId) if account == nil { // Can't look up the account, session cookie must be invalid, clear it. indexUrl, _ := router.Get("sign-out").URL() @@ -94,7 +98,7 @@ func indexHandler(w http.ResponseWriter, r *http.Request) { return } - oauthTransport := githubOAuthTransport(r) + oauthTransport := githubOAuthTransport(c) oauthTransport.Token = &account.OAuthToken githubClient := github.NewClient(oauthTransport.Client()) @@ -124,26 +128,50 @@ func sendDigestHandler(w http.ResponseWriter, r *http.Request) { return } - oauthTransport := githubOAuthTransport(r) + err = sendDigestForAccount(account, c) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + indexUrl, _ := router.Get("index").URL() + http.Redirect(w, r, indexUrl.String(), http.StatusFound) +} + +func digestCronHandler(w http.ResponseWriter, r *http.Request) { + var accounts []Account + c := appengine.NewContext(r) + getAllAccounts(c, &accounts) + for _, account := range accounts { + c.Infof("Sending digest for %d...", account.GitHubUserId) + err := sendDigestForAccount(&account, c) + if err != nil { + c.Errorf(" Error: %s", err.Error()) + } else { + c.Infof(" Sent!") + } + } + fmt.Fprint(w, "Done") +} + +func sendDigestForAccount(account *Account, c appengine.Context) error { + oauthTransport := githubOAuthTransport(c) oauthTransport.Token = &account.OAuthToken githubClient := github.NewClient(oauthTransport.Client()) digest, err := newDigest(githubClient) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return + return err } var digestHtml bytes.Buffer if err := templates.ExecuteTemplate(&digestHtml, "digest", digest); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return + return err } emails, _, err := githubClient.Users.ListEmails(nil) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return + return err } var primaryVerified *string for _, email := range emails { @@ -154,8 +182,7 @@ func sendDigestHandler(w http.ResponseWriter, r *http.Request) { } } if primaryVerified == nil { - http.Error(w, "No verified email addresses found in GitHub account", http.StatusBadRequest) - return + return errors.New("No verified email addresses found in GitHub account") } digestMessage := &mail.Message{ @@ -164,18 +191,14 @@ func sendDigestHandler(w http.ResponseWriter, r *http.Request) { Subject: "GitHop Digest", HTMLBody: digestHtml.String(), } - if err := mail.Send(c, digestMessage); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - indexUrl, _ := router.Get("index").URL() - http.Redirect(w, r, indexUrl.String(), http.StatusFound) + err = mail.Send(c, digestMessage) + return err } func githubOAuthCallbackHandler(w http.ResponseWriter, r *http.Request) { code := r.FormValue("code") - oauthTransport := githubOAuthTransport(r) + c := appengine.NewContext(r) + oauthTransport := githubOAuthTransport(c) token, err := oauthTransport.Exchange(code) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -194,7 +217,7 @@ func githubOAuthCallbackHandler(w http.ResponseWriter, r *http.Request) { GitHubUserId: *user.ID, OAuthToken: *token, } - err = account.put(appengine.NewContext(r)) + err = account.put(c) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -207,12 +230,11 @@ func githubOAuthCallbackHandler(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, indexUrl.String(), http.StatusFound) } -func githubOAuthTransport(r *http.Request) *oauth.Transport { - appengineContext := appengine.NewContext(r) - appengineTransport := &urlfetch.Transport{Context: appengineContext} +func githubOAuthTransport(c appengine.Context) *oauth.Transport { + appengineTransport := &urlfetch.Transport{Context: c} cachingTransport := &CachingTransport{ Transport: appengineTransport, - Context: appengineContext, + Context: c, } return &oauth.Transport{ Config: &githubOauthConfig,