add methods for mg templates

This commit is contained in:
Ефанов Руслан 2023-05-17 17:17:34 +03:00
parent d08ed4e1b2
commit 16e2bc304c
8 changed files with 409 additions and 2 deletions

View File

@ -16,8 +16,6 @@ output:
linters:
disable-all: true
enable:
- paralleltest
- tparallel
- asciicheck
- asasalint
- varnamelen

View File

@ -6531,3 +6531,54 @@ func (c *Client) NotificationsSend(req NotificationsSendRequest) (int, error) {
return status, nil
}
func (c *Client) ListMGChannelTemplates(channelID, page, limit int) (MGChannelTemplatesResponse, int, error) {
var resp MGChannelTemplatesResponse
values := url.Values{
"page": {fmt.Sprintf("%d", page)},
"limit": {fmt.Sprintf("%d", limit)},
"channel_id": {fmt.Sprintf("%d", channelID)},
}
data, code, err := c.GetRequest(fmt.Sprintf("/reference/mg-channels/templates?%s", values.Encode()))
if err != nil {
return resp, code, err
}
err = json.Unmarshal(data, &resp)
if err != nil {
return resp, code, err
}
return resp, code, nil
}
func (c *Client) EditMGChannelTemplate(req EditMGChannelTemplateRequest) (int, error) {
templates, err := json.Marshal(req.Templates)
if err != nil {
return 0, err
}
removed, err := json.Marshal(req.Removed)
if err != nil {
return 0, err
}
values := url.Values{
"templates": {string(templates)},
"removed": {string(removed)},
}
_, code, err := c.PostRequest("/reference/mg-channels/templates/edit", values)
if err != nil {
return code, err
}
return code, nil
}

View File

@ -7771,3 +7771,119 @@ func TestClient_NotificationsSend(t *testing.T) {
assert.NoError(t, err)
assert.True(t, statuses[status])
}
func TestClient_ListMGChannelTemplates(t *testing.T) {
defer gock.Off()
gock.New(crmURL).
Get("/reference/mg-channels/templates").
MatchParams(map[string]string{
"limit": "10",
"page": "5",
"channel_id": "1",
}).
Reply(http.StatusOK).
JSON(getMGTemplatesResponse())
resp, code, err := client().ListMGChannelTemplates(1, 5, 10)
assert.NoError(t, err)
assert.True(t, statuses[code])
assert.NotEmpty(t, resp.Pagination)
assert.NotEmpty(t, resp.Templates)
template := resp.Templates[0]
assert.Equal(t, 1, template.ID)
assert.Equal(t, 1, template.Channel.ID)
assert.Equal(t, 1, template.Channel.ExternalID)
assert.Equal(t, "NAMEAAA", template.Name)
assert.Equal(t, TemplateItemTypeText, template.BodyTemplate[0].Type)
assert.Equal(t, TemplateItemTypeVar, template.BodyTemplate[1].Type)
assert.Equal(t, "en", template.Lang)
assert.Equal(t, "test_0", template.Category)
assert.Equal(t, []string{"Hello,", "{{1}}"}, template.Header.Text.Parts)
assert.Equal(t, []string{"AAAAAA"}, template.Header.Text.Example)
assert.Equal(t, "https://example.com/file/123.png", template.Header.Image.Example)
assert.Equal(t, "https://example.com/file/123.pdf", template.Header.Document.Example)
assert.Equal(t, "https://example.com/file/123.mp4", template.Header.Video.Example)
assert.Equal(t, "footer_0", template.Footer)
assert.Equal(t, PhoneNumber, template.Buttons[0].Type)
assert.Equal(t, "your-phone-button-text", template.Buttons[0].Text)
assert.Equal(t, "+79895553535", template.Buttons[0].PhoneNumber)
assert.Equal(t, QuickReply, template.Buttons[1].Type)
assert.Equal(t, "Yes", template.Buttons[1].Text)
assert.Equal(t, URL, template.Buttons[2].Type)
assert.Equal(t, "button", template.Buttons[2].Text)
assert.Equal(t, "https://example.com/file/{{1}}", template.Buttons[2].URL)
assert.Equal(t, []string{"https://www.website.com/dynamic-url-example"}, template.Buttons[2].Example)
assert.Equal(t, "APPROVED", template.VerificationStatus)
data, err := json.Marshal(template.BodyTemplate)
assert.NoError(t, err)
assert.NotEmpty(t, `["Text_0",{}]`, string(data))
}
func TestClient_ListMGChannelEmptyHeaderButtons(t *testing.T) {
defer gock.Off()
gock.New(crmURL).
Get("/reference/mg-channels/templates").
MatchParams(map[string]string{
"limit": "1",
"page": "1",
"channel_id": "1",
}).
Reply(http.StatusOK).
JSON(`{
"success": true,
"pagination": {
"limit": 1,
"totalCount": 1,
"currentPage": 1,
"totalPageCount": 1
},
"templates": [
{
"id": 12,
"externalId": 0,
"channel": {
"id": 1,
"externalId": 1
},
"name": "name_0"
}
]
}`)
resp, code, err := client().ListMGChannelTemplates(1, 1, 1)
assert.NoError(t, err)
assert.True(t, statuses[code])
assert.NotEmpty(t, resp.Pagination)
assert.NotEmpty(t, resp.Templates)
assert.Nil(t, resp.Templates[0].Header)
assert.Nil(t, resp.Templates[0].Buttons)
}
func TestClient_EditMGChannelTemplate(t *testing.T) {
defer gock.Off()
tmplsJSON := getMGTemplatesForEdit()
var tmpls []MGChannelTemplate
assert.NoError(t, json.Unmarshal([]byte(tmplsJSON), &tmpls))
request := EditMGChannelTemplateRequest{Templates: tmpls, Removed: []int{1, 2, 3}}
reqValue := url.Values{
"templates": {tmplsJSON},
"removed": {"[1,2,3]"},
}
gock.New(crmURL).
Post("reference/mg-channels/templates/edit").
Body(strings.NewReader(reqValue.Encode())).
Reply(http.StatusOK)
code, err := client().EditMGChannelTemplate(request)
assert.NoError(t, err)
assert.True(t, statuses[code])
}

