From 33aa4f74380ab117673a1cc30bead3a7f2b3cb4b Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 11 Apr 2014 21:47:39 -0400 Subject: Support private repo --- modules/auth/repo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'modules/auth/repo.go') diff --git a/modules/auth/repo.go b/modules/auth/repo.go index eddd6475..1c740bed 100644 --- a/modules/auth/repo.go +++ b/modules/auth/repo.go @@ -18,7 +18,7 @@ import ( type CreateRepoForm struct { RepoName string `form:"repo" binding:"Required;AlphaDash"` - Visibility string `form:"visibility"` + Private string `form:"private"` Description string `form:"desc" binding:"MaxSize(100)"` Language string `form:"language"` License string `form:"license"` -- cgit v1.2.3 From 90f6aa8cd19e489723ddffc40d6507782c29756c Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 12 Apr 2014 20:35:35 -0400 Subject: Add repo mirror and import --- README.md | 9 ++- README_ZH.md | 9 ++- gogs.go | 2 +- models/access.go | 2 +- models/models.go | 3 +- models/repo.go | 131 +++++++++++++++++++++++++++++++++++----- modules/auth/auth.go | 4 +- modules/auth/repo.go | 42 ++++++++++++- modules/middleware/repo.go | 24 -------- routers/repo/repo.go | 50 +++++++++------ routers/user/user.go | 14 +++++ templates/base/navbar.tmpl | 2 +- templates/repo/commits.tmpl | 2 +- templates/repo/diff.tmpl | 2 +- templates/repo/migrate.tmpl | 99 ++++++++++++++++++++++++++++++ templates/repo/mirror.tmpl | 81 ------------------------- templates/repo/nav.tmpl | 2 +- templates/repo/single_bare.tmpl | 14 ----- templates/user/dashboard.tmpl | 2 +- templates/user/profile.tmpl | 24 ++++---- web.go | 14 ++--- 21 files changed, 339 insertions(+), 193 deletions(-) create mode 100644 templates/repo/migrate.tmpl delete mode 100644 templates/repo/mirror.tmpl (limited to 'modules/auth/repo.go') diff --git a/README.md b/README.md index d48c05c2..fa48027d 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language ![Demo](http://gowalker.org/public/gogs_demo.gif) -##### Current version: 0.2.7 Alpha +##### Current version: 0.2.8 Alpha #### Due to testing purpose, data of [try.gogits.org](http://try.gogits.org) has been reset in April 6, 2014 and will reset multiple times after. Please do NOT put your important data on the site. @@ -31,13 +31,12 @@ More importantly, Gogs only needs one binary to setup your own project hosting o - Activity timeline - SSH/HTTP(S) protocol support. - Register/delete/rename account. -- Create/delete/watch/rename/transfer public/private repository. -- Repository viewer. -- Issue tracker. +- Create/migrate/mirror/delete/watch/rename/transfer public/private repository. +- Repository viewer/issue tracker. - Gravatar and cache support. - Mail service(register, issue). - Administration panel. -- Supports MySQL, PostgreSQL and SQLite3(binary release only). +- Supports MySQL, PostgreSQL and SQLite3. ## Installation diff --git a/README_ZH.md b/README_ZH.md index 62996d4d..beb5a105 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -5,7 +5,7 @@ Gogs(Go Git Service) 是一个由 Go 语言编写的自助 Git 托管服务。 ![Demo](http://gowalker.org/public/gogs_demo.gif) -##### 当前版本:0.2.7 Alpha +##### 当前版本:0.2.8 Alpha ## 开发目的 @@ -25,13 +25,12 @@ Gogs 完全使用 Go 语言来实现对 Git 数据的操作,实现 **零** 依 - 活动时间线 - SSH/HTTP(S) 协议支持 - 注册/删除/重命名用户 -- 创建/删除/关注/重命名/转移 公开/私有 仓库 -- 仓库浏览器 -- Bug 追踪系统 +- 创建/迁移/镜像/删除/关注/重命名/转移 公开/私有 仓库 +- 仓库 浏览器/Bug 追踪 - Gravatar 以及缓存支持 - 邮件服务(注册、Issue) - 管理员面板 -- 支持 MySQL、PostgreSQL 以及 SQLite3(仅限二进制版本) +- 支持 MySQL、PostgreSQL 以及 SQLite3 ## 安装部署 diff --git a/gogs.go b/gogs.go index f726e809..a42d7225 100644 --- a/gogs.go +++ b/gogs.go @@ -19,7 +19,7 @@ import ( // Test that go1.2 tag above is included in builds. main.go refers to this definition. const go12tag = true -const APP_VER = "0.2.7.0411 Alpha" +const APP_VER = "0.2.8.0412 Alpha" func init() { base.AppVer = APP_VER diff --git a/models/access.go b/models/access.go index 5bf93f1b..f20f8fa7 100644 --- a/models/access.go +++ b/models/access.go @@ -21,7 +21,7 @@ const ( type Access struct { Id int64 UserName string `xorm:"unique(s)"` - RepoName string `xorm:"unique(s)"` + RepoName string `xorm:"unique(s)"` // / Mode int `xorm:"unique(s)"` Created time.Time `xorm:"created"` } diff --git a/models/models.go b/models/models.go index b8374a3d..f1d43531 100644 --- a/models/models.go +++ b/models/models.go @@ -32,7 +32,8 @@ var ( func init() { tables = append(tables, new(User), new(PublicKey), new(Repository), new(Watch), - new(Action), new(Access), new(Issue), new(Comment), new(Oauth2), new(Follow)) + new(Action), new(Access), new(Issue), new(Comment), new(Oauth2), new(Follow), + new(Mirror)) } func LoadModelsConfig() { diff --git a/models/repo.go b/models/repo.go index d01a716b..a2c63a50 100644 --- a/models/repo.go +++ b/models/repo.go @@ -78,6 +78,7 @@ type Repository struct { NumClosedIssues int NumOpenIssues int `xorm:"-"` IsPrivate bool + IsMirror bool IsBare bool IsGoget bool DefaultBranch string @@ -119,13 +120,92 @@ func IsLegalName(repoName string) bool { return true } +// Mirror represents a mirror information of repository. +type Mirror struct { + Id int64 + RepoId int64 + RepoName string // / + Interval int // Hour. + Updated time.Time `xorm:"UPDATED"` + NextUpdate time.Time +} + +// MirrorRepository creates a mirror repository from source. +func MirrorRepository(repoId int64, userName, repoName, repoPath, url string) error { + _, stderr, err := com.ExecCmd("git", "clone", "--mirror", url, repoPath) + if err != nil { + return err + } else if strings.Contains(stderr, "fatal:") { + return errors.New(stderr) + } + + if _, err = orm.InsertOne(&Mirror{ + RepoId: repoId, + RepoName: strings.ToLower(userName + "/" + repoName), + Interval: 24, + NextUpdate: time.Now().Add(24 * time.Hour), + }); err != nil { + return err + } + + return git.UnpackRefs(repoPath) +} + +// MigrateRepository migrates a existing repository from other project hosting. +func MigrateRepository(user *User, name, desc string, private, mirror bool, url string) (*Repository, error) { + repo, err := CreateRepository(user, name, desc, "", "", private, mirror, false) + if err != nil { + return nil, err + } + + // Clone to temprory path and do the init commit. + tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond())) + os.MkdirAll(tmpDir, os.ModePerm) + + repoPath := RepoPath(user.Name, name) + + repo.IsBare = false + if mirror { + if err = MirrorRepository(repo.Id, user.Name, repo.Name, repoPath, url); err != nil { + return repo, err + } + repo.IsMirror = true + return repo, UpdateRepository(repo) + } + + // Clone from local repository. + _, stderr, err := com.ExecCmd("git", "clone", repoPath, tmpDir) + if err != nil { + return repo, err + } else if strings.Contains(stderr, "fatal:") { + return repo, errors.New("git clone: " + stderr) + } + + // Pull data from source. + _, stderr, err = com.ExecCmdDir(tmpDir, "git", "pull", url) + if err != nil { + return repo, err + } else if strings.Contains(stderr, "fatal:") { + return repo, errors.New("git pull: " + stderr) + } + + // Push data to local repository. + if _, stderr, err = com.ExecCmdDir(tmpDir, "git", "push", "origin", "master"); err != nil { + return repo, err + } else if strings.Contains(stderr, "fatal:") { + return repo, errors.New("git push: " + stderr) + } + + return repo, UpdateRepository(repo) +} + // CreateRepository creates a repository for given user or orgnaziation. -func CreateRepository(user *User, repoName, desc, repoLang, license string, private bool, initReadme bool) (*Repository, error) { - if !IsLegalName(repoName) { +func CreateRepository(user *User, name, desc, lang, license string, private, mirror, initReadme bool) (*Repository, error) { + if !IsLegalName(name) { return nil, ErrRepoNameIllegal } - isExist, err := IsRepositoryExist(user, repoName) + isExist, err := IsRepositoryExist(user, name) if err != nil { return nil, err } else if isExist { @@ -134,13 +214,13 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv repo := &Repository{ OwnerId: user.Id, - Name: repoName, - LowerName: strings.ToLower(repoName), + Name: name, + LowerName: strings.ToLower(name), Description: desc, IsPrivate: private, - IsBare: repoLang == "" && license == "" && !initReadme, + IsBare: lang == "" && license == "" && !initReadme, } - repoPath := RepoPath(user.Name, repoName) + repoPath := RepoPath(user.Name, repo.Name) sess := orm.NewSession() defer sess.Close() @@ -150,23 +230,27 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv if err2 := os.RemoveAll(repoPath); err2 != nil { log.Error("repo.CreateRepository(repo): %v", err) return nil, errors.New(fmt.Sprintf( - "delete repo directory %s/%s failed(1): %v", user.Name, repoName, err2)) + "delete repo directory %s/%s failed(1): %v", user.Name, repo.Name, err2)) } sess.Rollback() return nil, err } + mode := AU_WRITABLE + if mirror { + mode = AU_READABLE + } access := Access{ UserName: user.LowerName, RepoName: strings.ToLower(path.Join(user.Name, repo.Name)), - Mode: AU_WRITABLE, + Mode: mode, } if _, err = sess.Insert(&access); err != nil { sess.Rollback() if err2 := os.RemoveAll(repoPath); err2 != nil { log.Error("repo.CreateRepository(access): %v", err) return nil, errors.New(fmt.Sprintf( - "delete repo directory %s/%s failed(2): %v", user.Name, repoName, err2)) + "delete repo directory %s/%s failed(2): %v", user.Name, repo.Name, err2)) } return nil, err } @@ -177,7 +261,7 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv if err2 := os.RemoveAll(repoPath); err2 != nil { log.Error("repo.CreateRepository(repo count): %v", err) return nil, errors.New(fmt.Sprintf( - "delete repo directory %s/%s failed(3): %v", user.Name, repoName, err2)) + "delete repo directory %s/%s failed(3): %v", user.Name, repo.Name, err2)) } return nil, err } @@ -187,7 +271,7 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv if err2 := os.RemoveAll(repoPath); err2 != nil { log.Error("repo.CreateRepository(commit): %v", err) return nil, errors.New(fmt.Sprintf( - "delete repo directory %s/%s failed(3): %v", user.Name, repoName, err2)) + "delete repo directory %s/%s failed(3): %v", user.Name, repo.Name, err2)) } return nil, err } @@ -202,7 +286,12 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv log.Error("repo.CreateRepository(WatchRepo): %v", err) } - if err = initRepository(repoPath, user, repo, initReadme, repoLang, license); err != nil { + // No need for init for mirror. + if mirror { + return repo, nil + } + + if err = initRepository(repoPath, user, repo, initReadme, lang, license); err != nil { return nil, err } @@ -304,9 +393,13 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond())) os.MkdirAll(tmpDir, os.ModePerm) - if _, _, err := com.ExecCmd("git", "clone", repoPath, tmpDir); err != nil { + _, stderr, err := com.ExecCmd("git", "clone", repoPath, tmpDir) + if err != nil { return err } + if len(stderr) > 0 { + log.Trace("repo.initRepository(git clone): %s", stderr) + } // README if initReadme { @@ -379,6 +472,7 @@ func GetRepos(num, offset int) ([]UserRepo, error) { return urepos, nil } +// RepoPath returns repository path by given user and repository name. func RepoPath(userName, repoName string) string { return filepath.Join(UserPath(userName), strings.ToLower(repoName)+".git") } @@ -519,8 +613,7 @@ func DeleteRepository(userId, repoId int64, userName string) (err error) { sess.Rollback() return err } - rawSql := "UPDATE `user` SET num_repos = num_repos - 1 WHERE id = ?" - if _, err = sess.Exec(rawSql, userId); err != nil { + if _, err := sess.Delete(&Action{RepoId: repo.Id}); err != nil { sess.Rollback() return err } @@ -528,6 +621,12 @@ func DeleteRepository(userId, repoId int64, userName string) (err error) { sess.Rollback() return err } + + rawSql := "UPDATE `user` SET num_repos = num_repos - 1 WHERE id = ?" + if _, err = sess.Exec(rawSql, userId); err != nil { + sess.Rollback() + return err + } if err = sess.Commit(); err != nil { sess.Rollback() return err diff --git a/modules/auth/auth.go b/modules/auth/auth.go index 4561dd83..7329cbdc 100644 --- a/modules/auth/auth.go +++ b/modules/auth/auth.go @@ -130,7 +130,9 @@ func validate(errors *binding.Errors, data base.TmplData, form Form) { case binding.MaxSizeError: data["ErrorMsg"] = form.Name(field.Name) + " must contain at most " + getMinMaxSize(field) + " characters" case binding.EmailError: - data["ErrorMsg"] = form.Name(field.Name) + " is not valid" + data["ErrorMsg"] = form.Name(field.Name) + " is not a valid e-mail address" + case binding.UrlError: + data["ErrorMsg"] = form.Name(field.Name) + " is not a valid URL" default: data["ErrorMsg"] = "Unknown error: " + err } diff --git a/modules/auth/repo.go b/modules/auth/repo.go index 1c740bed..aa94058f 100644 --- a/modules/auth/repo.go +++ b/modules/auth/repo.go @@ -18,11 +18,11 @@ import ( type CreateRepoForm struct { RepoName string `form:"repo" binding:"Required;AlphaDash"` - Private string `form:"private"` + Private bool `form:"private"` Description string `form:"desc" binding:"MaxSize(100)"` Language string `form:"language"` License string `form:"license"` - InitReadme string `form:"initReadme"` + InitReadme bool `form:"initReadme"` } func (f *CreateRepoForm) Name(field string) string { @@ -51,3 +51,41 @@ func (f *CreateRepoForm) Validate(errors *binding.Errors, req *http.Request, con validate(errors, data, f) } + +type MigrateRepoForm struct { + Url string `form:"url" binding:"Url"` + AuthUserName string `form:"auth_username"` + AuthPasswd string `form:"auth_password"` + RepoName string `form:"repo" binding:"Required;AlphaDash"` + Mirror bool `form:"mirror"` + Private bool `form:"private"` + Description string `form:"desc" binding:"MaxSize(100)"` +} + +func (f *MigrateRepoForm) Name(field string) string { + names := map[string]string{ + "Url": "Migration URL", + "RepoName": "Repository name", + "Description": "Description", + } + return names[field] +} + +func (f *MigrateRepoForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) { + if req.Method == "GET" || errors.Count() == 0 { + return + } + + data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData) + data["HasError"] = true + AssignForm(f, data) + + if len(errors.Overall) > 0 { + for _, err := range errors.Overall { + log.Error("MigrateRepoForm.Validate: %v", err) + } + return + } + + validate(errors, data, f) +} diff --git a/modules/middleware/repo.go b/modules/middleware/repo.go index 75d9f999..2a6d300e 100644 --- a/modules/middleware/repo.go +++ b/modules/middleware/repo.go @@ -190,27 +190,3 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler { ctx.Data["IsRepositoryWatching"] = ctx.Repo.IsWatching } } - -func WriteAccess() martini.Handler { - return func(ctx *Context) { - if ctx.Repo.Repository.IsPrivate { - ctx.Repo.HasAccess = false - ctx.Data["HasAccess"] = false - if ctx.User == nil { - ctx.Handle(404, "WriteAccess", nil) - return - } - - hasAccess, err := models.HasAccess(ctx.User.Name, ctx.Repo.Owner.Name+"/"+ctx.Repo.Repository.Name, models.AU_WRITABLE) - if err != nil { - ctx.Handle(500, "WriteAccess(HasAccess)", err) - return - } else if !hasAccess { - ctx.Handle(404, "WriteAccess(HasAccess)", nil) - return - } - } - ctx.Repo.HasAccess = true - ctx.Data["HasAccess"] = true - } -} diff --git a/routers/repo/repo.go b/routers/repo/repo.go index 3ffbde1a..f19ae02e 100644 --- a/routers/repo/repo.go +++ b/routers/repo/repo.go @@ -40,8 +40,8 @@ func CreatePost(ctx *middleware.Context, form auth.CreateRepoForm) { return } - _, err := models.CreateRepository(ctx.User, form.RepoName, form.Description, - form.Language, form.License, form.Private == "on", form.InitReadme == "on") + repo, err := models.CreateRepository(ctx.User, form.RepoName, form.Description, + form.Language, form.License, form.Private, false, form.InitReadme) if err == nil { log.Trace("%s Repository created: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, form.RepoName) ctx.Redirect("/" + ctx.User.Name + "/" + form.RepoName) @@ -53,38 +53,56 @@ func CreatePost(ctx *middleware.Context, form auth.CreateRepoForm) { ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), "repo/create", &form) return } + + if repo != nil { + if errDelete := models.DeleteRepository(ctx.User.Id, repo.Id, ctx.User.Name); errDelete != nil { + log.Error("repo.MigratePost(CreatePost): %v", errDelete) + } + } ctx.Handle(500, "repo.Create", err) } -func Mirror(ctx *middleware.Context) { - ctx.Data["Title"] = "Mirror repository" +func Migrate(ctx *middleware.Context) { + ctx.Data["Title"] = "Migrate repository" ctx.Data["PageIsNewRepo"] = true - ctx.HTML(200, "repo/mirror") + ctx.HTML(200, "repo/migrate") } -func MirrorPost(ctx *middleware.Context, form auth.CreateRepoForm) { - ctx.Data["Title"] = "Mirror repository" +func MigratePost(ctx *middleware.Context, form auth.MigrateRepoForm) { + ctx.Data["Title"] = "Migrate repository" ctx.Data["PageIsNewRepo"] = true if ctx.HasError() { - ctx.HTML(200, "repo/mirror") + ctx.HTML(200, "repo/migrate") return } - _, err := models.CreateRepository(ctx.User, form.RepoName, form.Description, - "", form.License, form.Private == "on", false) + url := strings.Replace(form.Url, "://", fmt.Sprintf("://%s:%s@", form.AuthUserName, form.AuthPasswd), 1) + repo, err := models.MigrateRepository(ctx.User, form.RepoName, form.Description, form.Private, + form.Mirror, url) if err == nil { - log.Trace("%s Repository created: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, form.RepoName) + log.Trace("%s Repository migrated: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, form.RepoName) ctx.Redirect("/" + ctx.User.Name + "/" + form.RepoName) return } else if err == models.ErrRepoAlreadyExist { - ctx.RenderWithErr("Repository name has already been used", "repo/mirror", &form) + ctx.RenderWithErr("Repository name has already been used", "repo/migrate", &form) return } else if err == models.ErrRepoNameIllegal { - ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), "repo/mirror", &form) + ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), "repo/migrate", &form) + return + } + + if repo != nil { + if errDelete := models.DeleteRepository(ctx.User.Id, repo.Id, ctx.User.Name); errDelete != nil { + log.Error("repo.MigratePost(DeleteRepository): %v", errDelete) + } + } + + if strings.Contains(err.Error(), "Authentication failed") { + ctx.RenderWithErr(err.Error(), "repo/migrate", &form) return } - ctx.Handle(500, "repo.Mirror", err) + ctx.Handle(500, "repo.Migrate", err) } func Single(ctx *middleware.Context, params martini.Params) { @@ -425,7 +443,3 @@ func Action(ctx *middleware.Context, params martini.Params) { "ok": true, }) } - -func Import(ctx *middleware.Context, params martini.Params) { - ctx.ResponseWriter.Write([]byte("not done yet")) -} diff --git a/routers/user/user.go b/routers/user/user.go index 8585267a..e5328173 100644 --- a/routers/user/user.go +++ b/routers/user/user.go @@ -74,6 +74,20 @@ func Profile(ctx *middleware.Context, params martini.Params) { ctx.HTML(200, "user/profile") } +func Email2User(ctx *middleware.Context) { + u, err := models.GetUserByEmail(ctx.Query("email")) + if err != nil { + if err == models.ErrUserNotExist { + ctx.Handle(404, "user.Email2User", err) + } else { + ctx.Handle(500, "user.Email2User(GetUserByEmail)", err) + } + return + } + + ctx.Redirect("/user/" + u.Name) +} + func SignIn(ctx *middleware.Context) { ctx.Data["Title"] = "Log In" diff --git a/templates/base/navbar.tmpl b/templates/base/navbar.tmpl index 708a3976..cd3f2b83 100644 --- a/templates/base/navbar.tmpl +++ b/templates/base/navbar.tmpl @@ -28,7 +28,7 @@ diff --git a/templates/repo/commits.tmpl b/templates/repo/commits.tmpl index 68b14035..8125feda 100644 --- a/templates/repo/commits.tmpl +++ b/templates/repo/commits.tmpl @@ -31,7 +31,7 @@ {{$r := List .Commits}} {{range $r}} - {{.Author.Name}} + {{.Author.Name}} {{SubStr .Id.String 0 10}} {{.Message}} {{TimeSince .Author.When}} diff --git a/templates/repo/diff.tmpl b/templates/repo/diff.tmpl index 796a8e94..f3d935ae 100644 --- a/templates/repo/diff.tmpl +++ b/templates/repo/diff.tmpl @@ -14,7 +14,7 @@

- {{.Commit.Author.Name}} + {{.Commit.Author.Name}} {{TimeSince .Commit.Author.When}}

diff --git a/templates/repo/migrate.tmpl b/templates/repo/migrate.tmpl new file mode 100644 index 00000000..34a4077e --- /dev/null +++ b/templates/repo/migrate.tmpl @@ -0,0 +1,99 @@ +{{template "base/head" .}} +{{template "base/navbar" .}} +
+
+ {{.CsrfTokenHtml}} +

Repository Migration

+ {{template "base/alert" .}} + + +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+
+ +
+

{{.SignedUserName}}

+ +
+
+ +
+ +
+ + Great repository names are short and memorable. +
+
+ +
+ +
+
+ +
+
+
+ +
+ +
+
+ +
+
+
+ +
+ +
+ +
+
+ +
+
+ + Cancel +
+
+
+
+{{template "base/footer" .}} \ No newline at end of file diff --git a/templates/repo/mirror.tmpl b/templates/repo/mirror.tmpl deleted file mode 100644 index 0f10d1f5..00000000 --- a/templates/repo/mirror.tmpl +++ /dev/null @@ -1,81 +0,0 @@ -{{template "base/head" .}} -{{template "base/navbar" .}} -
-
- {{.CsrfTokenHtml}} -

Create Repository Mirror

- {{template "base/alert" .}} -
- -
- -
-
-
- -
- -
-
-
- -
-
- -
- -
-
-
- -
- -
-
-
-
-
-
- -
-

{{.SignedUserName}}

- -
-
- -
- -
- - Great repository names are short and memorable. -
-
- -
- -
-

Public

- -
-
- -
- -
- -
-
- -
-
- - Cancel -
-
-
-
-{{template "base/footer" .}} \ No newline at end of file diff --git a/templates/repo/nav.tmpl b/templates/repo/nav.tmpl index f365ab64..f9f74dd1 100644 --- a/templates/repo/nav.tmpl +++ b/templates/repo/nav.tmpl @@ -2,7 +2,7 @@
-

{{.Owner.Name}} / {{.Repository.Name}}{{if .Repository.IsPrivate}} Private {{end}}

+

{{.Owner.Name}} / {{.Repository.Name}} {{if .Repository.IsPrivate}}Private{{else if .Repository.IsMirror}}Mirror{{end}}

{{.Repository.Description}}{{if .Repository.Website}} {{.Repository.Website}}{{end}}

diff --git a/templates/repo/single_bare.tmpl b/templates/repo/single_bare.tmpl index 7d7016e5..fc0a3bd9 100644 --- a/templates/repo/single_bare.tmpl +++ b/templates/repo/single_bare.tmpl @@ -9,20 +9,6 @@

Quick Guide

-
- {{.CsrfTokenHtml}} -

Clone from existing repository

-
- - - - - - - -
-
-

Clone this repository

diff --git a/templates/user/dashboard.tmpl b/templates/user/dashboard.tmpl index efa78d88..167ae9ab 100644 --- a/templates/user/dashboard.tmpl +++ b/templates/user/dashboard.tmpl @@ -34,7 +34,7 @@ diff --git a/templates/user/profile.tmpl b/templates/user/profile.tmpl index 0cbd3489..255eb71c 100644 --- a/templates/user/profile.tmpl +++ b/templates/user/profile.tmpl @@ -10,7 +10,18 @@
    -
  • + {{if .Owner.Location}} +
  • {{.Owner.Location}}
  • + {{end}} + {{if .Owner.Email}} +
  • {{.Owner.Email}}
  • + {{end}} + {{if .Owner.Website}} +
  • {{.Owner.Website}}
  • + {{end}} +
  • Joined on {{DateFormat .Owner.Created "M d, Y"}}
  • +
    +
  • 123 @@ -22,16 +33,7 @@
  • - {{if .Owner.Location}} -
  • {{.Owner.Location}}
  • - {{end}} - {{if .Owner.Email}} -
  • {{.Owner.Email}}
  • - {{end}} - {{if .Owner.Website}} -
  • {{.Owner.Website}}
  • - {{end}} -
  • Joined on {{DateFormat .Owner.Created "M d, Y"}}
  • +
diff --git a/web.go b/web.go index de8e7250..a17be2a3 100644 --- a/web.go +++ b/web.go @@ -104,6 +104,7 @@ func runWeb(*cli.Context) { m.Group("/user", func(r martini.Router) { r.Get("/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds) r.Get("/activate", user.Activate) + r.Get("/email2user", user.Email2User) r.Get("/forget_password", user.ForgotPasswd) r.Post("/forget_password", user.ForgotPasswdPost) }) @@ -120,8 +121,8 @@ func runWeb(*cli.Context) { m.Group("/repo", func(r martini.Router) { m.Get("/create", repo.Create) m.Post("/create", bindIgnErr(auth.CreateRepoForm{}), repo.CreatePost) - m.Get("/mirror", repo.Mirror) - m.Post("/mirror", bindIgnErr(auth.CreateRepoForm{}), repo.MirrorPost) + m.Get("/migrate", repo.Migrate) + m.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), repo.MigratePost) }, reqSignIn) adminReq := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: true, AdminRequire: true}) @@ -144,24 +145,21 @@ func runWeb(*cli.Context) { m.Get("/template/**", dev.TemplatePreview) } - writeable := middleware.WriteAccess() - m.Group("/:username/:reponame", func(r martini.Router) { - r.Get("/settings", writeable, repo.Setting) - r.Post("/settings", writeable, repo.SettingPost) + r.Get("/settings", repo.Setting) + r.Post("/settings", repo.SettingPost) r.Get("/action/:action", repo.Action) r.Get("/issues/new", repo.CreateIssue) r.Post("/issues/new", bindIgnErr(auth.CreateIssueForm{}), repo.CreateIssuePost) r.Post("/issues/:index", bindIgnErr(auth.CreateIssueForm{}), repo.UpdateIssue) r.Post("/comment/:action", repo.Comment) - r.Post("/import", writeable, repo.Import) }, reqSignIn, middleware.RepoAssignment(true)) m.Group("/:username/:reponame", func(r martini.Router) { r.Get("/issues", repo.Issues) r.Get("/issues/:index", repo.ViewIssue) r.Get("/releases", repo.Releases) - r.Any("/releases/new", writeable, repo.ReleasesNew) // TODO: + r.Any("/releases/new", repo.ReleasesNew) // TODO: r.Get("/pulls", repo.Pulls) r.Get("/branches", repo.Branches) }, ignSignIn, middleware.RepoAssignment(true)) -- cgit v1.2.3 From 5c2da610a2cfd3fa9666d20739e054c3588b4d05 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 13 Apr 2014 01:57:42 -0400 Subject: Move binding as subrepo --- bee.json | 2 - gogs.go | 2 +- modules/auth/admin.go | 4 +- modules/auth/auth.go | 22 +- modules/auth/issue.go | 4 +- modules/auth/repo.go | 6 +- modules/auth/setting.go | 4 +- modules/auth/user.go | 5 +- modules/base/base.go | 46 +++ modules/middleware/binding.go | 426 ++++++++++++++++++++++ modules/middleware/binding_test.go | 701 +++++++++++++++++++++++++++++++++++++ routers/user/home.go | 196 +++++++++++ routers/user/social.go | 206 ++++++++++- routers/user/social_github.go | 73 ---- routers/user/social_google.go | 71 ---- routers/user/social_qq.go | 83 ----- routers/user/user.go | 183 ---------- web.go | 5 +- 18 files changed, 1594 insertions(+), 445 deletions(-) create mode 100644 modules/middleware/binding.go create mode 100644 modules/middleware/binding_test.go create mode 100644 routers/user/home.go delete mode 100644 routers/user/social_github.go delete mode 100644 routers/user/social_google.go delete mode 100644 routers/user/social_qq.go (limited to 'modules/auth/repo.go') diff --git a/bee.json b/bee.json index ff120f0c..e427c552 100644 --- a/bee.json +++ b/bee.json @@ -12,8 +12,6 @@ "models": "", "others": [ "modules", - "$GOPATH/src/github.com/gogits/binding", - "$GOPATH/src/github.com/gogits/webdav", "$GOPATH/src/github.com/gogits/logs", "$GOPATH/src/github.com/gogits/git", "$GOPATH/src/github.com/gogits/gfm" diff --git a/gogs.go b/gogs.go index a42d7225..1e614f49 100644 --- a/gogs.go +++ b/gogs.go @@ -19,7 +19,7 @@ import ( // Test that go1.2 tag above is included in builds. main.go refers to this definition. const go12tag = true -const APP_VER = "0.2.8.0412 Alpha" +const APP_VER = "0.2.8.0413 Alpha" func init() { base.AppVer = APP_VER diff --git a/modules/auth/admin.go b/modules/auth/admin.go index fe889c23..877af19a 100644 --- a/modules/auth/admin.go +++ b/modules/auth/admin.go @@ -10,8 +10,6 @@ import ( "github.com/go-martini/martini" - "github.com/gogits/binding" - "github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/modules/log" ) @@ -35,7 +33,7 @@ func (f *AdminEditUserForm) Name(field string) string { return names[field] } -func (f *AdminEditUserForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) { +func (f *AdminEditUserForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) { if req.Method == "GET" || errors.Count() == 0 { return } diff --git a/modules/auth/auth.go b/modules/auth/auth.go index 7329cbdc..350ef4fc 100644 --- a/modules/auth/auth.go +++ b/modules/auth/auth.go @@ -11,8 +11,6 @@ import ( "github.com/go-martini/martini" - "github.com/gogits/binding" - "github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/modules/log" ) @@ -39,7 +37,7 @@ func (f *RegisterForm) Name(field string) string { return names[field] } -func (f *RegisterForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) { +func (f *RegisterForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) { if req.Method == "GET" || errors.Count() == 0 { return } @@ -72,7 +70,7 @@ func (f *LogInForm) Name(field string) string { return names[field] } -func (f *LogInForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) { +func (f *LogInForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) { if req.Method == "GET" || errors.Count() == 0 { return } @@ -100,7 +98,7 @@ func getMinMaxSize(field reflect.StructField) string { return "" } -func validate(errors *binding.Errors, data base.TmplData, form Form) { +func validate(errors *base.BindingErrors, data base.TmplData, form Form) { typ := reflect.TypeOf(form) val := reflect.ValueOf(form) @@ -121,17 +119,17 @@ func validate(errors *binding.Errors, data base.TmplData, form Form) { if err, ok := errors.Fields[field.Name]; ok { data["Err_"+field.Name] = true switch err { - case binding.RequireError: + case base.BindingRequireError: data["ErrorMsg"] = form.Name(field.Name) + " cannot be empty" - case binding.AlphaDashError: + case base.BindingAlphaDashError: data["ErrorMsg"] = form.Name(field.Name) + " must be valid alpha or numeric or dash(-_) characters" - case binding.MinSizeError: + case base.BindingMinSizeError: data["ErrorMsg"] = form.Name(field.Name) + " must contain at least " + getMinMaxSize(field) + " characters" - case binding.MaxSizeError: + case base.BindingMaxSizeError: data["ErrorMsg"] = form.Name(field.Name) + " must contain at most " + getMinMaxSize(field) + " characters" - case binding.EmailError: + case base.BindingEmailError: data["ErrorMsg"] = form.Name(field.Name) + " is not a valid e-mail address" - case binding.UrlError: + case base.BindingUrlError: data["ErrorMsg"] = form.Name(field.Name) + " is not a valid URL" default: data["ErrorMsg"] = "Unknown error: " + err @@ -196,7 +194,7 @@ func (f *InstallForm) Name(field string) string { return names[field] } -func (f *InstallForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) { +func (f *InstallForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) { if req.Method == "GET" || errors.Count() == 0 { return } diff --git a/modules/auth/issue.go b/modules/auth/issue.go index 36c87627..f73ddc74 100644 --- a/modules/auth/issue.go +++ b/modules/auth/issue.go @@ -10,8 +10,6 @@ import ( "github.com/go-martini/martini" - "github.com/gogits/binding" - "github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/modules/log" ) @@ -31,7 +29,7 @@ func (f *CreateIssueForm) Name(field string) string { return names[field] } -func (f *CreateIssueForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) { +func (f *CreateIssueForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) { if req.Method == "GET" || errors.Count() == 0 { return } diff --git a/modules/auth/repo.go b/modules/auth/repo.go index aa94058f..f67fbf67 100644 --- a/modules/auth/repo.go +++ b/modules/auth/repo.go @@ -10,8 +10,6 @@ import ( "github.com/go-martini/martini" - "github.com/gogits/binding" - "github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/modules/log" ) @@ -33,7 +31,7 @@ func (f *CreateRepoForm) Name(field string) string { return names[field] } -func (f *CreateRepoForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) { +func (f *CreateRepoForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) { if req.Method == "GET" || errors.Count() == 0 { return } @@ -71,7 +69,7 @@ func (f *MigrateRepoForm) Name(field string) string { return names[field] } -func (f *MigrateRepoForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) { +func (f *MigrateRepoForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) { if req.Method == "GET" || errors.Count() == 0 { return } diff --git a/modules/auth/setting.go b/modules/auth/setting.go index cada7eea..7cee00de 100644 --- a/modules/auth/setting.go +++ b/modules/auth/setting.go @@ -11,8 +11,6 @@ import ( "github.com/go-martini/martini" - "github.com/gogits/binding" - "github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/modules/log" ) @@ -30,7 +28,7 @@ func (f *AddSSHKeyForm) Name(field string) string { return names[field] } -func (f *AddSSHKeyForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) { +func (f *AddSSHKeyForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) { data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData) AssignForm(f, data) diff --git a/modules/auth/user.go b/modules/auth/user.go index 015059f7..97389422 100644 --- a/modules/auth/user.go +++ b/modules/auth/user.go @@ -10,7 +10,6 @@ import ( "github.com/go-martini/martini" - "github.com/gogits/binding" "github.com/gogits/session" "github.com/gogits/gogs/models" @@ -93,7 +92,7 @@ func (f *UpdateProfileForm) Name(field string) string { return names[field] } -func (f *UpdateProfileForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) { +func (f *UpdateProfileForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) { if req.Method == "GET" || errors.Count() == 0 { return } @@ -126,7 +125,7 @@ func (f *UpdatePasswdForm) Name(field string) string { return names[field] } -func (f *UpdatePasswdForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) { +func (f *UpdatePasswdForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) { if req.Method == "GET" || errors.Count() == 0 { return } diff --git a/modules/base/base.go b/modules/base/base.go index 7c08dcc5..84cf41c8 100644 --- a/modules/base/base.go +++ b/modules/base/base.go @@ -8,3 +8,49 @@ type ( // Type TmplData represents data in the templates. TmplData map[string]interface{} ) + +// __________.__ .___.__ +// \______ \__| ____ __| _/|__| ____ ____ +// | | _/ |/ \ / __ | | |/ \ / ___\ +// | | \ | | \/ /_/ | | | | \/ /_/ > +// |______ /__|___| /\____ | |__|___| /\___ / +// \/ \/ \/ \//_____/ + +// Errors represents the contract of the response body when the +// binding step fails before getting to the application. +type BindingErrors struct { + Overall map[string]string `json:"overall"` + Fields map[string]string `json:"fields"` +} + +// Total errors is the sum of errors with the request overall +// and errors on individual fields. +func (err BindingErrors) Count() int { + return len(err.Overall) + len(err.Fields) +} + +func (this *BindingErrors) Combine(other BindingErrors) { + for key, val := range other.Fields { + if _, exists := this.Fields[key]; !exists { + this.Fields[key] = val + } + } + for key, val := range other.Overall { + if _, exists := this.Overall[key]; !exists { + this.Overall[key] = val + } + } +} + +const ( + BindingRequireError string = "Required" + BindingAlphaDashError string = "AlphaDash" + BindingMinSizeError string = "MinSize" + BindingMaxSizeError string = "MaxSize" + BindingEmailError string = "Email" + BindingUrlError string = "Url" + BindingDeserializationError string = "DeserializationError" + BindingIntegerTypeError string = "IntegerTypeError" + BindingBooleanTypeError string = "BooleanTypeError" + BindingFloatTypeError string = "FloatTypeError" +) diff --git a/modules/middleware/binding.go b/modules/middleware/binding.go new file mode 100644 index 00000000..cde9ae9c --- /dev/null +++ b/modules/middleware/binding.go @@ -0,0 +1,426 @@ +// Copyright 2013 The Martini Contrib Authors. All rights reserved. +// 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 middleware + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "reflect" + "regexp" + "strconv" + "strings" + "unicode/utf8" + + "github.com/go-martini/martini" + + "github.com/gogits/gogs/modules/base" +) + +/* + To the land of Middle-ware Earth: + + One func to rule them all, + One func to find them, + One func to bring them all, + And in this package BIND them. +*/ + +// Bind accepts a copy of an empty struct and populates it with +// values from the request (if deserialization is successful). It +// wraps up the functionality of the Form and Json middleware +// according to the Content-Type of the request, and it guesses +// if no Content-Type is specified. Bind invokes the ErrorHandler +// middleware to bail out if errors occurred. If you want to perform +// your own error handling, use Form or Json middleware directly. +// An interface pointer can be added as a second argument in order +// to map the struct to a specific interface. +func Bind(obj interface{}, ifacePtr ...interface{}) martini.Handler { + return func(context martini.Context, req *http.Request) { + contentType := req.Header.Get("Content-Type") + + if strings.Contains(contentType, "form-urlencoded") { + context.Invoke(Form(obj, ifacePtr...)) + } else if strings.Contains(contentType, "multipart/form-data") { + context.Invoke(MultipartForm(obj, ifacePtr...)) + } else if strings.Contains(contentType, "json") { + context.Invoke(Json(obj, ifacePtr...)) + } else { + context.Invoke(Json(obj, ifacePtr...)) + if getErrors(context).Count() > 0 { + context.Invoke(Form(obj, ifacePtr...)) + } + } + + context.Invoke(ErrorHandler) + } +} + +// BindIgnErr will do the exactly same thing as Bind but without any +// error handling, which user has freedom to deal with them. +// This allows user take advantages of validation. +func BindIgnErr(obj interface{}, ifacePtr ...interface{}) martini.Handler { + return func(context martini.Context, req *http.Request) { + contentType := req.Header.Get("Content-Type") + + if strings.Contains(contentType, "form-urlencoded") { + context.Invoke(Form(obj, ifacePtr...)) + } else if strings.Contains(contentType, "multipart/form-data") { + context.Invoke(MultipartForm(obj, ifacePtr...)) + } else if strings.Contains(contentType, "json") { + context.Invoke(Json(obj, ifacePtr...)) + } else { + context.Invoke(Json(obj, ifacePtr...)) + if getErrors(context).Count() > 0 { + context.Invoke(Form(obj, ifacePtr...)) + } + } + } +} + +// Form is middleware to deserialize form-urlencoded data from the request. +// It gets data from the form-urlencoded body, if present, or from the +// query string. It uses the http.Request.ParseForm() method +// to perform deserialization, then reflection is used to map each field +// into the struct with the proper type. Structs with primitive slice types +// (bool, float, int, string) can support deserialization of repeated form +// keys, for example: key=val1&key=val2&key=val3 +// An interface pointer can be added as a second argument in order +// to map the struct to a specific interface. +func Form(formStruct interface{}, ifacePtr ...interface{}) martini.Handler { + return func(context martini.Context, req *http.Request) { + ensureNotPointer(formStruct) + formStruct := reflect.New(reflect.TypeOf(formStruct)) + errors := newErrors() + parseErr := req.ParseForm() + + // Format validation of the request body or the URL would add considerable overhead, + // and ParseForm does not complain when URL encoding is off. + // Because an empty request body or url can also mean absence of all needed values, + // it is not in all cases a bad request, so let's return 422. + if parseErr != nil { + errors.Overall[base.BindingDeserializationError] = parseErr.Error() + } + + mapForm(formStruct, req.Form, errors) + + validateAndMap(formStruct, context, errors, ifacePtr...) + } +} + +func MultipartForm(formStruct interface{}, ifacePtr ...interface{}) martini.Handler { + return func(context martini.Context, req *http.Request) { + ensureNotPointer(formStruct) + formStruct := reflect.New(reflect.TypeOf(formStruct)) + errors := newErrors() + + // Workaround for multipart forms returning nil instead of an error + // when content is not multipart + // https://code.google.com/p/go/issues/detail?id=6334 + multipartReader, err := req.MultipartReader() + if err != nil { + errors.Overall[base.BindingDeserializationError] = err.Error() + } else { + form, parseErr := multipartReader.ReadForm(MaxMemory) + + if parseErr != nil { + errors.Overall[base.BindingDeserializationError] = parseErr.Error() + } + + req.MultipartForm = form + } + + mapForm(formStruct, req.MultipartForm.Value, errors) + + validateAndMap(formStruct, context, errors, ifacePtr...) + } +} + +// Json is middleware to deserialize a JSON payload from the request +// into the struct that is passed in. The resulting struct is then +// validated, but no error handling is actually performed here. +// An interface pointer can be added as a second argument in order +// to map the struct to a specific interface. +func Json(jsonStruct interface{}, ifacePtr ...interface{}) martini.Handler { + return func(context martini.Context, req *http.Request) { + ensureNotPointer(jsonStruct) + jsonStruct := reflect.New(reflect.TypeOf(jsonStruct)) + errors := newErrors() + + if req.Body != nil { + defer req.Body.Close() + } + + if err := json.NewDecoder(req.Body).Decode(jsonStruct.Interface()); err != nil && err != io.EOF { + errors.Overall[base.BindingDeserializationError] = err.Error() + } + + validateAndMap(jsonStruct, context, errors, ifacePtr...) + } +} + +// Validate is middleware to enforce required fields. If the struct +// passed in is a Validator, then the user-defined Validate method +// is executed, and its errors are mapped to the context. This middleware +// performs no error handling: it merely detects them and maps them. +func Validate(obj interface{}) martini.Handler { + return func(context martini.Context, req *http.Request) { + errors := newErrors() + validateStruct(errors, obj) + + if validator, ok := obj.(Validator); ok { + validator.Validate(errors, req, context) + } + context.Map(*errors) + } +} + +var ( + alphaDashPattern = regexp.MustCompile("[^\\d\\w-_]") + emailPattern = regexp.MustCompile("[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?") + urlPattern = regexp.MustCompile(`(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?`) +) + +func validateStruct(errors *base.BindingErrors, obj interface{}) { + typ := reflect.TypeOf(obj) + val := reflect.ValueOf(obj) + + if typ.Kind() == reflect.Ptr { + typ = typ.Elem() + val = val.Elem() + } + + for i := 0; i < typ.NumField(); i++ { + field := typ.Field(i) + + // Allow ignored fields in the struct + if field.Tag.Get("form") == "-" { + continue + } + + fieldValue := val.Field(i).Interface() + if field.Type.Kind() == reflect.Struct { + validateStruct(errors, fieldValue) + continue + } + + zero := reflect.Zero(field.Type).Interface() + + // Match rules. + for _, rule := range strings.Split(field.Tag.Get("binding"), ";") { + if len(rule) == 0 { + continue + } + + switch { + case rule == "Required": + if reflect.DeepEqual(zero, fieldValue) { + errors.Fields[field.Name] = base.BindingRequireError + break + } + case rule == "AlphaDash": + if alphaDashPattern.MatchString(fmt.Sprintf("%v", fieldValue)) { + errors.Fields[field.Name] = base.BindingAlphaDashError + break + } + case strings.HasPrefix(rule, "MinSize("): + min, err := strconv.Atoi(rule[8 : len(rule)-1]) + if err != nil { + errors.Overall["MinSize"] = err.Error() + break + } + if str, ok := fieldValue.(string); ok && utf8.RuneCountInString(str) < min { + errors.Fields[field.Name] = base.BindingMinSizeError + break + } + v := reflect.ValueOf(fieldValue) + if v.Kind() == reflect.Slice && v.Len() < min { + errors.Fields[field.Name] = base.BindingMinSizeError + break + } + case strings.HasPrefix(rule, "MaxSize("): + max, err := strconv.Atoi(rule[8 : len(rule)-1]) + if err != nil { + errors.Overall["MaxSize"] = err.Error() + break + } + if str, ok := fieldValue.(string); ok && utf8.RuneCountInString(str) > max { + errors.Fields[field.Name] = base.BindingMaxSizeError + break + } + v := reflect.ValueOf(fieldValue) + if v.Kind() == reflect.Slice && v.Len() > max { + errors.Fields[field.Name] = base.BindingMinSizeError + break + } + case rule == "Email": + if !emailPattern.MatchString(fmt.Sprintf("%v", fieldValue)) { + errors.Fields[field.Name] = base.BindingEmailError + break + } + case rule == "Url": + if !urlPattern.MatchString(fmt.Sprintf("%v", fieldValue)) { + errors.Fields[field.Name] = base.BindingUrlError + break + } + } + } + } +} + +func mapForm(formStruct reflect.Value, form map[string][]string, errors *base.BindingErrors) { + typ := formStruct.Elem().Type() + + for i := 0; i < typ.NumField(); i++ { + typeField := typ.Field(i) + if inputFieldName := typeField.Tag.Get("form"); inputFieldName != "" { + structField := formStruct.Elem().Field(i) + if !structField.CanSet() { + continue + } + + inputValue, exists := form[inputFieldName] + + if !exists { + continue + } + + numElems := len(inputValue) + if structField.Kind() == reflect.Slice && numElems > 0 { + sliceOf := structField.Type().Elem().Kind() + slice := reflect.MakeSlice(structField.Type(), numElems, numElems) + for i := 0; i < numElems; i++ { + setWithProperType(sliceOf, inputValue[i], slice.Index(i), inputFieldName, errors) + } + formStruct.Elem().Field(i).Set(slice) + } else { + setWithProperType(typeField.Type.Kind(), inputValue[0], structField, inputFieldName, errors) + } + } + } +} + +// ErrorHandler simply counts the number of errors in the +// context and, if more than 0, writes a 400 Bad Request +// response and a JSON payload describing the errors with +// the "Content-Type" set to "application/json". +// Middleware remaining on the stack will not even see the request +// if, by this point, there are any errors. +// This is a "default" handler, of sorts, and you are +// welcome to use your own instead. The Bind middleware +// invokes this automatically for convenience. +func ErrorHandler(errs base.BindingErrors, resp http.ResponseWriter) { + if errs.Count() > 0 { + resp.Header().Set("Content-Type", "application/json; charset=utf-8") + if _, ok := errs.Overall[base.BindingDeserializationError]; ok { + resp.WriteHeader(http.StatusBadRequest) + } else { + resp.WriteHeader(422) + } + errOutput, _ := json.Marshal(errs) + resp.Write(errOutput) + return + } +} + +// This sets the value in a struct of an indeterminate type to the +// matching value from the request (via Form middleware) in the +// same type, so that not all deserialized values have to be strings. +// Supported types are string, int, float, and bool. +func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value, nameInTag string, errors *base.BindingErrors) { + switch valueKind { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if val == "" { + val = "0" + } + intVal, err := strconv.ParseInt(val, 10, 64) + if err != nil { + errors.Fields[nameInTag] = base.BindingIntegerTypeError + } else { + structField.SetInt(intVal) + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if val == "" { + val = "0" + } + uintVal, err := strconv.ParseUint(val, 10, 64) + if err != nil { + errors.Fields[nameInTag] = base.BindingIntegerTypeError + } else { + structField.SetUint(uintVal) + } + case reflect.Bool: + structField.SetBool(val == "on") + case reflect.Float32: + if val == "" { + val = "0.0" + } + floatVal, err := strconv.ParseFloat(val, 32) + if err != nil { + errors.Fields[nameInTag] = base.BindingFloatTypeError + } else { + structField.SetFloat(floatVal) + } + case reflect.Float64: + if val == "" { + val = "0.0" + } + floatVal, err := strconv.ParseFloat(val, 64) + if err != nil { + errors.Fields[nameInTag] = base.BindingFloatTypeError + } else { + structField.SetFloat(floatVal) + } + case reflect.String: + structField.SetString(val) + } +} + +// Don't pass in pointers to bind to. Can lead to bugs. See: +// https://github.com/codegangsta/martini-contrib/issues/40 +// https://github.com/codegangsta/martini-contrib/pull/34#issuecomment-29683659 +func ensureNotPointer(obj interface{}) { + if reflect.TypeOf(obj).Kind() == reflect.Ptr { + panic("Pointers are not accepted as binding models") + } +} + +// Performs validation and combines errors from validation +// with errors from deserialization, then maps both the +// resulting struct and the errors to the context. +func validateAndMap(obj reflect.Value, context martini.Context, errors *base.BindingErrors, ifacePtr ...interface{}) { + context.Invoke(Validate(obj.Interface())) + errors.Combine(getErrors(context)) + context.Map(*errors) + context.Map(obj.Elem().Interface()) + if len(ifacePtr) > 0 { + context.MapTo(obj.Elem().Interface(), ifacePtr[0]) + } +} + +func newErrors() *base.BindingErrors { + return &base.BindingErrors{make(map[string]string), make(map[string]string)} +} + +func getErrors(context martini.Context) base.BindingErrors { + return context.Get(reflect.TypeOf(base.BindingErrors{})).Interface().(base.BindingErrors) +} + +type ( + // Implement the Validator interface to define your own input + // validation before the request even gets to your application. + // The Validate method will be executed during the validation phase. + Validator interface { + Validate(*base.BindingErrors, *http.Request, martini.Context) + } +) + +var ( + // Maximum amount of memory to use when parsing a multipart form. + // Set this to whatever value you prefer; default is 10 MB. + MaxMemory = int64(1024 * 1024 * 10) +) diff --git a/modules/middleware/binding_test.go b/modules/middleware/binding_test.go new file mode 100644 index 00000000..654cef29 --- /dev/null +++ b/modules/middleware/binding_test.go @@ -0,0 +1,701 @@ +// Copyright 2013 The Martini Contrib Authors. All rights reserved. +// 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 middleware + +import ( + "bytes" + "mime/multipart" + "net/http" + "net/http/httptest" + "strconv" + "strings" + "testing" + + "github.com/codegangsta/martini" +) + +func TestBind(t *testing.T) { + testBind(t, false) +} + +func TestBindWithInterface(t *testing.T) { + testBind(t, true) +} + +func TestMultipartBind(t *testing.T) { + index := 0 + for test, expectStatus := range bindMultipartTests { + handler := func(post BlogPost, errors Errors) { + handle(test, t, index, post, errors) + } + recorder := testMultipart(t, test, Bind(BlogPost{}), handler, index) + + if recorder.Code != expectStatus { + t.Errorf("On test case %v, got status code %d but expected %d", test, recorder.Code, expectStatus) + } + + index++ + } +} + +func TestForm(t *testing.T) { + testForm(t, false) +} + +func TestFormWithInterface(t *testing.T) { + testForm(t, true) +} + +func TestEmptyForm(t *testing.T) { + testEmptyForm(t) +} + +func TestMultipartForm(t *testing.T) { + for index, test := range multipartformTests { + handler := func(post BlogPost, errors Errors) { + handle(test, t, index, post, errors) + } + testMultipart(t, test, MultipartForm(BlogPost{}), handler, index) + } +} + +func TestMultipartFormWithInterface(t *testing.T) { + for index, test := range multipartformTests { + handler := func(post Modeler, errors Errors) { + post.Create(test, t, index) + } + testMultipart(t, test, MultipartForm(BlogPost{}, (*Modeler)(nil)), handler, index) + } +} + +func TestJson(t *testing.T) { + testJson(t, false) +} + +func TestJsonWithInterface(t *testing.T) { + testJson(t, true) +} + +func TestEmptyJson(t *testing.T) { + testEmptyJson(t) +} + +func TestValidate(t *testing.T) { + handlerMustErr := func(errors Errors) { + if errors.Count() == 0 { + t.Error("Expected at least one error, got 0") + } + } + handlerNoErr := func(errors Errors) { + if errors.Count() > 0 { + t.Error("Expected no errors, got", errors.Count()) + } + } + + performValidationTest(&BlogPost{"", "...", 0, 0, []int{}}, handlerMustErr, t) + performValidationTest(&BlogPost{"Good Title", "Good content", 0, 0, []int{}}, handlerNoErr, t) + + performValidationTest(&User{Name: "Jim", Home: Address{"", ""}}, handlerMustErr, t) + performValidationTest(&User{Name: "Jim", Home: Address{"required", ""}}, handlerNoErr, t) +} + +func handle(test testCase, t *testing.T, index int, post BlogPost, errors Errors) { + assertEqualField(t, "Title", index, test.ref.Title, post.Title) + assertEqualField(t, "Content", index, test.ref.Content, post.Content) + assertEqualField(t, "Views", index, test.ref.Views, post.Views) + + for i := range test.ref.Multiple { + if i >= len(post.Multiple) { + t.Errorf("Expected: %v (size %d) to have same size as: %v (size %d)", post.Multiple, len(post.Multiple), test.ref.Multiple, len(test.ref.Multiple)) + break + } + if test.ref.Multiple[i] != post.Multiple[i] { + t.Errorf("Expected: %v to deep equal: %v", post.Multiple, test.ref.Multiple) + break + } + } + + if test.ok && errors.Count() > 0 { + t.Errorf("%+v should be OK (0 errors), but had errors: %+v", test, errors) + } else if !test.ok && errors.Count() == 0 { + t.Errorf("%+v should have errors, but was OK (0 errors): %+v", test) + } +} + +func handleEmpty(test emptyPayloadTestCase, t *testing.T, index int, section BlogSection, errors Errors) { + assertEqualField(t, "Title", index, test.ref.Title, section.Title) + assertEqualField(t, "Content", index, test.ref.Content, section.Content) + + if test.ok && errors.Count() > 0 { + t.Errorf("%+v should be OK (0 errors), but had errors: %+v", test, errors) + } else if !test.ok && errors.Count() == 0 { + t.Errorf("%+v should have errors, but was OK (0 errors): %+v", test) + } +} + +func testBind(t *testing.T, withInterface bool) { + index := 0 + for test, expectStatus := range bindTests { + m := martini.Classic() + recorder := httptest.NewRecorder() + handler := func(post BlogPost, errors Errors) { handle(test, t, index, post, errors) } + binding := Bind(BlogPost{}) + + if withInterface { + handler = func(post BlogPost, errors Errors) { + post.Create(test, t, index) + } + binding = Bind(BlogPost{}, (*Modeler)(nil)) + } + + switch test.method { + case "GET": + m.Get(route, binding, handler) + case "POST": + m.Post(route, binding, handler) + } + + req, err := http.NewRequest(test.method, test.path, strings.NewReader(test.payload)) + req.Header.Add("Content-Type", test.contentType) + + if err != nil { + t.Error(err) + } + m.ServeHTTP(recorder, req) + + if recorder.Code != expectStatus { + t.Errorf("On test case %v, got status code %d but expected %d", test, recorder.Code, expectStatus) + } + + index++ + } +} + +func testJson(t *testing.T, withInterface bool) { + for index, test := range jsonTests { + recorder := httptest.NewRecorder() + handler := func(post BlogPost, errors Errors) { handle(test, t, index, post, errors) } + binding := Json(BlogPost{}) + + if withInterface { + handler = func(post BlogPost, errors Errors) { + post.Create(test, t, index) + } + binding = Bind(BlogPost{}, (*Modeler)(nil)) + } + + m := martini.Classic() + switch test.method { + case "GET": + m.Get(route, binding, handler) + case "POST": + m.Post(route, binding, handler) + case "PUT": + m.Put(route, binding, handler) + case "DELETE": + m.Delete(route, binding, handler) + } + + req, err := http.NewRequest(test.method, route, strings.NewReader(test.payload)) + if err != nil { + t.Error(err) + } + m.ServeHTTP(recorder, req) + } +} + +func testEmptyJson(t *testing.T) { + for index, test := range emptyPayloadTests { + recorder := httptest.NewRecorder() + handler := func(section BlogSection, errors Errors) { handleEmpty(test, t, index, section, errors) } + binding := Json(BlogSection{}) + + m := martini.Classic() + switch test.method { + case "GET": + m.Get(route, binding, handler) + case "POST": + m.Post(route, binding, handler) + case "PUT": + m.Put(route, binding, handler) + case "DELETE": + m.Delete(route, binding, handler) + } + + req, err := http.NewRequest(test.method, route, strings.NewReader(test.payload)) + if err != nil { + t.Error(err) + } + m.ServeHTTP(recorder, req) + } +} + +func testForm(t *testing.T, withInterface bool) { + for index, test := range formTests { + recorder := httptest.NewRecorder() + handler := func(post BlogPost, errors Errors) { handle(test, t, index, post, errors) } + binding := Form(BlogPost{}) + + if withInterface { + handler = func(post BlogPost, errors Errors) { + post.Create(test, t, index) + } + binding = Form(BlogPost{}, (*Modeler)(nil)) + } + + m := martini.Classic() + switch test.method { + case "GET": + m.Get(route, binding, handler) + case "POST": + m.Post(route, binding, handler) + } + + req, err := http.NewRequest(test.method, test.path, nil) + if err != nil { + t.Error(err) + } + m.ServeHTTP(recorder, req) + } +} + +func testEmptyForm(t *testing.T) { + for index, test := range emptyPayloadTests { + recorder := httptest.NewRecorder() + handler := func(section BlogSection, errors Errors) { handleEmpty(test, t, index, section, errors) } + binding := Form(BlogSection{}) + + m := martini.Classic() + switch test.method { + case "GET": + m.Get(route, binding, handler) + case "POST": + m.Post(route, binding, handler) + } + + req, err := http.NewRequest(test.method, test.path, nil) + if err != nil { + t.Error(err) + } + m.ServeHTTP(recorder, req) + } +} + +func testMultipart(t *testing.T, test testCase, middleware martini.Handler, handler martini.Handler, index int) *httptest.ResponseRecorder { + recorder := httptest.NewRecorder() + + m := martini.Classic() + m.Post(route, middleware, handler) + + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + writer.WriteField("title", test.ref.Title) + writer.WriteField("content", test.ref.Content) + writer.WriteField("views", strconv.Itoa(test.ref.Views)) + if len(test.ref.Multiple) != 0 { + for _, value := range test.ref.Multiple { + writer.WriteField("multiple", strconv.Itoa(value)) + } + } + + req, err := http.NewRequest(test.method, test.path, body) + req.Header.Add("Content-Type", writer.FormDataContentType()) + + if err != nil { + t.Error(err) + } + + err = writer.Close() + if err != nil { + t.Error(err) + } + + m.ServeHTTP(recorder, req) + + return recorder +} + +func assertEqualField(t *testing.T, fieldname string, testcasenumber int, expected interface{}, got interface{}) { + if expected != got { + t.Errorf("%s: expected=%s, got=%s in test case %d\n", fieldname, expected, got, testcasenumber) + } +} + +func performValidationTest(data interface{}, handler func(Errors), t *testing.T) { + recorder := httptest.NewRecorder() + m := martini.Classic() + m.Get(route, Validate(data), handler) + + req, err := http.NewRequest("GET", route, nil) + if err != nil { + t.Error("HTTP error:", err) + } + + m.ServeHTTP(recorder, req) +} + +func (self BlogPost) Validate(errors *Errors, req *http.Request) { + if len(self.Title) < 4 { + errors.Fields["Title"] = "Too short; minimum 4 characters" + } + if len(self.Content) > 1024 { + errors.Fields["Content"] = "Too long; maximum 1024 characters" + } + if len(self.Content) < 5 { + errors.Fields["Content"] = "Too short; minimum 5 characters" + } +} + +func (self BlogPost) Create(test testCase, t *testing.T, index int) { + assertEqualField(t, "Title", index, test.ref.Title, self.Title) + assertEqualField(t, "Content", index, test.ref.Content, self.Content) + assertEqualField(t, "Views", index, test.ref.Views, self.Views) + + for i := range test.ref.Multiple { + if i >= len(self.Multiple) { + t.Errorf("Expected: %v (size %d) to have same size as: %v (size %d)", self.Multiple, len(self.Multiple), test.ref.Multiple, len(test.ref.Multiple)) + break + } + if test.ref.Multiple[i] != self.Multiple[i] { + t.Errorf("Expected: %v to deep equal: %v", self.Multiple, test.ref.Multiple) + break + } + } +} + +func (self BlogSection) Create(test emptyPayloadTestCase, t *testing.T, index int) { + // intentionally left empty +} + +type ( + testCase struct { + method string + path string + payload string + contentType string + ok bool + ref *BlogPost + } + + emptyPayloadTestCase struct { + method string + path string + payload string + contentType string + ok bool + ref *BlogSection + } + + Modeler interface { + Create(test testCase, t *testing.T, index int) + } + + BlogPost struct { + Title string `form:"title" json:"title" binding:"required"` + Content string `form:"content" json:"content"` + Views int `form:"views" json:"views"` + internal int `form:"-"` + Multiple []int `form:"multiple"` + } + + BlogSection struct { + Title string `form:"title" json:"title"` + Content string `form:"content" json:"content"` + } + + User struct { + Name string `json:"name" binding:"required"` + Home Address `json:"address" binding:"required"` + } + + Address struct { + Street1 string `json:"street1" binding:"required"` + Street2 string `json:"street2"` + } +) + +var ( + bindTests = map[testCase]int{ + // These should bail at the deserialization/binding phase + testCase{ + "POST", + path, + `{ bad JSON `, + "application/json", + false, + new(BlogPost), + }: http.StatusBadRequest, + testCase{ + "POST", + path, + `not multipart but has content-type`, + "multipart/form-data", + false, + new(BlogPost), + }: http.StatusBadRequest, + testCase{ + "POST", + path, + `no content-type and not URL-encoded or JSON"`, + "", + false, + new(BlogPost), + }: http.StatusBadRequest, + + // These should deserialize, then bail at the validation phase + testCase{ + "POST", + path + "?title= This is wrong ", + `not URL-encoded but has content-type`, + "x-www-form-urlencoded", + false, + new(BlogPost), + }: 422, // according to comments in Form() -> although the request is not url encoded, ParseForm does not complain + testCase{ + "GET", + path + "?content=This+is+the+content", + ``, + "x-www-form-urlencoded", + false, + &BlogPost{Title: "", Content: "This is the content"}, + }: 422, + testCase{ + "GET", + path + "", + `{"content":"", "title":"Blog Post Title"}`, + "application/json", + false, + &BlogPost{Title: "Blog Post Title", Content: ""}, + }: 422, + + // These should succeed + testCase{ + "GET", + path + "", + `{"content":"This is the content", "title":"Blog Post Title"}`, + "application/json", + true, + &BlogPost{Title: "Blog Post Title", Content: "This is the content"}, + }: http.StatusOK, + testCase{ + "GET", + path + "?content=This+is+the+content&title=Blog+Post+Title", + ``, + "", + true, + &BlogPost{Title: "Blog Post Title", Content: "This is the content"}, + }: http.StatusOK, + testCase{ + "GET", + path + "?content=This is the content&title=Blog+Post+Title", + `{"content":"This is the content", "title":"Blog Post Title"}`, + "", + true, + &BlogPost{Title: "Blog Post Title", Content: "This is the content"}, + }: http.StatusOK, + testCase{ + "GET", + path + "", + `{"content":"This is the content", "title":"Blog Post Title"}`, + "", + true, + &BlogPost{Title: "Blog Post Title", Content: "This is the content"}, + }: http.StatusOK, + } + + bindMultipartTests = map[testCase]int{ + // This should deserialize, then bail at the validation phase + testCase{ + "POST", + path, + "", + "multipart/form-data", + false, + &BlogPost{Title: "", Content: "This is the content"}, + }: 422, + // This should succeed + testCase{ + "POST", + path, + "", + "multipart/form-data", + true, + &BlogPost{Title: "This is the Title", Content: "This is the content"}, + }: http.StatusOK, + } + + formTests = []testCase{ + { + "GET", + path + "?content=This is the content", + "", + "", + false, + &BlogPost{Title: "", Content: "This is the content"}, + }, + { + "POST", + path + "?content=This+is+the+content&title=Blog+Post+Title&views=3", + "", + "", + false, // false because POST requests should have a body, not just a query string + &BlogPost{Title: "Blog Post Title", Content: "This is the content", Views: 3}, + }, + { + "GET", + path + "?content=This+is+the+content&title=Blog+Post+Title&views=3&multiple=5&multiple=10&multiple=15&multiple=20", + "", + "", + true, + &BlogPost{Title: "Blog Post Title", Content: "This is the content", Views: 3, Multiple: []int{5, 10, 15, 20}}, + }, + } + + multipartformTests = []testCase{ + { + "POST", + path, + "", + "multipart/form-data", + false, + &BlogPost{Title: "", Content: "This is the content"}, + }, + { + "POST", + path, + "", + "multipart/form-data", + false, + &BlogPost{Title: "Blog Post Title", Views: 3}, + }, + { + "POST", + path, + "", + "multipart/form-data", + true, + &BlogPost{Title: "Blog Post Title", Content: "This is the content", Views: 3, Multiple: []int{5, 10, 15, 20}}, + }, + } + + emptyPayloadTests = []emptyPayloadTestCase{ + { + "GET", + "", + "", + "", + true, + &BlogSection{}, + }, + { + "POST", + "", + "", + "", + true, + &BlogSection{}, + }, + { + "PUT", + "", + "", + "", + true, + &BlogSection{}, + }, + { + "DELETE", + "", + "", + "", + true, + &BlogSection{}, + }, + } + + jsonTests = []testCase{ + // bad requests + { + "GET", + "", + `{blah blah blah}`, + "", + false, + &BlogPost{}, + }, + { + "POST", + "", + `{asdf}`, + "", + false, + &BlogPost{}, + }, + { + "PUT", + "", + `{blah blah blah}`, + "", + false, + &BlogPost{}, + }, + { + "DELETE", + "", + `{;sdf _SDf- }`, + "", + false, + &BlogPost{}, + }, + + // Valid-JSON requests + { + "GET", + "", + `{"content":"This is the content"}`, + "", + false, + &BlogPost{Title: "", Content: "This is the content"}, + }, + { + "POST", + "", + `{}`, + "application/json", + false, + &BlogPost{Title: "", Content: ""}, + }, + { + "POST", + "", + `{"content":"This is the content", "title":"Blog Post Title"}`, + "", + true, + &BlogPost{Title: "Blog Post Title", Content: "This is the content"}, + }, + { + "PUT", + "", + `{"content":"This is the content", "title":"Blog Post Title"}`, + "", + true, + &BlogPost{Title: "Blog Post Title", Content: "This is the content"}, + }, + { + "DELETE", + "", + `{"content":"This is the content", "title":"Blog Post Title"}`, + "", + true, + &BlogPost{Title: "Blog Post Title", Content: "This is the content"}, + }, + } +) + +const ( + route = "/blogposts/create" + path = "http://localhost:3000" + route +) diff --git a/routers/user/home.go b/routers/user/home.go new file mode 100644 index 00000000..50f16f09 --- /dev/null +++ b/routers/user/home.go @@ -0,0 +1,196 @@ +// 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 user + +import ( + "fmt" + + "github.com/go-martini/martini" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/modules/auth" + "github.com/gogits/gogs/modules/base" + "github.com/gogits/gogs/modules/middleware" +) + +func Dashboard(ctx *middleware.Context) { + ctx.Data["Title"] = "Dashboard" + ctx.Data["PageIsUserDashboard"] = true + repos, err := models.GetRepositories(&models.User{Id: ctx.User.Id}) + if err != nil { + ctx.Handle(500, "user.Dashboard", err) + return + } + ctx.Data["MyRepos"] = repos + + feeds, err := models.GetFeeds(ctx.User.Id, 0, false) + if err != nil { + ctx.Handle(500, "user.Dashboard", err) + return + } + ctx.Data["Feeds"] = feeds + ctx.HTML(200, "user/dashboard") +} + +func Profile(ctx *middleware.Context, params martini.Params) { + ctx.Data["Title"] = "Profile" + + // TODO: Need to check view self or others. + user, err := models.GetUserByName(params["username"]) + if err != nil { + ctx.Handle(500, "user.Profile", err) + return + } + + ctx.Data["Owner"] = user + + tab := ctx.Query("tab") + ctx.Data["TabName"] = tab + + switch tab { + case "activity": + feeds, err := models.GetFeeds(user.Id, 0, true) + if err != nil { + ctx.Handle(500, "user.Profile", err) + return + } + ctx.Data["Feeds"] = feeds + default: + repos, err := models.GetRepositories(user) + if err != nil { + ctx.Handle(500, "user.Profile", err) + return + } + ctx.Data["Repos"] = repos + } + + ctx.Data["PageIsUserProfile"] = true + ctx.HTML(200, "user/profile") +} + +func Email2User(ctx *middleware.Context) { + u, err := models.GetUserByEmail(ctx.Query("email")) + if err != nil { + if err == models.ErrUserNotExist { + ctx.Handle(404, "user.Email2User", err) + } else { + ctx.Handle(500, "user.Email2User(GetUserByEmail)", err) + } + return + } + + ctx.Redirect("/user/" + u.Name) +} + +const ( + TPL_FEED = ` +
%s
%s
` +) + +func Feeds(ctx *middleware.Context, form auth.FeedsForm) { + actions, err := models.GetFeeds(form.UserId, form.Page*20, false) + if err != nil { + ctx.JSON(500, err) + } + + feeds := make([]string, len(actions)) + for i := range actions { + feeds[i] = fmt.Sprintf(TPL_FEED, base.ActionIcon(actions[i].OpType), + base.TimeSince(actions[i].Created), base.ActionDesc(actions[i])) + } + ctx.JSON(200, &feeds) +} + +func Issues(ctx *middleware.Context) { + ctx.Data["Title"] = "Your Issues" + ctx.Data["ViewType"] = "all" + + page, _ := base.StrTo(ctx.Query("page")).Int() + repoId, _ := base.StrTo(ctx.Query("repoid")).Int64() + + ctx.Data["RepoId"] = repoId + + var posterId int64 = 0 + if ctx.Query("type") == "created_by" { + posterId = ctx.User.Id + ctx.Data["ViewType"] = "created_by" + } + + // Get all repositories. + repos, err := models.GetRepositories(ctx.User) + if err != nil { + ctx.Handle(200, "user.Issues(get repositories)", err) + return + } + + showRepos := make([]models.Repository, 0, len(repos)) + + isShowClosed := ctx.Query("state") == "closed" + var closedIssueCount, createdByCount, allIssueCount int + + // Get all issues. + allIssues := make([]models.Issue, 0, 5*len(repos)) + for i, repo := range repos { + issues, err := models.GetIssues(0, repo.Id, posterId, 0, page, isShowClosed, false, "", "") + if err != nil { + ctx.Handle(200, "user.Issues(get issues)", err) + return + } + + allIssueCount += repo.NumIssues + closedIssueCount += repo.NumClosedIssues + + // Set repository information to issues. + for j := range issues { + issues[j].Repo = &repos[i] + } + allIssues = append(allIssues, issues...) + + repos[i].NumOpenIssues = repo.NumIssues - repo.NumClosedIssues + if repos[i].NumOpenIssues > 0 { + showRepos = append(showRepos, repos[i]) + } + } + + showIssues := make([]models.Issue, 0, len(allIssues)) + ctx.Data["IsShowClosed"] = isShowClosed + + // Get posters and filter issues. + for i := range allIssues { + u, err := models.GetUserById(allIssues[i].PosterId) + if err != nil { + ctx.Handle(200, "user.Issues(get poster): %v", err) + return + } + allIssues[i].Poster = u + if u.Id == ctx.User.Id { + createdByCount++ + } + + if repoId > 0 && repoId != allIssues[i].Repo.Id { + continue + } + + if isShowClosed == allIssues[i].IsClosed { + showIssues = append(showIssues, allIssues[i]) + } + } + + ctx.Data["Repos"] = showRepos + ctx.Data["Issues"] = showIssues + ctx.Data["AllIssueCount"] = allIssueCount + ctx.Data["ClosedIssueCount"] = closedIssueCount + ctx.Data["OpenIssueCount"] = allIssueCount - closedIssueCount + ctx.Data["CreatedByCount"] = createdByCount + ctx.HTML(200, "issue/user") +} + +func Pulls(ctx *middleware.Context) { + ctx.HTML(200, "user/pulls") +} + +func Stars(ctx *middleware.Context) { + ctx.HTML(200, "user/stars") +} diff --git a/routers/user/social.go b/routers/user/social.go index ea47d71b..29c4fa97 100644 --- a/routers/user/social.go +++ b/routers/user/social.go @@ -7,12 +7,14 @@ package user import ( "encoding/json" "fmt" + "net/http" "net/url" + "strconv" "strings" "code.google.com/p/goauth2/oauth" - "github.com/go-martini/martini" + "github.com/gogits/gogs/models" "github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/modules/log" @@ -115,3 +117,205 @@ func SocialSignIn(params martini.Params, ctx *middleware.Context) { log.Trace("socialId: %v", oa.Id) ctx.Redirect(next) } + +// ________.__ __ ___ ___ ___. +// / _____/|__|/ |_ / | \ __ _\_ |__ +// / \ ___| \ __\/ ~ \ | \ __ \ +// \ \_\ \ || | \ Y / | / \_\ \ +// \______ /__||__| \___|_ /|____/|___ / +// \/ \/ \/ + +type SocialGithub struct { + Token *oauth.Token + *oauth.Transport +} + +func (s *SocialGithub) Type() int { + return models.OT_GITHUB +} + +func init() { + github := &SocialGithub{} + name := "github" + config := &oauth.Config{ + ClientId: "09383403ff2dc16daaa1", //base.OauthService.GitHub.ClientId, // FIXME: panic when set + ClientSecret: "0e4aa0c3630df396cdcea01a9d45cacf79925fea", //base.OauthService.GitHub.ClientSecret, + RedirectURL: strings.TrimSuffix(base.AppUrl, "/") + "/user/login/" + name, //ctx.Req.URL.RequestURI(), + Scope: "https://api.github.com/user", + AuthURL: "https://github.com/login/oauth/authorize", + TokenURL: "https://github.com/login/oauth/access_token", + } + github.Transport = &oauth.Transport{ + Config: config, + Transport: http.DefaultTransport, + } + SocialMap[name] = github +} + +func (s *SocialGithub) SetRedirectUrl(url string) { + s.Transport.Config.RedirectURL = url +} + +func (s *SocialGithub) UserInfo(token *oauth.Token, _ *url.URL) (*BasicUserInfo, error) { + transport := &oauth.Transport{ + Token: token, + } + var data struct { + Id int `json:"id"` + Name string `json:"login"` + Email string `json:"email"` + } + var err error + r, err := transport.Client().Get(s.Transport.Scope) + if err != nil { + return nil, err + } + defer r.Body.Close() + if err = json.NewDecoder(r.Body).Decode(&data); err != nil { + return nil, err + } + return &BasicUserInfo{ + Identity: strconv.Itoa(data.Id), + Name: data.Name, + Email: data.Email, + }, nil +} + +// ________ .__ +// / _____/ ____ ____ ____ | | ____ +// / \ ___ / _ \ / _ \ / ___\| | _/ __ \ +// \ \_\ ( <_> | <_> ) /_/ > |_\ ___/ +// \______ /\____/ \____/\___ /|____/\___ > +// \/ /_____/ \/ + +type SocialGoogle struct { + Token *oauth.Token + *oauth.Transport +} + +func (s *SocialGoogle) Type() int { + return models.OT_GOOGLE +} + +func init() { + google := &SocialGoogle{} + name := "google" + // get client id and secret from + // https://console.developers.google.com/project + config := &oauth.Config{ + ClientId: "849753812404-mpd7ilvlb8c7213qn6bre6p6djjskti9.apps.googleusercontent.com", //base.OauthService.GitHub.ClientId, // FIXME: panic when set + ClientSecret: "VukKc4MwaJUSmiyv3D7ANVCa", //base.OauthService.GitHub.ClientSecret, + Scope: "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile", + AuthURL: "https://accounts.google.com/o/oauth2/auth", + TokenURL: "https://accounts.google.com/o/oauth2/token", + } + google.Transport = &oauth.Transport{ + Config: config, + Transport: http.DefaultTransport, + } + SocialMap[name] = google +} + +func (s *SocialGoogle) SetRedirectUrl(url string) { + s.Transport.Config.RedirectURL = url +} + +func (s *SocialGoogle) UserInfo(token *oauth.Token, _ *url.URL) (*BasicUserInfo, error) { + transport := &oauth.Transport{Token: token} + var data struct { + Id string `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + } + var err error + + reqUrl := "https://www.googleapis.com/oauth2/v1/userinfo" + r, err := transport.Client().Get(reqUrl) + if err != nil { + return nil, err + } + defer r.Body.Close() + if err = json.NewDecoder(r.Body).Decode(&data); err != nil { + return nil, err + } + return &BasicUserInfo{ + Identity: data.Id, + Name: data.Name, + Email: data.Email, + }, nil +} + +// ________ ________ +// \_____ \ \_____ \ +// / / \ \ / / \ \ +// / \_/. \/ \_/. \ +// \_____\ \_/\_____\ \_/ +// \__> \__> + +type SocialQQ struct { + Token *oauth.Token + *oauth.Transport + reqUrl string +} + +func (s *SocialQQ) Type() int { + return models.OT_QQ +} + +func init() { + qq := &SocialQQ{} + name := "qq" + config := &oauth.Config{ + ClientId: "801497180", //base.OauthService.GitHub.ClientId, // FIXME: panic when set + ClientSecret: "16cd53b8ad2e16a36fc2c8f87d9388f2", //base.OauthService.GitHub.ClientSecret, + Scope: "all", + AuthURL: "https://open.t.qq.com/cgi-bin/oauth2/authorize", + TokenURL: "https://open.t.qq.com/cgi-bin/oauth2/access_token", + } + qq.reqUrl = "https://open.t.qq.com/api/user/info" + qq.Transport = &oauth.Transport{ + Config: config, + Transport: http.DefaultTransport, + } + SocialMap[name] = qq +} + +func (s *SocialQQ) SetRedirectUrl(url string) { + s.Transport.Config.RedirectURL = url +} + +func (s *SocialQQ) UserInfo(token *oauth.Token, URL *url.URL) (*BasicUserInfo, error) { + var data struct { + Data struct { + Id string `json:"openid"` + Name string `json:"name"` + Email string `json:"email"` + } `json:"data"` + } + var err error + // https://open.t.qq.com/api/user/info? + //oauth_consumer_key=APP_KEY& + //access_token=ACCESSTOKEN&openid=openid + //clientip=CLIENTIP&oauth_version=2.a + //scope=all + var urls = url.Values{ + "oauth_consumer_key": {s.Transport.Config.ClientId}, + "access_token": {token.AccessToken}, + "openid": URL.Query()["openid"], + "oauth_version": {"2.a"}, + "scope": {"all"}, + } + r, err := http.Get(s.reqUrl + "?" + urls.Encode()) + if err != nil { + return nil, err + } + defer r.Body.Close() + if err = json.NewDecoder(r.Body).Decode(&data); err != nil { + return nil, err + } + return &BasicUserInfo{ + Identity: data.Data.Id, + Name: data.Data.Name, + Email: data.Data.Email, + }, nil +} diff --git a/routers/user/social_github.go b/routers/user/social_github.go deleted file mode 100644 index e532efd0..00000000 --- a/routers/user/social_github.go +++ /dev/null @@ -1,73 +0,0 @@ -// 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 user - -import ( - "encoding/json" - "net/http" - "net/url" - "strconv" - "strings" - - "code.google.com/p/goauth2/oauth" - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/modules/base" -) - -type SocialGithub struct { - Token *oauth.Token - *oauth.Transport -} - -func (s *SocialGithub) Type() int { - return models.OT_GITHUB -} - -func init() { - github := &SocialGithub{} - name := "github" - config := &oauth.Config{ - ClientId: "09383403ff2dc16daaa1", //base.OauthService.GitHub.ClientId, // FIXME: panic when set - ClientSecret: "0e4aa0c3630df396cdcea01a9d45cacf79925fea", //base.OauthService.GitHub.ClientSecret, - RedirectURL: strings.TrimSuffix(base.AppUrl, "/") + "/user/login/" + name, //ctx.Req.URL.RequestURI(), - Scope: "https://api.github.com/user", - AuthURL: "https://github.com/login/oauth/authorize", - TokenURL: "https://github.com/login/oauth/access_token", - } - github.Transport = &oauth.Transport{ - Config: config, - Transport: http.DefaultTransport, - } - SocialMap[name] = github -} - -func (s *SocialGithub) SetRedirectUrl(url string) { - s.Transport.Config.RedirectURL = url -} - -func (s *SocialGithub) UserInfo(token *oauth.Token, _ *url.URL) (*BasicUserInfo, error) { - transport := &oauth.Transport{ - Token: token, - } - var data struct { - Id int `json:"id"` - Name string `json:"login"` - Email string `json:"email"` - } - var err error - r, err := transport.Client().Get(s.Transport.Scope) - if err != nil { - return nil, err - } - defer r.Body.Close() - if err = json.NewDecoder(r.Body).Decode(&data); err != nil { - return nil, err - } - return &BasicUserInfo{ - Identity: strconv.Itoa(data.Id), - Name: data.Name, - Email: data.Email, - }, nil -} diff --git a/routers/user/social_google.go b/routers/user/social_google.go deleted file mode 100644 index b585386f..00000000 --- a/routers/user/social_google.go +++ /dev/null @@ -1,71 +0,0 @@ -// 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 user - -import ( - "encoding/json" - "net/http" - "net/url" - "github.com/gogits/gogs/models" - - "code.google.com/p/goauth2/oauth" -) - -type SocialGoogle struct { - Token *oauth.Token - *oauth.Transport -} - -func (s *SocialGoogle) Type() int { - return models.OT_GOOGLE -} - -func init() { - google := &SocialGoogle{} - name := "google" - // get client id and secret from - // https://console.developers.google.com/project - config := &oauth.Config{ - ClientId: "849753812404-mpd7ilvlb8c7213qn6bre6p6djjskti9.apps.googleusercontent.com", //base.OauthService.GitHub.ClientId, // FIXME: panic when set - ClientSecret: "VukKc4MwaJUSmiyv3D7ANVCa", //base.OauthService.GitHub.ClientSecret, - Scope: "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile", - AuthURL: "https://accounts.google.com/o/oauth2/auth", - TokenURL: "https://accounts.google.com/o/oauth2/token", - } - google.Transport = &oauth.Transport{ - Config: config, - Transport: http.DefaultTransport, - } - SocialMap[name] = google -} - -func (s *SocialGoogle) SetRedirectUrl(url string) { - s.Transport.Config.RedirectURL = url -} - -func (s *SocialGoogle) UserInfo(token *oauth.Token, _ *url.URL) (*BasicUserInfo, error) { - transport := &oauth.Transport{Token: token} - var data struct { - Id string `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - } - var err error - - reqUrl := "https://www.googleapis.com/oauth2/v1/userinfo" - r, err := transport.Client().Get(reqUrl) - if err != nil { - return nil, err - } - defer r.Body.Close() - if err = json.NewDecoder(r.Body).Decode(&data); err != nil { - return nil, err - } - return &BasicUserInfo{ - Identity: data.Id, - Name: data.Name, - Email: data.Email, - }, nil -} diff --git a/routers/user/social_qq.go b/routers/user/social_qq.go deleted file mode 100644 index d08892ef..00000000 --- a/routers/user/social_qq.go +++ /dev/null @@ -1,83 +0,0 @@ -// 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. - -// api reference: http://wiki.open.t.qq.com/index.php/OAuth2.0%E9%89%B4%E6%9D%83/Authorization_code%E6%8E%88%E6%9D%83%E6%A1%88%E4%BE%8B -package user - -import ( - "encoding/json" - "net/http" - "net/url" - "github.com/gogits/gogs/models" - - "code.google.com/p/goauth2/oauth" -) - -type SocialQQ struct { - Token *oauth.Token - *oauth.Transport - reqUrl string -} - -func (s *SocialQQ) Type() int { - return models.OT_QQ -} - -func init() { - qq := &SocialQQ{} - name := "qq" - config := &oauth.Config{ - ClientId: "801497180", //base.OauthService.GitHub.ClientId, // FIXME: panic when set - ClientSecret: "16cd53b8ad2e16a36fc2c8f87d9388f2", //base.OauthService.GitHub.ClientSecret, - Scope: "all", - AuthURL: "https://open.t.qq.com/cgi-bin/oauth2/authorize", - TokenURL: "https://open.t.qq.com/cgi-bin/oauth2/access_token", - } - qq.reqUrl = "https://open.t.qq.com/api/user/info" - qq.Transport = &oauth.Transport{ - Config: config, - Transport: http.DefaultTransport, - } - SocialMap[name] = qq -} - -func (s *SocialQQ) SetRedirectUrl(url string) { - s.Transport.Config.RedirectURL = url -} - -func (s *SocialQQ) UserInfo(token *oauth.Token, URL *url.URL) (*BasicUserInfo, error) { - var data struct { - Data struct { - Id string `json:"openid"` - Name string `json:"name"` - Email string `json:"email"` - } `json:"data"` - } - var err error - // https://open.t.qq.com/api/user/info? - //oauth_consumer_key=APP_KEY& - //access_token=ACCESSTOKEN&openid=openid - //clientip=CLIENTIP&oauth_version=2.a - //scope=all - var urls = url.Values{ - "oauth_consumer_key": {s.Transport.Config.ClientId}, - "access_token": {token.AccessToken}, - "openid": URL.Query()["openid"], - "oauth_version": {"2.a"}, - "scope": {"all"}, - } - r, err := http.Get(s.reqUrl + "?" + urls.Encode()) - if err != nil { - return nil, err - } - defer r.Body.Close() - if err = json.NewDecoder(r.Body).Decode(&data); err != nil { - return nil, err - } - return &BasicUserInfo{ - Identity: data.Data.Id, - Name: data.Data.Name, - Email: data.Data.Email, - }, nil -} diff --git a/routers/user/user.go b/routers/user/user.go index e5328173..bcb2e978 100644 --- a/routers/user/user.go +++ b/routers/user/user.go @@ -5,12 +5,9 @@ package user import ( - "fmt" "net/url" "strings" - "github.com/go-martini/martini" - "github.com/gogits/gogs/models" "github.com/gogits/gogs/modules/auth" "github.com/gogits/gogs/modules/base" @@ -19,75 +16,6 @@ import ( "github.com/gogits/gogs/modules/middleware" ) -func Dashboard(ctx *middleware.Context) { - ctx.Data["Title"] = "Dashboard" - ctx.Data["PageIsUserDashboard"] = true - repos, err := models.GetRepositories(&models.User{Id: ctx.User.Id}) - if err != nil { - ctx.Handle(500, "user.Dashboard", err) - return - } - ctx.Data["MyRepos"] = repos - - feeds, err := models.GetFeeds(ctx.User.Id, 0, false) - if err != nil { - ctx.Handle(500, "user.Dashboard", err) - return - } - ctx.Data["Feeds"] = feeds - ctx.HTML(200, "user/dashboard") -} - -func Profile(ctx *middleware.Context, params martini.Params) { - ctx.Data["Title"] = "Profile" - - // TODO: Need to check view self or others. - user, err := models.GetUserByName(params["username"]) - if err != nil { - ctx.Handle(500, "user.Profile", err) - return - } - - ctx.Data["Owner"] = user - - tab := ctx.Query("tab") - ctx.Data["TabName"] = tab - - switch tab { - case "activity": - feeds, err := models.GetFeeds(user.Id, 0, true) - if err != nil { - ctx.Handle(500, "user.Profile", err) - return - } - ctx.Data["Feeds"] = feeds - default: - repos, err := models.GetRepositories(user) - if err != nil { - ctx.Handle(500, "user.Profile", err) - return - } - ctx.Data["Repos"] = repos - } - - ctx.Data["PageIsUserProfile"] = true - ctx.HTML(200, "user/profile") -} - -func Email2User(ctx *middleware.Context) { - u, err := models.GetUserByEmail(ctx.Query("email")) - if err != nil { - if err == models.ErrUserNotExist { - ctx.Handle(404, "user.Email2User", err) - } else { - ctx.Handle(500, "user.Email2User(GetUserByEmail)", err) - } - return - } - - ctx.Redirect("/user/" + u.Name) -} - func SignIn(ctx *middleware.Context) { ctx.Data["Title"] = "Log In" @@ -329,117 +257,6 @@ func DeletePost(ctx *middleware.Context) { ctx.Redirect("/user/delete") } -const ( - TPL_FEED = ` -
%s
%s
` -) - -func Feeds(ctx *middleware.Context, form auth.FeedsForm) { - actions, err := models.GetFeeds(form.UserId, form.Page*20, false) - if err != nil { - ctx.JSON(500, err) - } - - feeds := make([]string, len(actions)) - for i := range actions { - feeds[i] = fmt.Sprintf(TPL_FEED, base.ActionIcon(actions[i].OpType), - base.TimeSince(actions[i].Created), base.ActionDesc(actions[i])) - } - ctx.JSON(200, &feeds) -} - -func Issues(ctx *middleware.Context) { - ctx.Data["Title"] = "Your Issues" - ctx.Data["ViewType"] = "all" - - page, _ := base.StrTo(ctx.Query("page")).Int() - repoId, _ := base.StrTo(ctx.Query("repoid")).Int64() - - ctx.Data["RepoId"] = repoId - - var posterId int64 = 0 - if ctx.Query("type") == "created_by" { - posterId = ctx.User.Id - ctx.Data["ViewType"] = "created_by" - } - - // Get all repositories. - repos, err := models.GetRepositories(ctx.User) - if err != nil { - ctx.Handle(200, "user.Issues(get repositories)", err) - return - } - - showRepos := make([]models.Repository, 0, len(repos)) - - isShowClosed := ctx.Query("state") == "closed" - var closedIssueCount, createdByCount, allIssueCount int - - // Get all issues. - allIssues := make([]models.Issue, 0, 5*len(repos)) - for i, repo := range repos { - issues, err := models.GetIssues(0, repo.Id, posterId, 0, page, isShowClosed, false, "", "") - if err != nil { - ctx.Handle(200, "user.Issues(get issues)", err) - return - } - - allIssueCount += repo.NumIssues - closedIssueCount += repo.NumClosedIssues - - // Set repository information to issues. - for j := range issues { - issues[j].Repo = &repos[i] - } - allIssues = append(allIssues, issues...) - - repos[i].NumOpenIssues = repo.NumIssues - repo.NumClosedIssues - if repos[i].NumOpenIssues > 0 { - showRepos = append(showRepos, repos[i]) - } - } - - showIssues := make([]models.Issue, 0, len(allIssues)) - ctx.Data["IsShowClosed"] = isShowClosed - - // Get posters and filter issues. - for i := range allIssues { - u, err := models.GetUserById(allIssues[i].PosterId) - if err != nil { - ctx.Handle(200, "user.Issues(get poster): %v", err) - return - } - allIssues[i].Poster = u - if u.Id == ctx.User.Id { - createdByCount++ - } - - if repoId > 0 && repoId != allIssues[i].Repo.Id { - continue - } - - if isShowClosed == allIssues[i].IsClosed { - showIssues = append(showIssues, allIssues[i]) - } - } - - ctx.Data["Repos"] = showRepos - ctx.Data["Issues"] = showIssues - ctx.Data["AllIssueCount"] = allIssueCount - ctx.Data["ClosedIssueCount"] = closedIssueCount - ctx.Data["OpenIssueCount"] = allIssueCount - closedIssueCount - ctx.Data["CreatedByCount"] = createdByCount - ctx.HTML(200, "issue/user") -} - -func Pulls(ctx *middleware.Context) { - ctx.HTML(200, "user/pulls") -} - -func Stars(ctx *middleware.Context) { - ctx.HTML(200, "user/stars") -} - func Activate(ctx *middleware.Context) { code := ctx.Query("code") if len(code) == 0 { diff --git a/web.go b/web.go index a17be2a3..8ae074ec 100644 --- a/web.go +++ b/web.go @@ -14,7 +14,6 @@ import ( qlog "github.com/qiniu/log" - "github.com/gogits/binding" "github.com/gogits/gogs/modules/auth" "github.com/gogits/gogs/modules/avatar" "github.com/gogits/gogs/modules/base" @@ -67,7 +66,7 @@ func runWeb(*cli.Context) { reqSignOut := middleware.Toggle(&middleware.ToggleOptions{SignOutRequire: true}) - bindIgnErr := binding.BindIgnErr + bindIgnErr := middleware.BindIgnErr // Routers. m.Get("/", ignSignIn, routers.Home) @@ -102,7 +101,7 @@ func runWeb(*cli.Context) { r.Post("/setting", bindIgnErr(auth.UpdateProfileForm{}), user.SettingPost) }, reqSignIn) m.Group("/user", func(r martini.Router) { - r.Get("/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds) + r.Get("/feeds", middleware.Bind(auth.FeedsForm{}), user.Feeds) r.Get("/activate", user.Activate) r.Get("/email2user", user.Email2User) r.Get("/forget_password", user.ForgotPasswd) -- cgit v1.2.3