retrogit/app/githop.go
Mihai Parparita fe11e2a97a Basic timezone support.
Digests are generated for day boundaries in the given timezone, and timestamps
that are displayed are in that timezone too. No UI for actually specifying a
timezone.
2014-08-03 23:00:48 -07:00

255 lines
7.1 KiB
Go

package githop
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"html/template"
"io/ioutil"
"log"
"net/http"
"appengine"
"appengine/mail"
"appengine/urlfetch"
"code.google.com/p/goauth2/oauth"
"github.com/google/go-github/github"
"github.com/gorilla/mux"
"github.com/gorilla/sessions"
)
var router *mux.Router
var githubOauthConfig oauth.Config
var sessionStore *sessions.CookieStore
var sessionConfig SessionConfig
func initGithubOAuthConfig() {
path := "config/github-oauth"
if appengine.IsDevAppServer() {
path += "-dev"
}
path += ".json"
configBytes, err := ioutil.ReadFile(path)
if err != nil {
log.Panicf("Could not read GitHub OAuth config from %s: %s", path, err.Error())
}
err = json.Unmarshal(configBytes, &githubOauthConfig)
if err != nil {
log.Panicf("Could not parse GitHub OAuth config %s: %s", configBytes, err.Error())
}
githubOauthConfig.Scope = "repo, user:email"
githubOauthConfig.AuthURL = "https://github.com/login/oauth/authorize"
githubOauthConfig.TokenURL = "https://github.com/login/oauth/access_token"
}
func init() {
sessionStore, sessionConfig = initSession()
initGithubOAuthConfig()
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)
http.Handle("/", router)
}
var templates = template.Must(template.ParseGlob("templates/*.html"))
func signInHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, githubOauthConfig.AuthCodeURL(""), http.StatusFound)
}
func signOutHandler(w http.ResponseWriter, r *http.Request) {
session, _ := sessionStore.Get(r, sessionConfig.CookieName)
session.Options.MaxAge = -1
session.Save(r, w)
indexUrl, _ := router.Get("index").URL()
http.Redirect(w, r, indexUrl.String(), http.StatusFound)
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
session, _ := sessionStore.Get(r, sessionConfig.CookieName)
userId, ok := session.Values[sessionConfig.UserIdKey].(int)
if !ok {
signInUrl, _ := router.Get("sign-in").URL()
var data = map[string]string{
"SignInUrl": signInUrl.String(),
}
if err := templates.ExecuteTemplate(w, "index-signed-out", data); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
return
}
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()
http.Redirect(w, r, indexUrl.String(), http.StatusFound)
return
}
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
oauthTransport := githubOAuthTransport(c)
oauthTransport.Token = &account.OAuthToken
githubClient := github.NewClient(oauthTransport.Client())
digest, err := newDigest(githubClient, account)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
signOutUrl, _ := router.Get("sign-out").URL()
sendDigestUrl, _ := router.Get("send-digest").URL()
var data = map[string]interface{}{
"SignOutUrl": signOutUrl.String(),
"SendDigestUrl": sendDigestUrl.String(),
"Digest": digest,
}
if err := templates.ExecuteTemplate(w, "index", data); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func sendDigestHandler(w http.ResponseWriter, r *http.Request) {
session, _ := sessionStore.Get(r, sessionConfig.CookieName)
userId := session.Values[sessionConfig.UserIdKey].(int)
c := appengine.NewContext(r)
account, err := getAccount(c, userId)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
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, account)
if err != nil {
return err
}
var digestHtml bytes.Buffer
digestHtml.WriteString("<html><head><style>")
digestHtml.Write(getDigestStyles())
digestHtml.WriteString("</style></head><body>")
if err := templates.ExecuteTemplate(&digestHtml, "digest", digest); err != nil {
return err
}
digestHtml.WriteString("</body></html>")
emails, _, err := githubClient.Users.ListEmails(nil)
if err != nil {
return err
}
var primaryVerified *string
for _, email := range emails {
if email.Primary != nil && *email.Primary &&
email.Verified != nil && *email.Verified {
primaryVerified = email.Email
break
}
}
if primaryVerified == nil {
return errors.New("No verified email addresses found in GitHub account")
}
digestMessage := &mail.Message{
Sender: "GitHop <mihai.parparita@gmail.com>",
To: []string{*primaryVerified},
Subject: "GitHop Digest",
HTMLBody: digestHtml.String(),
}
err = mail.Send(c, digestMessage)
return err
}
func getDigestStyles() []byte {
b, err := ioutil.ReadFile("static/digest.css")
if err != nil {
log.Panicf("Could not read digest CSS: %s", err.Error())
}
return b
}
func githubOAuthCallbackHandler(w http.ResponseWriter, r *http.Request) {
code := r.FormValue("code")
c := appengine.NewContext(r)
oauthTransport := githubOAuthTransport(c)
token, err := oauthTransport.Exchange(code)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
oauthTransport.Token = token
githubClient := github.NewClient(oauthTransport.Client())
user, _, err := githubClient.Users.Get("")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
account := &Account{
GitHubUserId: *user.ID,
OAuthToken: *token,
}
err = account.put(c)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
session, _ := sessionStore.Get(r, sessionConfig.CookieName)
session.Values[sessionConfig.UserIdKey] = user.ID
session.Save(r, w)
indexUrl, _ := router.Get("index").URL()
http.Redirect(w, r, indexUrl.String(), http.StatusFound)
}
func githubOAuthTransport(c appengine.Context) *oauth.Transport {
appengineTransport := &urlfetch.Transport{Context: c}
cachingTransport := &CachingTransport{
Transport: appengineTransport,
Context: c,
}
return &oauth.Transport{
Config: &githubOauthConfig,
Transport: cachingTransport,
}
}