#1193 Make organization emails non-mandatory

This commit is contained in:
Unknwon 2015-09-06 10:08:14 -04:00
parent aa67de910a
commit 80701d45bb
11 changed files with 135 additions and 164 deletions

View file

@ -630,7 +630,6 @@ release.tag_name_already_exist = Release with this tag name has already existed.
[org] [org]
org_name_holder = Organization Name org_name_holder = Organization Name
org_name_helper = Great organization names are short and memorable. org_name_helper = Great organization names are short and memorable.
org_email_helper = Organization's E-mail receives all notifications and confirmations.
create_org = Create Organization create_org = Create Organization
repo_updated = Updated repo_updated = Updated
people = People people = People

View file

@ -9,8 +9,6 @@ import (
"fmt" "fmt"
"os" "os"
"strings" "strings"
"github.com/gogits/gogs/modules/base"
) )
var ( var (
@ -93,17 +91,6 @@ func (org *User) RemoveOrgRepo(repoID int64) error {
return org.removeOrgRepo(x, repoID) return org.removeOrgRepo(x, repoID)
} }
// IsOrgEmailUsed returns true if the e-mail has been used in organization account.
func IsOrgEmailUsed(email string) (bool, error) {
if len(email) == 0 {
return false, nil
}
return x.Get(&User{
Email: email,
Type: ORGANIZATION,
})
}
// CreateOrganization creates record of a new organization. // CreateOrganization creates record of a new organization.
func CreateOrganization(org, owner *User) (err error) { func CreateOrganization(org, owner *User) (err error) {
if err = IsUsableName(org.Name); err != nil { if err = IsUsableName(org.Name); err != nil {
@ -117,18 +104,9 @@ func CreateOrganization(org, owner *User) (err error) {
return ErrUserAlreadyExist{org.Name} return ErrUserAlreadyExist{org.Name}
} }
isExist, err = IsOrgEmailUsed(org.Email)
if err != nil {
return err
} else if isExist {
return ErrEmailAlreadyUsed{org.Email}
}
org.LowerName = strings.ToLower(org.Name) org.LowerName = strings.ToLower(org.Name)
org.FullName = org.Name org.FullName = org.Name
org.Avatar = base.EncodeMd5(org.Email) org.UseCustomAvatar = true
org.AvatarEmail = org.Email
// No password for organization.
org.NumTeams = 1 org.NumTeams = 1
org.NumMembers = 1 org.NumMembers = 1
@ -141,6 +119,17 @@ func CreateOrganization(org, owner *User) (err error) {
if _, err = sess.Insert(org); err != nil { if _, err = sess.Insert(org); err != nil {
return fmt.Errorf("insert organization: %v", err) return fmt.Errorf("insert organization: %v", err)
} }
org.GenerateRandomAvatar()
// Add initial creator to organization and owner team.
if _, err = sess.Insert(&OrgUser{
Uid: owner.Id,
OrgID: org.Id,
IsOwner: true,
NumTeams: 1,
}); err != nil {
return fmt.Errorf("insert org-user relation: %v", err)
}
// Create default owner team. // Create default owner team.
t := &Team{ t := &Team{
@ -154,23 +143,11 @@ func CreateOrganization(org, owner *User) (err error) {
return fmt.Errorf("insert owner team: %v", err) return fmt.Errorf("insert owner team: %v", err)
} }
// Add initial creator to organization and owner team. if _, err = sess.Insert(&TeamUser{
ou := &OrgUser{
Uid: owner.Id,
OrgID: org.Id,
IsOwner: true,
NumTeams: 1,
}
if _, err = sess.Insert(ou); err != nil {
return fmt.Errorf("insert org-user relation: %v", err)
}
tu := &TeamUser{
Uid: owner.Id, Uid: owner.Id,
OrgID: org.Id, OrgID: org.Id,
TeamID: t.ID, TeamID: t.ID,
} }); err != nil {
if _, err = sess.Insert(tu); err != nil {
return fmt.Errorf("insert team-user relation: %v", err) return fmt.Errorf("insert team-user relation: %v", err)
} }

View file

@ -55,12 +55,12 @@ type User struct {
Name string `xorm:"UNIQUE NOT NULL"` Name string `xorm:"UNIQUE NOT NULL"`
FullName string FullName string
// Email is the primary email address (to be used for communication). // Email is the primary email address (to be used for communication).
Email string `xorm:"UNIQUE(s) NOT NULL"` Email string `xorm:"NOT NULL"`
Passwd string `xorm:"NOT NULL"` Passwd string `xorm:"NOT NULL"`
LoginType LoginType LoginType LoginType
LoginSource int64 `xorm:"NOT NULL DEFAULT 0"` LoginSource int64 `xorm:"NOT NULL DEFAULT 0"`
LoginName string LoginName string
Type UserType `xorm:"UNIQUE(s)"` Type UserType
Orgs []*User `xorm:"-"` Orgs []*User `xorm:"-"`
Repos []*Repository `xorm:"-"` Repos []*Repository `xorm:"-"`
Location string Location string
@ -132,42 +132,56 @@ func (u *User) HomeLink() string {
return setting.AppSubUrl + "/" + u.Name return setting.AppSubUrl + "/" + u.Name
} }
// CustomAvatarPath returns user custom avatar file path.
func (u *User) CustomAvatarPath() string {
return filepath.Join(setting.AvatarUploadPath, com.ToStr(u.Id))
}
// GenerateRandomAvatar generates a random avatar for user.
func (u *User) GenerateRandomAvatar() error {
seed := u.Email
if len(seed) == 0 {
seed = u.Name
}
img, err := avatar.RandomImage([]byte(seed))
if err != nil {
return fmt.Errorf("RandomImage: %v", err)
}
if err = os.MkdirAll(path.Dir(u.CustomAvatarPath()), os.ModePerm); err != nil {
return fmt.Errorf("MkdirAll: %v", err)
}
fw, err := os.Create(u.CustomAvatarPath())
if err != nil {
return fmt.Errorf("Create: %v", err)
}
defer fw.Close()
if err = jpeg.Encode(fw, img, nil); err != nil {
return fmt.Errorf("Encode: %v", err)
}
log.Info("New random avatar created: %d", u.Id)
return nil
}
func (u *User) RelAvatarLink() string { func (u *User) RelAvatarLink() string {
defaultImgUrl := "/img/avatar_default.jpg" defaultImgUrl := "/img/avatar_default.jpg"
if u.Id == -1 { if u.Id == -1 {
return defaultImgUrl return defaultImgUrl
} }
imgPath := path.Join(setting.AvatarUploadPath, com.ToStr(u.Id))
switch { switch {
case u.UseCustomAvatar: case u.UseCustomAvatar:
if !com.IsExist(imgPath) { if !com.IsExist(u.CustomAvatarPath()) {
return defaultImgUrl return defaultImgUrl
} }
return "/avatars/" + com.ToStr(u.Id) return "/avatars/" + com.ToStr(u.Id)
case setting.DisableGravatar, setting.OfflineMode: case setting.DisableGravatar, setting.OfflineMode:
if !com.IsExist(imgPath) { if !com.IsExist(u.CustomAvatarPath()) {
img, err := avatar.RandomImage([]byte(u.Email)) if err := u.GenerateRandomAvatar(); err != nil {
if err != nil { log.Error(3, "GenerateRandomAvatar: %v", err)
log.Error(3, "RandomImage: %v", err)
return defaultImgUrl
} }
if err = os.MkdirAll(path.Dir(imgPath), os.ModePerm); err != nil {
log.Error(3, "MkdirAll: %v", err)
return defaultImgUrl
}
fw, err := os.Create(imgPath)
if err != nil {
log.Error(3, "Create: %v", err)
return defaultImgUrl
}
defer fw.Close()
if err = jpeg.Encode(fw, img, nil); err != nil {
log.Error(3, "Encode: %v", err)
return defaultImgUrl
}
log.Info("New random avatar created: %d", u.Id)
} }
return "/avatars/" + com.ToStr(u.Id) return "/avatars/" + com.ToStr(u.Id)
@ -208,11 +222,6 @@ func (u *User) ValidatePassword(passwd string) bool {
return u.Passwd == newUser.Passwd return u.Passwd == newUser.Passwd
} }
// CustomAvatarPath returns user custom avatar file path.
func (u *User) CustomAvatarPath() string {
return filepath.Join(setting.AvatarUploadPath, com.ToStr(u.Id))
}
// UploadAvatar saves custom avatar for user. // UploadAvatar saves custom avatar for user.
// FIXME: split uploads to different subdirs in case we have massive users. // FIXME: split uploads to different subdirs in case we have massive users.
func (u *User) UploadAvatar(data []byte) error { func (u *User) UploadAvatar(data []byte) error {
@ -494,6 +503,8 @@ func ChangeUserName(u *User, newUserName string) (err error) {
} }
func updateUser(e Engine, u *User) error { func updateUser(e Engine, u *User) error {
// Organization does not need e-mail.
if !u.IsOrganization() {
u.Email = strings.ToLower(u.Email) u.Email = strings.ToLower(u.Email)
has, err := e.Where("id!=?", u.Id).And("type=?", u.Type).And("email=?", u.Email).Get(new(User)) has, err := e.Where("id!=?", u.Id).And("type=?", u.Type).And("email=?", u.Email).Get(new(User))
if err != nil { if err != nil {
@ -502,6 +513,12 @@ func updateUser(e Engine, u *User) error {
return ErrEmailAlreadyUsed{u.Email} return ErrEmailAlreadyUsed{u.Email}
} }
if len(u.AvatarEmail) == 0 {
u.AvatarEmail = u.Email
}
u.Avatar = avatar.HashEmail(u.AvatarEmail)
}
u.LowerName = strings.ToLower(u.Name) u.LowerName = strings.ToLower(u.Name)
if len(u.Location) > 255 { if len(u.Location) > 255 {
@ -514,13 +531,8 @@ func updateUser(e Engine, u *User) error {
u.Description = u.Description[:255] u.Description = u.Description[:255]
} }
if u.AvatarEmail == "" {
u.AvatarEmail = u.Email
}
u.Avatar = avatar.HashEmail(u.AvatarEmail)
u.FullName = base.Sanitizer.Sanitize(u.FullName) u.FullName = base.Sanitizer.Sanitize(u.FullName)
_, err = e.Id(u.Id).AllCols().Update(u) _, err := e.Id(u.Id).AllCols().Update(u)
return err return err
} }

View file

@ -17,8 +17,7 @@ import (
// \/ /_____/ \/ \/ \/ \/ \/ // \/ /_____/ \/ \/ \/ \/ \/
type CreateOrgForm struct { type CreateOrgForm struct {
OrgName string `form:"org_name" binding:"Required;AlphaDashDot;MaxSize(30)"` OrgName string `binding:"Required;AlphaDashDot;MaxSize(30)"`
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
} }
func (f *CreateOrgForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *CreateOrgForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
@ -28,11 +27,9 @@ func (f *CreateOrgForm) Validate(ctx *macaron.Context, errs binding.Errors) bind
type UpdateOrgSettingForm struct { type UpdateOrgSettingForm struct {
OrgUserName string `form:"uname" binding:"Required;AlphaDashDot;MaxSize(30)" locale:"org.org_name_holder"` OrgUserName string `form:"uname" binding:"Required;AlphaDashDot;MaxSize(30)" locale:"org.org_name_holder"`
OrgFullName string `form:"fullname" binding:"MaxSize(100)"` OrgFullName string `form:"fullname" binding:"MaxSize(100)"`
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
Description string `form:"desc" binding:"MaxSize(255)"` Description string `form:"desc" binding:"MaxSize(255)"`
Website string `form:"website" binding:"Url;MaxSize(100)"` Website string `form:"website" binding:"Url;MaxSize(100)"`
Location string `form:"location" binding:"MaxSize(50)"` Location string `form:"location" binding:"MaxSize(50)"`
Avatar string `form:"avatar" binding:"Required;Email;MaxSize(50)"`
} }
func (f *UpdateOrgSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *UpdateOrgSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {

File diff suppressed because one or more lines are too long

View file

@ -17,11 +17,8 @@
} }
} }
.repository { @create-page-form-input-padding: 250px !important;
@input-padding: 250px !important; #create-page-form {
&.new.repo,
&.new.migrate,
&.new.fork {
form { form {
margin: auto; margin: auto;
width: 800px!important; width: 800px!important;
@ -29,16 +26,32 @@
text-align: center; text-align: center;
} }
.header { .header {
padding-left: @input-padding+30px; padding-left: @create-page-form-input-padding+30px;
} }
.inline.field > label { .inline.field > label {
text-align: right; text-align: right;
width: @input-padding; width: @create-page-form-input-padding;
word-wrap: break-word; word-wrap: break-word;
} }
.help { .help {
margin-left: @input-padding+15px; margin-left: @create-page-form-input-padding+15px;
} }
.optional .title {
margin-left: @create-page-form-input-padding;
}
input,
textarea {
width: 50%!important;
}
}
}
.repository {
&.new.repo,
&.new.migrate,
&.new.fork {
#create-page-form;
form {
.dropdown { .dropdown {
.dropdown.icon { .dropdown.icon {
margin-top: -7px!important; margin-top: -7px!important;
@ -50,13 +63,6 @@
} }
} }
} }
.optional .title {
margin-left: @input-padding;
}
input,
textarea {
width: 50%!important;
}
} }
} }
@ -66,7 +72,7 @@
width: 50%!important; width: 50%!important;
} }
#auto-init { #auto-init {
margin-left: @input-padding+15px; margin-left: @create-page-form-input-padding+15px;
} }
} }
} }

