Changes necessary to run with the Go 1.11 App Engine runtime.

This commit is contained in:
Mihai Parparita 2020-03-05 21:30:47 -08:00
parent eb7efc60bc
commit 6c662e2109
14 changed files with 118 additions and 99 deletions

View file

@ -1,6 +1,6 @@
# RetroGit # RetroGit
Service that shows you your GitHub commits from a year ago. Includes a mail digest to that you can see each day what you were up to in the past. Service that shows you your GitHub commits from previous years. Includes a mail digest to that you can see each day what you were up to in the past.
It's currently running at [https://www.retrogit.com/](https://www.retrogit.com/). It's currently running at [https://www.retrogit.com/](https://www.retrogit.com/).
@ -21,5 +21,5 @@ The server can the be accessed at [http://localhost:8080/](http://localhost:8080
## Deploying to App Engine ## Deploying to App Engine
``` ```
gcloud app deploy --project retrogit app/app.yaml ./deploy.sh
``` ```

View file

@ -1,13 +1,13 @@
package retrogit package main
import ( import (
"bytes" "bytes"
"context"
"encoding/gob" "encoding/gob"
"errors" "errors"
"time" "time"
"appengine" "google.golang.org/appengine/datastore"
"appengine/datastore"
"code.google.com/p/goauth2/oauth" "code.google.com/p/goauth2/oauth"
"github.com/google/go-github/github" "github.com/google/go-github/github"
@ -28,7 +28,7 @@ type Account struct {
WeeklyDay time.Weekday WeeklyDay time.Weekday
} }
func getAccount(c appengine.Context, githubUserId int) (*Account, error) { func getAccount(c context.Context, githubUserId int) (*Account, error) {
key := datastore.NewKey(c, "Account", "", int64(githubUserId), nil) key := datastore.NewKey(c, "Account", "", int64(githubUserId), nil)
account := new(Account) account := new(Account)
err := datastore.Get(c, key, account) err := datastore.Get(c, key, account)
@ -63,7 +63,7 @@ func initAccount(account *Account) error {
return nil return nil
} }
func getAllAccounts(c appengine.Context) ([]Account, error) { func getAllAccounts(c context.Context) ([]Account, error) {
q := datastore.NewQuery("Account") q := datastore.NewQuery("Account")
var accounts []Account var accounts []Account
_, err := q.GetAll(c, &accounts) _, err := q.GetAll(c, &accounts)
@ -88,7 +88,7 @@ func (account *Account) IsRepoIdExcluded(repoId int) bool {
return false return false
} }
func (account *Account) Put(c appengine.Context) error { func (account *Account) Put(c context.Context) error {
w := new(bytes.Buffer) w := new(bytes.Buffer)
err := gob.NewEncoder(w).Encode(&account.OAuthToken) err := gob.NewEncoder(w).Encode(&account.OAuthToken)
if err != nil { if err != nil {
@ -100,7 +100,7 @@ func (account *Account) Put(c appengine.Context) error {
return err return err
} }
func (account *Account) Delete(c appengine.Context) error { func (account *Account) Delete(c context.Context) error {
key := datastore.NewKey(c, "Account", "", int64(account.GitHubUserId), nil) key := datastore.NewKey(c, "Account", "", int64(account.GitHubUserId), nil)
err := datastore.Delete(c, key) err := datastore.Delete(c, key)
return err return err

View file

@ -1,11 +1,11 @@
package retrogit package main
import ( import (
"net/http" "net/http"
"sort" "sort"
"strconv" "strconv"
"appengine" "google.golang.org/appengine"
"github.com/google/go-github/github" "github.com/google/go-github/github"
) )

View file

@ -1,4 +1,4 @@
package retrogit package main
import ( import (
"encoding/json" "encoding/json"
@ -6,13 +6,14 @@ import (
"fmt" "fmt"
"html/template" "html/template"
"io/ioutil" "io/ioutil"
"log" log_ "log"
"net/http" "net/http"
"path/filepath" "path/filepath"
"strings" "strings"
"appengine" "google.golang.org/appengine"
"appengine/mail" "google.golang.org/appengine/log"
"google.golang.org/appengine/mail"
"github.com/google/go-github/github" "github.com/google/go-github/github"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
@ -208,14 +209,14 @@ func handleAppError(e *AppError, w http.ResponseWriter, r *http.Request) {
return return
} }
} else { } else {
c.Errorf("GitHub fetch error was not of type github.ErrorResponse") log.Errorf(c, "GitHub fetch error was not of type github.ErrorResponse")
} }
} else if e.Type == AppErrorTypeRedirect { } else if e.Type == AppErrorTypeRedirect {
http.Redirect(w, r, e.Message, e.Code) http.Redirect(w, r, e.Message, e.Code)
return return
} }
if e.Type != AppErrorTypeBadInput { if e.Type != AppErrorTypeBadInput {
c.Errorf("%v", e.Error) log.Errorf(c, "%v", e.Error)
if !appengine.IsDevAppServer() { if !appengine.IsDevAppServer() {
sendAppErrorMail(e, r) sendAppErrorMail(e, r)
} }
@ -226,11 +227,11 @@ func handleAppError(e *AppError, w http.ResponseWriter, r *http.Request) {
w.WriteHeader(e.Code) w.WriteHeader(e.Code)
templateError := templates["internal-error"].Render(w, data) templateError := templates["internal-error"].Render(w, data)
if templateError != nil { if templateError != nil {
c.Errorf("Error %s rendering error template.", templateError.Error.Error()) log.Errorf(c, "Error %s rendering error template.", templateError.Error.Error())
} }
return return
} else { } else {
c.Infof("%v", e.Error) log.Infof(c, "%v", e.Error)
} }
http.Error(w, e.Message, e.Code) http.Error(w, e.Message, e.Code)
} }
@ -260,7 +261,7 @@ Error: %s`,
c := appengine.NewContext(r) c := appengine.NewContext(r)
err := mail.Send(c, errorMessage) err := mail.Send(c, errorMessage)
if err != nil { if err != nil {
c.Errorf("Error %s sending error email.", err.Error()) log.Errorf(c, "Error %s sending error email.", err.Error())
} }
} }
@ -323,11 +324,11 @@ func loadTemplates() (templates map[string]*Template) {
} }
sharedFileNames, err := filepath.Glob("templates/shared/*.html") sharedFileNames, err := filepath.Glob("templates/shared/*.html")
if err != nil { if err != nil {
log.Panicf("Could not read shared template file names %s", err.Error()) log_.Panicf("Could not read shared template file names %s", err.Error())
} }
templateFileNames, err := filepath.Glob("templates/*.html") templateFileNames, err := filepath.Glob("templates/*.html")
if err != nil { if err != nil {
log.Panicf("Could not read template file names %s", err.Error()) log_.Panicf("Could not read template file names %s", err.Error())
} }
templates = make(map[string]*Template) templates = make(map[string]*Template)
for _, templateFileName := range templateFileNames { for _, templateFileName := range templateFileNames {
@ -344,7 +345,7 @@ func loadTemplates() (templates map[string]*Template) {
_, templateFileName = filepath.Split(fileNames[0]) _, templateFileName = filepath.Split(fileNames[0])
parsedTemplate, err := template.New(templateFileName).Funcs(funcMap).ParseFiles(fileNames...) parsedTemplate, err := template.New(templateFileName).Funcs(funcMap).ParseFiles(fileNames...)
if err != nil { if err != nil {
log.Printf("Could not parse template files for %s: %s", templateFileName, err.Error()) log_.Printf("Could not parse template files for %s: %s", templateFileName, err.Error())
} }
templates[templateName] = &Template{parsedTemplate} templates[templateName] = &Template{parsedTemplate}
} }
@ -354,13 +355,13 @@ func loadTemplates() (templates map[string]*Template) {
func loadStyles() (result map[string]template.CSS) { func loadStyles() (result map[string]template.CSS) {
stylesBytes, err := ioutil.ReadFile("config/styles.json") stylesBytes, err := ioutil.ReadFile("config/styles.json")
if err != nil { if err != nil {
log.Panicf("Could not read styles JSON: %s", err.Error()) log_.Panicf("Could not read styles JSON: %s", err.Error())
} }
var stylesJson interface{} var stylesJson interface{}
err = json.Unmarshal(stylesBytes, &stylesJson) err = json.Unmarshal(stylesBytes, &stylesJson)
result = make(map[string]template.CSS) result = make(map[string]template.CSS)
if err != nil { if err != nil {
log.Printf("Could not parse styles JSON %s: %s", stylesBytes, err.Error()) log_.Printf("Could not parse styles JSON %s: %s", stylesBytes, err.Error())
return return
} }
var parse func(string, map[string]interface{}, *string) var parse func(string, map[string]interface{}, *string)
@ -377,7 +378,7 @@ func loadStyles() (result map[string]template.CSS) {
parse(path+k, v.(map[string]interface{}), &nestedStyle) parse(path+k, v.(map[string]interface{}), &nestedStyle)
result[path+k] = template.CSS(nestedStyle) result[path+k] = template.CSS(nestedStyle)
default: default:
log.Printf("Unexpected type for %s in styles JSON, ignoring", k) log_.Printf("Unexpected type for %s in styles JSON, ignoring", k)
} }
} }
} }

View file

@ -1,5 +1,4 @@
runtime: go runtime: go111
api_version: go1
handlers: handlers:
- url: /static - url: /static
@ -11,11 +10,11 @@ handlers:
static_files: static/robots.txt static_files: static/robots.txt
upload: static/robots.txt upload: static/robots.txt
- url: /digest/cron - url: /digest/cron
script: _go_app script: auto
login: admin login: admin
- url: /admin/.* - url: /admin/.*
script: _go_app script: auto
login: admin login: admin
- url: /.* - url: /.*
script: _go_app script: auto
secure: always secure: always

View file

@ -1,8 +1,9 @@
package retrogit package main
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"context"
"crypto/md5" "crypto/md5"
"fmt" "fmt"
"io" "io"
@ -11,8 +12,8 @@ import (
"strings" "strings"
"time" "time"
"appengine" "google.golang.org/appengine/log"
"appengine/memcache" "google.golang.org/appengine/memcache"
) )
// Simple http.RoundTripper implementation which wraps an existing transport and // Simple http.RoundTripper implementation which wraps an existing transport and
@ -20,7 +21,7 @@ import (
// iteration cycle during development. // iteration cycle during development.
type CachingTransport struct { type CachingTransport struct {
Transport http.RoundTripper Transport http.RoundTripper
Context appengine.Context Context context.Context
} }
func (t *CachingTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) { func (t *CachingTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
@ -50,7 +51,7 @@ func (t *CachingTransport) RoundTrip(req *http.Request) (resp *http.Response, er
cachedRespItem, err := memcache.Get(t.Context, cacheKey) cachedRespItem, err := memcache.Get(t.Context, cacheKey)
if err != nil && err != memcache.ErrCacheMiss { if err != nil && err != memcache.ErrCacheMiss {
t.Context.Errorf("Error getting cached response: %v", err) log.Errorf(t.Context, "Error getting cached response: %v", err)
return t.Transport.RoundTrip(req) return t.Transport.RoundTrip(req)
} }
if err == nil { if err == nil {
@ -59,17 +60,17 @@ func (t *CachingTransport) RoundTrip(req *http.Request) (resp *http.Response, er
if err == nil { if err == nil {
return resp, nil return resp, nil
} else { } else {
t.Context.Errorf("Error readings bytes for cached response: %v", err) log.Errorf(t.Context, "Error readings bytes for cached response: %v", err)
} }
} }
t.Context.Infof("Fetching %s", req.URL) log.Infof(t.Context, "Fetching %s", req.URL)
resp, err = t.Transport.RoundTrip(req) resp, err = t.Transport.RoundTrip(req)
if err != nil || resp.StatusCode != 200 { if err != nil || resp.StatusCode != 200 {
return return
} }
respBytes, err := httputil.DumpResponse(resp, true) respBytes, err := httputil.DumpResponse(resp, true)
if err != nil { if err != nil {
t.Context.Errorf("Error dumping bytes for cached response: %v", err) log.Errorf(t.Context, "Error dumping bytes for cached response: %v", err)
return resp, nil return resp, nil
} }
var expiration time.Duration = time.Hour var expiration time.Duration = time.Hour
@ -86,7 +87,7 @@ func (t *CachingTransport) RoundTrip(req *http.Request) (resp *http.Response, er
Expiration: expiration, Expiration: expiration,
}) })
if err != nil { if err != nil {
t.Context.Errorf("Error setting cached response for %s (cache key %s, %d bytes to cache): %v", log.Errorf(t.Context, "Error setting cached response for %s (cache key %s, %d bytes to cache): %v",
req.URL, cacheKey, len(respBytes), err) req.URL, cacheKey, len(respBytes), err)
} }
return resp, nil return resp, nil

View file

@ -1,13 +1,14 @@
package retrogit package main
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
"sort" "sort"
"strings" "strings"
"time" "time"
"appengine" "google.golang.org/appengine/log"
"github.com/google/go-github/github" "github.com/google/go-github/github"
) )
@ -171,7 +172,7 @@ type Digest struct {
RepoErrors map[string]error RepoErrors map[string]error
} }
func newDigest(c appengine.Context, githubClient *github.Client, account *Account) (*Digest, error) { func newDigest(c context.Context, githubClient *github.Client, account *Account) (*Digest, error) {
user, _, err := githubClient.Users.Get("") user, _, err := githubClient.Users.Get("")
if err != nil { if err != nil {
return nil, err return nil, err
@ -225,7 +226,7 @@ func newDigest(c appengine.Context, githubClient *github.Client, account *Accoun
digest.fetch(githubClient) digest.fetch(githubClient)
for repoFullName, err := range digest.RepoErrors { for repoFullName, err := range digest.RepoErrors {
c.Errorf("Error fetching %s: %s", repoFullName, err.Error()) log.Errorf(c, "Error fetching %s: %s", repoFullName, err.Error())
} }
return digest, nil return digest, nil
} }

2
app/index.yaml Normal file
View file

@ -0,0 +1,2 @@
indexes:
# AUTOGENERATED

View file

@ -1,4 +1,4 @@
package retrogit package main
// From Martini's recovery package: // From Martini's recovery package:
// https://github.com/go-martini/martini/blob/master/recovery.go // https://github.com/go-martini/martini/blob/master/recovery.go

View file

@ -1,13 +1,15 @@
package retrogit package main
import ( import (
"context"
"fmt" "fmt"
"time" "time"
"appengine" "google.golang.org/appengine"
"appengine/datastore" "google.golang.org/appengine/datastore"
"appengine/delay" "google.golang.org/appengine/delay"
"appengine/taskqueue" "google.golang.org/appengine/log"
"google.golang.org/appengine/taskqueue"
"github.com/google/go-github/github" "github.com/google/go-github/github"
) )
@ -23,16 +25,16 @@ type RepoVintage struct {
Vintage time.Time `datastore:",noindex"` Vintage time.Time `datastore:",noindex"`
} }
func getVintageKey(c appengine.Context, userId int, repoId int) *datastore.Key { func getVintageKey(c context.Context, userId int, repoId int) *datastore.Key {
return datastore.NewKey(c, "RepoVintage", fmt.Sprintf("%d-%d", userId, repoId), 0, nil) return datastore.NewKey(c, "RepoVintage", fmt.Sprintf("%d-%d", userId, repoId), 0, nil)
} }
var computeVintageFunc *delay.Function var computeVintageFunc *delay.Function
func computeVintage(c appengine.Context, userId int, userLogin string, repoId int, repoOwnerLogin string, repoName string) error { func computeVintage(c context.Context, userId int, userLogin string, repoId int, repoOwnerLogin string, repoName string) error {
account, err := getAccount(c, userId) account, err := getAccount(c, userId)
if err != nil { if err != nil {
c.Errorf("Could not load account %d: %s. Presumed deleted, aborting computing vintage for %s/%s", userId, err.Error(), repoOwnerLogin, repoName) log.Errorf(c, "Could not load account %d: %s. Presumed deleted, aborting computing vintage for %s/%s", userId, err.Error(), repoOwnerLogin, repoName)
return nil return nil
} }
@ -42,7 +44,7 @@ func computeVintage(c appengine.Context, userId int, userLogin string, repoId in
repo, response, err := githubClient.Repositories.Get(repoOwnerLogin, repoName) repo, response, err := githubClient.Repositories.Get(repoOwnerLogin, repoName)
if response.StatusCode == 403 || response.StatusCode == 404 { if response.StatusCode == 403 || response.StatusCode == 404 {
c.Warningf("Got a %d when trying to look up %s/%s (%d)", response.StatusCode, repoOwnerLogin, repoName, repoId) log.Warningf(c, "Got a %d when trying to look up %s/%s (%d)", response.StatusCode, repoOwnerLogin, repoName, repoId)
_, err = datastore.Put(c, getVintageKey(c, userId, repoId), &RepoVintage{ _, err = datastore.Put(c, getVintageKey(c, userId, repoId), &RepoVintage{
UserId: userId, UserId: userId,
RepoId: repoId, RepoId: repoId,
@ -50,7 +52,7 @@ func computeVintage(c appengine.Context, userId int, userLogin string, repoId in
}) })
return err return err
} else if err != nil { } else if err != nil {
c.Errorf("Could not load repo %s/%s (%d): %s", repoOwnerLogin, repoName, repoId, err.Error()) log.Errorf(c, "Could not load repo %s/%s (%d): %s", repoOwnerLogin, repoName, repoId, err.Error())
return err return err
} }
@ -69,7 +71,7 @@ func computeVintage(c appengine.Context, userId int, userLogin string, repoId in
// GitHub returns with a 409 when a repository is empty. // GitHub returns with a 409 when a repository is empty.
commits = make([]github.RepositoryCommit, 0) commits = make([]github.RepositoryCommit, 0)
} else if err != nil { } else if err != nil {
c.Errorf("Could not load commits for repo %s (%d): %s", *repo.FullName, repoId, err.Error()) log.Errorf(c, "Could not load commits for repo %s (%d): %s", *repo.FullName, repoId, err.Error())
return err return err
} }
@ -78,10 +80,10 @@ func computeVintage(c appengine.Context, userId int, userLogin string, repoId in
if len(commits) > 0 { if len(commits) > 0 {
stats, response, err := githubClient.Repositories.ListContributorsStats(repoOwnerLogin, repoName) stats, response, err := githubClient.Repositories.ListContributorsStats(repoOwnerLogin, repoName)
if response.StatusCode == 202 { if response.StatusCode == 202 {
c.Infof("Stats were not available for %s, will try again later", *repo.FullName) log.Infof(c, "Stats were not available for %s, will try again later", *repo.FullName)
task, err := computeVintageFunc.Task(userId, userLogin, repoId, repoOwnerLogin, repoName) task, err := computeVintageFunc.Task(userId, userLogin, repoId, repoOwnerLogin, repoName)
if err != nil { if err != nil {
c.Errorf("Could create delayed task for %s: %s", *repo.FullName, err.Error()) log.Errorf(c, "Could create delayed task for %s: %s", *repo.FullName, err.Error())
return err return err
} }
task.Delay = time.Second * 10 task.Delay = time.Second * 10
@ -89,7 +91,7 @@ func computeVintage(c appengine.Context, userId int, userLogin string, repoId in
return nil return nil
} }
if err != nil { if err != nil {
c.Errorf("Could not load stats for repo %s: %s", *repo.FullName, err.Error()) log.Errorf(c, "Could not load stats for repo %s: %s", *repo.FullName, err.Error())
return err return err
} }
for _, stat := range stats { for _, stat := range stats {
@ -111,7 +113,7 @@ func computeVintage(c appengine.Context, userId int, userLogin string, repoId in
Vintage: vintage, Vintage: vintage,
}) })
if err != nil { if err != nil {
c.Errorf("Could save vintage for repo %s: %s", *repo.FullName, err.Error()) log.Errorf(c, "Could save vintage for repo %s: %s", *repo.FullName, err.Error())
return err return err
} }
@ -122,7 +124,7 @@ func init() {
computeVintageFunc = delay.Func("computeVintage", computeVintage) computeVintageFunc = delay.Func("computeVintage", computeVintage)
} }
func fillVintages(c appengine.Context, user *github.User, repos []*Repo) error { func fillVintages(c context.Context, user *github.User, repos []*Repo) error {
if len(repos) > VintageChunkSize { if len(repos) > VintageChunkSize {
for chunkStart := 0; chunkStart < len(repos); chunkStart += VintageChunkSize { for chunkStart := 0; chunkStart < len(repos); chunkStart += VintageChunkSize {
chunkEnd := chunkStart + VintageChunkSize chunkEnd := chunkStart + VintageChunkSize
@ -151,7 +153,7 @@ func fillVintages(c appengine.Context, user *github.User, repos []*Repo) error {
if err == datastore.ErrNoSuchEntity { if err == datastore.ErrNoSuchEntity {
vintages[i] = nil vintages[i] = nil
} else if err != nil { } else if err != nil {
c.Errorf("%d/%s vintage fetch error: %s", i, *repos[i].FullName, err.Error()) log.Errorf(c, "%d/%s vintage fetch error: %s", i, *repos[i].FullName, err.Error())
return err return err
} }
} }
@ -238,7 +240,7 @@ type UserRepos struct {
Repos []*Repo Repos []*Repo
} }
func getRepos(c appengine.Context, githubClient *github.Client, account *Account, user *github.User) (*Repos, error) { func getRepos(c context.Context, githubClient *github.Client, account *Account, user *github.User) (*Repos, error) {
clientUserRepos := make([]github.Repository, 0) clientUserRepos := make([]github.Repository, 0)
page := 1 page := 1
for { for {

View file

@ -1,11 +1,12 @@
package retrogit package main
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" log_ "log"
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
@ -13,11 +14,12 @@ import (
"sync" "sync"
"time" "time"
"appengine" "google.golang.org/appengine"
"appengine/datastore" "google.golang.org/appengine/datastore"
"appengine/delay" "google.golang.org/appengine/delay"
"appengine/mail" "google.golang.org/appengine/log"
"appengine/urlfetch" "google.golang.org/appengine/mail"
"google.golang.org/appengine/urlfetch"
"code.google.com/p/goauth2/oauth" "code.google.com/p/goauth2/oauth"
"github.com/google/go-github/github" "github.com/google/go-github/github"
@ -33,7 +35,7 @@ var sessionStore *sessions.CookieStore
var sessionConfig SessionConfig var sessionConfig SessionConfig
var templates map[string]*Template var templates map[string]*Template
func init() { func main() {
templates = loadTemplates() templates = loadTemplates()
timezones = initTimezones() timezones = initTimezones()
sessionStore, sessionConfig = initSession() sessionStore, sessionConfig = initSession()
@ -62,6 +64,8 @@ func init() {
router.Handle("/admin/repos", AppHandler(reposAdminHandler)).Name("repos-admin") router.Handle("/admin/repos", AppHandler(reposAdminHandler)).Name("repos-admin")
router.Handle("/admin/delete-account", AppHandler(deleteAccountAdminHandler)).Name("delete-account-admin") router.Handle("/admin/delete-account", AppHandler(deleteAccountAdminHandler)).Name("delete-account-admin")
http.Handle("/", router) http.Handle("/", router)
appengine.Main()
} }
func initGithubOAuthConfig(includePrivateRepos bool) (config oauth.Config) { func initGithubOAuthConfig(includePrivateRepos bool) (config oauth.Config) {
@ -72,11 +76,11 @@ func initGithubOAuthConfig(includePrivateRepos bool) (config oauth.Config) {
path += ".json" path += ".json"
configBytes, err := ioutil.ReadFile(path) configBytes, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
log.Panicf("Could not read GitHub OAuth config from %s: %s", path, err.Error()) log_.Panicf("Could not read GitHub OAuth config from %s: %s", path, err.Error())
} }
err = json.Unmarshal(configBytes, &config) err = json.Unmarshal(configBytes, &config)
if err != nil { if err != nil {
log.Panicf("Could not parse GitHub OAuth config %s: %s", configBytes, err.Error()) log_.Panicf("Could not parse GitHub OAuth config %s: %s", configBytes, err.Error())
} }
repoScopeModifier := "" repoScopeModifier := ""
if !includePrivateRepos { if !includePrivateRepos {
@ -232,12 +236,12 @@ func digestCronHandler(w http.ResponseWriter, r *http.Request) *AppError {
if account.Frequency == "weekly" { if account.Frequency == "weekly" {
now := time.Now().In(account.TimezoneLocation) now := time.Now().In(account.TimezoneLocation)
if now.Weekday() != account.WeeklyDay { if now.Weekday() != account.WeeklyDay {
c.Infof("Skipping %d, since it wants weekly digests on %ss and today is a %s.", log.Infof(c, "Skipping %d, since it wants weekly digests on %ss and today is a %s.",
account.GitHubUserId, account.WeeklyDay, now.Weekday()) account.GitHubUserId, account.WeeklyDay, now.Weekday())
continue continue
} }
} }
c.Infof("Enqueing task for %d...", account.GitHubUserId) log.Infof(c, "Enqueing task for %d...", account.GitHubUserId)
sendDigestForAccountFunc.Call(c, account.GitHubUserId) sendDigestForAccountFunc.Call(c, account.GitHubUserId)
} }
fmt.Fprint(w, "Done") fmt.Fprint(w, "Done")
@ -246,31 +250,31 @@ func digestCronHandler(w http.ResponseWriter, r *http.Request) *AppError {
var sendDigestForAccountFunc = delay.Func( var sendDigestForAccountFunc = delay.Func(
"sendDigestForAccount", "sendDigestForAccount",
func(c appengine.Context, githubUserId int) error { func(c context.Context, githubUserId int) error {
c.Infof("Sending digest for %d...", githubUserId) log.Infof(c, "Sending digest for %d...", githubUserId)
account, err := getAccount(c, githubUserId) account, err := getAccount(c, githubUserId)
if err != nil { if err != nil {
c.Errorf(" Error looking up account: %s", err.Error()) log.Errorf(c, " Error looking up account: %s", err.Error())
return err return err
} }
sent, err := sendDigestForAccount(account, c) sent, err := sendDigestForAccount(account, c)
if err != nil { if err != nil {
c.Errorf(" Error: %s", err.Error()) log.Errorf(c, " Error: %s", err.Error())
if !appengine.IsDevAppServer() { if !appengine.IsDevAppServer() {
sendDigestErrorMail(err, c, githubUserId) sendDigestErrorMail(err, c, githubUserId)
} }
} else if sent { } else if sent {
c.Infof(" Sent!") log.Infof(c, " Sent!")
} else { } else {
c.Infof(" Not sent, digest was empty") log.Infof(c, " Not sent, digest was empty")
} }
return err return err
}) })
func sendDigestErrorMail(e error, c appengine.Context, gitHubUserId int) { func sendDigestErrorMail(e error, c context.Context, gitHubUserId int) {
if strings.Contains(e.Error(), ": 502") { if strings.Contains(e.Error(), ": 502") {
// Ignore 502s from GitHub, there's nothing we do about them. // Ignore 502s from GitHub, there's nothing we do about them.
return; return
} }
errorMessage := &mail.Message{ errorMessage := &mail.Message{
Sender: "RetroGit Admin <digests@retrogit.com>", Sender: "RetroGit Admin <digests@retrogit.com>",
@ -280,11 +284,11 @@ func sendDigestErrorMail(e error, c appengine.Context, gitHubUserId int) {
} }
err := mail.Send(c, errorMessage) err := mail.Send(c, errorMessage)
if err != nil { if err != nil {
c.Errorf("Error %s sending error email.", err.Error()) log.Errorf(c, "Error %s sending error email.", err.Error())
} }
} }
func sendDigestForAccount(account *Account, c appengine.Context) (bool, error) { func sendDigestForAccount(account *Account, c context.Context) (bool, error) {
oauthTransport := githubOAuthTransport(c) oauthTransport := githubOAuthTransport(c)
oauthTransport.Token = &account.OAuthToken oauthTransport.Token = &account.OAuthToken
githubClient := github.NewClient(oauthTransport.Client()) githubClient := github.NewClient(oauthTransport.Client())
@ -295,7 +299,7 @@ func sendDigestForAccount(account *Account, c appengine.Context) (bool, error) {
gitHubStatus := gitHubError.Response.StatusCode gitHubStatus := gitHubError.Response.StatusCode
if gitHubStatus == http.StatusUnauthorized || if gitHubStatus == http.StatusUnauthorized ||
gitHubStatus == http.StatusForbidden { gitHubStatus == http.StatusForbidden {
c.Errorf(" GitHub auth error while getting email adddress, skipping: %s", err.Error()) log.Errorf(c, " GitHub auth error while getting email adddress, skipping: %s", err.Error())
return false, nil return false, nil
} }
} }
@ -312,7 +316,7 @@ func sendDigestForAccount(account *Account, c appengine.Context) (bool, error) {
gitHubStatus := gitHubError.Response.StatusCode gitHubStatus := gitHubError.Response.StatusCode
if gitHubStatus == http.StatusUnauthorized || if gitHubStatus == http.StatusUnauthorized ||
gitHubStatus == http.StatusForbidden { gitHubStatus == http.StatusForbidden {
c.Errorf(" GitHub auth error while getting digest, sending error email: %s", err.Error()) log.Errorf(c, " GitHub auth error while getting digest, sending error email: %s", err.Error())
var authErrorHtml bytes.Buffer var authErrorHtml bytes.Buffer
if err := templates["github-auth-error-email"].Execute(&authErrorHtml, nil); err != nil { if err := templates["github-auth-error-email"].Execute(&authErrorHtml, nil); err != nil {
return false, err return false, err
@ -516,11 +520,11 @@ func setInitialTimezoneHandler(w http.ResponseWriter, r *http.Request, state *Ap
var cacheDigestForAccountFunc = delay.Func( var cacheDigestForAccountFunc = delay.Func(
"cacheDigestForAccount", "cacheDigestForAccount",
func(c appengine.Context, githubUserId int) error { func(c context.Context, githubUserId int) error {
c.Infof("Caching digest for %d...", githubUserId) log.Infof(c, "Caching digest for %d...", githubUserId)
account, err := getAccount(c, githubUserId) account, err := getAccount(c, githubUserId)
if err != nil { if err != nil {
c.Errorf(" Error looking up account: %s", err.Error()) log.Errorf(c, " Error looking up account: %s", err.Error())
// Not returning error since we don't want these tasks to be // Not returning error since we don't want these tasks to be
// retried. // retried.
return nil return nil
@ -531,9 +535,9 @@ var cacheDigestForAccountFunc = delay.Func(
githubClient := github.NewClient(oauthTransport.Client()) githubClient := github.NewClient(oauthTransport.Client())
_, err = newDigest(c, githubClient, account) _, err = newDigest(c, githubClient, account)
if err != nil { if err != nil {
c.Errorf(" Error computing digest: %s", err.Error()) log.Errorf(c, " Error computing digest: %s", err.Error())
} }
c.Infof(" Done!") log.Infof(c, " Done!")
return nil return nil
}) })
@ -544,12 +548,12 @@ func deleteAccountHandler(w http.ResponseWriter, r *http.Request, state *AppSign
return RedirectToRoute("index") return RedirectToRoute("index")
} }
func githubOAuthTransport(c appengine.Context) *oauth.Transport { func githubOAuthTransport(c context.Context) *oauth.Transport {
appengineTransport := &urlfetch.Transport{Context: c} ctx_with_timeout, _ := context.WithTimeout(c, time.Second*60)
appengineTransport.Deadline = time.Second * 60 appengineTransport := &urlfetch.Transport{Context: ctx_with_timeout}
cachingTransport := &CachingTransport{ cachingTransport := &CachingTransport{
Transport: appengineTransport, Transport: appengineTransport,
Context: c, Context: ctx_with_timeout,
} }
return &oauth.Transport{ return &oauth.Transport{
Config: &githubOauthConfig, Config: &githubOauthConfig,

View file

@ -1,4 +1,4 @@
package retrogit package main
import ( import (
"encoding/base64" "encoding/base64"
@ -6,7 +6,7 @@ import (
"io/ioutil" "io/ioutil"
"log" "log"
"appengine" "google.golang.org/appengine"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
) )

View file

@ -1,4 +1,4 @@
package retrogit package main
import ( import (
"encoding/json" "encoding/json"

9
deploy.sh Executable file
View file

@ -0,0 +1,9 @@
#!/bin/sh
# With the Go 1.11 runtime, if we're not using modules, all source (including
# the app itself) must live under GOPATH. Copy it there before deploying.
DEST="$GOPATH/src/retrogit"
rm -rf $DEST
cp -r app $DEST
cd $DEST
gcloud app deploy --project retro-git app.yaml