retrogit/app/githop.go
2014-08-01 23:00:21 -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)
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)
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,
}
}