Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

misc: patch api for user attribute #6490

Merged
merged 3 commits into from
Apr 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 42 additions & 47 deletions api/restHandler/UserAttributesRestHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
type UserAttributesRestHandler interface {
AddUserAttributes(w http.ResponseWriter, r *http.Request)
UpdateUserAttributes(w http.ResponseWriter, r *http.Request)
PatchUserAttributes(w http.ResponseWriter, r *http.Request)
GetUserAttribute(w http.ResponseWriter, r *http.Request)
}

Expand All @@ -54,35 +55,13 @@ func NewUserAttributesRestHandlerImpl(logger *zap.SugaredLogger, enforcer casbin
}

func (handler *UserAttributesRestHandlerImpl) AddUserAttributes(w http.ResponseWriter, r *http.Request) {
userId, err := handler.userService.GetLoggedInUser(r)
if userId == 0 || err != nil {
common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized)
return
}
decoder := json.NewDecoder(r.Body)
var dto attributes.UserAttributesDto
err = decoder.Decode(&dto)
if err != nil {
handler.logger.Errorw("request err, AddUserAttributes", "err", err, "payload", dto)
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
dto, success := handler.validateUserAttributesRequest(w, r, "PatchUserAttributes")
if !success {
return
}

dto.UserId = userId
//if ok := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionCreate, "*"); !ok {
// common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden)
// return
//}
emailId, err := handler.userService.GetActiveEmailById(userId)
if err != nil {
handler.logger.Errorw("request err, UpdateUserAttributes", "err", err, "payload", dto)
common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden)
return
}
dto.EmailId = emailId

handler.logger.Infow("request payload, AddUserAttributes", "payload", dto)
resp, err := handler.userAttributesService.AddUserAttributes(&dto)
resp, err := handler.userAttributesService.AddUserAttributes(dto)
if err != nil {
handler.logger.Errorw("service err, AddUserAttributes", "err", err, "payload", dto)
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
Expand All @@ -98,43 +77,64 @@ func (handler *UserAttributesRestHandlerImpl) AddUserAttributes(w http.ResponseW
// @Success 200 {object} attributes.UserAttributesDto
// @Router /orchestrator/attributes/user/update [POST]
func (handler *UserAttributesRestHandlerImpl) UpdateUserAttributes(w http.ResponseWriter, r *http.Request) {
dto, success := handler.validateUserAttributesRequest(w, r, "PatchUserAttributes")
if !success {
return
}

handler.logger.Infow("request payload, UpdateUserAttributes", "payload", dto)
resp, err := handler.userAttributesService.UpdateUserAttributes(dto)
if err != nil {
handler.logger.Errorw("service err, UpdateUserAttributes", "err", err, "payload", dto)
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
return
}
common.WriteJsonResp(w, nil, resp, http.StatusOK)
}

func (handler *UserAttributesRestHandlerImpl) PatchUserAttributes(w http.ResponseWriter, r *http.Request) {
dto, success := handler.validateUserAttributesRequest(w, r, "PatchUserAttributes")
if !success {
return
}

handler.logger.Infow("request payload, PatchUserAttributes", "payload", dto)
resp, err := handler.userAttributesService.PatchUserAttributes(dto)
if err != nil {
handler.logger.Errorw("service err, PatchUserAttributes", "err", err, "payload", dto)
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
return
}
common.WriteJsonResp(w, nil, resp, http.StatusOK)
}

func (handler *UserAttributesRestHandlerImpl) validateUserAttributesRequest(w http.ResponseWriter, r *http.Request, operation string) (*attributes.UserAttributesDto, bool) {
userId, err := handler.userService.GetLoggedInUser(r)
if userId == 0 || err != nil {
common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized)
return
return nil, false
}

decoder := json.NewDecoder(r.Body)
var dto attributes.UserAttributesDto
err = decoder.Decode(&dto)
if err != nil {
handler.logger.Errorw("request err, UpdateUserAttributes", "err", err, "payload", dto)
handler.logger.Errorw("request err, "+operation, "err", err, "payload", dto)
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
return
return nil, false
}

dto.UserId = userId
//if ok := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionUpdate, "*"); !ok {
// common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden)
// return
//}

emailId, err := handler.userService.GetActiveEmailById(userId)
if err != nil {
handler.logger.Errorw("request err, UpdateUserAttributes", "err", err, "payload", dto)
handler.logger.Errorw("request err, "+operation, "err", err, "payload", dto)
common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden)
return
return nil, false
}
dto.EmailId = emailId

handler.logger.Infow("request payload, UpdateUserAttributes", "payload", dto)
resp, err := handler.userAttributesService.UpdateUserAttributes(&dto)
if err != nil {
handler.logger.Errorw("service err, UpdateUserAttributes", "err", err, "payload", dto)
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
return
}
common.WriteJsonResp(w, nil, resp, http.StatusOK)
return &dto, true
}

