From a962419c30a8288357ea0cd5708a7a23c37c891c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D0=B0=D0=B2=D0=B5=D0=BB?= Date: Tue, 7 Apr 2020 16:34:40 +0300 Subject: [PATCH] support for template methods and data structures --- go.mod | 8 +++ go.sum | 16 +++++ v1/client.go | 149 ++++++++++++++++++++++++++++++++++++++++++- v1/types.go | 9 +++ v1/types/template.go | 103 ++++++++++++++++++++++++++++++ 5 files changed, 284 insertions(+), 1 deletion(-) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 v1/types/template.go diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ad616cb --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module github.com/retailcrm/mg-transport-api-client-go + +go 1.11 + +require ( + github.com/google/go-querystring v1.0.0 + github.com/stretchr/testify v1.4.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..0ed215e --- /dev/null +++ b/go.sum @@ -0,0 +1,16 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/v1/client.go b/v1/client.go index 2993c55..23c5429 100644 --- a/v1/client.go +++ b/v1/client.go @@ -11,6 +11,7 @@ import ( "time" "github.com/google/go-querystring/query" + "github.com/retailcrm/mg-transport-api-client-go/v1/types" ) // New initialize client @@ -33,7 +34,7 @@ func NewWithClient(url string, token string, client *http.Client) *MgClient { // // var client = v1.New("https://token.url", "cb8ccf05e38a47543ad8477d4999be73bff503ea6") // -// data, status, err := client.TransportChannels{Channels{Active: true}} +// data, status, err := client.TransportChannels(Channels{Active: true}) // // if err != nil { // fmt.Printf("%v", err) @@ -61,6 +62,152 @@ func (c *MgClient) TransportChannels(request Channels) ([]ChannelListItem, int, return resp, status, err } +// TransportTemplates returns templates list +// +// Example: +// +// var client = v1.New("https://token.url", "cb8ccf05e38a47543ad8477d4999be73bff503ea6") +// +// data, status, err := client.TransportTemplates() +// +// if err != nil { +// fmt.Printf("%v", err) +// } +// +// fmt.Printf("Status: %v, Templates found: %v", status, len(data)) +func (c *MgClient) TransportTemplates() ([]types.TemplateItem, int, error) { + var resp []types.TemplateItem + + data, status, err := c.GetRequest("/templates", []byte{}) + if err != nil { + return resp, status, err + } + + if e := json.Unmarshal(data, &resp); e != nil { + return resp, status, e + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +// ActivateTransportChannel implements template activation +// +// Example: +// var client = v1.New("https://token.url", "cb8ccf05e38a47543ad8477d4999be73bff503ea6") +// +// request := v1.ActivateTemplateRequest{ +// Code: "code", +// Name: "name", +// Type: types.TemplateTypeText, +// Template: []types.TemplateItem{ +// { +// Type: types.TemplateItemTypeText, +// Text: "Hello, ", +// }, +// { +// Type: types.TemplateItemTypeVar, +// VarType: types.TemplateVarName, +// }, +// { +// Type: types.TemplateItemTypeText, +// Text: "!", +// }, +// }, +// } +// +// _, err := client.ActivateTemplate(uint64(1), request) +// +// if err != nil { +// fmt.Printf("%v", err) +// } +func (c *MgClient) ActivateTemplate(channelID uint64, request ActivateTemplateRequest) (int, error) { + outgoing, _ := json.Marshal(&request) + + data, status, err := c.PostRequest(fmt.Sprintf("/channels/%d/templates", channelID), bytes.NewBuffer(outgoing)) + if err != nil { + return status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return status, c.Error(data) + } + + return status, err +} + +// UpdateTemplate implements template updating +// Example: +// var client = New("https://token.url", "cb8ccf05e38a47543ad8477d4999be73bff503ea6") +// +// request := types.Template{ +// Code: "templateCode", +// ChannelID: 1, +// Name: "templateName", +// Template: []types.TemplateItem{ +// { +// Type: types.TemplateItemTypeText, +// Text: "Welcome, ", +// }, +// { +// Type: types.TemplateItemTypeVar, +// VarType: types.TemplateVarName, +// }, +// { +// Type: types.TemplateItemTypeText, +// Text: "!", +// }, +// }, +// } +// +// _, err := client.UpdateTemplate(request) +// +// if err != nil { +// fmt.Printf("%#v", err) +// } +func (c *MgClient) UpdateTemplate(request types.Template) (int, error) { + outgoing, _ := json.Marshal(&request) + + data, status, err := c.PutRequest(fmt.Sprintf("/channels/%d/templates/%s", request.ChannelID, request.Code), outgoing) + if err != nil { + return status, err + } + + if status != http.StatusOK { + return status, c.Error(data) + } + + return status, err +} + +// DeactivateTemplate implements template deactivation +// +// Example: +// +// var client = v1.New("https://token.url", "cb8ccf05e38a47543ad8477d4999be73bff503ea6") +// +// _, err := client.DeactivateTemplate(3053450384, "templateCode") +// +// if err != nil { +// fmt.Printf("%v", err) +// } +func (c *MgClient) DeactivateTemplate(channelID uint64, templateCode string) (int, error) { + data, status, err := c.DeleteRequest( + fmt.Sprintf("/channels/%d/templates/%s", channelID, templateCode), []byte{}) + if err != nil { + return status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return status, c.Error(data) + } + + return status, err +} + // ActivateTransportChannel implement channel activation // // Example: diff --git a/v1/types.go b/v1/types.go index 104d953..5bb2cab 100644 --- a/v1/types.go +++ b/v1/types.go @@ -4,6 +4,8 @@ import ( "errors" "net/http" "time" + + "github.com/retailcrm/mg-transport-api-client-go/v1/types" ) //noinspection ALL @@ -416,6 +418,13 @@ type TransportRequestMeta struct { Timestamp int64 `json:"timestamp"` } +type ActivateTemplateRequest struct { + Code string `binding:"required,min=1,max=512" json:"code"` + Name string `binding:"required,min=1,max=512" json:"name"` + Type string `binding:"required" json:"type"` + Template []types.TemplateItem `json:"template"` +} + var ErrInvalidOriginator = errors.New("invalid originator") // Originator of message diff --git a/v1/types/template.go b/v1/types/template.go new file mode 100644 index 0000000..f4fee4b --- /dev/null +++ b/v1/types/template.go @@ -0,0 +1,103 @@ +package types + +import ( + "encoding/json" + "errors" + "fmt" +) + +// TemplateTypeText is a text template type. There is no other types for now. +const TemplateTypeText = "text" + +const ( + // TemplateItemTypeText is a type for text chunk in template + TemplateItemTypeText = iota + // TemplateItemTypeVar is a type for variable in template + TemplateItemTypeVar +) + +const ( + // TemplateVarCustom is a custom variable type + TemplateVarCustom = "custom" + // TemplateVarName is a name variable type + TemplateVarName = "name" + // TemplateVarFirstName is a first name variable type + TemplateVarFirstName = "first_name" + // TemplateVarLastName is a last name variable type + TemplateVarLastName = "last_name" +) + +// templateVarAssoc for checking variable validity, only for internal use +var templateVarAssoc = map[string]interface{}{ + TemplateVarCustom: nil, + TemplateVarName: nil, + TemplateVarFirstName: nil, + TemplateVarLastName: nil, +} + +// Template struct +type Template struct { + Code string `json:"code"` + ChannelID int64 `json:"channel_id,omitempty"` + Name string `json:"name"` + Enabled bool `json:"enabled,omitempty"` + Type string `json:"type"` + Template []TemplateItem `json:"template"` +} + +// TemplateItem is a part of template +type TemplateItem struct { + Type uint8 + Text string + VarType string +} + +// MarshalJSON will correctly marshal TemplateItem +func (t *TemplateItem) MarshalJSON() ([]byte, error) { + switch t.Type { + case TemplateItemTypeText: + return json.Marshal(t.Text) + case TemplateItemTypeVar: + return json.Marshal(map[string]interface{}{ + "var": t.VarType, + }) + } + + return nil, errors.New("unknown TemplateItem type") +} + +// UnmarshalJSON will correctly unmarshal TemplateItem +func (t *TemplateItem) UnmarshalJSON(b []byte) error { + var obj interface{} + err := json.Unmarshal(b, &obj) + if err != nil { + return err + } + + switch v := obj.(type) { + case string: + t.Type = TemplateItemTypeText + t.Text = v + case map[string]interface{}: + // {} case + if len(v) == 0 { + t.Type = TemplateItemTypeVar + t.VarType = TemplateVarCustom + return nil + } + + if varTypeCurr, ok := v["var"].(string); ok { + if _, ok := templateVarAssoc[t.VarType]; !ok { + return fmt.Errorf("invalid placeholder var '%s'", varTypeCurr) + } + + t.Type = TemplateItemTypeVar + } else { + return errors.New("invalid TemplateItem") + } + default: + return errors.New("invalid TemplateItem") + } + + return nil +}