View File

@ -284,6 +284,11 @@ type NotificationsSendRequest struct {
UserIDs []string `json:"userIds,omitempty"`
}
type EditMGChannelTemplateRequest struct {
Templates []MGChannelTemplate `json:"templates"`
Removed []int `json:"removed"`
}
// SystemURL returns system URL from the connection request without trailing slash.
func (r ConnectRequest) SystemURL() string {
if r.URL == "" {

View File

@ -651,3 +651,9 @@ type ActionProductsGroupResponse struct {
SuccessfulResponse
ID int `json:"id"`
}
type MGChannelTemplatesResponse struct {
Pagination *Pagination `json:"pagination"`
Templates []MGChannelTemplate `json:"templates"`
SuccessfulResponse
}

122
template.go Normal file
View File

@ -0,0 +1,122 @@
package retailcrm
import (
"encoding/json"
"errors"
"fmt"
)
const (
// TemplateItemTypeText is a type for text chunk in template.
TemplateItemTypeText uint8 = iota
// TemplateItemTypeVar is a type for variable in template.
TemplateItemTypeVar
QuickReply ButtonType = "QUICK_REPLY"
PhoneNumber ButtonType = "PHONE_NUMBER"
URL ButtonType = "URL"
)
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,
}
type Text struct {
Parts []string `json:"parts"`
Example []string `json:"example,omitempty"`
}
type Media struct {
Example string `json:"example,omitempty"`
}
type Header struct {
Text *Text `json:"text,omitempty"`
Document *Media `json:"document,omitempty"`
Image *Media `json:"image,omitempty"`
Video *Media `json:"video,omitempty"`
}
type TemplateItemList []BodyTemplateItem
// BodyTemplateItem is a part of template.
type BodyTemplateItem struct {
Text string
VarType string
Type uint8
}
// MarshalJSON controls how BodyTemplateItem will be marshaled into JSON.
func (t BodyTemplateItem) 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 BodyTemplateItem type")
}
// UnmarshalJSON will correctly unmarshal BodyTemplateItem.
func (t *BodyTemplateItem) UnmarshalJSON(b []byte) error {
var obj interface{}
err := json.Unmarshal(b, &obj)
if err != nil {
return err
}
switch bodyPart := obj.(type) {
case string:
t.Type = TemplateItemTypeText
t.Text = bodyPart
case map[string]interface{}:
// {} case
if len(bodyPart) == 0 {
t.Type = TemplateItemTypeVar
t.VarType = TemplateVarCustom
return nil
}
if varTypeCurr, ok := bodyPart["var"].(string); ok {
if _, ok := templateVarAssoc[varTypeCurr]; !ok {
return fmt.Errorf("invalid placeholder var '%s'", varTypeCurr)
}
t.Type = TemplateItemTypeVar
t.VarType = varTypeCurr
} else {
return errors.New("invalid BodyTemplateItem")
}
default:
return errors.New("invalid BodyTemplateItem")
}
return nil
}
type ButtonType string
type Button struct {
Type ButtonType `json:"type"`
URL string `json:"url,omitempty"`
Text string `json:"text,omitempty"`
PhoneNumber string `json:"phoneNumber,omitempty"`
Example []string `json:"example,omitempty"`
}

