Add digest cron handler.

This commit is contained in:
Mihai Parparita 2014-07-30 22:17:04 -07:00
parent 1bc9d53325
commit 649bbd8fd9
5 changed files with 67 additions and 26 deletions

2
TODO
View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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,