From 8e47ae21024bc35a82215e16f1e586f94ae622c9 Mon Sep 17 00:00:00 2001 From: skyblue Date: Sun, 23 Mar 2014 12:24:09 +0800 Subject: add avatar inorder to view code on github --- modules/avatar/avatar.go | 136 ++++++++++++++++++++++++++++++++++++++++++ modules/avatar/avatar_test.go | 35 +++++++++++ 2 files changed, 171 insertions(+) create mode 100644 modules/avatar/avatar.go create mode 100644 modules/avatar/avatar_test.go (limited to 'modules') diff --git a/modules/avatar/avatar.go b/modules/avatar/avatar.go new file mode 100644 index 00000000..93f842ea --- /dev/null +++ b/modules/avatar/avatar.go @@ -0,0 +1,136 @@ +package avatar + +import ( + "crypto/md5" + "encoding/hex" + "fmt" + "io" + "log" + "net/http" + "net/url" + "os" + "path/filepath" + "strings" + "sync" + "time" +) + +var gravatar = "http://www.gravatar.com/avatar" + +// hash email to md5 string +func HashEmail(email string) string { + h := md5.New() + h.Write([]byte(strings.ToLower(email))) + return hex.EncodeToString(h.Sum(nil)) +} + +type Avatar struct { + Hash string + cacheDir string // image save dir + reqParams string + imagePath string +} + +func New(hash string, cacheDir string) *Avatar { + return &Avatar{ + Hash: hash, + cacheDir: cacheDir, + reqParams: url.Values{ + "d": {"retro"}, + "size": {"200"}, + "r": {"pg"}}.Encode(), + imagePath: filepath.Join(cacheDir, hash+".jpg"), + } +} + +// get image from gravatar.com +func (this *Avatar) Update() { + thunder.Fetch(gravatar+"/"+this.Hash+"?"+this.reqParams, + this.Hash+".jpg") +} + +func (this *Avatar) UpdateTimeout(timeout time.Duration) { + select { + case <-time.After(timeout): + log.Println("timeout") + case <-thunder.GoFetch(gravatar+"/"+this.Hash+"?"+this.reqParams, + this.Hash+".jpg"): + } +} + +var thunder = &Thunder{QueueSize: 10} + +type Thunder struct { + QueueSize int // download queue size + q chan *thunderTask + once sync.Once +} + +func (t *Thunder) init() { + if t.QueueSize < 1 { + t.QueueSize = 1 + } + t.q = make(chan *thunderTask, t.QueueSize) + for i := 0; i < t.QueueSize; i++ { + go func() { + for { + task := <-t.q + task.Fetch() + } + }() + } +} + +func (t *Thunder) Fetch(url string, saveFile string) error { + t.once.Do(t.init) + task := &thunderTask{ + Url: url, + SaveFile: saveFile, + } + task.Add(1) + t.q <- task + task.Wait() + return task.err +} + +func (t *Thunder) GoFetch(url, saveFile string) chan error { + c := make(chan error) + go func() { + c <- t.Fetch(url, saveFile) + }() + return c +} + +// thunder download +type thunderTask struct { + Url string + SaveFile string + sync.WaitGroup + err error +} + +func (this *thunderTask) Fetch() { + this.err = this.fetch() + this.Done() +} + +func (this *thunderTask) fetch() error { + resp, err := http.Get(this.Url) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode != 200 { + return fmt.Errorf("status code: %d", resp.StatusCode) + } + fd, err := os.Create(this.SaveFile) + if err != nil { + return err + } + defer fd.Close() + _, err = io.Copy(fd, resp.Body) + if err != nil { + return err + } + return nil +} diff --git a/modules/avatar/avatar_test.go b/modules/avatar/avatar_test.go new file mode 100644 index 00000000..49f8f91f --- /dev/null +++ b/modules/avatar/avatar_test.go @@ -0,0 +1,35 @@ +package avatar + +import ( + "log" + "strconv" + "testing" + "time" +) + +func TestFetch(t *testing.T) { + hash := HashEmail("ssx205@gmail.com") + avatar := New(hash, "./") + //avatar.Update() + avatar.UpdateTimeout(time.Millisecond * 200) + time.Sleep(5 * time.Second) +} + +func TestFetchMany(t *testing.T) { + log.Println("start") + var n = 50 + ch := make(chan bool, n) + for i := 0; i < n; i++ { + go func(i int) { + hash := HashEmail(strconv.Itoa(i) + "ssx205@gmail.com") + avatar := New(hash, "./") + avatar.Update() + log.Println("finish", hash) + ch <- true + }(i) + } + for i := 0; i < n; i++ { + <-ch + } + log.Println("end") +} -- cgit v1.2.3 From 79604f553f45af658a884544187b00fb9fa3169c Mon Sep 17 00:00:00 2001 From: skyblue Date: Sun, 23 Mar 2014 15:55:27 +0800 Subject: fix download part problem, add png support --- modules/avatar/avatar.go | 179 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 162 insertions(+), 17 deletions(-) (limited to 'modules') diff --git a/modules/avatar/avatar.go b/modules/avatar/avatar.go index 93f842ea..55d1e13d 100644 --- a/modules/avatar/avatar.go +++ b/modules/avatar/avatar.go @@ -3,7 +3,11 @@ package avatar import ( "crypto/md5" "encoding/hex" + "errors" "fmt" + "image" + "image/jpeg" + "image/png" "io" "log" "net/http" @@ -13,9 +17,14 @@ import ( "strings" "sync" "time" + + "github.com/nfnt/resize" ) -var gravatar = "http://www.gravatar.com/avatar" +var ( + gravatar = "http://www.gravatar.com/avatar" + defaultImagePath = "./default.jpg" +) // hash email to md5 string func HashEmail(email string) string { @@ -25,37 +34,145 @@ func HashEmail(email string) string { } type Avatar struct { - Hash string - cacheDir string // image save dir - reqParams string - imagePath string + Hash string + cacheDir string // image save dir + reqParams string + imagePath string + expireDuration time.Duration } func New(hash string, cacheDir string) *Avatar { return &Avatar{ - Hash: hash, - cacheDir: cacheDir, + Hash: hash, + cacheDir: cacheDir, + expireDuration: time.Minute * 10, reqParams: url.Values{ "d": {"retro"}, "size": {"200"}, "r": {"pg"}}.Encode(), - imagePath: filepath.Join(cacheDir, hash+".jpg"), + imagePath: filepath.Join(cacheDir, hash+".image"), //maybe png or jpeg + } +} + +func (this *Avatar) InCache() bool { + fileInfo, err := os.Stat(this.imagePath) + return err == nil && fileInfo.Mode().IsRegular() +} + +func (this *Avatar) Modtime() (modtime time.Time, err error) { + fileInfo, err := os.Stat(this.imagePath) + if err != nil { + return + } + return fileInfo.ModTime(), nil +} + +func (this *Avatar) Expired() bool { + if !this.InCache() { + return true + } + fileInfo, err := os.Stat(this.imagePath) + return err != nil || time.Since(fileInfo.ModTime()) > this.expireDuration +} + +// default image format: jpeg +func (this *Avatar) Encode(wr io.Writer, size int) (err error) { + var img image.Image + decodeImageFile := func(file string) (img image.Image, err error) { + fd, err := os.Open(file) + if err != nil { + return + } + defer fd.Close() + img, err = jpeg.Decode(fd) + if err != nil { + fd.Seek(0, os.SEEK_SET) + img, err = png.Decode(fd) + } + return + } + imgPath := this.imagePath + if !this.InCache() { + imgPath = defaultImagePath + } + img, err = decodeImageFile(imgPath) + if err != nil { + return } + m := resize.Resize(uint(size), 0, img, resize.Lanczos3) + return jpeg.Encode(wr, m, nil) } // get image from gravatar.com func (this *Avatar) Update() { thunder.Fetch(gravatar+"/"+this.Hash+"?"+this.reqParams, - this.Hash+".jpg") + this.imagePath) } -func (this *Avatar) UpdateTimeout(timeout time.Duration) { +func (this *Avatar) UpdateTimeout(timeout time.Duration) error { + var err error select { case <-time.After(timeout): - log.Println("timeout") - case <-thunder.GoFetch(gravatar+"/"+this.Hash+"?"+this.reqParams, - this.Hash+".jpg"): + err = errors.New("get gravatar image timeout") + case err = <-thunder.GoFetch(gravatar+"/"+this.Hash+"?"+this.reqParams, + this.imagePath): } + return err +} + +func init() { + log.SetFlags(log.Lshortfile | log.LstdFlags) +} + +// http.Handle("/avatar/", avatar.HttpHandler("./cache")) +func HttpHandler(cacheDir string) func(w http.ResponseWriter, r *http.Request) { + MustInt := func(r *http.Request, defaultValue int, keys ...string) int { + var v int + for _, k := range keys { + if _, err := fmt.Sscanf(r.FormValue(k), "%d", &v); err == nil { + defaultValue = v + } + } + return defaultValue + } + + return func(w http.ResponseWriter, r *http.Request) { + urlPath := r.URL.Path + hash := urlPath[strings.LastIndex(urlPath, "/")+1:] + hash = HashEmail(hash) + size := MustInt(r, 80, "s", "size") // size = 80*80 + + avatar := New(hash, cacheDir) + if avatar.Expired() { + err := avatar.UpdateTimeout(time.Millisecond * 500) + if err != nil { + log.Println(err) + } + } + if modtime, err := avatar.Modtime(); err == nil { + etag := fmt.Sprintf("size(%d)", size) + if t, err := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); err == nil && modtime.Before(t.Add(1*time.Second)) && etag == r.Header.Get("If-None-Match") { + h := w.Header() + delete(h, "Content-Type") + delete(h, "Content-Length") + w.WriteHeader(http.StatusNotModified) + return + } + w.Header().Set("Last-Modified", modtime.UTC().Format(http.TimeFormat)) + w.Header().Set("ETag", etag) + } + w.Header().Set("Content-Type", "image/jpeg") + err := avatar.Encode(w, size) + if err != nil { + log.Println(err) + w.WriteHeader(500) + } + } +} + +func init() { + http.HandleFunc("/", HttpHandler("./")) + log.Fatal(http.ListenAndServe(":8001", nil)) } var thunder = &Thunder{QueueSize: 10} @@ -114,8 +231,17 @@ func (this *thunderTask) Fetch() { this.Done() } +var client = &http.Client{} + func (this *thunderTask) fetch() error { - resp, err := http.Get(this.Url) + log.Println("thunder, fetch", this.Url) + req, _ := http.NewRequest("GET", this.Url, nil) + req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8") + req.Header.Set("Accept-Encoding", "gzip,deflate,sdch") + req.Header.Set("Accept-Language", "zh-CN,zh;q=0.8") + req.Header.Set("Cache-Control", "no-cache") + req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.154 Safari/537.36") + resp, err := client.Do(req) if err != nil { return err } @@ -123,14 +249,33 @@ func (this *thunderTask) fetch() error { if resp.StatusCode != 200 { return fmt.Errorf("status code: %d", resp.StatusCode) } - fd, err := os.Create(this.SaveFile) + + /* + log.Println("headers:", resp.Header) + switch resp.Header.Get("Content-Type") { + case "image/jpeg": + this.SaveFile += ".jpeg" + case "image/png": + this.SaveFile += ".png" + } + */ + /* + imgType := resp.Header.Get("Content-Type") + if imgType != "image/jpeg" && imgType != "image/png" { + return errors.New("not png or jpeg") + } + */ + + tmpFile := this.SaveFile + ".part" // mv to destination when finished + fd, err := os.Create(tmpFile) if err != nil { return err } - defer fd.Close() _, err = io.Copy(fd, resp.Body) + fd.Close() if err != nil { + os.Remove(tmpFile) return err } - return nil + return os.Rename(tmpFile, this.SaveFile) } -- cgit v1.2.3 From 964e537479c497a5ba42799a1c1a7c430720e990 Mon Sep 17 00:00:00 2001 From: Gogs Date: Sun, 23 Mar 2014 18:13:23 +0800 Subject: append route to web --- conf/app.ini | 8 +-- models/user.go | 2 +- modules/avatar/avatar.go | 123 ++++++++++++++++++++++++------------------ modules/avatar/avatar_test.go | 41 ++++++++++---- modules/base/tool.go | 2 +- public/img/avatar/default.jpg | Bin 0 -> 17379 bytes web.go | 5 +- 7 files changed, 111 insertions(+), 70 deletions(-) create mode 100644 public/img/avatar/default.jpg (limited to 'modules') diff --git a/conf/app.ini b/conf/app.ini index ecb0d251..160aef0f 100644 --- a/conf/app.ini +++ b/conf/app.ini @@ -7,7 +7,7 @@ RUN_USER = lunny RUN_MODE = dev [repository] -ROOT = /Users/%(RUN_USER)s/git/gogs-repositories +ROOT = /home/work/%(RUN_USER)s/git/gogs-repositories LANG_IGNS = Google Go|C|C++|Python|Ruby|C Sharp LICENSES = Apache v2 License|GPL v2|MIT License|Affero GPL|Artistic License 2.0|BSD (3-Clause) License @@ -15,7 +15,7 @@ LICENSES = Apache v2 License|GPL v2|MIT License|Affero GPL|Artistic License 2.0| DOMAIN = localhost ROOT_URL = http://%(DOMAIN)s:%(HTTP_PORT)s/ HTTP_ADDR = -HTTP_PORT = 3000 +HTTP_PORT = 8002 [database] ; Either "mysql", "postgres" or "sqlite3"(binary release only), it's your choice @@ -23,7 +23,7 @@ DB_TYPE = mysql HOST = NAME = gogs USER = root -PASSWD = +PASSWD = toor ; For "postgres" only, either "disable", "require" or "verify-full" SSL_MODE = disable ; For "sqlite3" only @@ -120,4 +120,4 @@ HOST = USER = PASSWD = ; Receivers, can be one or more, e.g. ["1@example.com","2@example.com"] -RECEIVERS = \ No newline at end of file +RECEIVERS = diff --git a/models/user.go b/models/user.go index 3c110912..cedf3424 100644 --- a/models/user.go +++ b/models/user.go @@ -72,7 +72,7 @@ func (user *User) HomeLink() string { // AvatarLink returns the user gravatar link. func (user *User) AvatarLink() string { - return "http://1.gravatar.com/avatar/" + user.Avatar + return "/avatar/" + user.Avatar } // NewGitSig generates and returns the signature of given user. diff --git a/modules/avatar/avatar.go b/modules/avatar/avatar.go index 55d1e13d..1a18d8a7 100644 --- a/modules/avatar/avatar.go +++ b/modules/avatar/avatar.go @@ -1,3 +1,8 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +// for www.gravatar.com image cache package avatar import ( @@ -22,11 +27,17 @@ import ( ) var ( - gravatar = "http://www.gravatar.com/avatar" - defaultImagePath = "./default.jpg" + gravatar = "http://www.gravatar.com/avatar" ) +func debug(a ...interface{}) { + if true { + log.Println(a...) + } +} + // hash email to md5 string +// keep this func in order to make this package indenpent func HashEmail(email string) string { h := md5.New() h.Write([]byte(strings.ToLower(email))) @@ -35,6 +46,7 @@ func HashEmail(email string) string { type Avatar struct { Hash string + AlterImage string // image path cacheDir string // image save dir reqParams string imagePath string @@ -54,7 +66,7 @@ func New(hash string, cacheDir string) *Avatar { } } -func (this *Avatar) InCache() bool { +func (this *Avatar) HasCache() bool { fileInfo, err := os.Stat(this.imagePath) return err == nil && fileInfo.Mode().IsRegular() } @@ -68,11 +80,8 @@ func (this *Avatar) Modtime() (modtime time.Time, err error) { } func (this *Avatar) Expired() bool { - if !this.InCache() { - return true - } - fileInfo, err := os.Stat(this.imagePath) - return err != nil || time.Since(fileInfo.ModTime()) > this.expireDuration + modtime, err := this.Modtime() + return err != nil || time.Since(modtime) > this.expireDuration } // default image format: jpeg @@ -92,8 +101,11 @@ func (this *Avatar) Encode(wr io.Writer, size int) (err error) { return } imgPath := this.imagePath - if !this.InCache() { - imgPath = defaultImagePath + if !this.HasCache() { + if this.AlterImage == "" { + return errors.New("request image failed, and no alt image offered") + } + imgPath = this.AlterImage } img, err = decodeImageFile(imgPath) if err != nil { @@ -120,61 +132,66 @@ func (this *Avatar) UpdateTimeout(timeout time.Duration) error { return err } -func init() { - log.SetFlags(log.Lshortfile | log.LstdFlags) +type avatarHandler struct { + cacheDir string + altImage string } -// http.Handle("/avatar/", avatar.HttpHandler("./cache")) -func HttpHandler(cacheDir string) func(w http.ResponseWriter, r *http.Request) { - MustInt := func(r *http.Request, defaultValue int, keys ...string) int { - var v int - for _, k := range keys { - if _, err := fmt.Sscanf(r.FormValue(k), "%d", &v); err == nil { - defaultValue = v - } +func (this *avatarHandler) mustInt(r *http.Request, defaultValue int, keys ...string) int { + var v int + for _, k := range keys { + if _, err := fmt.Sscanf(r.FormValue(k), "%d", &v); err == nil { + defaultValue = v } - return defaultValue } + return defaultValue +} - return func(w http.ResponseWriter, r *http.Request) { - urlPath := r.URL.Path - hash := urlPath[strings.LastIndex(urlPath, "/")+1:] - hash = HashEmail(hash) - size := MustInt(r, 80, "s", "size") // size = 80*80 +func (this *avatarHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + urlPath := r.URL.Path + hash := urlPath[strings.LastIndex(urlPath, "/")+1:] + //hash = HashEmail(hash) + size := this.mustInt(r, 80, "s", "size") // size = 80*80 - avatar := New(hash, cacheDir) - if avatar.Expired() { - err := avatar.UpdateTimeout(time.Millisecond * 500) - if err != nil { - log.Println(err) - } - } - if modtime, err := avatar.Modtime(); err == nil { - etag := fmt.Sprintf("size(%d)", size) - if t, err := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); err == nil && modtime.Before(t.Add(1*time.Second)) && etag == r.Header.Get("If-None-Match") { - h := w.Header() - delete(h, "Content-Type") - delete(h, "Content-Length") - w.WriteHeader(http.StatusNotModified) - return - } - w.Header().Set("Last-Modified", modtime.UTC().Format(http.TimeFormat)) - w.Header().Set("ETag", etag) - } - w.Header().Set("Content-Type", "image/jpeg") - err := avatar.Encode(w, size) + avatar := New(hash, this.cacheDir) + avatar.AlterImage = this.altImage + if avatar.Expired() { + err := avatar.UpdateTimeout(time.Millisecond * 500) if err != nil { - log.Println(err) - w.WriteHeader(500) + debug(err) + //log.Trace("avatar update error: %v", err) + } + } + if modtime, err := avatar.Modtime(); err == nil { + etag := fmt.Sprintf("size(%d)", size) + if t, err := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); err == nil && modtime.Before(t.Add(1*time.Second)) && etag == r.Header.Get("If-None-Match") { + h := w.Header() + delete(h, "Content-Type") + delete(h, "Content-Length") + w.WriteHeader(http.StatusNotModified) + return } + w.Header().Set("Last-Modified", modtime.UTC().Format(http.TimeFormat)) + w.Header().Set("ETag", etag) + } + w.Header().Set("Content-Type", "image/jpeg") + err := avatar.Encode(w, size) + if err != nil { + //log.Warn("avatar encode error: %v", err) // will panic when err != nil + debug(err) + w.WriteHeader(500) } } -func init() { - http.HandleFunc("/", HttpHandler("./")) - log.Fatal(http.ListenAndServe(":8001", nil)) +// http.Handle("/avatar/", avatar.HttpHandler("./cache")) +func HttpHandler(cacheDir string, defaultImgPath string) http.Handler { + return &avatarHandler{ + cacheDir: cacheDir, + altImage: defaultImgPath, + } } +// thunder downloader var thunder = &Thunder{QueueSize: 10} type Thunder struct { @@ -234,7 +251,7 @@ func (this *thunderTask) Fetch() { var client = &http.Client{} func (this *thunderTask) fetch() error { - log.Println("thunder, fetch", this.Url) + //log.Println("thunder, fetch", this.Url) req, _ := http.NewRequest("GET", this.Url, nil) req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8") req.Header.Set("Accept-Encoding", "gzip,deflate,sdch") diff --git a/modules/avatar/avatar_test.go b/modules/avatar/avatar_test.go index 49f8f91f..a337959c 100644 --- a/modules/avatar/avatar_test.go +++ b/modules/avatar/avatar_test.go @@ -1,29 +1,41 @@ -package avatar +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +package avatar_test import ( "log" + "os" "strconv" "testing" "time" + + "github.com/gogits/gogs/modules/avatar" ) +const TMPDIR = "test-avatar" + func TestFetch(t *testing.T) { - hash := HashEmail("ssx205@gmail.com") - avatar := New(hash, "./") - //avatar.Update() - avatar.UpdateTimeout(time.Millisecond * 200) - time.Sleep(5 * time.Second) + os.Mkdir(TMPDIR, 0755) + defer os.RemoveAll(TMPDIR) + + hash := avatar.HashEmail("ssx205@gmail.com") + a := avatar.New(hash, TMPDIR) + a.UpdateTimeout(time.Millisecond * 200) } func TestFetchMany(t *testing.T) { + os.Mkdir(TMPDIR, 0755) + defer os.RemoveAll(TMPDIR) + log.Println("start") - var n = 50 + var n = 5 ch := make(chan bool, n) for i := 0; i < n; i++ { go func(i int) { - hash := HashEmail(strconv.Itoa(i) + "ssx205@gmail.com") - avatar := New(hash, "./") - avatar.Update() + hash := avatar.HashEmail(strconv.Itoa(i) + "ssx205@gmail.com") + a := avatar.New(hash, TMPDIR) + a.Update() log.Println("finish", hash) ch <- true }(i) @@ -33,3 +45,12 @@ func TestFetchMany(t *testing.T) { } log.Println("end") } + +// cat +// wget http://www.artsjournal.com/artfulmanager/wp/wp-content/uploads/2013/12/200x200xmirror_cat.jpg.pagespeed.ic.GOZSv6v1_H.jpg -O default.jpg +/* +func TestHttp(t *testing.T) { + http.Handle("/", avatar.HttpHandler("./", "default.jpg")) + http.ListenAndServe(":8001", nil) +} +*/ diff --git a/modules/base/tool.go b/modules/base/tool.go index 8fabb8c5..8d0d3821 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -98,7 +98,7 @@ func CreateTimeLimitCode(data string, minutes int, startInf interface{}) string // AvatarLink returns avatar link by given e-mail. func AvatarLink(email string) string { - return "http://1.gravatar.com/avatar/" + EncodeMd5(email) + return "/avatar/" + EncodeMd5(email) } // Seconds-based time units diff --git a/public/img/avatar/default.jpg b/public/img/avatar/default.jpg new file mode 100644 index 00000000..c5a698da Binary files /dev/null and b/public/img/avatar/default.jpg differ diff --git a/web.go b/web.go index bb316a67..637ee7ce 100644 --- a/web.go +++ b/web.go @@ -18,6 +18,7 @@ import ( "github.com/gogits/gogs/models" "github.com/gogits/gogs/modules/auth" + "github.com/gogits/gogs/modules/avatar" "github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/mailer" @@ -114,6 +115,9 @@ func runWeb(*cli.Context) { m.Get("/help", routers.Help) + avatarHandler := avatar.HttpHandler("public/img/avatar", "public/img/avatar/default.jpg") + m.Get("/avatar/:hash", avatarHandler.ServeHTTP) + adminReq := middleware.AdminRequire() m.Get("/admin", reqSignIn, adminReq, admin.Dashboard) m.Get("/admin/users", reqSignIn, adminReq, admin.Users) @@ -136,7 +140,6 @@ func runWeb(*cli.Context) { ignSignIn, middleware.RepoAssignment(true), repo.Single) m.Get("/:username/:reponame/commit/:commitid/**", ignSignIn, middleware.RepoAssignment(true), repo.Single) m.Get("/:username/:reponame/commit/:commitid", ignSignIn, middleware.RepoAssignment(true), repo.Single) - m.Get("/:username/:reponame", ignSignIn, middleware.RepoAssignment(true), repo.Single) m.Any("/:username/:reponame/**", ignSignIn, repo.Http) -- cgit v1.2.3