code refactor

This commit is contained in:
MaysWind
2024-08-19 23:47:55 +08:00
parent 8fa19df113
commit 4977979b08
16 changed files with 273 additions and 69 deletions
+10
View File
@@ -4,6 +4,7 @@ import (
"encoding/json"
"os"
"github.com/mayswind/ezbookkeeping/pkg/avatars"
"github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/datastore"
"github.com/mayswind/ezbookkeeping/pkg/duplicatechecker"
@@ -107,6 +108,15 @@ func initializeSystem(c *core.CliContext) (*settings.Config, error) {
return nil, err
}
err = avatars.InitializeAvatarProvider(config)
if err != nil {
if !isDisableBootLog {
log.BootErrorf(c, "[initializer.initializeSystem] initializes avatar provider failed, because %s", err.Error())
}
return nil, err
}
err = mail.InitializeMailer(config)
if err != nil {
+10
View File
@@ -3,6 +3,7 @@ package api
import (
"github.com/pquerna/otp/totp"
"github.com/mayswind/ezbookkeeping/pkg/avatars"
"github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/errs"
"github.com/mayswind/ezbookkeeping/pkg/log"
@@ -14,6 +15,7 @@ import (
// AuthorizationsApi represents authorization api
type AuthorizationsApi struct {
ApiUsingConfig
ApiWithUserInfo
users *services.UserService
tokens *services.TokenService
twoFactorAuthorizations *services.TwoFactorAuthorizationService
@@ -25,6 +27,14 @@ var (
ApiUsingConfig: ApiUsingConfig{
container: settings.Container,
},
ApiWithUserInfo: ApiWithUserInfo{
ApiUsingConfig: ApiUsingConfig{
container: settings.Container,
},
ApiUsingAvatarProvider: ApiUsingAvatarProvider{
container: avatars.Container,
},
},
users: services.Users,
tokens: services.Tokens,
twoFactorAuthorizations: services.TwoFactorAuthorizations,
+22 -5
View File
@@ -1,6 +1,7 @@
package api
import (
"github.com/mayswind/ezbookkeeping/pkg/avatars"
"github.com/mayswind/ezbookkeeping/pkg/duplicatechecker"
"github.com/mayswind/ezbookkeeping/pkg/models"
"github.com/mayswind/ezbookkeeping/pkg/settings"
@@ -16,11 +17,6 @@ func (a *ApiUsingConfig) CurrentConfig() *settings.Config {
return a.container.Current
}
// GetUserBasicInfo returns the view-object of user basic info according to the user model
func (a *ApiUsingConfig) GetUserBasicInfo(user *models.User) *models.UserBasicInfo {
return user.ToUserBasicInfo(a.CurrentConfig().AvatarProvider, a.CurrentConfig().RootUrl)
}
// GetAfterRegisterNotificationContent returns the notification content displayed each time users register
func (a *ApiUsingConfig) GetAfterRegisterNotificationContent(userLanguage string, clientLanguage string) string {
language := userLanguage
@@ -92,3 +88,24 @@ func (a *ApiUsingDuplicateChecker) GetSubmissionRemark(checkerType duplicatechec
func (a *ApiUsingDuplicateChecker) SetSubmissionRemark(checkerType duplicatechecker.DuplicateCheckerType, uid int64, identification string, remark string) {
a.container.SetSubmissionRemark(checkerType, uid, identification, remark)
}
// ApiUsingAvatarProvider represents an api that need to use avatar provider
type ApiUsingAvatarProvider struct {
container *avatars.AvatarProviderContainer
}
// GetAvatarUrl returns the avatar url by the current user avatar provider
func (a *ApiUsingAvatarProvider) GetAvatarUrl(user *models.User) string {
return a.container.GetAvatarUrl(user)
}
// ApiWithUserInfo represents an api that can returns user info
type ApiWithUserInfo struct {
ApiUsingConfig
ApiUsingAvatarProvider
}
// GetUserBasicInfo returns the view-object of user basic info according to the user model
func (a *ApiWithUserInfo) GetUserBasicInfo(user *models.User) *models.UserBasicInfo {
return user.ToUserBasicInfo(a.CurrentConfig().AvatarProvider, a.GetAvatarUrl(user))
}
+10
View File
@@ -4,6 +4,7 @@ import (
"sort"
"time"
"github.com/mayswind/ezbookkeeping/pkg/avatars"
"github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/errs"
"github.com/mayswind/ezbookkeeping/pkg/log"
@@ -16,6 +17,7 @@ import (
// TokensApi represents token api
type TokensApi struct {
ApiUsingConfig
ApiWithUserInfo
tokens *services.TokenService
users *services.UserService
}
@@ -26,6 +28,14 @@ var (
ApiUsingConfig: ApiUsingConfig{
container: settings.Container,
},
ApiWithUserInfo: ApiWithUserInfo{
ApiUsingConfig: ApiUsingConfig{
container: settings.Container,
},
ApiUsingAvatarProvider: ApiUsingAvatarProvider{
container: avatars.Container,
},
},
tokens: services.Tokens,
users: services.Users,
}
+11 -1
View File
@@ -6,6 +6,7 @@ import (
"github.com/gin-gonic/gin/binding"
"github.com/mayswind/ezbookkeeping/pkg/avatars"
"github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/errs"
"github.com/mayswind/ezbookkeeping/pkg/locales"
@@ -20,6 +21,7 @@ import (
// UsersApi represents user api
type UsersApi struct {
ApiUsingConfig
ApiWithUserInfo
users *services.UserService
tokens *services.TokenService
accounts *services.AccountService
@@ -31,6 +33,14 @@ var (
ApiUsingConfig: ApiUsingConfig{
container: settings.Container,
},
ApiWithUserInfo: ApiWithUserInfo{
ApiUsingConfig: ApiUsingConfig{
container: settings.Container,
},
ApiUsingAvatarProvider: ApiUsingAvatarProvider{
container: avatars.Container,
},
},
users: services.Users,
tokens: services.Tokens,
accounts: services.Accounts,
@@ -721,5 +731,5 @@ func (a *UsersApi) UserGetAvatarHandler(c *core.WebContext) ([]byte, string, *er
}
func (a *UsersApi) getUserProfileResponse(user *models.User) *models.UserProfileResponse {
return user.ToUserProfileResponse(a.CurrentConfig().AvatarProvider, a.CurrentConfig().RootUrl)
return user.ToUserProfileResponse(a.GetUserBasicInfo(user))
}
+8
View File
@@ -0,0 +1,8 @@
package avatars
import "github.com/mayswind/ezbookkeeping/pkg/models"
// AvatarProvider is user avatar provider interface
type AvatarProvider interface {
GetAvatarUrl(user *models.User) string
}
+39
View File
@@ -0,0 +1,39 @@
package avatars
import (
"github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/errs"
"github.com/mayswind/ezbookkeeping/pkg/models"
"github.com/mayswind/ezbookkeeping/pkg/settings"
)
// AvatarProviderContainer contains the current user avatar provider
type AvatarProviderContainer struct {
Current AvatarProvider
}
// Initialize a user avatar provider container singleton instance
var (
Container = &AvatarProviderContainer{}
)
// InitializeAvatarProvider initializes the current user avatar provider according to the config
func InitializeAvatarProvider(config *settings.Config) error {
if config.AvatarProvider == core.USER_AVATAR_PROVIDER_INTERNAL {
Container.Current = NewInternalStorageAvatarProvider(config)
return nil
} else if config.AvatarProvider == core.USER_AVATAR_PROVIDER_GRAVATAR {
Container.Current = NewGravatarAvatarProvider()
return nil
} else if config.AvatarProvider == "" {
Container.Current = NewNullAvatarProvider()
return nil
}
return errs.ErrInvalidAvatarProvider
}
// GetAvatarUrl returns the avatar url by the current user avatar provider
func (p *AvatarProviderContainer) GetAvatarUrl(user *models.User) string {
return p.Current.GetAvatarUrl(user)
}
+31
View File
@@ -0,0 +1,31 @@
package avatars
import (
"fmt"
"strings"
"github.com/mayswind/ezbookkeeping/pkg/models"
"github.com/mayswind/ezbookkeeping/pkg/utils"
)
// Reference: https://en.gravatar.com/site/implement/hash/
const gravatarUrlFormat = "https://www.gravatar.com/avatar/%s"
// GravatarAvatarProvider represents the gravatar avatar provider
type GravatarAvatarProvider struct {
}
// NewGravatarAvatarProvider returns a new gravatar avatar provider
func NewGravatarAvatarProvider() *GravatarAvatarProvider {
return &GravatarAvatarProvider{}
}
// GetAvatarUrl returns the gravatar url
func (p *GravatarAvatarProvider) GetAvatarUrl(user *models.User) string {
email := user.Email
email = strings.TrimSpace(email)
email = strings.ToLower(email)
emailMd5 := utils.MD5EncodeToString([]byte(email))
return fmt.Sprintf(gravatarUrlFormat, emailMd5)
}
+20
View File
@@ -0,0 +1,20 @@
package avatars
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/mayswind/ezbookkeeping/pkg/models"
)
func TestGravatarAvatarProvider_GetGravatarUrl(t *testing.T) {
avatarProvider := NewGravatarAvatarProvider()
expectedValue := "https://www.gravatar.com/avatar/0bc83cb571cd1c50ba6f3e8a78ef1346"
actualValue := avatarProvider.GetAvatarUrl(&models.User{
Email: "MyEmailAddress@example.com",
})
assert.Equal(t, expectedValue, actualValue)
}
+31
View File
@@ -0,0 +1,31 @@
package avatars
import (
"fmt"
"github.com/mayswind/ezbookkeeping/pkg/models"
"github.com/mayswind/ezbookkeeping/pkg/settings"
)
const internalAvatarUrlFormat = "%savatar/%d.%s"
// InternalStorageAvatarProvider represents the internal storage avatar provider
type InternalStorageAvatarProvider struct {
webRootUrl string
}
// NewInternalStorageAvatarProvider returns a new internal storage avatar provider
func NewInternalStorageAvatarProvider(config *settings.Config) *InternalStorageAvatarProvider {
return &InternalStorageAvatarProvider{
webRootUrl: config.RootUrl,
}
}
// GetAvatarUrl returns the built-in avatar url
func (p *InternalStorageAvatarProvider) GetAvatarUrl(user *models.User) string {
if user.CustomAvatarType == "" {
return ""
}
return fmt.Sprintf(internalAvatarUrlFormat, p.webRootUrl, user.Uid, user.CustomAvatarType)
}
@@ -0,0 +1,38 @@
package avatars
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/mayswind/ezbookkeeping/pkg/models"
"github.com/mayswind/ezbookkeeping/pkg/settings"
)
func TestInternalStorageAvatarProvider_GetAvatarUrl(t *testing.T) {
avatarProvider := NewInternalStorageAvatarProvider(&settings.Config{
RootUrl: "https://foo.bar/",
})
expectedValue := "https://foo.bar/avatar/1234567890.jpg"
actualValue := avatarProvider.GetAvatarUrl(&models.User{
Uid: 1234567890,
CustomAvatarType: "jpg",
})
assert.Equal(t, expectedValue, actualValue)
}
func TestInternalStorageAvatarProvider_GetAvatarUrl_EmptyCustomAvatarType(t *testing.T) {
avatarProvider := NewInternalStorageAvatarProvider(&settings.Config{
RootUrl: "https://foo.bar/",
})
expectedValue := ""
actualValue := avatarProvider.GetAvatarUrl(&models.User{
Uid: 1234567890,
CustomAvatarType: "",
})
assert.Equal(t, expectedValue, actualValue)
}
+19
View File
@@ -0,0 +1,19 @@
package avatars
import (
"github.com/mayswind/ezbookkeeping/pkg/models"
)
// NullAvatarProvider represents the null avatar provider
type NullAvatarProvider struct {
}
// NewNullAvatarProvider returns a new null avatar provider
func NewNullAvatarProvider() *NullAvatarProvider {
return &NullAvatarProvider{}
}
// GetAvatarUrl returns an empty url
func (p *NullAvatarProvider) GetAvatarUrl(user *models.User) string {
return ""
}
+20
View File
@@ -0,0 +1,20 @@
package avatars
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/mayswind/ezbookkeeping/pkg/models"
)
func TestNullAvatarProvider_GetGravatarUrl(t *testing.T) {
avatarProvider := NewNullAvatarProvider()
expectedValue := ""
actualValue := avatarProvider.GetAvatarUrl(&models.User{
Email: "MyEmailAddress@example.com",
})
assert.Equal(t, expectedValue, actualValue)
}
+4 -14
View File
@@ -252,12 +252,12 @@ func (u *User) CanEditTransactionByTransactionTime(transactionTime int64, utcOff
}
// ToUserBasicInfo returns a user basic view-object according to database model
func (u *User) ToUserBasicInfo(avatarProvider core.UserAvatarProviderType, rootUrl string) *UserBasicInfo {
func (u *User) ToUserBasicInfo(avatarProvider core.UserAvatarProviderType, avatarUrl string) *UserBasicInfo {
return &UserBasicInfo{
Username: u.Username,
Email: u.Email,
Nickname: u.Nickname,
AvatarUrl: u.getAvatarUrl(avatarProvider, rootUrl),
AvatarUrl: avatarUrl,
AvatarProvider: string(avatarProvider),
DefaultAccountId: u.DefaultAccountId,
TransactionEditScope: u.TransactionEditScope,
@@ -279,19 +279,9 @@ func (u *User) ToUserBasicInfo(avatarProvider core.UserAvatarProviderType, rootU
}
// ToUserProfileResponse returns a user profile view-object according to database model
func (u *User) ToUserProfileResponse(avatarProvider core.UserAvatarProviderType, rootUrl string) *UserProfileResponse {
func (u *User) ToUserProfileResponse(basicInfo *UserBasicInfo) *UserProfileResponse {
return &UserProfileResponse{
UserBasicInfo: u.ToUserBasicInfo(avatarProvider, rootUrl),
UserBasicInfo: basicInfo,
LastLoginAt: u.LastLoginUnixTime,
}
}
func (u *User) getAvatarUrl(avatarProvider core.UserAvatarProviderType, rootUrl string) string {
if avatarProvider == core.USER_AVATAR_PROVIDER_INTERNAL {
return utils.GetInternalAvatarUrl(u.Uid, u.CustomAvatarType, rootUrl)
} else if avatarProvider == core.USER_AVATAR_PROVIDER_GRAVATAR {
return utils.GetGravatarUrl(u.Email)
}
return ""
}
-25
View File
@@ -1,25 +0,0 @@
package utils
import (
"fmt"
"strings"
)
const gravatarUrlFormat = "https://www.gravatar.com/avatar/%s"
// GetInternalAvatarUrl returns the internal avatar url
func GetInternalAvatarUrl(uid int64, avatarFileExtension string, webRootUrl string) string {
if avatarFileExtension == "" {
return ""
}
return fmt.Sprintf("%savatar/%d.%s", webRootUrl, uid, avatarFileExtension)
}
// GetGravatarUrl returns the Gravatar url according to the specified user email address
func GetGravatarUrl(email string) string {
email = strings.TrimSpace(email)
email = strings.ToLower(email)
emailMd5 := MD5EncodeToString([]byte(email))
return fmt.Sprintf(gravatarUrlFormat, emailMd5)
}
-24
View File
@@ -1,24 +0,0 @@
package utils
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetInternalAvatarUrl(t *testing.T) {
expectedValue := "https://demo.ezbookkeeping.mayswind.net/avatar/1234567890.jpg"
actualValue := GetInternalAvatarUrl(1234567890, "jpg", "https://demo.ezbookkeeping.mayswind.net/")
assert.Equal(t, expectedValue, actualValue)
expectedValue = ""
actualValue = GetInternalAvatarUrl(1234567890, "", "https://demo.ezbookkeeping.mayswind.net/")
assert.Equal(t, expectedValue, actualValue)
}
func TestGetGravatarUrl(t *testing.T) {
// Reference: https://en.gravatar.com/site/implement/hash/
expectedValue := "https://www.gravatar.com/avatar/0bc83cb571cd1c50ba6f3e8a78ef1346"
actualValue := GetGravatarUrl("MyEmailAddress@example.com")
assert.Equal(t, expectedValue, actualValue)
}