#1193 Make organization emails non-mandatory

tokarchuk/v1.17
Unknwon 9 years ago
parent aa67de910a
commit 80701d45bb
  1. 1
      conf/locale/locale_en-US.ini
  2. 51
      models/org.go
  3. 96
      models/user.go
  4. 5
      modules/auth/org.go
  5. 2
      public/css/gogs.min.css
  6. 56
      public/less/_form.less
  7. 4
      public/less/_organization.less
  8. 11
      routers/org/org.go
  9. 10
      routers/org/setting.go
  10. 55
      templates/org/create.tmpl
  11. 8
      templates/org/settings/options.tmpl

@ -630,7 +630,6 @@ release.tag_name_already_exist = Release with this tag name has already existed.
[org]
org_name_holder = Organization Name
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
repo_updated = Updated
people = People

@ -9,8 +9,6 @@ import (
"fmt"
"os"
"strings"
"github.com/gogits/gogs/modules/base"
)
var (
@ -93,17 +91,6 @@ func (org *User) RemoveOrgRepo(repoID int64) error {
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.
func CreateOrganization(org, owner *User) (err error) {
if err = IsUsableName(org.Name); err != nil {
@ -117,18 +104,9 @@ func CreateOrganization(org, owner *User) (err error) {
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.FullName = org.Name
org.Avatar = base.EncodeMd5(org.Email)
org.AvatarEmail = org.Email
// No password for organization.
org.UseCustomAvatar = true
org.NumTeams = 1
org.NumMembers = 1
@ -141,6 +119,17 @@ func CreateOrganization(org, owner *User) (err error) {
if _, err = sess.Insert(org); err != nil {
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.
t := &Team{
@ -154,23 +143,11 @@ func CreateOrganization(org, owner *User) (err error) {
return fmt.Errorf("insert owner team: %v", err)
}
// Add initial creator to organization and owner team.
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{
if _, err = sess.Insert(&TeamUser{
Uid: owner.Id,
OrgID: org.Id,
TeamID: t.ID,
}
if _, err = sess.Insert(tu); err != nil {
}); err != nil {
return fmt.Errorf("insert team-user relation: %v", err)
}

@ -55,12 +55,12 @@ type User struct {
Name string `xorm:"UNIQUE NOT NULL"`
FullName string
// 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"`
LoginType LoginType
LoginSource int64 `xorm:"NOT NULL DEFAULT 0"`
LoginName string
Type UserType `xorm:"UNIQUE(s)"`
Type UserType
Orgs []*User `xorm:"-"`
Repos []*Repository `xorm:"-"`
Location string
@ -132,42 +132,56 @@ func (u *User) HomeLink() string {
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 {
defaultImgUrl := "/img/avatar_default.jpg"
if u.Id == -1 {
return defaultImgUrl
}
imgPath := path.Join(setting.AvatarUploadPath, com.ToStr(u.Id))
switch {
case u.UseCustomAvatar:
if !com.IsExist(imgPath) {
if !com.IsExist(u.CustomAvatarPath()) {
return defaultImgUrl
}
return "/avatars/" + com.ToStr(u.Id)
case setting.DisableGravatar, setting.OfflineMode:
if !com.IsExist(imgPath) {
img, err := avatar.RandomImage([]byte(u.Email))
if err != nil {
log.Error(3, "RandomImage: %v", err)
return defaultImgUrl
if !com.IsExist(u.CustomAvatarPath()) {
if err := u.GenerateRandomAvatar(); err != nil {
log.Error(3, "GenerateRandomAvatar: %v", err)
}
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)
@ -208,11 +222,6 @@ func (u *User) ValidatePassword(passwd string) bool {
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.
// FIXME: split uploads to different subdirs in case we have massive users.
func (u *User) UploadAvatar(data []byte) error {
@ -494,12 +503,20 @@ func ChangeUserName(u *User, newUserName string) (err error) {
}
func updateUser(e Engine, u *User) error {
u.Email = strings.ToLower(u.Email)
has, err := e.Where("id!=?", u.Id).And("type=?", u.Type).And("email=?", u.Email).Get(new(User))
if err != nil {
return err
} else if has {
return ErrEmailAlreadyUsed{u.Email}
// Organization does not need e-mail.
if !u.IsOrganization() {
u.Email = strings.ToLower(u.Email)
has, err := e.Where("id!=?", u.Id).And("type=?", u.Type).And("email=?", u.Email).Get(new(User))
if err != nil {
return err
} else if has {
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)
@ -514,13 +531,8 @@ func updateUser(e Engine, u *User) error {
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)
_, err = e.Id(u.Id).AllCols().Update(u)
_, err := e.Id(u.Id).AllCols().Update(u)
return err
}

@ -17,8 +17,7 @@ import (
// \/ /_____/ \/ \/ \/ \/ \/
type CreateOrgForm struct {
OrgName string `form:"org_name" binding:"Required;AlphaDashDot;MaxSize(30)"`
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
OrgName string `binding:"Required;AlphaDashDot;MaxSize(30)"`
}
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 {
OrgUserName string `form:"uname" binding:"Required;AlphaDashDot;MaxSize(30)" locale:"org.org_name_holder"`
OrgFullName string `form:"fullname" binding:"MaxSize(100)"`
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
Description string `form:"desc" binding:"MaxSize(255)"`
Website string `form:"website" binding:"Url;MaxSize(100)"`
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 {

File diff suppressed because one or more lines are too long

@ -17,28 +17,41 @@
}
}
@create-page-form-input-padding: 250px !important;
#create-page-form {
form {
margin: auto;
width: 800px!important;
.ui.message {
text-align: center;
}
.header {
padding-left: @create-page-form-input-padding+30px;
}
.inline.field > label {
text-align: right;
width: @create-page-form-input-padding;
word-wrap: break-word;
}
.help {
margin-left: @create-page-form-input-padding+15px;
}
.optional .title {
margin-left: @create-page-form-input-padding;
}
input,
textarea {
width: 50%!important;
}
}
}
.repository {
@input-padding: 250px !important;
&.new.repo,
&.new.migrate,
&.new.fork {
#create-page-form;
form {
margin: auto;
width: 800px!important;
.ui.message {
text-align: center;
}
.header {
padding-left: @input-padding+30px;
}
.inline.field > label {
text-align: right;
width: @input-padding;
word-wrap: break-word;
}
.help {
margin-left: @input-padding+15px;
}
.dropdown {
.dropdown.icon {
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;
}
#auto-init {
margin-left: @input-padding+15px;
margin-left: @create-page-form-input-padding+15px;
}
}
}

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

@ -59,25 +59,18 @@ func CreatePost(ctx *middleware.Context, form auth.CreateOrgForm) {
org := &models.User{
Name: form.OrgName,
Email: form.Email,
IsActive: true,
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 {
case models.IsErrUserAlreadyExist(err):
ctx.Data["Err_OrgName"] = true
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):
ctx.Data["Err_OrgName"] = true
ctx.RenderWithErr(ctx.Tr("org.form.name_reserved", err.(models.ErrNameReserved).Name), CREATE, &form)
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)
default:
ctx.Handle(500, "CreateOrganization", err)

@ -61,19 +61,11 @@ func SettingsPost(ctx *middleware.Context, form auth.UpdateOrgSettingForm) {
}
org.FullName = form.OrgFullName
org.Email = form.Email
org.Description = form.Description
org.Website = form.Website
org.Location = form.Location
org.Avatar = base.EncodeMd5(form.Avatar)
org.AvatarEmail = form.Avatar
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
}
log.Trace("Organization setting updated: %s", org.Name)

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

@ -30,10 +30,6 @@
<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}}" />
</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">
<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>
@ -46,10 +42,6 @@
<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}}" />
</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">
<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>

Loading…
Cancel
Save