浏览代码

refactor(db): move some methods from `user.go` to `users.go` (#7195)

Joe Chen 2 年之前
父节点
当前提交
c502dc6ed8

+ 2 - 0
Taskfile.yml

@@ -5,6 +5,8 @@ vars:
     sh: echo '{{if eq OS "windows"}}.exe{{end}}'
 
 tasks:
+  default:
+    deps: [build]
   web:
     desc: Build the binary and start the web server
     deps: [build]

+ 1 - 1
internal/cmd/web.go

@@ -99,7 +99,7 @@ func newMacaron() *macaron.Macaron {
 		conf.Picture.AvatarUploadPath,
 		macaron.StaticOptions{
 			ETag:        true,
-			Prefix:      db.USER_AVATAR_URL_PREFIX,
+			Prefix:      conf.UsersAvatarURLPath,
 			SkipLogging: conf.Server.DisableRouterLog,
 		},
 	))

+ 8 - 0
internal/conf/mocks.go

@@ -16,6 +16,14 @@ func SetMockApp(t *testing.T, opts AppOpts) {
 	})
 }
 
+func SetMockAuth(t *testing.T, otps AuthOpts) {
+	before := Auth
+	Auth = otps
+	t.Cleanup(func() {
+		Auth = before
+	})
+}
+
 func SetMockServer(t *testing.T, opts ServerOpts) {
 	before := Server
 	Server = opts

+ 22 - 14
internal/conf/static.go

@@ -12,6 +12,12 @@ import (
 	"github.com/gogs/go-libravatar"
 )
 
+const (
+	// UsersAvatarURLPath is used to identify whether a URL is to access user
+	// avatars.
+	UsersAvatarURLPath = "avatars"
+)
+
 // ℹ️ README: This file contains static values that should only be set at initialization time.
 //
 // ⚠️ WARNING: After changing any options, do not forget to update template of
@@ -71,20 +77,6 @@ var (
 		FromEmail string `ini:"-"` // Parsed email address of From without person's name.
 	}
 
-	// Authentication settings
-	Auth struct {
-		ActivateCodeLives         int
-		ResetPasswordCodeLives    int
-		RequireEmailConfirmation  bool
-		RequireSigninView         bool
-		DisableRegistration       bool
-		EnableRegistrationCaptcha bool
-
-		EnableReverseProxyAuthentication   bool
-		EnableReverseProxyAutoRegistration bool
-		ReverseProxyAuthenticationHeader   string
-	}
-
 	// User settings
 	User struct {
 		EnableEmailNotification bool
@@ -268,6 +260,22 @@ type AppOpts struct {
 // Application settings
 var App AppOpts
 
+type AuthOpts struct {
+	ActivateCodeLives         int
+	ResetPasswordCodeLives    int
+	RequireEmailConfirmation  bool
+	RequireSigninView         bool
+	DisableRegistration       bool
+	EnableRegistrationCaptcha bool
+
+	EnableReverseProxyAuthentication   bool
+	EnableReverseProxyAutoRegistration bool
+	ReverseProxyAuthenticationHeader   string
+}
+
+// Authentication settings
+var Auth AuthOpts
+
 type ServerOpts struct {
 	ExternalURL          string `ini:"EXTERNAL_URL"`
 	Domain               string

+ 8 - 5
internal/db/issue_mail.go

@@ -13,6 +13,7 @@ import (
 	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/email"
 	"gogs.io/gogs/internal/markup"
+	"gogs.io/gogs/internal/userutil"
 )
 
 func (issue *Issue) MailSubject() string {
@@ -36,12 +37,14 @@ func (this mailerUser) Email() string {
 	return this.user.Email
 }
 
-func (this mailerUser) GenerateActivateCode() string {
-	return this.user.GenerateActivateCode()
-}
-
 func (this mailerUser) GenerateEmailActivateCode(email string) string {
-	return this.user.GenerateEmailActivateCode(email)
+	return userutil.GenerateActivateCode(
+		this.user.ID,
+		email,
+		this.user.Name,
+		this.user.Passwd,
+		this.user.Rands,
+	)
 }
 
 func NewMailerUser(u *User) email.User {

+ 1 - 1
internal/db/org.go

@@ -177,7 +177,7 @@ func GetOrgByName(name string) (*User, error) {
 	}
 	u := &User{
 		LowerName: strings.ToLower(name),
-		Type:      UserOrganization,
+		Type:      UserTypeOrganization,
 	}
 	has, err := x.Get(u)
 	if err != nil {

+ 4 - 4
internal/db/repo.go

@@ -1157,8 +1157,8 @@ func (err ErrReachLimitOfRepo) Error() string {
 
 // CreateRepository creates a repository for given user or organization.
 func CreateRepository(doer, owner *User, opts CreateRepoOptionsLegacy) (_ *Repository, err error) {
-	if !owner.CanCreateRepo() {
-		return nil, ErrReachLimitOfRepo{Limit: owner.RepoCreationNum()}
+	if !owner.canCreateRepo() {
+		return nil, ErrReachLimitOfRepo{Limit: owner.maxNumRepos()}
 	}
 
 	repo := &Repository{
@@ -2467,8 +2467,8 @@ func HasForkedRepo(ownerID, repoID int64) (*Repository, bool, error) {
 
 // ForkRepository creates a fork of target repository under another user domain.
 func ForkRepository(doer, owner *User, baseRepo *Repository, name, desc string) (_ *Repository, err error) {
-	if !owner.CanCreateRepo() {
-		return nil, ErrReachLimitOfRepo{Limit: owner.RepoCreationNum()}
+	if !owner.canCreateRepo() {
+		return nil, ErrReachLimitOfRepo{Limit: owner.maxNumRepos()}
 	}
 
 	repo := &Repository{

+ 7 - 146
internal/db/user.go

@@ -27,7 +27,6 @@ import (
 	"xorm.io/xorm"
 
 	"github.com/gogs/git-module"
-	api "github.com/gogs/go-gogs-client"
 
 	"gogs.io/gogs/internal/avatar"
 	"gogs.io/gogs/internal/conf"
@@ -37,77 +36,13 @@ import (
 	"gogs.io/gogs/internal/tool"
 )
 
-// USER_AVATAR_URL_PREFIX is used to identify a URL is to access user avatar.
-const USER_AVATAR_URL_PREFIX = "avatars"
-
-type UserType int
-
-const (
-	UserIndividual UserType = iota // Historic reason to make it starts at 0.
-	UserOrganization
-)
-
-// User represents the object of individual and member of organization.
-type User struct {
-	ID        int64  `gorm:"primaryKey"`
-	LowerName string `xorm:"UNIQUE NOT NULL" gorm:"unique;not null"`
-	Name      string `xorm:"UNIQUE NOT NULL" gorm:"not null"`
-	FullName  string
-	// Email is the primary email address (to be used for communication)
-	Email       string `xorm:"NOT NULL" gorm:"not null"`
-	Passwd      string `xorm:"NOT NULL" gorm:"not null"`
-	LoginSource int64  `xorm:"NOT NULL DEFAULT 0" gorm:"not null;default:0"`
-	LoginName   string
-	Type        UserType
-	OwnedOrgs   []*User       `xorm:"-" gorm:"-" json:"-"`
-	Orgs        []*User       `xorm:"-" gorm:"-" json:"-"`
-	Repos       []*Repository `xorm:"-" gorm:"-" json:"-"`
-	Location    string
-	Website     string
-	Rands       string `xorm:"VARCHAR(10)" gorm:"type:VARCHAR(10)"`
-	Salt        string `xorm:"VARCHAR(10)" gorm:"type:VARCHAR(10)"`
-
-	Created     time.Time `xorm:"-" gorm:"-" json:"-"`
-	CreatedUnix int64
-	Updated     time.Time `xorm:"-" gorm:"-" json:"-"`
-	UpdatedUnix int64
-
-	// Remember visibility choice for convenience, true for private
-	LastRepoVisibility bool
-	// Maximum repository creation limit, -1 means use global default
-	MaxRepoCreation int `xorm:"NOT NULL DEFAULT -1" gorm:"not null;default:-1"`
-
-	// Permissions
-	IsActive         bool // Activate primary email
-	IsAdmin          bool
-	AllowGitHook     bool
-	AllowImportLocal bool // Allow migrate repository by local path
-	ProhibitLogin    bool
-
-	// Avatar
-	Avatar          string `xorm:"VARCHAR(2048) NOT NULL" gorm:"type:VARCHAR(2048);not null"`
-	AvatarEmail     string `xorm:"NOT NULL" gorm:"not null"`
-	UseCustomAvatar bool
-
-	// Counters
-	NumFollowers int
-	NumFollowing int `xorm:"NOT NULL DEFAULT 0" gorm:"not null;default:0"`
-	NumStars     int
-	NumRepos     int
-
-	// For organization
-	Description string
-	NumTeams    int
-	NumMembers  int
-	Teams       []*Team `xorm:"-" gorm:"-" json:"-"`
-	Members     []*User `xorm:"-" gorm:"-" json:"-"`
-}
-
+// TODO(unknwon): Delete me once refactoring is done.
 func (u *User) BeforeInsert() {
 	u.CreatedUnix = time.Now().Unix()
 	u.UpdatedUnix = u.CreatedUnix
 }
 
+// TODO(unknwon): Refactoring together with methods that do updates.
 func (u *User) BeforeUpdate() {
 	if u.MaxRepoCreation < -1 {
 		u.MaxRepoCreation = -1
@@ -115,6 +50,7 @@ func (u *User) BeforeUpdate() {
 	u.UpdatedUnix = time.Now().Unix()
 }
 
+// TODO(unknwon): Delete me once refactoring is done.
 func (u *User) AfterSet(colName string, _ xorm.Cell) {
 	switch colName {
 	case "created_unix":
@@ -124,81 +60,6 @@ func (u *User) AfterSet(colName string, _ xorm.Cell) {
 	}
 }
 
-func (u *User) APIFormat() *api.User {
-	return &api.User{
-		ID:        u.ID,
-		UserName:  u.Name,
-		Login:     u.Name,
-		FullName:  u.FullName,
-		Email:     u.Email,
-		AvatarUrl: u.AvatarLink(),
-	}
-}
-
-func (u *User) RepoCreationNum() int {
-	if u.MaxRepoCreation <= -1 {
-		return conf.Repository.MaxCreationLimit
-	}
-	return u.MaxRepoCreation
-}
-
-func (u *User) CanCreateRepo() bool {
-	if u.MaxRepoCreation <= -1 {
-		if conf.Repository.MaxCreationLimit <= -1 {
-			return true
-		}
-		return u.NumRepos < conf.Repository.MaxCreationLimit
-	}
-	return u.NumRepos < u.MaxRepoCreation
-}
-
-func (u *User) CanCreateOrganization() bool {
-	return !conf.Admin.DisableRegularOrgCreation || u.IsAdmin
-}
-
-// CanEditGitHook returns true if user can edit Git hooks.
-func (u *User) CanEditGitHook() bool {
-	return u.IsAdmin || u.AllowGitHook
-}
-
-// CanImportLocal returns true if user can migrate repository by local path.
-func (u *User) CanImportLocal() bool {
-	return conf.Repository.EnableLocalPathMigration && (u.IsAdmin || u.AllowImportLocal)
-}
-
-// DashboardLink returns the user dashboard page link.
-func (u *User) DashboardLink() string {
-	if u.IsOrganization() {
-		return conf.Server.Subpath + "/org/" + u.Name + "/dashboard/"
-	}
-	return conf.Server.Subpath + "/"
-}
-
-// HomeLink returns the user or organization home page link.
-func (u *User) HomeLink() string {
-	return conf.Server.Subpath + "/" + u.Name
-}
-
-func (u *User) HTMLURL() string {
-	return conf.Server.ExternalURL + u.Name
-}
-
-// GenerateEmailActivateCode generates an activate code based on user information and given e-mail.
-func (u *User) GenerateEmailActivateCode(email string) string {
-	code := tool.CreateTimeLimitCode(
-		com.ToStr(u.ID)+email+u.LowerName+u.Passwd+u.Rands,
-		conf.Auth.ActivateCodeLives, nil)
-
-	// Add tail hex username
-	code += hex.EncodeToString([]byte(u.LowerName))
-	return code
-}
-
-// GenerateActivateCode generates an activate code based on user information.
-func (u *User) GenerateActivateCode() string {
-	return u.GenerateEmailActivateCode(u.Email)
-}
-
 // CustomAvatarPath returns user custom avatar file path.
 func (u *User) CustomAvatarPath() string {
 	return filepath.Join(conf.Picture.AvatarUploadPath, com.ToStr(u.ID))
@@ -246,7 +107,7 @@ func (u *User) RelAvatarLink() string {
 		if !com.IsExist(u.CustomAvatarPath()) {
 			return defaultImgUrl
 		}
-		return fmt.Sprintf("%s/%s/%d", conf.Server.Subpath, USER_AVATAR_URL_PREFIX, u.ID)
+		return fmt.Sprintf("%s/%s/%d", conf.Server.Subpath, conf.UsersAvatarURLPath, u.ID)
 	case conf.Picture.DisableGravatar:
 		if !com.IsExist(u.CustomAvatarPath()) {
 			if err := u.GenerateRandomAvatar(); err != nil {
@@ -254,7 +115,7 @@ func (u *User) RelAvatarLink() string {
 			}
 		}
 
-		return fmt.Sprintf("%s/%s/%d", conf.Server.Subpath, USER_AVATAR_URL_PREFIX, u.ID)
+		return fmt.Sprintf("%s/%s/%d", conf.Server.Subpath, conf.UsersAvatarURLPath, u.ID)
 	}
 	return tool.AvatarLink(u.AvatarEmail)
 }
@@ -374,7 +235,7 @@ func (u *User) IsWriterOfRepo(repo *Repository) bool {
 
 // IsOrganization returns true if user is actually a organization.
 func (u *User) IsOrganization() bool {
-	return u.Type == UserOrganization
+	return u.Type == UserTypeOrganization
 }
 
 // IsUserOrgOwner returns true if user is in the owner team of given organization.
@@ -434,7 +295,7 @@ func (u *User) GetOrganizations(showPrivate bool) error {
 	}
 
 	u.Orgs = make([]*User, 0, len(orgIDs))
-	if err = x.Where("type = ?", UserOrganization).In("id", orgIDs).Find(&u.Orgs); err != nil {
+	if err = x.Where("type = ?", UserTypeOrganization).In("id", orgIDs).Find(&u.Orgs); err != nil {
 		return err
 	}
 	return nil

+ 1 - 1
internal/db/user_mail.go

@@ -69,7 +69,7 @@ func isEmailUsed(e Engine, email string) (bool, error) {
 	}
 
 	// We need to check primary email of users as well.
-	return e.Where("type=?", UserIndividual).And("email=?", email).Get(new(User))
+	return e.Where("type=?", UserTypeIndividual).And("email=?", email).Get(new(User))
 }
 
 // IsEmailUsed returns true if the email has been used.

+ 147 - 22
internal/db/users.go

@@ -11,10 +11,12 @@ import (
 	"time"
 
 	"github.com/go-macaron/binding"
+	api "github.com/gogs/go-gogs-client"
 	"github.com/pkg/errors"
 	"gorm.io/gorm"
 
 	"gogs.io/gogs/internal/auth"
+	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/cryptoutil"
 	"gogs.io/gogs/internal/errutil"
 )
@@ -54,27 +56,6 @@ type UsersStore interface {
 
 var Users UsersStore
 
-// BeforeCreate implements the GORM create hook.
-func (u *User) BeforeCreate(tx *gorm.DB) error {
-	if u.CreatedUnix == 0 {
-		u.CreatedUnix = tx.NowFunc().Unix()
-		u.UpdatedUnix = u.CreatedUnix
-	}
-	return nil
-}
-
-// AfterFind implements the GORM query hook.
-func (u *User) AfterFind(_ *gorm.DB) error {
-	u.Created = time.Unix(u.CreatedUnix, 0).Local()
-	u.Updated = time.Unix(u.UpdatedUnix, 0).Local()
-	return nil
-}
-
-// IsLocal returns true if user is created as local account.
-func (u *User) IsLocal() bool {
-	return u.LoginSource <= 0
-}
-
 var _ UsersStore = (*users)(nil)
 
 type users struct {
@@ -303,7 +284,7 @@ func (db *users) GetByEmail(ctx context.Context, email string) (*User, error) {
 	// First try to find the user by primary email
 	user := new(User)
 	err := db.WithContext(ctx).
-		Where("email = ? AND type = ? AND is_active = ?", email, UserIndividual, true).
+		Where("email = ? AND type = ? AND is_active = ?", email, UserTypeIndividual, true).
 		First(user).
 		Error
 	if err == nil {
@@ -357,3 +338,147 @@ func (db *users) HasForkedRepository(ctx context.Context, userID, repoID int64)
 	db.WithContext(ctx).Model(new(Repository)).Where("owner_id = ? AND fork_id = ?", userID, repoID).Count(&count)
 	return count > 0
 }
+
+// UserType indicates the type of the user account.
+type UserType int
+
+const (
+	UserTypeIndividual UserType = iota // NOTE: Historic reason to make it starts at 0.
+	UserTypeOrganization
+)
+
+// User represents the object of an individual or an organization.
+type User struct {
+	ID        int64  `gorm:"primaryKey"`
+	LowerName string `xorm:"UNIQUE NOT NULL" gorm:"unique;not null"`
+	Name      string `xorm:"UNIQUE NOT NULL" gorm:"not null"`
+	FullName  string
+	// Email is the primary email address (to be used for communication)
+	Email       string `xorm:"NOT NULL" gorm:"not null"`
+	Passwd      string `xorm:"NOT NULL" gorm:"not null"`
+	LoginSource int64  `xorm:"NOT NULL DEFAULT 0" gorm:"not null;default:0"`
+	LoginName   string
+	Type        UserType
+	OwnedOrgs   []*User       `xorm:"-" gorm:"-" json:"-"`
+	Orgs        []*User       `xorm:"-" gorm:"-" json:"-"`
+	Repos       []*Repository `xorm:"-" gorm:"-" json:"-"`
+	Location    string
+	Website     string
+	Rands       string `xorm:"VARCHAR(10)" gorm:"type:VARCHAR(10)"`
+	Salt        string `xorm:"VARCHAR(10)" gorm:"type:VARCHAR(10)"`
+
+	Created     time.Time `xorm:"-" gorm:"-" json:"-"`
+	CreatedUnix int64
+	Updated     time.Time `xorm:"-" gorm:"-" json:"-"`
+	UpdatedUnix int64
+
+	// Remember visibility choice for convenience, true for private
+	LastRepoVisibility bool
+	// Maximum repository creation limit, -1 means use global default
+	MaxRepoCreation int `xorm:"NOT NULL DEFAULT -1" gorm:"not null;default:-1"`
+
+	// Permissions
+	IsActive         bool // Activate primary email
+	IsAdmin          bool
+	AllowGitHook     bool
+	AllowImportLocal bool // Allow migrate repository by local path
+	ProhibitLogin    bool
+
+	// Avatar
+	Avatar          string `xorm:"VARCHAR(2048) NOT NULL" gorm:"type:VARCHAR(2048);not null"`
+	AvatarEmail     string `xorm:"NOT NULL" gorm:"not null"`
+	UseCustomAvatar bool
+
+	// Counters
+	NumFollowers int
+	NumFollowing int `xorm:"NOT NULL DEFAULT 0" gorm:"not null;default:0"`
+	NumStars     int
+	NumRepos     int
+
+	// For organization
+	Description string
+	NumTeams    int
+	NumMembers  int
+	Teams       []*Team `xorm:"-" gorm:"-" json:"-"`
+	Members     []*User `xorm:"-" gorm:"-" json:"-"`
+}
+
+// BeforeCreate implements the GORM create hook.
+func (u *User) BeforeCreate(tx *gorm.DB) error {
+	if u.CreatedUnix == 0 {
+		u.CreatedUnix = tx.NowFunc().Unix()
+		u.UpdatedUnix = u.CreatedUnix
+	}
+	return nil
+}
+
+// AfterFind implements the GORM query hook.
+func (u *User) AfterFind(_ *gorm.DB) error {
+	u.Created = time.Unix(u.CreatedUnix, 0).Local()
+	u.Updated = time.Unix(u.UpdatedUnix, 0).Local()
+	return nil
+}
+
+// IsLocal returns true if user is created as local account.
+func (u *User) IsLocal() bool {
+	return u.LoginSource <= 0
+}
+
+// APIFormat returns the API format of a user.
+func (u *User) APIFormat() *api.User {
+	return &api.User{
+		ID:        u.ID,
+		UserName:  u.Name,
+		Login:     u.Name,
+		FullName:  u.FullName,
+		Email:     u.Email,
+		AvatarUrl: u.AvatarLink(),
+	}
+}
+
+// maxNumRepos returns the maximum number of repositories that the user can have
+// direct ownership.
+func (u *User) maxNumRepos() int {
+	if u.MaxRepoCreation <= -1 {
+		return conf.Repository.MaxCreationLimit
+	}
+	return u.MaxRepoCreation
+}
+
+// canCreateRepo returns true if the user can create a repository.
+func (u *User) canCreateRepo() bool {
+	return u.maxNumRepos() <= -1 || u.NumRepos < u.maxNumRepos()
+}
+
+// CanCreateOrganization returns true if user can create organizations.
+func (u *User) CanCreateOrganization() bool {
+	return !conf.Admin.DisableRegularOrgCreation || u.IsAdmin
+}
+
+// CanEditGitHook returns true if user can edit Git hooks.
+func (u *User) CanEditGitHook() bool {
+	return u.IsAdmin || u.AllowGitHook
+}
+
+// CanImportLocal returns true if user can migrate repositories by local path.
+func (u *User) CanImportLocal() bool {
+	return conf.Repository.EnableLocalPathMigration && (u.IsAdmin || u.AllowImportLocal)
+}
+
+// HomeURLPath returns the URL path to the user or organization home page.
+//
+// TODO(unknwon): This is also used in templates, which should be fixed by
+// having a dedicated type `template.User` and move this to the "userutil"
+// package.
+func (u *User) HomeURLPath() string {
+	return conf.Server.Subpath + "/" + u.Name
+}
+
+// HTMLURL returns the HTML URL to the user or organization home page.
+//
+// TODO(unknwon): This is also used in templates, which should be fixed by
+// having a dedicated type `template.User` and move this to the "userutil"
+// package.
+func (u *User) HTMLURL() string {
+	return conf.Server.ExternalURL + u.Name
+}

+ 1 - 1
internal/db/users_test.go

@@ -198,7 +198,7 @@ func usersGetByEmail(t *testing.T, db *users) {
 		org, err := db.Create(ctx, "gogs", "[email protected]", CreateUserOptions{})
 		require.NoError(t, err)
 
-		err = db.Model(&User{}).Where("id", org.ID).UpdateColumn("type", UserOrganization).Error
+		err = db.Model(&User{}).Where("id", org.ID).UpdateColumn("type", UserTypeOrganization).Error
 		require.NoError(t, err)
 
 		_, err = db.GetByEmail(ctx, org.Email)

+ 2 - 3
internal/email/email.go

@@ -86,7 +86,6 @@ type User interface {
 	ID() int64
 	DisplayName() string
 	Email() string
-	GenerateActivateCode() string
 	GenerateEmailActivateCode(string) string
 }
 
@@ -122,11 +121,11 @@ func SendUserMail(_ *macaron.Context, u User, tpl, code, subject, info string) {
 }
 
 func SendActivateAccountMail(c *macaron.Context, u User) {
-	SendUserMail(c, u, MAIL_AUTH_ACTIVATE, u.GenerateActivateCode(), c.Tr("mail.activate_account"), "activate account")
+	SendUserMail(c, u, MAIL_AUTH_ACTIVATE, u.GenerateEmailActivateCode(u.Email()), c.Tr("mail.activate_account"), "activate account")
 }
 
 func SendResetPasswordMail(c *macaron.Context, u User) {
-	SendUserMail(c, u, MAIL_AUTH_RESET_PASSWORD, u.GenerateActivateCode(), c.Tr("mail.reset_password"), "reset password")
+	SendUserMail(c, u, MAIL_AUTH_RESET_PASSWORD, u.GenerateEmailActivateCode(u.Email()), c.Tr("mail.reset_password"), "reset password")
 }
 
 // SendActivateAccountMail sends confirmation email.

+ 1 - 1
internal/route/admin/orgs.go

@@ -21,7 +21,7 @@ func Organizations(c *context.Context) {
 	c.Data["PageIsAdminOrganizations"] = true
 
 	route.RenderUserSearch(c, &route.UserSearchOptions{
-		Type:     db.UserOrganization,
+		Type:     db.UserTypeOrganization,
 		Counter:  db.CountOrganizations,
 		Ranger:   db.Organizations,
 		PageSize: conf.UI.Admin.OrgPagingNum,

+ 1 - 1
internal/route/admin/users.go

@@ -30,7 +30,7 @@ func Users(c *context.Context) {
 	c.Data["PageIsAdminUsers"] = true
 
 	route.RenderUserSearch(c, &route.UserSearchOptions{
-		Type:     db.UserIndividual,
+		Type:     db.UserTypeIndividual,
 		Counter:  db.CountUsers,
 		Ranger:   db.ListUsers,
 		PageSize: conf.UI.Admin.UserPagingNum,

+ 1 - 1
internal/route/api/v1/org/org.go

@@ -27,7 +27,7 @@ func CreateOrgForUser(c *context.APIContext, apiForm api.CreateOrgOption, user *
 		Website:     apiForm.Website,
 		Location:    apiForm.Location,
 		IsActive:    true,
-		Type:        db.UserOrganization,
+		Type:        db.UserTypeOrganization,
 	}
 	if err := db.CreateOrganization(org, user); err != nil {
 		if db.IsErrUserAlreadyExist(err) ||

+ 1 - 1
internal/route/api/v1/user/user.go

@@ -19,7 +19,7 @@ import (
 func Search(c *context.APIContext) {
 	opts := &db.SearchUserOptions{
 		Keyword:  c.Query("q"),
-		Type:     db.UserIndividual,
+		Type:     db.UserTypeIndividual,
 		PageSize: com.StrTo(c.Query("limit")).MustInt(),
 	}
 	if opts.PageSize == 0 {

+ 2 - 2
internal/route/home.go

@@ -138,7 +138,7 @@ func ExploreUsers(c *context.Context) {
 	c.Data["PageIsExploreUsers"] = true
 
 	RenderUserSearch(c, &UserSearchOptions{
-		Type:     db.UserIndividual,
+		Type:     db.UserTypeIndividual,
 		Counter:  db.CountUsers,
 		Ranger:   db.ListUsers,
 		PageSize: conf.UI.ExplorePagingNum,
@@ -153,7 +153,7 @@ func ExploreOrganizations(c *context.Context) {
 	c.Data["PageIsExploreOrganizations"] = true
 
 	RenderUserSearch(c, &UserSearchOptions{
-		Type:     db.UserOrganization,
+		Type:     db.UserTypeOrganization,
 		Counter:  db.CountOrganizations,
 		Ranger:   db.Organizations,
 		PageSize: conf.UI.ExplorePagingNum,

+ 1 - 1
internal/route/org/org.go

@@ -32,7 +32,7 @@ func CreatePost(c *context.Context, f form.CreateOrg) {
 	org := &db.User{
 		Name:     f.OrgName,
 		IsActive: true,
-		Type:     db.UserOrganization,
+		Type:     db.UserTypeOrganization,
 	}
 
 	if err := db.CreateOrganization(org, c.User); err != nil {

+ 1 - 1
internal/route/repo/pull.go

@@ -135,7 +135,7 @@ func ForkPost(c *context.Context, f form.CreateRepo) {
 		c.Data["Err_RepoName"] = true
 		switch {
 		case db.IsErrReachLimitOfRepo(err):
-			c.RenderWithErr(c.Tr("repo.form.reach_limit_of_creation", c.User.RepoCreationNum()), FORK, &f)
+			c.RenderWithErr(c.Tr("repo.form.reach_limit_of_creation", err.(db.ErrReachLimitOfRepo).Limit), FORK, &f)
 		case db.IsErrRepoAlreadyExist(err):
 			c.RenderWithErr(c.Tr("repo.settings.new_owner_has_same_repo"), FORK, &f)
 		case db.IsErrNameNotAllowed(err):

+ 4 - 4
internal/route/repo/repo.go

@@ -86,10 +86,10 @@ func Create(c *context.Context) {
 	c.Success(CREATE)
 }
 
-func handleCreateError(c *context.Context, owner *db.User, err error, name, tpl string, form interface{}) {
+func handleCreateError(c *context.Context, err error, name, tpl string, form interface{}) {
 	switch {
 	case db.IsErrReachLimitOfRepo(err):
-		c.RenderWithErr(c.Tr("repo.form.reach_limit_of_creation", owner.RepoCreationNum()), tpl, form)
+		c.RenderWithErr(c.Tr("repo.form.reach_limit_of_creation", err.(db.ErrReachLimitOfRepo).Limit), tpl, form)
 	case db.IsErrRepoAlreadyExist(err):
 		c.Data["Err_RepoName"] = true
 		c.RenderWithErr(c.Tr("form.repo_name_been_taken"), tpl, form)
@@ -141,7 +141,7 @@ func CreatePost(c *context.Context, f form.CreateRepo) {
 		}
 	}
 
-	handleCreateError(c, ctxUser, err, "CreatePost", CREATE, &f)
+	handleCreateError(c, err, "CreatePost", CREATE, &f)
 }
 
 func Migrate(c *context.Context) {
@@ -227,7 +227,7 @@ func MigratePost(c *context.Context, f form.MigrateRepo) {
 		return
 	}
 
-	handleCreateError(c, ctxUser, err, "MigratePost", MIGRATE, &f)
+	handleCreateError(c, err, "MigratePost", MIGRATE, &f)
 }
 
 func Action(c *context.Context) {

+ 2 - 1
internal/route/repo/setting.go

@@ -22,6 +22,7 @@ import (
 	"gogs.io/gogs/internal/form"
 	"gogs.io/gogs/internal/osutil"
 	"gogs.io/gogs/internal/tool"
+	"gogs.io/gogs/internal/userutil"
 )
 
 const (
@@ -269,7 +270,7 @@ func SettingsPost(c *context.Context, f form.RepoSetting) {
 		log.Trace("Repository deleted: %s/%s", c.Repo.Owner.Name, repo.Name)
 
 		c.Flash.Success(c.Tr("repo.settings.deletion_success"))
-		c.Redirect(c.Repo.Owner.DashboardLink())
+		c.Redirect(userutil.DashboardURLPath(c.Repo.Owner.Name, c.Repo.Owner.IsOrganization()))
 
 	case "delete-wiki":
 		if !c.Repo.IsOwner() {

+ 1 - 1
internal/route/user/profile.go

@@ -118,7 +118,7 @@ func Action(c *context.Context, puser *context.ParamsUser) {
 
 	redirectTo := c.Query("redirect_to")
 	if !tool.IsSameSiteURLPath(redirectTo) {
-		redirectTo = puser.HomeLink()
+		redirectTo = puser.HomeURLPath()
 	}
 	c.Redirect(redirectTo)
 }

+ 36 - 0
internal/userutil/userutil.go

@@ -0,0 +1,36 @@
+// Copyright 2022 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 userutil
+
+import (
+	"encoding/hex"
+	"fmt"
+	"strings"
+
+	"gogs.io/gogs/internal/conf"
+	"gogs.io/gogs/internal/tool"
+)
+
+// DashboardURLPath returns the URL path to the user or organization dashboard.
+func DashboardURLPath(name string, isOrganization bool) string {
+	if isOrganization {
+		return conf.Server.Subpath + "/org/" + name + "/dashboard/"
+	}
+	return conf.Server.Subpath + "/"
+}
+
+// GenerateActivateCode generates an activate code based on user information and
+// the given email.
+func GenerateActivateCode(id int64, email, name, password, rands string) string {
+	code := tool.CreateTimeLimitCode(
+		fmt.Sprintf("%d%s%s%s%s", id, email, strings.ToLower(name), password, rands),
+		conf.Auth.ActivateCodeLives,
+		nil,
+	)
+
+	// Add tailing hex username
+	code += hex.EncodeToString([]byte(strings.ToLower(name)))
+	return code
+}

+ 40 - 0
internal/userutil/userutil_test.go

@@ -0,0 +1,40 @@
+// Copyright 2022 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 userutil
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"gogs.io/gogs/internal/conf"
+	"gogs.io/gogs/internal/tool"
+)
+
+func TestDashboardURLPath(t *testing.T) {
+	t.Run("user", func(t *testing.T) {
+		got := DashboardURLPath("alice", false)
+		want := "/"
+		assert.Equal(t, want, got)
+	})
+
+	t.Run("organization", func(t *testing.T) {
+		got := DashboardURLPath("acme", true)
+		want := "/org/acme/dashboard/"
+		assert.Equal(t, want, got)
+	})
+}
+
+func TestGenerateActivateCode(t *testing.T) {
+	conf.SetMockAuth(t,
+		conf.AuthOpts{
+			ActivateCodeLives: 10,
+		},
+	)
+
+	code := GenerateActivateCode(1, "[email protected]", "Alice", "123456", "rands")
+	got := tool.VerifyTimeLimitCode("[email protected]", conf.Auth.ActivateCodeLives, code[:tool.TIME_LIMIT_CODE_LENGTH])
+	assert.True(t, got)
+}

+ 2 - 2
templates/user/profile.tmpl

@@ -48,7 +48,7 @@
 							{{/*
 							<li>
 								<i class="octicon octicon-star"></i>
-								<a href="{{.Owner.HomeLink}}/stars">
+								<a href="{{.Owner.HomeURLPath}}/stars">
 									{{.Owner.NumStars}} {{.i18n.Tr "user.starred"}}
 								</a>
 							</li>
@@ -56,7 +56,7 @@
 							{{if .Orgs}}
 							<li>
 								{{range .Orgs}}
-									<a href="{{.HomeLink}}"><img class="ui mini image poping up" src="{{.RelAvatarLink}}" data-content="{{.Name}}" data-position="top center" data-variation="tiny inverted"></a>
+									<a href="{{.HomeURLPath}}"><img class="ui mini image poping up" src="{{.RelAvatarLink}}" data-content="{{.Name}}" data-position="top center" data-variation="tiny inverted"></a>
 								{{end}}
 							</li>
 							{{end}}