From ce25881c880d9711f211be05f92a809304a436e3 Mon Sep 17 00:00:00 2001
From: Joe Chen <jc@unknwon.io>
Date: Sat, 22 Oct 2022 20:01:38 +0800
Subject: refactor(db): move some methods off `user.go` (#7199)

---
 internal/db/actions.go |  2 +-
 internal/db/org.go     |  3 +-
 internal/db/user.go    | 79 ++++----------------------------------------------
 internal/db/users.go   | 52 +++++++++++++++++++++++++++++++--
 4 files changed, 59 insertions(+), 77 deletions(-)

(limited to 'internal/db')

diff --git a/internal/db/actions.go b/internal/db/actions.go
index b7f0cca9..74c15291 100644
--- a/internal/db/actions.go
+++ b/internal/db/actions.go
@@ -954,7 +954,7 @@ func (pcs *PushCommits) AvatarLink(email string) string {
 				log.Error("Failed to get user [email: %s]: %v", email, err)
 			}
 		} else {
-			pcs.avatars[email] = u.RelAvatarLink()
+			pcs.avatars[email] = u.AvatarURLPath()
 		}
 	}
 
diff --git a/internal/db/org.go b/internal/db/org.go
index c5c55f44..93350746 100644
--- a/internal/db/org.go
+++ b/internal/db/org.go
@@ -14,6 +14,7 @@ import (
 	"xorm.io/xorm"
 
 	"gogs.io/gogs/internal/errutil"
+	"gogs.io/gogs/internal/userutil"
 )
 
 var ErrOrgNotExist = errors.New("Organization does not exist")
