feat: add permanent avatar switch to organization settings (#5295)

This commit is contained in:
Modo
2026-03-21 09:21:11 +08:00
committed by GitHub
parent 1260db8c27
commit d23e8b205b
3 changed files with 61 additions and 3 deletions

View File

@@ -67,6 +67,7 @@ type Organization struct {
PasswordExpireDays int `json:"passwordExpireDays"`
CountryCodes []string `xorm:"mediumtext" json:"countryCodes"`
DefaultAvatar string `xorm:"varchar(200)" json:"defaultAvatar"`
UsePermanentAvatar bool `xorm:"bool" json:"usePermanentAvatar"`
DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"`
UserTypes []string `xorm:"mediumtext" json:"userTypes"`
Tags []string `xorm:"mediumtext" json:"tags"`

View File

@@ -249,9 +249,17 @@ func SetUserOAuthProperties(organization *Organization, user *User, providerType
if userInfo.AvatarUrl != "" {
propertyName := fmt.Sprintf("oauth_%s_avatarUrl", providerType)
setUserProperty(user, propertyName, userInfo.AvatarUrl)
if user.Avatar == "" || user.Avatar == organization.DefaultAvatar {
user.Avatar = userInfo.AvatarUrl
if organization.UsePermanentAvatar {
err := syncOAuthAvatarToPermanentStorage(organization, user, propertyName, userInfo.AvatarUrl)
if err != nil {
return false, err
}
} else {
setUserProperty(user, propertyName, userInfo.AvatarUrl)
if user.Avatar == "" || user.Avatar == organization.DefaultAvatar {
user.Avatar = userInfo.AvatarUrl
}
}
}
@@ -284,6 +292,45 @@ func SetUserOAuthProperties(organization *Organization, user *User, providerType
return UpdateUserForAllFields(user.GetId(), user)
}
// syncOAuthAvatarToPermanentStorage ensures the user's avatar is stored in permanent storage.
// It checks whether a permanent avatar already exists for the given sourceAvatarURL.
// If not, it uploads the avatar and retrieves a permanent URL.
// Finally, it updates the user's avatar fields with the resolved permanent URL.
func syncOAuthAvatarToPermanentStorage(organization *Organization, user *User, propertyName, sourceAvatarUrl string) error {
oldAvatarUrl := getUserProperty(user, propertyName)
avatarUrl := sourceAvatarUrl
permanentAvatarUrl, err := getPermanentAvatarUrl(user.Owner, user.Name, sourceAvatarUrl, false)
if err != nil {
return err
}
if permanentAvatarUrl != "" {
avatarUrl = permanentAvatarUrl
if oldAvatarUrl != permanentAvatarUrl {
avatarUrl, err = getPermanentAvatarUrl(user.Owner, user.Name, sourceAvatarUrl, true)
if err != nil {
return err
}
if avatarUrl == "" {
avatarUrl = permanentAvatarUrl
}
}
}
setUserProperty(user, propertyName, avatarUrl)
if user.Avatar == "" ||
user.Avatar == organization.DefaultAvatar ||
user.Avatar == sourceAvatarUrl ||
(oldAvatarUrl != "" && user.Avatar == oldAvatarUrl) {
user.Avatar = avatarUrl
}
return nil
}
func applyUserMapping(user *User, extraClaims map[string]string, userMapping map[string]string) {
// Map of user fields that can be set from IDP claims
for userField, claimName := range userMapping {

View File

@@ -633,6 +633,16 @@ class OrganizationEditPage extends React.Component {
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("organization:Use permanent avatar"), i18next.t("organization:Use permanent avatar - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.organization.usePermanentAvatar} onChange={checked => {
this.updateOrganizationField("usePermanentAvatar", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("organization:Admin navbar items"), i18next.t("organization:Admin navbar items - Tooltip"))} :