package users import ( "context" "encoding/json" "fmt" "log" "net/http" "reflect" "strings" "time" "fr.latosa-escrima/core" "fr.latosa-escrima/core/models" "fr.latosa-escrima/utils" "github.com/google/uuid" "github.com/uptrace/bun" ) type UpdateUserArgs struct { FirstName *string `json:"firstname,omitempty"` LastName *string `json:"lastname,omitempty"` Email *string `json:"email,omitempty"` Password *string `json:"password,omitempty"` Phone *string `json:"phone,omitempty"` Attributes *models.UserAttributes `json:"attributes"` Roles *[]models.Role `json:"roles"` } func HandleUpdate(w http.ResponseWriter, r *http.Request) { ctx := context.Background() var updateArgs UpdateUserArgs err := json.NewDecoder(r.Body).Decode(&updateArgs) if err != nil { core.JSONError{ Status: core.Error, Message: err.Error(), }.Respond(w, http.StatusInternalServerError) return } user_uuid := r.PathValue("user_uuid") uid, err := uuid.Parse(user_uuid) if err != nil { return } var user models.User err = core.DB. NewSelect(). Model(&user). Where("user_id = ?", user_uuid). Relation("Roles"). Scan(ctx) if err != nil { core.JSONError{ Status: core.Error, Message: err.Error(), }.Respond(w, http.StatusInternalServerError) return } updateQuery := core.DB.NewUpdate().Model(&user) rolesInsert := []*bun.InsertQuery{} rolesRemoved := []*bun.DeleteQuery{} val := reflect.ValueOf(updateArgs) typ := reflect.TypeOf(updateArgs) for i := range val.NumField() { field := val.Field(i) fieldname := typ.Field(i).Name tag := typ.Field(i).Tag.Get("bun") if tag == "" { tag = typ.Field(i).Tag.Get("json") } // Only add fields that are non-nil and non-zero if field.IsValid() && !field.IsNil() && !field.IsZero() { if fieldname == "Password" { updateQuery.Set(fmt.Sprintf("%s = crypt(?, gen_salt('bf'))", strings.Split(tag, ",")[0]), field.Interface()) } else if fieldname == "Roles" { _roles := field.Interface().(*[]models.Role) if _roles == nil { continue } currentRoles := utils.Map(user.Roles, func(role models.Role) uuid.UUID { return role.ID }) roles := utils.Map(*_roles, func(role models.Role) uuid.UUID { return role.ID }) log.Println(user.Roles) toAdd, toRemove := utils.GetDiff(currentRoles, roles) fmt.Println(toAdd, toRemove) rolesInsert = utils.Map(toAdd, func(id uuid.UUID) *bun.InsertQuery { userRole := models.UserToRole{ UserID: uid, RoleID: id, } return core.DB.NewInsert().Model(&userRole).Ignore() }) rolesRemoved = utils.Map(toRemove, func(id uuid.UUID) *bun.DeleteQuery { return core.DB.NewDelete().Model((*models.UserToRole)(nil)). Where("user_id = ? AND role_id = ?", uid, id) }) } else { updateQuery.Set(fmt.Sprintf("%s = ?", strings.Split(tag, ",")[0]), field.Interface()) } } } // Always update the `updated_at` field updateQuery.Set("updated_at = ?", time.Now()) _, err = updateQuery. Where("user_id = ?", user_uuid). Exec(ctx) if err != nil { core.JSONError{ Status: core.Error, Message: err.Error(), }.Respond(w, http.StatusInternalServerError) return } for _, insert := range rolesInsert { _, err = insert.Exec(ctx) if err != nil { core.JSONError{ Status: core.Error, Message: err.Error(), }.Respond(w, http.StatusInternalServerError) return } } for _, remove := range rolesRemoved { _, err = remove.Exec(ctx) if err != nil { core.JSONError{ Status: core.Error, Message: err.Error(), }.Respond(w, http.StatusInternalServerError) return } } user.Password = "" core.JSONSuccess{ Status: core.Success, Message: "User updated.", Data: user, }.Respond(w, http.StatusOK) }