// @Summary get user attributes
Expand All @@ -158,11 +158,6 @@ func (handler *UserAttributesRestHandlerImpl) GetUserAttribute(w http.ResponseWr
return
}

//if ok := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*"); !ok {
// common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden)
// return
//}

dto := attributes.UserAttributesDto{}

emailId, err := handler.userService.GetActiveEmailById(userId)
Expand Down
2 changes: 2 additions & 0 deletions api/router/UserAttributesRouter.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ func NewUserAttributesRouterImpl(userAttributesRestHandler user.UserAttributesRe
func (router UserAttributesRouterImpl) InitUserAttributesRouter(attributesRouter *mux.Router) {
attributesRouter.Path("/update").
HandlerFunc(router.userAttributesRestHandler.UpdateUserAttributes).Methods("POST")
attributesRouter.Path("/patch").
HandlerFunc(router.userAttributesRestHandler.PatchUserAttributes).Methods("PATCH")
attributesRouter.Path("/get").
HandlerFunc(router.userAttributesRestHandler.GetUserAttribute).Queries("key", "{key}").Methods("GET")
}
92 changes: 92 additions & 0 deletions pkg/attributes/UserAttributesService.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,18 @@
package attributes

import (
"encoding/json"
"errors"
"github.com/devtron-labs/devtron/internal/sql/repository"
"github.com/go-pg/pg"
"go.uber.org/zap"
"reflect"
)

type UserAttributesService interface {
AddUserAttributes(request *UserAttributesDto) (*UserAttributesDto, error)
UpdateUserAttributes(request *UserAttributesDto) (*UserAttributesDto, error)
PatchUserAttributes(request *UserAttributesDto) (*UserAttributesDto, error)
GetUserAttribute(request *UserAttributesDto) (*UserAttributesDto, error)
}

Expand Down Expand Up @@ -95,6 +98,95 @@ func (impl UserAttributesServiceImpl) UpdateUserAttributes(request *UserAttribut
return request, nil
}

func (impl UserAttributesServiceImpl) PatchUserAttributes(request *UserAttributesDto) (*UserAttributesDto, error) {
userAttribute, err := impl.GetUserAttribute(request)
if err != nil {
impl.logger.Errorw("error while getting user attributes during patch request", "req", request, "error", err)
return nil, errors.New("error occurred while updating user attributes")
}
if userAttribute == nil {
impl.logger.Info("no data found for request, so going to add instead of update", "req", request)
attributes, err := impl.AddUserAttributes(request)
if err != nil {
impl.logger.Errorw("error in adding new user attributes", "req", request, "error", err)
return nil, errors.New("error occurred while updating user attributes")
}
return attributes, nil
}

// Parse existing JSON
var existingData map[string]interface{}
if userAttribute.Value != "" {
err = json.Unmarshal([]byte(userAttribute.Value), &existingData)
if err != nil {
impl.logger.Errorw("error parsing existing json value", "value", userAttribute.Value, "error", err)
return nil, errors.New("error occurred while updating user attributes")
}
} else {
existingData = make(map[string]interface{})
}

// Parse new JSON
var newData map[string]interface{}
if request.Value != "" {
err = json.Unmarshal([]byte(request.Value), &newData)
if err != nil {
impl.logger.Errorw("error parsing request json value", "value", request.Value, "error", err)
return nil, errors.New("error occurred while updating user attributes")
}
} else {
newData = make(map[string]interface{})
}

// Check if there are any changes
anyChanges := false

// Merge the objects (patch style)
for key, newValue := range newData {
existingValue, exists := existingData[key]
if !exists || !reflect.DeepEqual(existingValue, newValue) {
existingData[key] = newValue
anyChanges = true
}
}

// If no changes, return the existing data
if !anyChanges {
impl.logger.Infow("no change detected, skipping update", "key", request.Key)
return userAttribute, nil
}

// Convert back to JSON string
mergedJson, err := json.Marshal(existingData)
if err != nil {
impl.logger.Errorw("error converting merged data to json", "data", existingData, "error", err)
return nil, errors.New("error occurred while updating user attributes")
}

dao := &repository.UserAttributesDao{
EmailId: request.EmailId,
Key: request.Key,
Value: string(mergedJson),
UserId: request.UserId,
}

err = impl.attributesRepository.UpdateDataValByKey(dao)
if err != nil {
impl.logger.Errorw("error in update attributes", "req", dao, "error", err)
return nil, errors.New("error occurred while updating user attributes")
}

// Return the updated data
result := &UserAttributesDto{
EmailId: request.EmailId,
Key: request.Key,
Value: string(mergedJson),
UserId: request.UserId,
}

return result, nil
}

func (impl UserAttributesServiceImpl) GetUserAttribute(request *UserAttributesDto) (*UserAttributesDto, error) {

dao := &repository.UserAttributesDao{
Expand Down