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;