@@ -131,7 +132,7 @@ func CreateOrganization(org, owner *User) (err error) {
 	if _, err = sess.Insert(org); err != nil {
 		return fmt.Errorf("insert organization: %v", err)
 	}
-	_ = org.GenerateRandomAvatar()
+	_ = userutil.GenerateRandomAvatar(org.ID, org.Name, org.Email)
 
 	// Add initial creator to organization and owner team.
 	if _, err = sess.Insert(&OrgUser{
diff --git a/internal/db/user.go b/internal/db/user.go
index 11e4c4a6..48444b1e 100644
--- a/internal/db/user.go
+++ b/internal/db/user.go
@@ -34,6 +34,7 @@ import (
 	"gogs.io/gogs/internal/errutil"
 	"gogs.io/gogs/internal/strutil"
 	"gogs.io/gogs/internal/tool"
+	"gogs.io/gogs/internal/userutil"
 )
 
 // TODO(unknwon): Delete me once refactoring is done.
@@ -60,75 +61,6 @@ func (u *User) AfterSet(colName string, _ xorm.Cell) {
 	}
 }
 
-// CustomAvatarPath returns user custom avatar file path.
-func (u *User) CustomAvatarPath() string {
-	return filepath.Join(conf.Picture.AvatarUploadPath, com.ToStr(u.ID))
-}
-
-// GenerateRandomAvatar generates a random avatar for user.
-func (u *User) GenerateRandomAvatar() error {
-	seed := u.Email
-	if seed == "" {
-		seed = u.Name
-	}
-
-	img, err := avatar.RandomImage([]byte(seed))
-	if err != nil {
-		return fmt.Errorf("RandomImage: %v", err)
-	}
-	if err = os.MkdirAll(filepath.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 = png.Encode(fw, img); err != nil {
-		return fmt.Errorf("Encode: %v", err)
-	}
-
-	log.Info("New random avatar created: %d", u.ID)
-	return nil
-}
-
-// RelAvatarLink returns relative avatar link to the site domain,
-// which includes app sub-url as prefix. However, it is possible
-// to return full URL if user enables Gravatar-like service.
-func (u *User) RelAvatarLink() string {
-	defaultImgUrl := conf.Server.Subpath + "/img/avatar_default.png"
-	if u.ID == -1 {
-		return defaultImgUrl
-	}
-
-	switch {
-	case u.UseCustomAvatar:
-		if !com.IsExist(u.CustomAvatarPath()) {
-			return defaultImgUrl
-		}
-		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 {
-				log.Error("GenerateRandomAvatar: %v", err)
-			}
-		}
-
-		return fmt.Sprintf("%s/%s/%d", conf.Server.Subpath, conf.UsersAvatarURLPath, u.ID)
-	}
-	return tool.AvatarLink(u.AvatarEmail)
-}
-
-// AvatarLink returns user avatar absolute link.
-func (u *User) AvatarLink() string {
-	link := u.RelAvatarLink()
-	if link[0] == '/' && link[1] != '/' {
-		return conf.Server.ExternalURL + strings.TrimPrefix(link, conf.Server.Subpath)[1:]
-	}
-	return link
-}
-
 // User.GetFollowers returns range of user's followers.
 func (u *User) GetFollowers(page int) ([]*User, error) {
 	users := make([]*User, 0, ItemsPerPage)
@@ -188,7 +120,7 @@ func (u *User) UploadAvatar(data []byte) error {
 	}
 
 	_ = os.MkdirAll(conf.Picture.AvatarUploadPath, os.ModePerm)
-	fw, err := os.Create(u.CustomAvatarPath())
+	fw, err := os.Create(userutil.CustomAvatarPath(u.ID))
 	if err != nil {
 		return fmt.Errorf("create custom avatar directory: %v", err)
 	}
@@ -204,8 +136,9 @@ func (u *User) UploadAvatar(data []byte) error {
 
 // DeleteAvatar deletes the user's custom avatar.
 func (u *User) DeleteAvatar() error {
-	log.Trace("DeleteAvatar [%d]: %s", u.ID, u.CustomAvatarPath())
-	if err := os.Remove(u.CustomAvatarPath()); err != nil {
+	avatarPath := userutil.CustomAvatarPath(u.ID)
+	log.Trace("DeleteAvatar [%d]: %s", u.ID, avatarPath)
+	if err := os.Remove(avatarPath); err != nil {
 		return err
 	}
 
@@ -705,7 +638,7 @@ func deleteUser(e *xorm.Session, u *User) error {
 	//	so just keep error logs of those operations.
 
 	_ = os.RemoveAll(UserPath(u.Name))
-	_ = os.Remove(u.CustomAvatarPath())
+	_ = os.Remove(userutil.CustomAvatarPath(u.ID))
 
 	return nil
 }
diff --git a/internal/db/users.go b/internal/db/users.go
index 1ae50064..bc57f317 100644
--- a/internal/db/users.go
+++ b/internal/db/users.go
@@ -14,11 +14,15 @@ import (
 	api "github.com/gogs/go-gogs-client"
 	"github.com/pkg/errors"
 	"gorm.io/gorm"
+	log "unknwon.dev/clog/v2"
 
 	"gogs.io/gogs/internal/auth"
 	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/cryptoutil"
 	"gogs.io/gogs/internal/errutil"
+	"gogs.io/gogs/internal/osutil"
+	"gogs.io/gogs/internal/tool"
+	"gogs.io/gogs/internal/userutil"
 )
 
 // UsersStore is the persistent interface for users.
@@ -432,7 +436,7 @@ func (u *User) APIFormat() *api.User {
 		Login:     u.Name,
 		FullName:  u.FullName,
 		Email:     u.Email,
-		AvatarUrl: u.AvatarLink(),
+		AvatarUrl: u.AvatarURL(),
 	}
 }
 
@@ -474,7 +478,7 @@ func (u *User) HomeURLPath() string {
 	return conf.Server.Subpath + "/" + u.Name
 }
 
-// HTMLURL returns the HTML URL to the user or organization home page.
+// HTMLURL returns the full 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"
@@ -482,3 +486,47 @@ func (u *User) HomeURLPath() string {
 func (u *User) HTMLURL() string {
 	return conf.Server.ExternalURL + u.Name
 }
+
+// AvatarURLPath returns the URL path to the user or organization avatar. If the
+// user enables Gravatar-like service, then an external URL will be returned.
+//
+// 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) AvatarURLPath() string {
+	defaultURLPath := conf.UserDefaultAvatarURLPath()
+	if u.ID <= 0 {
+		return defaultURLPath
+	}
+
+	hasCustomAvatar := osutil.IsFile(userutil.CustomAvatarPath(u.ID))
+	switch {
+	case u.UseCustomAvatar:
+		if !hasCustomAvatar {
+			return defaultURLPath
+		}
+		return fmt.Sprintf("%s/%s/%d", conf.Server.Subpath, conf.UsersAvatarPathPrefix, u.ID)
+	case conf.Picture.DisableGravatar:
+		if !hasCustomAvatar {
+			if err := userutil.GenerateRandomAvatar(u.ID, u.Name, u.Email); err != nil {
+				log.Error("Failed to generate random avatar [user_id: %d]: %v", u.ID, err)
+			}
+		}
+		return fmt.Sprintf("%s/%s/%d", conf.Server.Subpath, conf.UsersAvatarPathPrefix, u.ID)
+	}
+	return tool.AvatarLink(u.AvatarEmail)
+}
+
+// AvatarURL returns the full URL to the user or organization avatar. If the
+// user enables Gravatar-like service, then an external URL will be returned.
+//
+// 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) AvatarURL() string {
+	link := u.AvatarURLPath()
+	if link[0] == '/' && link[1] != '/' {
+		return conf.Server.ExternalURL + strings.TrimPrefix(link, conf.Server.Subpath)[1:]
+	}
+	return link
+}
-- 
cgit v1.2.3