This is an automated email from the ASF dual-hosted git repository.

juzhiyuan pushed a commit to branch refactor
in repository https://gitbox.apache.org/repos/asf/apisix-dashboard.git


The following commit(s) were added to refs/heads/refactor by this push:
     new 0bbcc75  feat: compatible with PUT method of `admin api` and nodes of 
upstream (#561)
0bbcc75 is described below

commit 0bbcc7534e0ab570e7d876291301d79e6b5f93bf
Author: nic-chen <33000667+nic-c...@users.noreply.github.com>
AuthorDate: Mon Oct 19 09:52:28 2020 +0800

    feat: compatible with PUT method of `admin api` and nodes of upstream (#561)
    
    * feat: support labels
    
    * feat: compatible with PUT method of `admin api`
    
    * fix mock test fail
    
    * feat: upstream nodes format
    
    * test: add test case
    
    * fix code style
    
    * fix: update schema sync tool
---
 api/build-tools/schema-sync.lua                |  1 +
 api/internal/core/entity/entity.go             | 85 ++++++++++++++++++--------
 api/internal/core/entity/format.go             | 52 ++++++++++++++++
 api/internal/core/entity/format_test.go        | 39 ++++++++++++
 api/internal/core/store/store.go               | 42 ++++++-------
 api/internal/core/store/store_test.go          |  8 +--
 api/internal/handler/consumer/consumer.go      | 10 ++-
 api/internal/handler/consumer/consumer_test.go | 29 +++++++++
 api/internal/handler/route/route.go            | 15 ++++-
 api/internal/handler/route/route_test.go       |  6 +-
 api/internal/handler/service/service.go        | 15 ++++-
 api/internal/handler/ssl/ssl.go                |  6 +-
 api/internal/handler/upstream/upstream.go      | 15 ++++-
 13 files changed, 256 insertions(+), 67 deletions(-)

diff --git a/api/build-tools/schema-sync.lua b/api/build-tools/schema-sync.lua
index 596f6d8..257d220 100644
--- a/api/build-tools/schema-sync.lua
+++ b/api/build-tools/schema-sync.lua
@@ -46,6 +46,7 @@ local fake_module_list = {
     'resty.openidc',
     'resty.random',
     'resty.redis',
+    'resty.rediscluster',
     'resty.signal',
     'resty.string',
 
diff --git a/api/internal/core/entity/entity.go 
b/api/internal/core/entity/entity.go
index 06f4d31..c15a5d5 100644
--- a/api/internal/core/entity/entity.go
+++ b/api/internal/core/entity/entity.go
@@ -16,6 +16,12 @@
  */
 package entity
 
+import (
+       "time"
+
+       "github.com/apisix/manager-api/internal/utils"
+)
+
 type BaseInfo struct {
        ID         string `json:"id"`
        CreateTime int64  `json:"create_time"`
@@ -26,6 +32,24 @@ func (info *BaseInfo) GetBaseInfo() *BaseInfo {
        return info
 }
 
+func (info *BaseInfo) Creating() {
+       if info.ID == "" {
+               info.ID = utils.GetFlakeUidStr()
+       }
+       info.CreateTime = time.Now().Unix()
+       info.UpdateTime = time.Now().Unix()
+}
+
+func (info *BaseInfo) Updating(storedInfo *BaseInfo) {
+       info.ID = storedInfo.ID
+       info.CreateTime = storedInfo.CreateTime
+       info.UpdateTime = time.Now().Unix()
+}
+
+type BaseInfoSetter interface {
+       GetBaseInfo() *BaseInfo
+}
+
 type BaseInfoGetter interface {
        GetBaseInfo() *BaseInfo
 }
@@ -46,10 +70,11 @@ type Route struct {
        FilterFunc      string                 `json:"filter_func,omitempty"`
        Script          interface{}            `json:"script,omitempty"`
        Plugins         map[string]interface{} `json:"plugins,omitempty"`
-       Upstream        interface{}            `json:"upstream,omitempty"`
+       Upstream        *UpstreamDef           `json:"upstream,omitempty"`
        ServiceID       string                 `json:"service_id,omitempty"`
        UpstreamID      string                 `json:"upstream_id,omitempty"`
        ServiceProtocol string                 
`json:"service_protocol,omitempty"`
+       Labels          map[string]string      `json:"labels,omitempty"`
 }
 
 // --- structures for upstream start  ---
@@ -112,22 +137,27 @@ type HealthChecker struct {
        Passive Passive `json:"passive,omitempty"`
 }
 
+type UpstreamDef struct {
+       Nodes           interface{}       `json:"nodes,omitempty"`
+       Retries         int               `json:"retries,omitempty"`
+       Timeout         interface{}       `json:"timeout,omitempty"`
+       K8sInfo         interface{}       `json:"k8s_deployment_info,omitempty"`
+       Type            string            `json:"type,omitempty"`
+       Checks          interface{}       `json:"checks,omitempty"`
+       HashOn          string            `json:"hash_on,omitempty"`
+       Key             string            `json:"key,omitempty"`
+       EnableWebsocket bool              `json:"enable_websocket,omitempty"`
+       PassHost        string            `json:"pass_host,omitempty"`
+       UpstreamHost    string            `json:"upstream_host,omitempty"`
+       Name            string            `json:"name,omitempty"`
+       Desc            string            `json:"desc,omitempty"`
+       ServiceName     string            `json:"service_name,omitempty"`
+       Labels          map[string]string `json:"labels,omitempty"`
+}
+
 type Upstream struct {
        BaseInfo
-       Nodes           []interface{} `json:"nodes,omitempty"`
-       Retries         int           `json:"retries,omitempty"`
-       Timeout         interface{}   `json:"timeout,omitempty"`
-       K8sInfo         interface{}   `json:"k8s_deployment_info,omitempty"`
-       Type            string        `json:"type,omitempty"`
-       Checks          interface{}   `json:"checks,omitempty"`
-       HashOn          string        `json:"hash_on,omitempty"`
-       Key             string        `json:"key,omitempty"`
-       EnableWebsocket bool          `json:"enable_websocket,omitempty"`
-       PassHost        string        `json:"pass_host,omitempty"`
-       UpstreamHost    string        `json:"upstream_host,omitempty"`
-       Name            string        `json:"name,omitempty"`
-       Desc            string        `json:"desc,omitempty"`
-       ServiceName     string        `json:"service_name,omitempty"`
+       UpstreamDef
 }
 
 type UpstreamNameResponse struct {
@@ -150,30 +180,33 @@ type Consumer struct {
        Username string                 `json:"username"`
        Desc     string                 `json:"desc,omitempty"`
        Plugins  map[string]interface{} `json:"plugins,omitempty"`
+       Labels   map[string]string      `json:"labels,omitempty"`
 }
 
 type SSL struct {
        BaseInfo
-       Cert          string   `json:"cert,omitempty"`
-       Key           string   `json:"key,omitempty"`
-       Sni           string   `json:"sni,omitempty"`
-       Snis          []string `json:"snis,omitempty"`
-       Certs         []string `json:"certs,omitempty"`
-       Keys          []string `json:"keys,omitempty"`
-       ExpTime       int64    `json:"exptime,omitempty"`
-       Status        int      `json:"status"`
-       ValidityStart int64    `json:"validity_start,omitempty"`
-       ValidityEnd   int64    `json:"validity_end,omitempty"`
+       Cert          string            `json:"cert,omitempty"`
+       Key           string            `json:"key,omitempty"`
+       Sni           string            `json:"sni,omitempty"`
+       Snis          []string          `json:"snis,omitempty"`
+       Certs         []string          `json:"certs,omitempty"`
+       Keys          []string          `json:"keys,omitempty"`
+       ExpTime       int64             `json:"exptime,omitempty"`
+       Status        int               `json:"status"`
+       ValidityStart int64             `json:"validity_start,omitempty"`
+       ValidityEnd   int64             `json:"validity_end,omitempty"`
+       Labels        map[string]string `json:"labels,omitempty"`
 }
 
 type Service struct {
        BaseInfo
        Name       string                 `json:"name,omitempty"`
        Desc       string                 `json:"desc,omitempty"`
-       Upstream   interface{}            `json:"upstream,omitempty"`
+       Upstream   *UpstreamDef           `json:"upstream,omitempty"`
        UpstreamID string                 `json:"upstream_id,omitempty"`
        Plugins    map[string]interface{} `json:"plugins,omitempty"`
        Script     string                 `json:"script,omitempty"`
+       Labels     map[string]string      `json:"labels,omitempty"`
 }
 
 type Script struct {
diff --git a/api/internal/core/entity/format.go 
b/api/internal/core/entity/format.go
new file mode 100644
index 0000000..0c5d8d1
--- /dev/null
+++ b/api/internal/core/entity/format.go
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package entity
+
+import (
+       "log"
+       "strconv"
+       "strings"
+)
+
+func NodesFormat(obj interface{}) interface{} {
+       if value, ok := obj.(map[string]float64); ok {
+               var nodes []*Node
+               var strArr []string
+               for key, val := range value {
+                       node := &Node{}
+                       strArr = strings.Split(key, ":")
+                       if len(strArr) != 2 {
+                               log.Println("length of string array is not 2")
+                               return obj
+                       }
+
+                       port, err := strconv.Atoi(strArr[1])
+                       if err != nil {
+                               log.Println("parse int fail:", err)
+                               return obj
+                       }
+
+                       node.Host = strArr[0]
+                       node.Port = port
+                       node.Weight = int(val)
+                       nodes = append(nodes, node)
+               }
+               return nodes
+       }
+
+       return obj
+}
diff --git a/api/internal/core/entity/format_test.go 
b/api/internal/core/entity/format_test.go
new file mode 100644
index 0000000..cc51bf2
--- /dev/null
+++ b/api/internal/core/entity/format_test.go
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package entity
+
+import (
+       "encoding/json"
+       "testing"
+
+       "github.com/stretchr/testify/assert"
+)
+
+func TestConsumer(t *testing.T) {
+       nodesStr := `{
+    "127.0.0.1:8080": 1
+  }`
+       nodesMap := map[string]float64{}
+       json.Unmarshal([]byte(nodesStr), &nodesMap)
+       res := NodesFormat(nodesMap)
+       nodes := res.([]*Node)
+
+       assert.Equal(t, 1, len(nodes))
+       assert.Equal(t, "127.0.0.1", nodes[0].Host)
+       assert.Equal(t, 8080, nodes[0].Port)
+       assert.Equal(t, 1, nodes[0].Weight)
+}
diff --git a/api/internal/core/store/store.go b/api/internal/core/store/store.go
index 8a4b71c..b285183 100644
--- a/api/internal/core/store/store.go
+++ b/api/internal/core/store/store.go
@@ -31,14 +31,13 @@ import (
 
        "github.com/apisix/manager-api/internal/core/entity"
        "github.com/apisix/manager-api/internal/core/storage"
-       "github.com/apisix/manager-api/internal/utils"
 )
 
 type Interface interface {
        Get(key string) (interface{}, error)
        List(input ListInput) (*ListOutput, error)
        Create(ctx context.Context, obj interface{}) error
-       Update(ctx context.Context, obj interface{}) error
+       Update(ctx context.Context, obj interface{}, createOnFail bool) error
        BatchDelete(ctx context.Context, keys []string) error
 }
 
@@ -92,6 +91,9 @@ func (s *GenericStore) Init() error {
                return err
        }
        for i := range ret {
+               if ret[i] == "init_dir" {
+                       continue
+               }
                objPtr, err := s.StringToObjPtr(ret[i])
                if err != nil {
                        return err
@@ -136,6 +138,7 @@ func (s *GenericStore) Get(key string) (interface{}, error) 
{
 
 type ListInput struct {
        Predicate func(obj interface{}) bool
+       Format    func(obj interface{}) interface{}
        PageSize  int
        // start from 1
        PageNumber int
@@ -165,6 +168,9 @@ func (s *GenericStore) List(input ListInput) (*ListOutput, 
error) {
                if input.Predicate != nil && !input.Predicate(value) {
                        return true
                }
+               if input.Format != nil {
+                       value = input.Format(value)
+               }
                ret = append(ret, value)
                return true
        })
@@ -223,13 +229,9 @@ func (s *GenericStore) ingestValidate(obj interface{}) 
(err error) {
 }
 
 func (s *GenericStore) Create(ctx context.Context, obj interface{}) error {
-       if getter, ok := obj.(entity.BaseInfoGetter); ok {
-               info := getter.GetBaseInfo()
-               if info.ID == "" {
-                       info.ID = utils.GetFlakeUidStr()
-               }
-               info.CreateTime = time.Now().Unix()
-               info.UpdateTime = time.Now().Unix()
+       if setter, ok := obj.(entity.BaseInfoSetter); ok {
+               info := setter.GetBaseInfo()
+               info.Creating()
        }
 
        if err := s.ingestValidate(obj); err != nil {
@@ -256,7 +258,7 @@ func (s *GenericStore) Create(ctx context.Context, obj 
interface{}) error {
        return nil
 }
 
-func (s *GenericStore) Update(ctx context.Context, obj interface{}) error {
+func (s *GenericStore) Update(ctx context.Context, obj interface{}, 
createIfNotExist bool) error {
        if err := s.ingestValidate(obj); err != nil {
                return err
        }
@@ -265,21 +267,19 @@ func (s *GenericStore) Update(ctx context.Context, obj 
interface{}) error {
        if key == "" {
                return fmt.Errorf("key is required")
        }
-       oldObj, ok := s.cache.Load(key)
+       storedObj, ok := s.cache.Load(key)
        if !ok {
+               if createIfNotExist {
+                       return s.Create(ctx, obj)
+               }
                return fmt.Errorf("key: %s is not found", key)
        }
 
-       createTime := int64(0)
-       if oldGetter, ok := oldObj.(entity.BaseInfoGetter); ok {
-               oldInfo := oldGetter.GetBaseInfo()
-               createTime = oldInfo.CreateTime
-       }
-
-       if getter, ok := obj.(entity.BaseInfoGetter); ok {
-               info := getter.GetBaseInfo()
-               info.CreateTime = createTime
-               info.UpdateTime = time.Now().Unix()
+       if setter, ok := obj.(entity.BaseInfoGetter); ok {
+               storedGetter := storedObj.(entity.BaseInfoGetter)
+               storedInfo := storedGetter.GetBaseInfo()
+               info := setter.GetBaseInfo()
+               info.Updating(storedInfo)
        }
 
        bs, err := json.Marshal(obj)
diff --git a/api/internal/core/store/store_test.go 
b/api/internal/core/store/store_test.go
index f7ad5f4..53f6a85 100644
--- a/api/internal/core/store/store_test.go
+++ b/api/internal/core/store/store_test.go
@@ -624,7 +624,7 @@ func TestGenericStore_Update(t *testing.T) {
                                Field2: "test2",
                        },
                        giveCache: map[string]interface{}{
-                               "test1": struct{}{},
+                               "test1": &TestStruct{},
                        },
                        giveStore: &GenericStore{
                                opt: GenericStoreOption{
@@ -643,7 +643,7 @@ func TestGenericStore_Update(t *testing.T) {
                                Field2: "test2",
                        },
                        giveCache: map[string]interface{}{
-                               "test1": struct{}{},
+                               "test1": &TestStruct{},
                        },
                        giveStore: &GenericStore{
                                opt: GenericStoreOption{
@@ -664,7 +664,7 @@ func TestGenericStore_Update(t *testing.T) {
                                Field2: "test2",
                        },
                        giveCache: map[string]interface{}{
-                               "test2": struct{}{},
+                               "test2": &TestStruct{},
                        },
                        giveStore: &GenericStore{
                                opt: GenericStoreOption{
@@ -704,7 +704,7 @@ func TestGenericStore_Update(t *testing.T) {
                tc.giveStore.Stg = mStorage
                tc.giveStore.opt.Validator = mValidator
 
-               err := tc.giveStore.Update(context.TODO(), tc.giveObj)
+               err := tc.giveStore.Update(context.TODO(), tc.giveObj, false)
                assert.True(t, validateCalled, tc.caseDesc)
                if err != nil {
                        assert.Equal(t, tc.wantErr, err, tc.caseDesc)
diff --git a/api/internal/handler/consumer/consumer.go 
b/api/internal/handler/consumer/consumer.go
index 1fdf3b3..f00d1d2 100644
--- a/api/internal/handler/consumer/consumer.go
+++ b/api/internal/handler/consumer/consumer.go
@@ -50,6 +50,8 @@ func (h *Handler) ApplyRoute(r *gin.Engine) {
                wrapper.InputType(reflect.TypeOf(entity.Consumer{}))))
        r.PUT("/apisix/admin/consumers/:username", wgin.Wraps(h.Update,
                wrapper.InputType(reflect.TypeOf(UpdateInput{}))))
+       r.PUT("/apisix/admin/consumers", wgin.Wraps(h.Update,
+               wrapper.InputType(reflect.TypeOf(UpdateInput{}))))
        r.DELETE("/apisix/admin/consumers/:usernames", wgin.Wraps(h.BatchDelete,
                wrapper.InputType(reflect.TypeOf(BatchDelete{}))))
 }
@@ -117,10 +119,12 @@ func (h *Handler) Update(c droplet.Context) (interface{}, 
error) {
        if input.ID != "" && input.ID != input.Username {
                return nil, fmt.Errorf("consumer's id and username must be a 
same value")
        }
-       input.Consumer.Username = input.Username
-       input.Consumer.ID = input.Username
+       if input.Username != "" {
+               input.Consumer.Username = input.Username
+       }
+       input.Consumer.ID = input.Consumer.Username
 
-       if err := h.consumerStore.Update(c.Context(), &input.Consumer); err != 
nil {
+       if err := h.consumerStore.Update(c.Context(), &input.Consumer, true); 
err != nil {
                //if not exists, create
                if err.Error() == fmt.Sprintf("key: %s is not found", 
input.Username) {
                        if err := h.consumerStore.Create(c.Context(), 
&input.Consumer); err != nil {
diff --git a/api/internal/handler/consumer/consumer_test.go 
b/api/internal/handler/consumer/consumer_test.go
index 567c796..22664fa 100644
--- a/api/internal/handler/consumer/consumer_test.go
+++ b/api/internal/handler/consumer/consumer_test.go
@@ -197,4 +197,33 @@ func TestConsumer(t *testing.T) {
        _, err = handler.Create(ctx)
        assert.NotNil(t, err)
 
+       //create consumer using Update
+       consumer6 := &UpdateInput{}
+       reqBody = `{
+      "username": "nnn",
+      "plugins": {
+          "limit-count": {
+              "count": 2,
+              "time_window": 60,
+              "rejected_code": 503,
+              "key": "remote_addr"
+          }
+      },
+    "desc": "test description"
+  }`
+       json.Unmarshal([]byte(reqBody), consumer6)
+       ctx.SetInput(consumer6)
+       _, err = handler.Update(ctx)
+       assert.Nil(t, err)
+
+       //sleep
+       time.Sleep(time.Duration(100) * time.Millisecond)
+
+       //delete consumer
+       reqBody = `{"usernames": "nnn"}`
+       json.Unmarshal([]byte(reqBody), inputDel)
+       ctx.SetInput(inputDel)
+       _, err = handler.BatchDelete(ctx)
+       assert.Nil(t, err)
+
 }
diff --git a/api/internal/handler/route/route.go 
b/api/internal/handler/route/route.go
index 0895a6a..adb1954 100644
--- a/api/internal/handler/route/route.go
+++ b/api/internal/handler/route/route.go
@@ -61,8 +61,11 @@ func (h *Handler) ApplyRoute(r *gin.Engine) {
                wrapper.InputType(reflect.TypeOf(ListInput{}))))
        r.POST("/apisix/admin/routes", wgin.Wraps(h.Create,
                wrapper.InputType(reflect.TypeOf(entity.Route{}))))
+       r.PUT("/apisix/admin/routes", wgin.Wraps(h.Update,
+               wrapper.InputType(reflect.TypeOf(UpdateInput{}))))
        r.PUT("/apisix/admin/routes/:id", wgin.Wraps(h.Update,
                wrapper.InputType(reflect.TypeOf(UpdateInput{}))))
+
        r.DELETE("/apisix/admin/routes/:ids", wgin.Wraps(h.BatchDelete,
                wrapper.InputType(reflect.TypeOf(BatchDelete{}))))
 
@@ -88,6 +91,9 @@ func (h *Handler) Get(c droplet.Context) (interface{}, error) 
{
                route.Script = script.(*entity.Script).Script
        }
 
+       //format
+       route.Upstream.Nodes = entity.NodesFormat(route.Upstream.Nodes)
+
        return route, nil
 }
 
@@ -129,6 +135,11 @@ func (h *Handler) List(c droplet.Context) (interface{}, 
error) {
                        }
                        return true
                },
+               Format: func(obj interface{}) interface{} {
+                       route := obj.(*entity.Route)
+                       route.Upstream.Nodes = 
entity.NodesFormat(route.Upstream.Nodes)
+                       return route
+               },
                PageSize:   input.PageSize,
                PageNumber: input.PageNumber,
        })
@@ -261,7 +272,7 @@ func (h *Handler) Update(c droplet.Context) (interface{}, 
error) {
                        return nil, err
                }
                //save original conf
-               if err = h.scriptStore.Update(c.Context(), script); err != nil {
+               if err = h.scriptStore.Update(c.Context(), script, true); err 
!= nil {
                        //if not exists, create
                        if err.Error() == fmt.Sprintf("key: %s is not found", 
script.ID) {
                                if err := h.scriptStore.Create(c.Context(), 
script); err != nil {
@@ -273,7 +284,7 @@ func (h *Handler) Update(c droplet.Context) (interface{}, 
error) {
                }
        }
 
-       if err := h.routeStore.Update(c.Context(), &input.Route); err != nil {
+       if err := h.routeStore.Update(c.Context(), &input.Route, true); err != 
nil {
                return nil, err
        }
 
diff --git a/api/internal/handler/route/route_test.go 
b/api/internal/handler/route/route_test.go
index 611dccc..438a058 100644
--- a/api/internal/handler/route/route_test.go
+++ b/api/internal/handler/route/route_test.go
@@ -799,11 +799,7 @@ func TestRoute(t *testing.T) {
       "methods": ["PUT", "GET"],
       "upstream": {
           "type": "roundrobin",
-          "nodes": [{
-              "host": "www.a.com",
-              "port": 80,
-              "weight": 1
-          }]
+          "nodes": {"www.a.com:80": 1}
       }
   }`
        json.Unmarshal([]byte(reqBody), route3)
diff --git a/api/internal/handler/service/service.go 
b/api/internal/handler/service/service.go
index 7ee3c0f..deee899 100644
--- a/api/internal/handler/service/service.go
+++ b/api/internal/handler/service/service.go
@@ -48,6 +48,8 @@ func (h *Handler) ApplyRoute(r *gin.Engine) {
                wrapper.InputType(reflect.TypeOf(ListInput{}))))
        r.POST("/apisix/admin/services", wgin.Wraps(h.Create,
                wrapper.InputType(reflect.TypeOf(entity.Service{}))))
+       r.PUT("/apisix/admin/services", wgin.Wraps(h.Update,
+               wrapper.InputType(reflect.TypeOf(UpdateInput{}))))
        r.PUT("/apisix/admin/services/:id", wgin.Wraps(h.Update,
                wrapper.InputType(reflect.TypeOf(UpdateInput{}))))
        r.PATCH("/apisix/admin/services/:id", wgin.Wraps(h.Patch,
@@ -67,6 +69,10 @@ func (h *Handler) Get(c droplet.Context) (interface{}, 
error) {
        if err != nil {
                return nil, err
        }
+
+       service := r.(*entity.Service)
+       service.Upstream.Nodes = entity.NodesFormat(service.Upstream.Nodes)
+
        return r, nil
 }
 
@@ -85,6 +91,11 @@ func (h *Handler) List(c droplet.Context) (interface{}, 
error) {
                        }
                        return true
                },
+               Format: func(obj interface{}) interface{} {
+                       service := obj.(*entity.Service)
+                       service.Upstream.Nodes = 
entity.NodesFormat(service.Upstream.Nodes)
+                       return service
+               },
                PageSize:   input.PageSize,
                PageNumber: input.PageNumber,
        })
@@ -114,7 +125,7 @@ func (h *Handler) Update(c droplet.Context) (interface{}, 
error) {
        input := c.Input().(*UpdateInput)
        input.Service.ID = input.ID
 
-       if err := h.serviceStore.Update(c.Context(), &input.Service); err != 
nil {
+       if err := h.serviceStore.Update(c.Context(), &input.Service, true); err 
!= nil {
                return nil, err
        }
 
@@ -167,7 +178,7 @@ func (h *Handler) Patch(c droplet.Context) (interface{}, 
error) {
                return nil, err
        }
 
-       if err := h.serviceStore.Update(c.Context(), &stored); err != nil {
+       if err := h.serviceStore.Update(c.Context(), &stored, false); err != 
nil {
                return nil, err
        }
 
diff --git a/api/internal/handler/ssl/ssl.go b/api/internal/handler/ssl/ssl.go
index a7f5701..3291846 100644
--- a/api/internal/handler/ssl/ssl.go
+++ b/api/internal/handler/ssl/ssl.go
@@ -55,6 +55,8 @@ func (h *Handler) ApplyRoute(r *gin.Engine) {
                wrapper.InputType(reflect.TypeOf(ListInput{}))))
        r.POST("/apisix/admin/ssl", wgin.Wraps(h.Create,
                wrapper.InputType(reflect.TypeOf(entity.SSL{}))))
+       r.PUT("/apisix/admin/ssl", wgin.Wraps(h.Update,
+               wrapper.InputType(reflect.TypeOf(UpdateInput{}))))
        r.PUT("/apisix/admin/ssl/:id", wgin.Wraps(h.Update,
                wrapper.InputType(reflect.TypeOf(UpdateInput{}))))
        r.PATCH("/apisix/admin/ssl/:id", wgin.Wraps(h.Patch,
@@ -163,7 +165,7 @@ func (h *Handler) Update(c droplet.Context) (interface{}, 
error) {
        }
 
        ssl.ID = input.ID
-       if err := h.sslStore.Update(c.Context(), ssl); err != nil {
+       if err := h.sslStore.Update(c.Context(), ssl, true); err != nil {
                return nil, err
        }
 
@@ -203,7 +205,7 @@ func (h *Handler) Patch(c droplet.Context) (interface{}, 
error) {
                panic(err)
        }
 
-       if err := h.sslStore.Update(c.Context(), &stored); err != nil {
+       if err := h.sslStore.Update(c.Context(), &stored, false); err != nil {
                return nil, err
        }
 
diff --git a/api/internal/handler/upstream/upstream.go 
b/api/internal/handler/upstream/upstream.go
index 5071ff2..d7e3365 100644
--- a/api/internal/handler/upstream/upstream.go
+++ b/api/internal/handler/upstream/upstream.go
@@ -49,6 +49,8 @@ func (h *Handler) ApplyRoute(r *gin.Engine) {
                wrapper.InputType(reflect.TypeOf(ListInput{}))))
        r.POST("/apisix/admin/upstreams", wgin.Wraps(h.Create,
                wrapper.InputType(reflect.TypeOf(entity.Upstream{}))))
+       r.PUT("/apisix/admin/upstreams", wgin.Wraps(h.Update,
+               wrapper.InputType(reflect.TypeOf(UpdateInput{}))))
        r.PUT("/apisix/admin/upstreams/:id", wgin.Wraps(h.Update,
                wrapper.InputType(reflect.TypeOf(UpdateInput{}))))
        r.PATCH("/apisix/admin/upstreams/:id", wgin.Wraps(h.Patch,
@@ -71,6 +73,10 @@ func (h *Handler) Get(c droplet.Context) (interface{}, 
error) {
        if err != nil {
                return nil, err
        }
+
+       upstream := r.(*entity.Upstream)
+       upstream.Nodes = entity.NodesFormat(upstream.Nodes)
+
        return r, nil
 }
 
@@ -89,6 +95,11 @@ func (h *Handler) List(c droplet.Context) (interface{}, 
error) {
                        }
                        return true
                },
+               Format: func(obj interface{}) interface{} {
+                       upstream := obj.(*entity.Upstream)
+                       upstream.Nodes = entity.NodesFormat(upstream.Nodes)
+                       return upstream
+               },
                PageSize:   input.PageSize,
                PageNumber: input.PageNumber,
        })
@@ -118,7 +129,7 @@ func (h *Handler) Update(c droplet.Context) (interface{}, 
error) {
        input := c.Input().(*UpdateInput)
        input.Upstream.ID = input.ID
 
-       if err := h.upstreamStore.Update(c.Context(), &input.Upstream); err != 
nil {
+       if err := h.upstreamStore.Update(c.Context(), &input.Upstream, true); 
err != nil {
                return nil, err
        }
 
@@ -171,7 +182,7 @@ func (h *Handler) Patch(c droplet.Context) (interface{}, 
error) {
                return nil, err
        }
 
-       if err := h.upstreamStore.Update(c.Context(), &stored); err != nil {
+       if err := h.upstreamStore.Update(c.Context(), &stored, false); err != 
nil {
                return nil, err
        }
 

Reply via email to