View file

@ -14,4 +14,8 @@
} }
} }
} }
&.new.org {
#create-page-form;
}
} }

View file

@ -59,25 +59,18 @@ func CreatePost(ctx *middleware.Context, form auth.CreateOrgForm) {
org := &models.User{ org := &models.User{
Name: form.OrgName, Name: form.OrgName,
Email: form.Email,
IsActive: true, IsActive: true,
Type: models.ORGANIZATION, Type: models.ORGANIZATION,
} }
var err error if err := models.CreateOrganization(org, ctx.User); err != nil {
if err = models.CreateOrganization(org, ctx.User); err != nil { ctx.Data["Err_OrgName"] = true
switch { switch {
case models.IsErrUserAlreadyExist(err): case models.IsErrUserAlreadyExist(err):
ctx.Data["Err_OrgName"] = true
ctx.RenderWithErr(ctx.Tr("form.org_name_been_taken"), CREATE, &form) ctx.RenderWithErr(ctx.Tr("form.org_name_been_taken"), CREATE, &form)
case models.IsErrEmailAlreadyUsed(err):
ctx.Data["Err_Email"] = true
ctx.RenderWithErr(ctx.Tr("form.email_been_used"), CREATE, &form)
case models.IsErrNameReserved(err): case models.IsErrNameReserved(err):
ctx.Data["Err_OrgName"] = true
ctx.RenderWithErr(ctx.Tr("org.form.name_reserved", err.(models.ErrNameReserved).Name), CREATE, &form) ctx.RenderWithErr(ctx.Tr("org.form.name_reserved", err.(models.ErrNameReserved).Name), CREATE, &form)
case models.IsErrNamePatternNotAllowed(err): case models.IsErrNamePatternNotAllowed(err):
ctx.Data["Err_OrgName"] = true
ctx.RenderWithErr(ctx.Tr("org.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), CREATE, &form) ctx.RenderWithErr(ctx.Tr("org.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), CREATE, &form)
default: default:
ctx.Handle(500, "CreateOrganization", err) ctx.Handle(500, "CreateOrganization", err)

View file

@ -61,19 +61,11 @@ func SettingsPost(ctx *middleware.Context, form auth.UpdateOrgSettingForm) {
} }
org.FullName = form.OrgFullName org.FullName = form.OrgFullName
org.Email = form.Email
org.Description = form.Description org.Description = form.Description
org.Website = form.Website org.Website = form.Website
org.Location = form.Location org.Location = form.Location
org.Avatar = base.EncodeMd5(form.Avatar)
org.AvatarEmail = form.Avatar
if err := models.UpdateUser(org); err != nil { if err := models.UpdateUser(org); err != nil {
if models.IsErrEmailAlreadyUsed(err) {
ctx.Data["Err_Email"] = true
ctx.RenderWithErr(ctx.Tr("form.email_been_used"), SETTINGS_OPTIONS, &form)
} else {
ctx.Handle(500, "UpdateUser", err) ctx.Handle(500, "UpdateUser", err)
}
return return
} }
log.Trace("Organization setting updated: %s", org.Name) log.Trace("Organization setting updated: %s", org.Name)

View file

@ -1,31 +1,30 @@
{{template "ng/base/head" .}} {{template "base/head" .}}
{{template "ng/base/header" .}} <div class="organization new org">
<div id="sign-wrapper"> <div class="ui middle very relaxed page grid">
<form class="form-align form panel sign-panel sign-form container panel-radius" id="sign-up-form" action="{{AppSubUrl}}/org/create" method="post"> <div class="column">
<form class="ui form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="panel-header"> <h3 class="ui top attached header">
<h2>{{.i18n.Tr "new_org"}}</h2> {{.i18n.Tr "new_org"}}
</div> </h3>
<div class="panel-content"> <div class="ui attached segment">
{{template "ng/base/alert" .}} {{template "base/alert" .}}
<div class="field"> <div class="inline required field {{if .Err_OrgName}}error{{end}}">
<label class="req" for="org_name">{{.i18n.Tr "org.org_name_holder"}}</label> <label for="org_name">{{.i18n.Tr "org.org_name_holder"}}</label>
<input class="ipt ipt-large ipt-radius {{if .Err_OrgName}}ipt-error{{end}}" id="org_name" name="org_name" type="text" value="{{.org_name}}" required/> <input id="org_name" name="org_name" value="{{.org_name}}" autofocus required>
<label></label>
<span class="help">{{.i18n.Tr "org.org_name_helper"}}</span> <span class="help">{{.i18n.Tr "org.org_name_helper"}}</span>
</div> </div>
<div class="field">
<label class="req" for="email">{{.i18n.Tr "email"}}</label> <div class="inline field">
<input class="ipt ipt-large ipt-radius {{if .Err_Email}}ipt-error{{end}}" id="email" name="email" type="email" value="{{.email}}" required/>
<label></label> <label></label>
<span class="help">{{.i18n.Tr "org.org_email_helper"}}</span> <button class="ui green button">
</div> {{.i18n.Tr "org.create_org"}}
<div class="field"> </button>
<span class="form-label"></span> <a class="ui button" href="{{AppSubUrl}}/">{{.i18n.Tr "cancel"}}</a>
<button class="btn btn-large btn-blue btn-radius">{{.i18n.Tr "org.create_org"}}</button>
<a class="btn btn-small btn-gray btn-radius" id="repo-create-cancel" href="{{AppSubUrl}}/"><strong>{{.i18n.Tr "cancel"}}</strong></a>
</div> </div>
</div> </div>
</form> </form>
</div>
</div>
</div> </div>
{{template "ng/base/footer" .}} {{template "base/footer" .}}

View file

@ -30,10 +30,6 @@
<label for="full-name">{{.i18n.Tr "org.settings.full_name"}}</label> <label for="full-name">{{.i18n.Tr "org.settings.full_name"}}</label>
<input class="ipt ipt-large ipt-radius {{if .Err_FullName}}ipt-error{{end}}" id="full-name" name="fullname" value="{{.Org.FullName}}" /> <input class="ipt ipt-large ipt-radius {{if .Err_FullName}}ipt-error{{end}}" id="full-name" name="fullname" value="{{.Org.FullName}}" />
</div> </div>
<div class="field">
<label class="req" for="email">{{.i18n.Tr "email"}}</label>
<input class="ipt ipt-large ipt-radius {{if .Err_Email}}ipt-error{{end}}" id="email" name="email" type="email" value="{{.Org.Email}}" required />
</div>
<div class="field clear"> <div class="field clear">
<label class="left" for="desc">{{.i18n.Tr "org.org_desc"}}</label> <label class="left" for="desc">{{.i18n.Tr "org.org_desc"}}</label>
<textarea class="ipt ipt-large ipt-radius {{if .Err_Description}}ipt-error{{end}}" id="desc" name="desc">{{.Org.Description}}</textarea> <textarea class="ipt ipt-large ipt-radius {{if .Err_Description}}ipt-error{{end}}" id="desc" name="desc">{{.Org.Description}}</textarea>
@ -46,10 +42,6 @@
<label for="location">{{.i18n.Tr "org.settings.location"}}</label> <label for="location">{{.i18n.Tr "org.settings.location"}}</label>
<input class="ipt ipt-large ipt-radius {{if .Err_Location}}ipt-error{{end}}" id="location" name="location" type="text" value="{{.Org.Location}}" /> <input class="ipt ipt-large ipt-radius {{if .Err_Location}}ipt-error{{end}}" id="location" name="location" type="text" value="{{.Org.Location}}" />
</div> </div>
<div class="field {{if DisableGravatar}}hide{{end}}">
<label class="req" for="gravatar-email">Gravatar {{.i18n.Tr "email"}}</label>
<input class="ipt ipt-large ipt-radius {{if .Err_Avatar}}ipt-error{{end}}" id="gravatar-email" name="avatar" type="text" value="{{.Org.AvatarEmail}}" />
</div>
<div class="field"> <div class="field">
<span class="form-label"></span> <span class="form-label"></span>
<button class="btn btn-green btn-large btn-radius" id="change-orgname-btn" href="#change-orgname-modal">{{.i18n.Tr "org.settings.update_settings"}}</button> <button class="btn btn-green btn-large btn-radius" id="change-orgname-btn" href="#change-orgname-modal">{{.i18n.Tr "org.settings.update_settings"}}</button>