View File

@ -433,3 +433,86 @@ func getLoyaltyResponse() string {
}
}`
}
func getMGTemplatesResponse() string {
return `{
"success": true,
"pagination": {
"limit": 10,
"totalCount": 100,
"currentPage": 5,
"totalPageCount": 10
},
"templates": [
{
"id": 1,
"externalId": 0,
"channel": {
"allowedSendByPhone": false,
"id": 1,
"externalId": 1,
"type": "fbmessenger",
"active": true,
"name": "fbmessenger"
},
"code": "namespace#NAMEAAA#ru",
"name": "NAMEAAA",
"active": true,
"template": [
"Text_0",
{
"var": "custom"
}
],
"namespace": "namespace_0",
"lang": "en",
"category": "test_0",
"header": {
"text": {
"parts": [
"Hello,",
"{{1}}"
],
"example": [
"AAAAAA"
]
},
"image": {
"example": "https://example.com/file/123.png"
},
"document": {
"example": "https://example.com/file/123.pdf"
},
"video": {
"example": "https://example.com/file/123.mp4"
}
},
"footer": "footer_0",
"buttons": [
{
"type": "PHONE_NUMBER",
"text": "your-phone-button-text",
"phoneNumber": "+79895553535"
},
{
"type": "QUICK_REPLY",
"text": "Yes"
},
{
"type": "URL",
"url": "https://example.com/file/{{1}}",
"text": "button",
"example": [
"https://www.website.com/dynamic-url-example"
]
}
],
"verificationStatus": "APPROVED"
}
]
}`
}
func getMGTemplatesForEdit() string {
return `[{"header":{"text":{"parts":["Hello,","{{1}}"],"example":["Henry"]},"document":{"example":"https://example.com/file/123.pdf"},"image":{"example":"https://example.com/file/123.png"},"video":{"example":"https://example.com/file/123.mp4"}},"lang":"en","category":"test_0","code":"namespace#name_0#ru","name":"name_0","namespace":"namespace_0","footer":"footer_0","verificationStatus":"REJECTED","template":["Text_0",{"var":"custom"}],"buttons":[{"type":"PHONE_NUMBER","text":"your-phone-button-text","phoneNumber":"+79895553535"},{"type":"QUICK_REPLY","text":"Yes"},{"type":"URL","url":"https://example.com/file/{{1}}","text":"button","example":["https://www.website.com/dynamic-url-example"]}],"channel":{"type":"fbmessenger","name":"JABAAAAAAAAAA","id":1,"externalId":1,"allowedSendByPhone":false,"active":true},"id":1,"externalId":10,"active":true}]`
}

View File

@ -1463,3 +1463,29 @@ type ExternalID struct {
type UserGroupType string
type NotificationType string
type MGChannel struct {
Type string `json:"type"`
Name string `json:"name"`
ID int `json:"id"`
ExternalID int `json:"externalId"`
AllowedSendByPhone bool `json:"allowedSendByPhone"`
Active bool `json:"active"`
}
type MGChannelTemplate struct {
Header *Header `json:"header"`
Lang string `json:"lang"`
Category string `json:"category"`
Code string `json:"code"`
Name string `json:"name"`
Namespace string `json:"namespace"`
Footer string `json:"footer,omitempty"`
VerificationStatus string `json:"verificationStatus"`
BodyTemplate TemplateItemList `json:"template,omitempty"`
Buttons []Button `json:"buttons,omitempty"`
Channel MGChannel `json:"channel"`
ID int `json:"id"`
ExternalID int `json:"externalId,omitempty"`
Active bool `json:"active"`
}