go: add PATCH /api/v1/users/me user settings update (#15297)

### What problem does this PR solve?

- Add Go implementation parity for `PATCH /api/v1/users/me`.

- This updates the Go user settings endpoint to match the Python
behavior for updating the current user's profile settings.

### Changes

- Route `PATCH /api/v1/users/me` through the authenticated current user
from middleware.
- Add `password` and `new_password` support to `UpdateSettingsRequest`.
- Prevent `email` from being updated through this endpoint, matching the
Python blacklist behavior.
  - Support updating:
    - `nickname`
    - `avatar`
    - `language`
    - `color_schema`
    - `timezone`
    - `password`
  - Align password handling with Python:
    - invalid plaintext password payload returns `CodeExceptionError`
    - wrong old password returns `Password error!`
- successful update returns `{ code: 0, data: true, message: "success"
}`

### Test

Tested manually with Python and Go backends using the same request
bodies:

  - `PATCH /api/v1/users/me` with nickname/timezone update
- plaintext password payload returns Python-compatible `Incorrect
padding`
  - wrong old password returns `Password error!`
This commit is contained in:
Hz_
2026-05-28 07:08:50 +08:00
committed by GitHub
parent f0cb7a544b
commit b472ceeb68
2 changed files with 51 additions and 26 deletions

View File

@@ -411,27 +411,11 @@ func (h *UserHandler) Info(c *gin.Context) {
// @Security ApiKeyAuth
// @Param request body service.UpdateSettingsRequest true "user settings"
// @Success 200 {object} map[string]interface{}
// @Router /v1/user/setting [post]
// @Router /api/v1/users/me [patch]
func (h *UserHandler) Setting(c *gin.Context) {
// Extract token from request
token := c.GetHeader("Authorization")
if token == "" {
c.JSON(http.StatusOK, gin.H{
"code": common.CodeUnauthorized,
"message": "Missing Authorization header",
"data": false,
})
return
}
// Get user by token
user, code, err := h.userService.GetUserByToken(token)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"code": code,
"message": err.Error(),
"data": false,
})
user, errorCode, errorMessage := GetUser(c)
if errorCode != common.CodeSuccess {
jsonError(c, errorCode, errorMessage)
return
}
@@ -447,8 +431,16 @@ func (h *UserHandler) Setting(c *gin.Context) {
}
// Update user settings
code, err = h.userService.UpdateUserSettings(user, &req)
code, err := h.userService.UpdateUserSettings(user, &req)
if err != nil {
if code == common.CodeExceptionError {
c.JSON(http.StatusOK, gin.H{
"code": code,
"message": err.Error(),
"data": nil,
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": code,
"message": err.Error(),
@@ -459,7 +451,7 @@ func (h *UserHandler) Setting(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"code": common.CodeSuccess,
"message": "settings updated successfully",
"message": "success",
"data": true,
})
}

View File

@@ -80,11 +80,12 @@ type EmailLoginRequest struct {
// UpdateSettingsRequest update user settings request
type UpdateSettingsRequest struct {
Nickname *string `json:"nickname,omitempty"`
Email *string `json:"email,omitempty" binding:"omitempty,email"`
Avatar *string `json:"avatar,omitempty"`
Language *string `json:"language,omitempty"`
ColorSchema *string `json:"color_schema,omitempty"`
Timezone *string `json:"timezone,omitempty"`
Password *string `json:"password,omitempty"`
NewPassword *string `json:"new_password,omitempty"`
}
// ChangePasswordRequest change password request
@@ -765,12 +766,44 @@ func (s *UserService) GetUserProfile(user *entity.User) map[string]interface{} {
// UpdateUserSettings updates user settings
func (s *UserService) UpdateUserSettings(user *entity.User, req *UpdateSettingsRequest) (common.ErrorCode, error) {
// Update fields if provided
if req.Password != nil {
ciphertext, err := base64.StdEncoding.DecodeString(*req.Password)
if err != nil {
return common.CodeExceptionError, fmt.Errorf("Error('Incorrect padding')")
}
privateKey, err := s.loadPrivateKey()
if err != nil {
return common.CodeExceptionError, err
}
oldPasswordBytes, err := rsa.DecryptPKCS1v15(nil, privateKey, ciphertext)
oldPassword := "Fail to decrypt password!"
if err == nil {
oldPassword = string(oldPasswordBytes)
}
if user.Password == nil || !s.VerifyPassword(*user.Password, oldPassword) {
return common.CodeAuthenticationError, fmt.Errorf("Password error!")
}
if req.NewPassword != nil {
ciphertext, err := base64.StdEncoding.DecodeString(*req.NewPassword)
if err != nil {
return common.CodeExceptionError, fmt.Errorf("Error('Incorrect padding')")
}
newPasswordBytes, err := rsa.DecryptPKCS1v15(nil, privateKey, ciphertext)
if err != nil {
return common.CodeExceptionError, err
}
hashedPassword, err := s.HashPassword(string(newPasswordBytes))
if err != nil {
return common.CodeExceptionError, err
}
user.Password = &hashedPassword
}
}
if req.Nickname != nil {
user.Nickname = *req.Nickname
}
if req.Email != nil {
user.Email = *req.Email
}
if req.Avatar != nil {
// In Go version, avatar might be stored differently
// For now, just update if field exists