137 lines
4.4 KiB
Go
137 lines
4.4 KiB
Go
package auth_service
|
|
|
|
import (
|
|
"bytes"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/XingfenD/yoresee_doc/internal/config"
|
|
"github.com/XingfenD/yoresee_doc/internal/dto"
|
|
"github.com/XingfenD/yoresee_doc/internal/media"
|
|
"github.com/XingfenD/yoresee_doc/internal/status"
|
|
"github.com/XingfenD/yoresee_doc/internal/utils"
|
|
"github.com/XingfenD/yoresee_doc/pkg/storage"
|
|
)
|
|
|
|
func (s *AuthService) UpdateProfile(userExternalID string, req *dto.UpdateProfileRequest) (*dto.UserResponse, error) {
|
|
if strings.TrimSpace(userExternalID) == "" || req == nil {
|
|
return nil, status.GenErrWithCustomMsg(status.StatusParamError, "user_external_id and request are required")
|
|
}
|
|
if req.Username == nil && req.Email == nil && req.Nickname == nil && req.Password == nil && req.AvatarFile == nil {
|
|
return nil, status.GenErrWithCustomMsg(status.StatusParamError, "no profile fields to update")
|
|
}
|
|
|
|
user, err := s.userRepo.GetByExternalID(userExternalID).Exec()
|
|
if err != nil {
|
|
return nil, status.GenErrWithCustomMsg(status.StatusUserNotFound, "user not found")
|
|
}
|
|
|
|
if req.Username != nil {
|
|
username := strings.TrimSpace(*req.Username)
|
|
if username == "" {
|
|
return nil, status.GenErrWithCustomMsg(status.StatusParamError, "username cannot be empty")
|
|
}
|
|
user.Username = username
|
|
}
|
|
|
|
if req.Email != nil {
|
|
email := strings.TrimSpace(*req.Email)
|
|
if email == "" {
|
|
return nil, status.GenErrWithCustomMsg(status.StatusParamError, "email cannot be empty")
|
|
}
|
|
existingUser, getErr := s.userRepo.GetByEmail(email).Exec()
|
|
if getErr == nil && existingUser.ID != user.ID {
|
|
return nil, status.GenErrWithCustomMsg(status.StatusUserAlreadyExists, "email already registered")
|
|
}
|
|
user.Email = email
|
|
}
|
|
|
|
if req.Nickname != nil {
|
|
user.Nickname = strings.TrimSpace(*req.Nickname)
|
|
}
|
|
|
|
if req.Password != nil {
|
|
password := strings.TrimSpace(*req.Password)
|
|
if password == "" {
|
|
return nil, status.GenErrWithCustomMsg(status.StatusParamError, "password cannot be empty")
|
|
}
|
|
hashedPwd, hashErr := utils.HashPassword(password)
|
|
if hashErr != nil {
|
|
return nil, status.GenErrWithCustomMsg(status.StatusServiceInternalError, "hash password failed")
|
|
}
|
|
user.PasswordHash = hashedPwd
|
|
}
|
|
|
|
var oldAvatarObjectKey string
|
|
if req.AvatarFile != nil {
|
|
oldAvatarObjectKey = user.AvatarObjectKey
|
|
avatarObjectKey, nextVersion, uploadErr := uploadAvatar(
|
|
user.ExternalID,
|
|
user.AvatarVersion+1,
|
|
req.AvatarFile,
|
|
req.AvatarFilename,
|
|
req.AvatarContentType,
|
|
)
|
|
if uploadErr != nil {
|
|
return nil, status.GenErrWithCustomMsg(uploadErr, "upload avatar failed")
|
|
}
|
|
now := time.Now()
|
|
user.AvatarObjectKey = avatarObjectKey
|
|
user.AvatarVersion = nextVersion
|
|
user.AvatarUpdatedAt = &now
|
|
user.Avatar = ""
|
|
}
|
|
|
|
if err := s.userRepo.Update(user).Exec(); err != nil {
|
|
return nil, status.StatusWriteDBError
|
|
}
|
|
|
|
if oldAvatarObjectKey != "" && oldAvatarObjectKey != user.AvatarObjectKey {
|
|
_ = storage.DeleteFile(config.GlobalConfig.Minio.Bucket, oldAvatarObjectKey)
|
|
}
|
|
|
|
resp := dto.NewUserResponseFromModel(user)
|
|
resp.Avatar = media.BuildAvatarURL(resp.ExternalID, resp.AvatarObjectKey, resp.AvatarVersion)
|
|
return resp, nil
|
|
}
|
|
|
|
func uploadAvatar(userExternalID string, avatarVersion int64, avatarFile []byte, filename, contentType *string) (string, int64, error) {
|
|
if len(avatarFile) == 0 {
|
|
return "", 0, status.GenErrWithCustomMsg(status.StatusParamError, "avatar file is empty")
|
|
}
|
|
if len(avatarFile) > media.MaxAvatarSize {
|
|
return "", 0, status.GenErrWithCustomMsg(status.StatusParamError, "avatar file exceeds 5MB limit")
|
|
}
|
|
if storage.MinioClient == nil {
|
|
return "", 0, status.GenErrWithCustomMsg(status.StatusServiceInternalError, "minio client is not initialized")
|
|
}
|
|
|
|
fileName := "avatar"
|
|
if filename != nil {
|
|
fileName = strings.TrimSpace(*filename)
|
|
}
|
|
|
|
fileContentType := "application/octet-stream"
|
|
if contentType != nil {
|
|
fileContentType = strings.TrimSpace(*contentType)
|
|
}
|
|
fileContentType = media.NormalizeAvatarContentType(avatarFile, fileContentType)
|
|
if !media.IsSupportedAvatarContentType(fileContentType) {
|
|
return "", 0, status.GenErrWithCustomMsg(status.StatusParamError, "avatar content type is not supported")
|
|
}
|
|
|
|
objectName := media.BuildAvatarObjectKey(userExternalID, avatarVersion, media.ResolveAvatarExt(fileName, fileContentType))
|
|
|
|
err := storage.PutFile(
|
|
config.GlobalConfig.Minio.Bucket,
|
|
objectName,
|
|
bytes.NewReader(avatarFile),
|
|
int64(len(avatarFile)),
|
|
fileContentType,
|
|
)
|
|
if err != nil {
|
|
return "", 0, err
|
|
}
|
|
return objectName, avatarVersion, nil
|
|
}
|