diff --git a/conf/ezbookkeeping.ini b/conf/ezbookkeeping.ini index ec91c24b..66fcda7b 100644 --- a/conf/ezbookkeeping.ini +++ b/conf/ezbookkeeping.ini @@ -106,6 +106,11 @@ request_id_header = true # Set to true to allow users to register account by themselves enable_register = true +# User avatar provider, supports the following types: +# "gravatar": https://gravatar.com +# Leave blank if you want to disable user avatar +avatar_provider = + [data] # Set to true to allow users to export their data enable_export = true diff --git a/pkg/models/user.go b/pkg/models/user.go index 32f9541e..34890738 100644 --- a/pkg/models/user.go +++ b/pkg/models/user.go @@ -4,6 +4,7 @@ import ( "fmt" "time" + "github.com/mayswind/ezbookkeeping/pkg/settings" "github.com/mayswind/ezbookkeeping/pkg/utils" ) @@ -77,6 +78,7 @@ type UserBasicInfo struct { Username string `json:"username"` Email string `json:"email"` Nickname string `json:"nickname"` + AvatarUrl string `json:"avatar"` DefaultAccountId int64 `json:"defaultAccountId,string"` TransactionEditScope TransactionEditScope `json:"transactionEditScope"` Language string `json:"language"` @@ -133,6 +135,7 @@ type UserProfileResponse struct { Username string `json:"username"` Email string `json:"email"` Nickname string `json:"nickname"` + AvatarUrl string `json:"avatar"` DefaultAccountId int64 `json:"defaultAccountId,string"` TransactionEditScope TransactionEditScope `json:"transactionEditScope"` Language string `json:"language"` @@ -193,6 +196,7 @@ func (u *User) ToUserBasicInfo() *UserBasicInfo { Username: u.Username, Email: u.Email, Nickname: u.Nickname, + AvatarUrl: u.getAvatarUrl(), DefaultAccountId: u.DefaultAccountId, TransactionEditScope: u.TransactionEditScope, Language: u.Language, @@ -211,6 +215,7 @@ func (u *User) ToUserProfileResponse() *UserProfileResponse { Username: u.Username, Email: u.Email, Nickname: u.Nickname, + AvatarUrl: u.getAvatarUrl(), DefaultAccountId: u.DefaultAccountId, TransactionEditScope: u.TransactionEditScope, Language: u.Language, @@ -223,3 +228,13 @@ func (u *User) ToUserProfileResponse() *UserProfileResponse { LastLoginAt: u.LastLoginUnixTime, } } + +func (u *User) getAvatarUrl() string { + avatarProvider := settings.Container.Current.AvatarProvider + + if avatarProvider == settings.GravatarProvider { + return utils.GetGravatarUrl(u.Email) + } + + return "" +} diff --git a/pkg/settings/setting.go b/pkg/settings/setting.go index 3c7fea3d..c26a9e4e 100644 --- a/pkg/settings/setting.go +++ b/pkg/settings/setting.go @@ -62,6 +62,11 @@ const ( InternalUuidGeneratorType string = "internal" ) +// User avatar provider types +const ( + GravatarProvider string = "gravatar" +) + // Map provider types const ( OpenStreetMapProvider string = "openstreetmap" @@ -185,6 +190,7 @@ type Config struct { // User EnableUserRegister bool + AvatarProvider string // Data EnableDataExport bool @@ -444,6 +450,12 @@ func loadSecurityConfiguration(config *Config, configFile *ini.File, sectionName func loadUserConfiguration(config *Config, configFile *ini.File, sectionName string) error { config.EnableUserRegister = getConfigItemBoolValue(configFile, sectionName, "enable_register", false) + if getConfigItemStringValue(configFile, sectionName, "avatar_provider") == "" { + config.AvatarProvider = "" + } else if getConfigItemStringValue(configFile, sectionName, "avatar_provider") == GravatarProvider { + config.AvatarProvider = GravatarProvider + } + return nil } diff --git a/pkg/utils/avatar.go b/pkg/utils/avatar.go new file mode 100644 index 00000000..bb0bb313 --- /dev/null +++ b/pkg/utils/avatar.go @@ -0,0 +1,11 @@ +package utils + +import "fmt" + +const gravatarUrlFormat = "https://www.gravatar.com/avatar/%s" + +// GetGravatarUrl returns the Gravatar url according to the specified user email address +func GetGravatarUrl(email string) string { + emailMd5 := MD5EncodeToString([]byte(email)) + return fmt.Sprintf(gravatarUrlFormat, emailMd5) +} diff --git a/pkg/utils/strings.go b/pkg/utils/strings.go index fce44246..5674d06f 100644 --- a/pkg/utils/strings.go +++ b/pkg/utils/strings.go @@ -7,6 +7,7 @@ import ( "crypto/rand" "crypto/sha256" "encoding/base64" + "encoding/hex" "strings" "unicode" @@ -133,6 +134,12 @@ func MD5Encode(data []byte) []byte { return m.Sum(nil) } +// MD5EncodeToString returns a hashed string by md5 +func MD5EncodeToString(data []byte) string { + hash := MD5Encode(data) + return hex.EncodeToString(hash) +} + // AESGCMEncrypt returns a encrypted string by aes-gcm func AESGCMEncrypt(key []byte, plainText []byte) ([]byte, error) { block, err := aes.NewCipher(key) diff --git a/src/stores/user.js b/src/stores/user.js index f495387a..bff0c1f9 100644 --- a/src/stores/user.js +++ b/src/stores/user.js @@ -16,6 +16,10 @@ export const useUserStore = defineStore('user', { const userInfo = state.currentUserInfo || {}; return userInfo.nickname || userInfo.username || null; }, + currentUserAvatar(state) { + const userInfo = state.currentUserInfo || {}; + return userInfo.avatar || null; + }, currentUserDefaultAccountId(state) { const userInfo = state.currentUserInfo || {}; return userInfo.defaultAccountId || ''; diff --git a/src/views/desktop/MainLayout.vue b/src/views/desktop/MainLayout.vue index df156ebd..d98eedb5 100644 --- a/src/views/desktop/MainLayout.vue +++ b/src/views/desktop/MainLayout.vue @@ -113,14 +113,16 @@ - + + @@ -264,6 +266,9 @@ export default { currentNickName() { return this.userStore.currentUserNickname || this.$t('User'); }, + currentUserAvatar() { + return this.userStore.currentUserAvatar; + }, theme: { get: function () { return this.settingsStore.appSettings.theme;