| @@ -56,19 +56,19 @@ func parseCmd(cmd string) (string, string) { | |||
| } | |||
| var ( | |||
| COMMANDS_READONLY = map[string]int{ | |||
| "git-upload-pack": models.AU_WRITABLE, | |||
| "git upload-pack": models.AU_WRITABLE, | |||
| "git-upload-archive": models.AU_WRITABLE, | |||
| COMMANDS_READONLY = map[string]models.AccessType{ | |||
| "git-upload-pack": models.WRITABLE, | |||
| "git upload-pack": models.WRITABLE, | |||
| "git-upload-archive": models.WRITABLE, | |||
| } | |||
| COMMANDS_WRITE = map[string]int{ | |||
| "git-receive-pack": models.AU_READABLE, | |||
| "git receive-pack": models.AU_READABLE, | |||
| COMMANDS_WRITE = map[string]models.AccessType{ | |||
| "git-receive-pack": models.READABLE, | |||
| "git receive-pack": models.READABLE, | |||
| } | |||
| ) | |||
| func In(b string, sl map[string]int) bool { | |||
| func In(b string, sl map[string]models.AccessType) bool { | |||
| _, e := sl[b] | |||
| return e | |||
| } | |||
| @@ -129,7 +129,7 @@ func runServ(k *cli.Context) { | |||
| // Access check. | |||
| switch { | |||
| case isWrite: | |||
| has, err := models.HasAccess(user.Name, path.Join(repoUserName, repoName), models.AU_WRITABLE) | |||
| has, err := models.HasAccess(user.Name, path.Join(repoUserName, repoName), models.WRITABLE) | |||
| if err != nil { | |||
| println("Gogs: internal error:", err) | |||
| log.GitLogger.Fatal("Fail to check write access:", err) | |||
| @@ -152,7 +152,7 @@ func runServ(k *cli.Context) { | |||
| break | |||
| } | |||
| has, err := models.HasAccess(user.Name, path.Join(repoUserName, repoName), models.AU_READABLE) | |||
| has, err := models.HasAccess(user.Name, path.Join(repoUserName, repoName), models.READABLE) | |||
| if err != nil { | |||
| println("Gogs: internal error:", err) | |||
| log.GitLogger.Fatal("Fail to check read access:", err) | |||
| @@ -188,14 +188,15 @@ func runWeb(*cli.Context) { | |||
| reqOwner := middleware.RequireOwner() | |||
| m.Group("/o", func(r martini.Router) { | |||
| m.Group("/org", func(r martini.Router) { | |||
| r.Get("/create", org.New) | |||
| r.Post("/create", bindIgnErr(auth.CreateOrganizationForm{}), org.NewPost) | |||
| r.Get("/:org", org.Organization) | |||
| r.Get("/:org/dashboard", org.Dashboard) | |||
| r.Get("/:org/members", org.Members) | |||
| r.Get("/:org/teams", org.Teams) | |||
| r.Get("/:org/setting", org.Setting) | |||
| }) | |||
| }, reqSignIn) | |||
| m.Group("/:username/:reponame", func(r martini.Router) { | |||
| r.Get("/settings", repo.Setting) | |||
| @@ -17,7 +17,7 @@ import ( | |||
| "github.com/gogits/gogs/modules/setting" | |||
| ) | |||
| const APP_VER = "0.4.5.0624 Alpha" | |||
| const APP_VER = "0.4.5.0625 Alpha" | |||
| func init() { | |||
| runtime.GOMAXPROCS(runtime.NumCPU()) | |||
| @@ -11,19 +11,20 @@ import ( | |||
| "github.com/go-xorm/xorm" | |||
| ) | |||
| // Access types. | |||
| type AccessType int | |||
| const ( | |||
| AU_READABLE = iota + 1 | |||
| AU_WRITABLE | |||
| READABLE AccessType = iota + 1 | |||
| WRITABLE | |||
| ) | |||
| // Access represents the accessibility of user to repository. | |||
| type Access struct { | |||
| Id int64 | |||
| UserName string `xorm:"unique(s)"` | |||
| RepoName string `xorm:"unique(s)"` // <user name>/<repo name> | |||
| Mode int `xorm:"unique(s)"` | |||
| Created time.Time `xorm:"created"` | |||
| UserName string `xorm:"unique(s)"` | |||
| RepoName string `xorm:"unique(s)"` // <user name>/<repo name> | |||
| Mode AccessType `xorm:"unique(s)"` | |||
| Created time.Time `xorm:"created"` | |||
| } | |||
| // AddAccess adds new access record. | |||
| @@ -59,7 +60,7 @@ func UpdateAccessWithSession(sess *xorm.Session, access *Access) error { | |||
| // HasAccess returns true if someone can read or write to given repository. | |||
| // The repoName should be in format <username>/<reponame>. | |||
| func HasAccess(uname, repoName string, mode int) (bool, error) { | |||
| func HasAccess(uname, repoName string, mode AccessType) (bool, error) { | |||
| if len(repoName) == 0 { | |||
| return false, nil | |||
| } | |||
| @@ -213,9 +213,9 @@ func GetIssueCountByPoster(uid, rid int64, isClosed bool) int64 { | |||
| // IssueUser represents an issue-user relation. | |||
| type IssueUser struct { | |||
| Id int64 | |||
| Uid int64 // User ID. | |||
| Uid int64 `xorm:"INDEX"` // User ID. | |||
| IssueId int64 | |||
| RepoId int64 | |||
| RepoId int64 `xorm:"INDEX"` | |||
| MilestoneId int64 | |||
| IsRead bool | |||
| IsAssigned bool | |||
| @@ -255,7 +255,7 @@ func LoginUserLdapSource(user *User, name, passwd string, sourceId int64, cfg *L | |||
| Email: mail, | |||
| } | |||
| return RegisterUser(user) | |||
| return CreateUser(user) | |||
| } | |||
| type loginAuth struct { | |||
| @@ -359,5 +359,5 @@ func LoginUserSMTPSource(user *User, name, passwd string, sourceId int64, cfg *S | |||
| Passwd: passwd, | |||
| Email: name, | |||
| } | |||
| return RegisterUser(user) | |||
| return CreateUser(user) | |||
| } | |||
| @@ -35,7 +35,7 @@ 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(Mirror), new(Release), new(LoginSource), new(Webhook), new(IssueUser), | |||
| new(Milestone), new(Label), new(HookTask)) | |||
| new(Milestone), new(Label), new(HookTask), new(Team), new(OrgUser), new(TeamUser)) | |||
| } | |||
| func LoadModelsConfig() { | |||
| @@ -0,0 +1,69 @@ | |||
| // 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 models | |||
| type AuthorizeType int | |||
| const ( | |||
| ORG_READABLE AuthorizeType = iota + 1 | |||
| ORG_WRITABLE | |||
| ORG_ADMIN | |||
| ) | |||
| // Team represents a organization team. | |||
| type Team struct { | |||
| Id int64 | |||
| OrgId int64 `xorm:"INDEX"` | |||
| Name string | |||
| Description string | |||
| Authorize AuthorizeType | |||
| NumMembers int | |||
| NumRepos int | |||
| } | |||
| // NewTeam creates a record of new team. | |||
| func NewTeam(t *Team) error { | |||
| _, err := x.Insert(t) | |||
| return err | |||
| } | |||
| // ________ ____ ___ | |||
| // \_____ \_______ ____ | | \______ ___________ | |||
| // / | \_ __ \/ ___\| | / ___// __ \_ __ \ | |||
| // / | \ | \/ /_/ > | /\___ \\ ___/| | \/ | |||
| // \_______ /__| \___ /|______//____ >\___ >__| | |||
| // \/ /_____/ \/ \/ | |||
| // OrgUser represents an organization-user relation. | |||
| type OrgUser struct { | |||
| Id int64 | |||
| Uid int64 `xorm:"INDEX"` | |||
| OrgId int64 `xorm:"INDEX"` | |||
| IsPublic bool | |||
| IsOwner bool | |||
| NumTeam int | |||
| } | |||
| // GetOrgUsersByUserId returns all organization-user relations by user ID. | |||
| func GetOrgUsersByUserId(uid int64) ([]*OrgUser, error) { | |||
| ous := make([]*OrgUser, 0, 10) | |||
| err := x.Where("uid=?", uid).Find(&ous) | |||
| return ous, err | |||
| } | |||
| // ___________ ____ ___ | |||
| // \__ ___/___ _____ _____ | | \______ ___________ | |||
| // | |_/ __ \\__ \ / \| | / ___// __ \_ __ \ | |||
| // | |\ ___/ / __ \| Y Y \ | /\___ \\ ___/| | \/ | |||
| // |____| \___ >____ /__|_| /______//____ >\___ >__| | |||
| // \/ \/ \/ \/ \/ | |||
| // TeamUser represents an team-user relation. | |||
| type TeamUser struct { | |||
| Id int64 | |||
| Uid int64 | |||
| OrgId int64 `xorm:"INDEX"` | |||
| TeamId int64 | |||
| } | |||
| @@ -158,7 +158,7 @@ func IsRepositoryExist(u *User, repoName string) (bool, error) { | |||
| } | |||
| var ( | |||
| illegalEquals = []string{"raw", "install", "api", "avatar", "user", "help", "stars", "issues", "pulls", "commits", "repo", "template", "admin"} | |||
| illegalEquals = []string{"raw", "install", "api", "avatar", "user", "org", "help", "stars", "issues", "pulls", "commits", "repo", "template", "admin"} | |||
| illegalSuffixs = []string{".git"} | |||
| ) | |||
| @@ -483,7 +483,9 @@ func CreateRepository(user *User, name, desc, lang, license string, private, mir | |||
| sess := x.NewSession() | |||
| defer sess.Close() | |||
| sess.Begin() | |||
| if err = sess.Begin(); err != nil { | |||
| return nil, err | |||
| } | |||
| if _, err = sess.Insert(repo); err != nil { | |||
| if err2 := os.RemoveAll(repoPath); err2 != nil { | |||
| @@ -495,9 +497,9 @@ func CreateRepository(user *User, name, desc, lang, license string, private, mir | |||
| return nil, err | |||
| } | |||
| mode := AU_WRITABLE | |||
| mode := WRITABLE | |||
| if mirror { | |||
| mode = AU_READABLE | |||
| mode = READABLE | |||
| } | |||
| access := Access{ | |||
| UserName: user.LowerName, | |||
| @@ -21,10 +21,11 @@ import ( | |||
| "github.com/gogits/gogs/modules/setting" | |||
| ) | |||
| // User types. | |||
| type UserType int | |||
| const ( | |||
| UT_INDIVIDUAL = iota + 1 | |||
| UT_ORGANIZATION | |||
| INDIVIDUAL UserType = iota // Historic reason to make it starts at 0. | |||
| ORGANIZATION | |||
| ) | |||
| var ( | |||
| @@ -50,7 +51,8 @@ type User struct { | |||
| LoginType LoginType | |||
| LoginSource int64 `xorm:"not null default 0"` | |||
| LoginName string | |||
| Type int | |||
| Type UserType | |||
| Orgs []*User `xorm:"-"` | |||
| NumFollowers int | |||
| NumFollowings int | |||
| NumStars int | |||
| @@ -65,36 +67,60 @@ type User struct { | |||
| Salt string `xorm:"VARCHAR(10)"` | |||
| Created time.Time `xorm:"created"` | |||
| Updated time.Time `xorm:"updated"` | |||
| // For organization. | |||
| NumTeams int | |||
| NumMembers int | |||
| } | |||
| // HomeLink returns the user home page link. | |||
| func (user *User) HomeLink() string { | |||
| return "/user/" + user.Name | |||
| func (u *User) HomeLink() string { | |||
| return "/user/" + u.Name | |||
| } | |||
| // AvatarLink returns user gravatar link. | |||
| func (user *User) AvatarLink() string { | |||
| func (u *User) AvatarLink() string { | |||
| if setting.DisableGravatar { | |||
| return "/img/avatar_default.jpg" | |||
| } else if setting.Service.EnableCacheAvatar { | |||
| return "/avatar/" + user.Avatar | |||
| return "/avatar/" + u.Avatar | |||
| } | |||
| return "//1.gravatar.com/avatar/" + user.Avatar | |||
| return "//1.gravatar.com/avatar/" + u.Avatar | |||
| } | |||
| // NewGitSig generates and returns the signature of given user. | |||
| func (user *User) NewGitSig() *git.Signature { | |||
| func (u *User) NewGitSig() *git.Signature { | |||
| return &git.Signature{ | |||
| Name: user.Name, | |||
| Email: user.Email, | |||
| Name: u.Name, | |||
| Email: u.Email, | |||
| When: time.Now(), | |||
| } | |||
| } | |||
| // EncodePasswd encodes password to safe format. | |||
| func (user *User) EncodePasswd() { | |||
| newPasswd := base.PBKDF2([]byte(user.Passwd), []byte(user.Salt), 10000, 50, sha256.New) | |||
| user.Passwd = fmt.Sprintf("%x", newPasswd) | |||
| func (u *User) EncodePasswd() { | |||
| newPasswd := base.PBKDF2([]byte(u.Passwd), []byte(u.Salt), 10000, 50, sha256.New) | |||
| u.Passwd = fmt.Sprintf("%x", newPasswd) | |||
| } | |||
| func (u *User) IsOrganization() bool { | |||
| return u.Type == ORGANIZATION | |||
| } | |||
| func (u *User) GetOrganizations() error { | |||
| ous, err := GetOrgUsersByUserId(u.Id) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| u.Orgs = make([]*User, len(ous)) | |||
| for i, ou := range ous { | |||
| u.Orgs[i], err = GetUserById(ou.OrgId) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| // Member represents user is member of organization. | |||
| @@ -126,49 +152,135 @@ func GetUserSalt() string { | |||
| return base.GetRandomString(10) | |||
| } | |||
| // RegisterUser creates record of a new user. | |||
| func RegisterUser(user *User) (*User, error) { | |||
| // CreateUser creates record of a new user. | |||
| func CreateUser(u *User) (*User, error) { | |||
| if !IsLegalName(u.Name) { | |||
| return nil, ErrUserNameIllegal | |||
| } | |||
| isExist, err := IsUserExist(u.Name) | |||
| if err != nil { | |||
| return nil, err | |||
| } else if isExist { | |||
| return nil, ErrUserAlreadyExist | |||
| } | |||
| isExist, err = IsEmailUsed(u.Email) | |||
| if err != nil { | |||
| return nil, err | |||
| } else if isExist { | |||
| return nil, ErrEmailAlreadyUsed | |||
| } | |||
| u.LowerName = strings.ToLower(u.Name) | |||
| u.Avatar = base.EncodeMd5(u.Email) | |||
| u.AvatarEmail = u.Email | |||
| u.Rands = GetUserSalt() | |||
| u.Salt = GetUserSalt() | |||
| u.EncodePasswd() | |||
| sess := x.NewSession() | |||
| defer sess.Close() | |||
| if err = sess.Begin(); err != nil { | |||
| return nil, err | |||
| } | |||
| if !IsLegalName(user.Name) { | |||
| if _, err = sess.Insert(u); err != nil { | |||
| sess.Rollback() | |||
| return nil, err | |||
| } | |||
| if err = os.MkdirAll(UserPath(u.Name), os.ModePerm); err != nil { | |||
| sess.Rollback() | |||
| return nil, err | |||
| } | |||
| if err = sess.Commit(); err != nil { | |||
| return nil, err | |||
| } | |||
| // Auto-set admin for user whose ID is 1. | |||
| if u.Id == 1 { | |||
| u.IsAdmin = true | |||
| u.IsActive = true | |||
| _, err = x.Id(u.Id).UseBool().Update(u) | |||
| } | |||
| return u, err | |||
| } | |||
| // CreateOrganization creates record of a new organization. | |||
| func CreateOrganization(org, owner *User) (*User, error) { | |||
| if !IsLegalName(org.Name) { | |||
| return nil, ErrUserNameIllegal | |||
| } | |||
| isExist, err := IsUserExist(user.Name) | |||
| isExist, err := IsUserExist(org.Name) | |||
| if err != nil { | |||
| return nil, err | |||
| } else if isExist { | |||
| return nil, ErrUserAlreadyExist | |||
| } | |||
| isExist, err = IsEmailUsed(user.Email) | |||
| isExist, err = IsEmailUsed(org.Email) | |||
| if err != nil { | |||
| return nil, err | |||
| } else if isExist { | |||
| return nil, ErrEmailAlreadyUsed | |||
| } | |||
| user.LowerName = strings.ToLower(user.Name) | |||
| user.Avatar = base.EncodeMd5(user.Email) | |||
| user.AvatarEmail = user.Email | |||
| user.Rands = GetUserSalt() | |||
| user.Salt = GetUserSalt() | |||
| user.EncodePasswd() | |||
| if _, err = x.Insert(user); err != nil { | |||
| org.LowerName = strings.ToLower(org.Name) | |||
| org.Avatar = base.EncodeMd5(org.Email) | |||
| org.AvatarEmail = org.Email | |||
| // No password for organization. | |||
| org.NumTeams = 1 | |||
| org.NumMembers = 1 | |||
| sess := x.NewSession() | |||
| defer sess.Close() | |||
| if err = sess.Begin(); err != nil { | |||
| return nil, err | |||
| } | |||
| if _, err = sess.Insert(org); err != nil { | |||
| sess.Rollback() | |||
| return nil, err | |||
| } else if err = os.MkdirAll(UserPath(user.Name), os.ModePerm); err != nil { | |||
| if _, err := x.Id(user.Id).Delete(&User{}); err != nil { | |||
| return nil, errors.New(fmt.Sprintf( | |||
| "both create userpath %s and delete table record faild: %v", user.Name, err)) | |||
| } | |||
| } | |||
| // Create default owner team. | |||
| t := &Team{ | |||
| OrgId: org.Id, | |||
| Name: "Owner", | |||
| Authorize: ORG_ADMIN, | |||
| NumMembers: 1, | |||
| } | |||
| if _, err = sess.Insert(t); err != nil { | |||
| sess.Rollback() | |||
| return nil, err | |||
| } | |||
| if user.Id == 1 { | |||
| user.IsAdmin = true | |||
| user.IsActive = true | |||
| _, err = x.Id(user.Id).UseBool().Update(user) | |||
| // Add initial creator to organization and owner team. | |||
| ou := &OrgUser{ | |||
| Uid: owner.Id, | |||
| OrgId: org.Id, | |||
| IsOwner: true, | |||
| NumTeam: 1, | |||
| } | |||
| return user, err | |||
| if _, err = sess.Insert(ou); err != nil { | |||
| sess.Rollback() | |||
| return nil, err | |||
| } | |||
| tu := &TeamUser{ | |||
| Uid: owner.Id, | |||
| OrgId: org.Id, | |||
| TeamId: t.Id, | |||
| } | |||
| if _, err = sess.Insert(tu); err != nil { | |||
| sess.Rollback() | |||
| return nil, err | |||
| } | |||
| return org, sess.Commit() | |||
| } | |||
| // GetUsers returns given number of user objects with offset. | |||
| @@ -0,0 +1,33 @@ | |||
| // 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 auth | |||
| import ( | |||
| "net/http" | |||
| "reflect" | |||
| "github.com/go-martini/martini" | |||
| "github.com/gogits/gogs/modules/base" | |||
| "github.com/gogits/gogs/modules/middleware/binding" | |||
| ) | |||
| type CreateOrganizationForm struct { | |||
| OrgName string `form:"orgname" binding:"Required;AlphaDashDot;MaxSize(30)"` | |||
| Email string `form:"email" binding:"Required;Email;MaxSize(50)"` | |||
| } | |||
| func (f *CreateOrganizationForm) Name(field string) string { | |||
| names := map[string]string{ | |||
| "OrgName": "Organization name", | |||
| "Email": "E-mail address", | |||
| } | |||
| return names[field] | |||
| } | |||
| func (f *CreateOrganizationForm) Validate(errs *binding.Errors, req *http.Request, ctx martini.Context) { | |||
| data := ctx.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData) | |||
| validate(errs, data, f) | |||
| } | |||
| @@ -46,7 +46,7 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler { | |||
| // Collaborators who have write access can be seen as owners. | |||
| if ctx.IsSigned { | |||
| ctx.Repo.IsOwner, err = models.HasAccess(ctx.User.Name, userName+"/"+repoName, models.AU_WRITABLE) | |||
| ctx.Repo.IsOwner, err = models.HasAccess(ctx.User.Name, userName+"/"+repoName, models.WRITABLE) | |||
| if err != nil { | |||
| ctx.Handle(500, "RepoAssignment(HasAccess)", err) | |||
| return | |||
| @@ -107,7 +107,7 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler { | |||
| return | |||
| } | |||
| hasAccess, err := models.HasAccess(ctx.User.Name, ctx.Repo.Owner.Name+"/"+repo.Name, models.AU_READABLE) | |||
| hasAccess, err := models.HasAccess(ctx.User.Name, ctx.Repo.Owner.Name+"/"+repo.Name, models.READABLE) | |||
| if err != nil { | |||
| ctx.Handle(500, "RepoAssignment(HasAccess)", err) | |||
| return | |||
| @@ -67,7 +67,7 @@ func NewUserPost(ctx *middleware.Context, form auth.RegisterForm) { | |||
| } | |||
| var err error | |||
| if u, err = models.RegisterUser(u); err != nil { | |||
| if u, err = models.CreateUser(u); err != nil { | |||
| switch err { | |||
| case models.ErrUserAlreadyExist: | |||
| ctx.RenderWithErr("Username has been already taken", USER_NEW, &form) | |||
| @@ -76,7 +76,7 @@ func NewUserPost(ctx *middleware.Context, form auth.RegisterForm) { | |||
| case models.ErrUserNameIllegal: | |||
| ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), USER_NEW, &form) | |||
| default: | |||
| ctx.Handle(500, "admin.user.NewUser(RegisterUser)", err) | |||
| ctx.Handle(500, "admin.user.NewUser(CreateUser)", err) | |||
| } | |||
| return | |||
| } | |||
| @@ -227,7 +227,7 @@ func InstallPost(ctx *middleware.Context, form auth.InstallForm) { | |||
| GlobalInit() | |||
| // Create admin account. | |||
| if _, err := models.RegisterUser(&models.User{Name: form.AdminName, Email: form.AdminEmail, Passwd: form.AdminPasswd, | |||
| if _, err := models.CreateUser(&models.User{Name: form.AdminName, Email: form.AdminEmail, Passwd: form.AdminPasswd, | |||
| IsAdmin: true, IsActive: true}); err != nil { | |||
| if err != models.ErrUserAlreadyExist { | |||
| setting.InstallLock = false | |||
| @@ -1,33 +1,117 @@ | |||
| // 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 org | |||
| import ( | |||
| "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/log" | |||
| "github.com/gogits/gogs/modules/middleware" | |||
| "github.com/gogits/gogs/routers/user" | |||
| ) | |||
| const ( | |||
| NEW base.TplName = "org/new" | |||
| ) | |||
| func Organization(ctx *middleware.Context, params martini.Params) { | |||
| ctx.Data["Title"] = "Organization "+params["org"] | |||
| ctx.Data["Title"] = "Organization " + params["org"] | |||
| ctx.HTML(200, "org/org") | |||
| } | |||
| func Members(ctx *middleware.Context, params martini.Params) { | |||
| ctx.Data["Title"] = "Organization "+params["org"]+" Members" | |||
| ctx.Data["Title"] = "Organization " + params["org"] + " Members" | |||
| ctx.HTML(200, "org/members") | |||
| } | |||
| func Teams(ctx *middleware.Context, params martini.Params) { | |||
| ctx.Data["Title"] = "Organization "+params["org"]+" Teams" | |||
| ctx.Data["Title"] = "Organization " + params["org"] + " Teams" | |||
| ctx.HTML(200, "org/teams") | |||
| } | |||
| func New(ctx *middleware.Context) { | |||
| ctx.Data["Title"] = "Create an Organization" | |||
| ctx.HTML(200, "org/new") | |||
| ctx.Data["Title"] = "Create An Organization" | |||
| ctx.HTML(200, NEW) | |||
| } | |||
| func NewPost(ctx *middleware.Context, form auth.CreateOrganizationForm) { | |||
| ctx.Data["Title"] = "Create An Organization" | |||
| if ctx.HasError() { | |||
| ctx.HTML(200, NEW) | |||
| return | |||
| } | |||
| org := &models.User{ | |||
| Name: form.OrgName, | |||
| Email: form.Email, | |||
| IsActive: true, // NOTE: may need to set false when require e-mail confirmation. | |||
| Type: models.ORGANIZATION, | |||
| } | |||
| var err error | |||
| if org, err = models.CreateOrganization(org, ctx.User); err != nil { | |||
| switch err { | |||
| case models.ErrUserAlreadyExist: | |||
| ctx.Data["Err_OrgName"] = true | |||
| ctx.RenderWithErr("Organization name has been already taken", NEW, &form) | |||
| case models.ErrEmailAlreadyUsed: | |||
| ctx.Data["Err_Email"] = true | |||
| ctx.RenderWithErr("E-mail address has been already used", NEW, &form) | |||
| case models.ErrUserNameIllegal: | |||
| ctx.Data["Err_OrgName"] = true | |||
| ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), NEW, &form) | |||
| default: | |||
| ctx.Handle(500, "user.NewPost(CreateUser)", err) | |||
| } | |||
| return | |||
| } | |||
| log.Trace("%s Organization created: %s", ctx.Req.RequestURI, org.Name) | |||
| ctx.Redirect("/org/" + form.OrgName + "/dashboard") | |||
| } | |||
| func Dashboard(ctx *middleware.Context, params martini.Params) { | |||
| ctx.Data["Title"] = "Dashboard" | |||
| ctx.HTML(200, "org/dashboard") | |||
| ctx.Data["PageIsUserDashboard"] = true | |||
| ctx.Data["PageIsOrgDashboard"] = true | |||
| org, err := models.GetUserByName(params["org"]) | |||
| if err != nil { | |||
| if err == models.ErrUserNotExist { | |||
| ctx.Handle(404, "org.Dashboard(GetUserByName)", err) | |||
| } else { | |||
| ctx.Handle(500, "org.Dashboard(GetUserByName)", err) | |||
| } | |||
| return | |||
| } | |||
| if err := ctx.User.GetOrganizations(); err != nil { | |||
| ctx.Handle(500, "home.Dashboard(GetOrganizations)", err) | |||
| return | |||
| } | |||
| ctx.Data["Orgs"] = ctx.User.Orgs | |||
| ctx.Data["ContextUser"] = org | |||
| ctx.Data["MyRepos"], err = models.GetRepositories(org.Id, true) | |||
| if err != nil { | |||
| ctx.Handle(500, "org.Dashboard(GetRepositories)", err) | |||
| return | |||
| } | |||
| actions, err := models.GetFeeds(org.Id, 0, false) | |||
| if err != nil { | |||
| ctx.Handle(500, "org.Dashboard(GetFeeds)", err) | |||
| return | |||
| } | |||
| ctx.Data["Feeds"] = actions | |||
| ctx.HTML(200, user.DASHBOARD) | |||
| } | |||
| func Setting(ctx *middleware.Context, param martini.Params) { | |||
| @@ -107,9 +107,9 @@ func Http(ctx *middleware.Context, params martini.Params) { | |||
| } | |||
| if !isPublicPull { | |||
| var tp = models.AU_WRITABLE | |||
| var tp = models.WRITABLE | |||
| if isPull { | |||
| tp = models.AU_READABLE | |||
| tp = models.READABLE | |||
| } | |||
| has, err := models.HasAccess(authUsername, username+"/"+reponame, tp) | |||
| @@ -117,8 +117,8 @@ func Http(ctx *middleware.Context, params martini.Params) { | |||
| ctx.Handle(401, "no basic auth and digit auth", nil) | |||
| return | |||
| } else if !has { | |||
| if tp == models.AU_READABLE { | |||
| has, err = models.HasAccess(authUsername, username+"/"+reponame, models.AU_WRITABLE) | |||
| if tp == models.READABLE { | |||
| has, err = models.HasAccess(authUsername, username+"/"+reponame, models.WRITABLE) | |||
| if err != nil || !has { | |||
| ctx.Handle(401, "no basic auth and digit auth", nil) | |||
| return | |||
| @@ -175,7 +175,7 @@ func CollaborationPost(ctx *middleware.Context) { | |||
| ctx.Redirect(ctx.Req.RequestURI) | |||
| return | |||
| } | |||
| has, err := models.HasAccess(name, repoLink, models.AU_WRITABLE) | |||
| has, err := models.HasAccess(name, repoLink, models.WRITABLE) | |||
| if err != nil { | |||
| ctx.Handle(500, "setting.CollaborationPost(HasAccess)", err) | |||
| return | |||
| @@ -196,7 +196,7 @@ func CollaborationPost(ctx *middleware.Context) { | |||
| } | |||
| if err = models.AddAccess(&models.Access{UserName: name, RepoName: repoLink, | |||
| Mode: models.AU_WRITABLE}); err != nil { | |||
| Mode: models.WRITABLE}); err != nil { | |||
| ctx.Handle(500, "setting.CollaborationPost(AddAccess)", err) | |||
| return | |||
| } | |||
| @@ -20,7 +20,7 @@ import ( | |||
| const ( | |||
| DASHBOARD base.TplName = "user/dashboard" | |||
| PROFILE base.TplName = "user/profile" | |||
| ISSUES base.TplName = "user/issue" | |||
| ISSUES base.TplName = "user/issues" | |||
| PULLS base.TplName = "user/pulls" | |||
| STARS base.TplName = "user/stars" | |||
| ) | |||
| @@ -29,6 +29,13 @@ func Dashboard(ctx *middleware.Context) { | |||
| ctx.Data["Title"] = "Dashboard" | |||
| ctx.Data["PageIsUserDashboard"] = true | |||
| if err := ctx.User.GetOrganizations(); err != nil { | |||
| ctx.Handle(500, "home.Dashboard(GetOrganizations)", err) | |||
| return | |||
| } | |||
| ctx.Data["Orgs"] = ctx.User.Orgs | |||
| ctx.Data["ContextUser"] = ctx.User | |||
| var err error | |||
| ctx.Data["MyRepos"], err = models.GetRepositories(ctx.User.Id, true) | |||
| if err != nil { | |||
| @@ -53,7 +60,7 @@ func Dashboard(ctx *middleware.Context) { | |||
| for _, act := range actions { | |||
| if act.IsPrivate { | |||
| if has, _ := models.HasAccess(ctx.User.Name, act.RepoUserName+"/"+act.RepoName, | |||
| models.AU_READABLE); !has { | |||
| models.READABLE); !has { | |||
| continue | |||
| } | |||
| } | |||
| @@ -131,7 +138,7 @@ func Feeds(ctx *middleware.Context, form auth.FeedsForm) { | |||
| for _, act := range actions { | |||
| if act.IsPrivate { | |||
| if has, _ := models.HasAccess(ctx.User.Name, act.RepoUserName+"/"+act.RepoName, | |||
| models.AU_READABLE); !has { | |||
| models.READABLE); !has { | |||
| continue | |||
| } | |||
| } | |||
| @@ -226,7 +226,7 @@ func SignUpPost(ctx *middleware.Context, form auth.RegisterForm) { | |||
| } | |||
| var err error | |||
| if u, err = models.RegisterUser(u); err != nil { | |||
| if u, err = models.CreateUser(u); err != nil { | |||
| switch err { | |||
| case models.ErrUserAlreadyExist: | |||
| ctx.Data["Err_UserName"] = true | |||
| @@ -235,13 +235,14 @@ func SignUpPost(ctx *middleware.Context, form auth.RegisterForm) { | |||
| ctx.Data["Err_Email"] = true | |||
| ctx.RenderWithErr("E-mail address has been already used", SIGNUP, &form) | |||
| case models.ErrUserNameIllegal: | |||
| ctx.Data["Err_UserName"] = true | |||
| ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), SIGNUP, &form) | |||
| default: | |||
| ctx.Handle(500, "user.SignUpPost(RegisterUser)", err) | |||
| ctx.Handle(500, "user.SignUpPost(CreateUser)", err) | |||
| } | |||
| return | |||
| } | |||
| log.Trace("%s User created: %s", ctx.Req.RequestURI, form.UserName) | |||
| log.Trace("%s User created: %s", ctx.Req.RequestURI, u.Name) | |||
| // Bind social account. | |||
| if isOauth { | |||
| @@ -1 +1 @@ | |||
| 0.4.5.0624 Alpha | |||
| 0.4.5.0625 Alpha | |||
| @@ -1,73 +0,0 @@ | |||
| {{template "base/head" .}} | |||
| {{template "base/navbar" .}} | |||
| <div id="body-nav"> | |||
| <div class="container"> | |||
| <div class="btn-group pull-left" id="dashboard-switch"> | |||
| <button type="button" class="btn btn-default"> | |||
| <img src="//1.gravatar.com/avatar/f72f7454ce9d710baa506394f68f4132?s=28" alt="user-avatar" title="username"> | |||
| gogits | |||
| </button> | |||
| <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown"> | |||
| <span class="caret"></span> | |||
| </button> | |||
| <div class="dropdown-menu clone-group-btn no-propagation"> | |||
| <ul id="dashboard-switch-menu" class="list-unstyled"> | |||
| <li class="checked"><a href="#"><i class="fa fa-check"></i> | |||
| <img src="//1.gravatar.com/avatar/f72f7454ce9d710baa506394f68f4132?s=28" alt="user-avatar" title="username"> | |||
| gogits/gogs</a> | |||
| </li> | |||
| </ul> | |||
| </div> | |||
| </div> | |||
| <ul class="nav nav-pills pull-right"> | |||
| <li class="active"><a href="/">Feed</a></li> | |||
| <li><a href="/issues">Issues</a></li> | |||
| <li><a href="#">Setting</a></li> | |||
| <!-- <li><a href="/pulls">Pull Requests</a></li> | |||
| <li><a href="/stars">Stars</a></li> --> | |||
| </ul> | |||
| <h3>News Feed</h3> | |||
| </div> | |||
| </div> | |||
| <div id="body" class="container" data-page="user"> | |||
| {{if .HasInfo}}<div class="alert alert-info">{{.InfoMsg}}</div>{{end}} | |||
| <div id="feed-left" class="col-md-8"> | |||
| <ul class="list-unstyled activity-list"> | |||
| {{range .Feeds}} | |||
| <li> | |||
| <i class="icon fa fa-{{ActionIcon .OpType}}"></i> | |||
| <div class="info"><span class="meta">{{TimeSince .Created}}</span><br>{{ActionDesc . | str2html}}</div> | |||
| <span class="clearfix"></span> | |||
| </li> | |||
| {{else}} | |||
| <li>Oh. Looks like there isn't any activity here yet. Get Busy!</li> | |||
| {{end}} | |||
| </ul> | |||
| </div> | |||
| <div id="feed-right" class="col-md-4"> | |||
| <div class="panel panel-default repo-panel"> | |||
| <div class="panel-heading">Repositories | |||
| <div class="btn-group pull-right" id="user-dashboard-repo-new"> | |||
| <button type="button" class="btn btn-success btn-sm dropdown-toggle" data-toggle="dropdown"><i class="fa fa-plus-square"></i>New</button> | |||
| <div class="dropdown-menu dropdown-menu-right"> | |||
| <ul class="list-unstyled"> | |||
| <li><a href="/repo/create"><i class="fa fa-book"></i>Repository</a></li> | |||
| <li><a href="/repo/migrate"><i class="fa fa-clipboard"></i>Migration</a></li> | |||
| <!-- <li><a href="#"><i class="fa fa-users"></i>Organization</a></li> --> | |||
| </ul> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="panel-body"> | |||
| <ul class="list-group">{{range .MyRepos}} | |||
| <li class="list-group-item"><a href="/{{$.SignedUserName}}/{{.Name}}"> | |||
| <!-- <span class="stars pull-right"><i class="fa fa-star"></i>{{.NumStars}}</span> --> | |||
| <i class="fa fa-book"></i>{{.Name}}{{if .IsPrivate}} <span class="label label-default">Private</span>{{end}}</a> | |||
| </li>{{end}} | |||
| </ul> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| {{template "base/footer" .}} | |||
| @@ -1,22 +1,14 @@ | |||
| {{template "base/head" .}} | |||
| {{template "base/navbar" .}} | |||
| <div class="container" id="body"> | |||
| <form action="/repo/create" method="post" class="form-horizontal card" id="org-create"> | |||
| <form action="/org/create" method="post" class="form-horizontal card" id="org-create"> | |||
| {{.CsrfTokenHtml}} | |||
| <h3>Create New Organization</h3> | |||
| {{template "base/alert" .}} | |||
| <div class="form-group"> | |||
| <label class="col-md-2 control-label">Owner<strong class="text-danger">*</strong></label> | |||
| <div class="col-md-8"> | |||
| <p class="form-control-static">{{.SignedUserName}}</p> | |||
| <input type="hidden" value="{{.SignedUserId}}" name="userId"/> | |||
| </div> | |||
| </div> | |||
| <div class="form-group {{if .Err_RepoName}}has-error has-feedback{{end}}"> | |||
| <div class="form-group {{if .Err_OrgName}}has-error has-feedback{{end}}"> | |||
| <label class="col-md-2 control-label">Organization<strong class="text-danger">*</strong></label> | |||
| <div class="col-md-8"> | |||
| <input name="repo" type="text" class="form-control" placeholder="Type your repository name" value="{{.repo}}" required="required"> | |||
| <input name="orgname" type="text" class="form-control" placeholder="Type your organization name" value="{{.orgname}}" required="required"> | |||
| <span class="help-block">Great organization names are short and memorable. </span> | |||
| </div> | |||
| </div> | |||
| @@ -24,18 +16,10 @@ | |||
| <div class="form-group {{if .Err_Email}}has-error has-feedback{{end}}"> | |||
| <label class="col-md-2 control-label">Email<strong class="text-danger">*</strong></label> | |||
| <div class="col-md-8"> | |||
| <input name="email" type="text" class="form-control" placeholder="Type organization's email" value="" required="required"> | |||
| <input name="email" type="text" class="form-control" placeholder="Type organization's email" value="{{.email}}" required="required"> | |||
| <span class="help-block">Organization's Email receives all notifications and confirmations.</span> | |||
| </div> | |||
| </div> | |||
| <!-- | |||
| <div class="form-group"> | |||
| <label class="col-md-2 control-label">Owners<strong class="text-danger">*</strong></label> | |||
| <div class="col-md-8"> | |||
| owners | |||
| </div> | |||
| </div>--> | |||
| <div class="form-group"> | |||
| <div class="col-md-offset-2 col-md-8"> | |||
| @@ -4,29 +4,46 @@ | |||
| <div class="container"> | |||
| <div class="btn-group pull-left" id="dashboard-switch"> | |||
| <button type="button" class="btn btn-default"> | |||
| <img src="//1.gravatar.com/avatar/f72f7454ce9d710baa506394f68f4132?s=28" alt="user-avatar" title="username"> | |||
| fuxiaohei | |||
| <img src="{{.ContextUser.AvatarLink}}?s=28" alt="user-avatar" title="username"> | |||
| {{.ContextUser.Name}} | |||
| </button> | |||
| <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown"> | |||
| <span class="caret"></span> | |||
| </button> | |||
| <div class="dropdown-menu clone-group-btn no-propagation"> | |||
| <ul id="dashboard-switch-menu" class="list-unstyled"> | |||
| <li class="checked"><a href="#"><i class="fa fa-check"></i> | |||
| <img src="//1.gravatar.com/avatar/f72f7454ce9d710baa506394f68f4132?s=28" alt="user-avatar" title="username"> | |||
| gogits/gogs</a></li> | |||
| <li{{if not .PageIsOrgDashboard}} class="checked"{{end}}> | |||
| <a href="/"> | |||
| <i class="fa fa-check"></i> | |||
| <img src="{{.SignedUser.AvatarLink}}?s=28" alt="user-avatar" title="username"> | |||
| {{.SignedUser.Name}} | |||
| </a> | |||
| </li> | |||
| {{range .Orgs}} | |||
| <li{{if eq $.ContextUser.Id .Id}} class="checked"{{end}}> | |||
| <a href="/org/{{.Name}}/dashboard"> | |||
| <i class="fa fa-check"></i> | |||
| <img src="{{.AvatarLink}}?s=28" alt="user-avatar" title="username"> | |||
| {{.Name}} | |||
| </a> | |||
| </li> | |||
| {{end}} | |||
| <li> | |||
| <a href="/org/create">Create organization</a> | |||
| </li> | |||
| </ul> | |||
| </div> | |||
| </div> | |||
| <ul class="nav nav-pills pull-right"> | |||
| <li class="active"><a href="/">Feed</a></li> | |||
| <li><a href="/issues">Issues</a></li> | |||
| <li class="active"><a href="/{{if .PageIsOrgDashboard}}org/{{.ContextUser.Name}}/dashboard{{end}}">News Feed</a></li> | |||
| <li><a href="/{{if .PageIsOrgDashboard}}org/{{.ContextUser.Name}}/dashboard/{{end}}issues">Issues</a></li> | |||
| {{if .PageIsOrgDashboard}}<li><a href="/org/{{.ContextUser.Name}}/settings">Settings</a></li>{{end}} | |||
| <!-- <li><a href="/pulls">Pull Requests</a></li> | |||
| <li><a href="/stars">Stars</a></li> --> | |||
| </ul> | |||
| <h3>News Feed</h3> | |||
| </div> | |||
| </div> | |||
| <div id="body" class="container" data-page="user"> | |||
| {{if .HasInfo}}<div class="alert alert-info">{{.InfoMsg}}</div>{{end}} | |||
| <div id="feed-left" class="col-md-8"> | |||
| @@ -44,7 +61,7 @@ | |||
| </div> | |||
| <div id="feed-right" class="col-md-4"> | |||
| <div class="panel panel-default repo-panel"> | |||
| <div class="panel-heading">Your Repositories | |||
| <div class="panel-heading">{{if not .PageIsOrgDashboard}}Your {{end}}Repositories | |||
| <div class="btn-group pull-right" id="user-dashboard-repo-new"> | |||
| <button type="button" class="btn btn-success btn-sm dropdown-toggle" data-toggle="dropdown"><i class="fa fa-plus-square"></i>New</button> | |||
| <div class="dropdown-menu dropdown-menu-right"> | |||
| @@ -66,7 +83,8 @@ | |||
| </ul> | |||
| </div> | |||
| </div> | |||
| {{if not .PageIsOrgDashboard}} | |||
| <div class="panel panel-default repo-panel"> | |||
| <div class="panel-heading">Collaborative Repositories</div> | |||
| <div class="panel-body"> | |||
| @@ -78,6 +96,7 @@ | |||
| </ul> | |||
| </div> | |||
| </div> | |||
| {{end}} | |||
| </div> | |||
| </div> | |||
| {{template "base/footer" .}} | |||
| @@ -3,7 +3,7 @@ | |||
| <div id="body-nav"> | |||
| <div class="container"> | |||
| <ul class="nav nav-pills pull-right"> | |||
| <li><a href="/">Feed</a></li> | |||
| <li><a href="/">News Feed</a></li> | |||
| <li class="active"><a href="/issues">Issues</a></li> | |||
| <!-- <li><a href="/pulls">Pull Requests</a></li> | |||
| <li><a href="/stars">Stars</a></li> --> | |||
| @@ -11,6 +11,7 @@ | |||
| <h3>Your Issues</h3> | |||
| </div> | |||
| </div> | |||
| <div id="body" class="container" data-page="user"> | |||
| {{if .HasInfo}}<div class="alert alert-info">{{.InfoMsg}}</div>{{end}} | |||
| <div id="issue"> | |||