From 0291ca92a9d457f9cf80b04040770fe100654e14 Mon Sep 17 00:00:00 2001 From: Alex Lushpai Date: Tue, 16 Jan 2018 11:15:32 +0300 Subject: [PATCH] api & few customer methods, tests bootstrap --- .gitignore | 1 + client.go | 60 ++++++++++++++++++++++++--------- client_test.go | 7 ++-- config.json.dist | 5 +++ helper_test.go | 40 ++++++++++++++++++++++ methods_api.go | 46 +++++++++++++++++++++++++ methods_api_test.go | 42 +++++++++++++++++++++++ methods_customers.go | 60 +++++++++++++++++++++++++++++++++ methods_customers_test.go | 54 ++++++++++++++++++++++++++++++ types_api.go | 15 +++++++++ types_common.go | 60 +++++++++++++++++++++++++++++++++ types_customer.go | 70 +++++++++++++++++++++++++++++++++++++++ 12 files changed, 442 insertions(+), 18 deletions(-) create mode 100644 config.json.dist create mode 100644 helper_test.go create mode 100644 methods_api.go create mode 100644 methods_api_test.go create mode 100644 methods_customers.go create mode 100644 methods_customers_test.go create mode 100644 types_api.go create mode 100644 types_common.go create mode 100644 types_customer.go diff --git a/.gitignore b/.gitignore index 5f8dea8..df8cb4e 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ # Project ignores +/config.json diff --git a/client.go b/client.go index 1b37527..3c30d9f 100644 --- a/client.go +++ b/client.go @@ -12,39 +12,56 @@ import ( ) const ( - ApiPrefix = "/api/v5" + apiPrefix = "/api" ) +// Client type type Client struct { Url string - apiKey string + Key string + Version string httpClient *http.Client } +// ErrorResponse type type ErrorResponse struct { ErrorMsg string `json:"errorMsg,omitempty"` Errors map[string]string `json:"errors,omitempty"` } -func New(url string, apiKey string) *Client { - return &Client{ +// New initalize client +func New(url string, key string, version string) *Client { + return &Client { url, - apiKey, + key, + version, &http.Client{Timeout: 20 * time.Second}, } } -func (r *Client) GetRequest(urlWithParameters string) ([]byte, int, error) { - var res []byte +func buildUrl(url string, version string) string { + var versionPlaceholder string = "/" + version + if version == "" { + versionPlaceholder = "" + } - req, err := http.NewRequest("GET", fmt.Sprintf("%s%s%s", r.Url, ApiPrefix, urlWithParameters), nil) + var requestUrl = fmt.Sprintf("%s%s%s", url, apiPrefix, versionPlaceholder) + + return requestUrl +} + +func (c *Client) getRequest(urlWithParameters string) ([]byte, int, error) { + var res []byte + var reqUrl = buildUrl(c.Url, c.Version) + + req, err := http.NewRequest("GET", fmt.Sprintf("%s%s", reqUrl , urlWithParameters), nil) if err != nil { return res, 0, err } - req.Header.Set("X-API-KEY", r.apiKey) + req.Header.Set("X-API-KEY", c.Key) - resp, err := r.httpClient.Do(req) + resp, err := c.httpClient.Do(req) if err != nil { return res, 0, err } @@ -61,12 +78,13 @@ func (r *Client) GetRequest(urlWithParameters string) ([]byte, int, error) { return res, resp.StatusCode, nil } -func (r *Client) PostRequest(url string, postParams url.Values) ([]byte, int, error) { +func (c *Client) postRequest(url string, postParams url.Values) ([]byte, int, error) { var res []byte + var reqUrl = buildUrl(c.Url, c.Version) req, err := http.NewRequest( "POST", - fmt.Sprintf("%s%s%s", r.Url, ApiPrefix, url), + fmt.Sprintf("%s%s", reqUrl, url), strings.NewReader(postParams.Encode()), ) if err != nil { @@ -74,9 +92,9 @@ func (r *Client) PostRequest(url string, postParams url.Values) ([]byte, int, er } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - req.Header.Set("X-API-KEY", r.apiKey) + req.Header.Set("X-API-KEY", c.Key) - resp, err := r.httpClient.Do(req) + resp, err := c.httpClient.Do(req) if err != nil { return res, 0, err } @@ -104,9 +122,21 @@ func buildRawResponse(resp *http.Response) ([]byte, error) { return res, nil } -func (r *Client) ErrorResponse(data []byte) (*ErrorResponse, error) { +// ErrorResponse method +func (c *Client) ErrorResponse(data []byte) (*ErrorResponse, error) { var resp ErrorResponse err := json.Unmarshal(data, &resp) return &resp, err } + +// CheckBy select identifier type +func CheckBy(by string) string { + var context = "id" + + if by != "id" { + context = "externalId" + } + + return context +} diff --git a/client_test.go b/client_test.go index 80110ae..20e647d 100644 --- a/client_test.go +++ b/client_test.go @@ -9,17 +9,18 @@ import ( const ( TestUrl = "https://demo.retailcrm.ru" TestApiKey = "111" + TestVersion = "v5" WrongApiKeyMsg = "Wrong \"apiKey\" value." ) func client() *Client { - return New(TestUrl, TestApiKey) + return New(TestUrl, TestApiKey, TestVersion) } func TestGetRequest(t *testing.T) { c := client() - data, status, _ := c.GetRequest("/store/products") + data, status, _ := c.getRequest("/store/products") if status != http.StatusForbidden { t.Fail() } @@ -33,7 +34,7 @@ func TestGetRequest(t *testing.T) { func TestPostRequest(t *testing.T) { c := client() - data, status, _ := c.PostRequest("/orders/create", url.Values{}) + data, status, _ := c.postRequest("/orders/create", url.Values{}) if status != http.StatusForbidden { t.Fail() } diff --git a/config.json.dist b/config.json.dist new file mode 100644 index 0000000..c36db57 --- /dev/null +++ b/config.json.dist @@ -0,0 +1,5 @@ +{ + "url": "https://demo.retailcrm.ru", + "key": "Bz9SLca32BYgv2R6yQMlDCUSgO018257", + "version": "v5" +} diff --git a/helper_test.go b/helper_test.go new file mode 100644 index 0000000..3924b9a --- /dev/null +++ b/helper_test.go @@ -0,0 +1,40 @@ +package retailcrm + +import ( + "encoding/json" + "os" + "fmt" +) + +type Configuration struct { + Url string `json:"url"` + Key string `json:"key"` + Ver string `json:"version"` +} + +func buildConfiguration() *Configuration { + file, _ := os.Open("config.json") + decoder := json.NewDecoder(file) + configuration := Configuration{} + err := decoder.Decode(&configuration) + if err != nil { + fmt.Println("error:", err) + } + + return &Configuration { + configuration.Url, + configuration.Key, + configuration.Ver, + } +} + +func UnversionedClient() *Client { + configuration := buildConfiguration() + return New(configuration.Url, configuration.Key, "") +} + +func VersionedClient() *Client { + configuration := buildConfiguration() + return New(configuration.Url, configuration.Key, configuration.Ver) +} + diff --git a/methods_api.go b/methods_api.go new file mode 100644 index 0000000..7e8d305 --- /dev/null +++ b/methods_api.go @@ -0,0 +1,46 @@ +package retailcrm + +import ( + "fmt" + "encoding/json" + "net/http" + "errors" +) + +// ApiVersions get available API versions +func (c *Client) ApiVersions() (*VersionResponse, int, error) { + var resp VersionResponse + data, status, err := c.getRequest(fmt.Sprintf("/api-versions")) + + if err != nil { + return &resp, status, err + } + + if status >= http.StatusBadRequest { + return &resp, status, errors.New(fmt.Sprintf("HTTP request error. Status code: %d.\n", status)) + } + + err = json.Unmarshal(data, &resp) + + return &resp, status, err +} + +// ApiCredentials get available API methods +func (c *Client) ApiCredentials() (*CredentialResponse, int, error) { + var resp CredentialResponse + data, status, err := c.getRequest(fmt.Sprintf("/credentials")) + + if err != nil { + return &resp, status, err + } + + if status >= http.StatusBadRequest { + return &resp, status, errors.New(fmt.Sprintf("HTTP request error. Status code: %d.\n", status)) + } + + err = json.Unmarshal(data, &resp) + + return &resp, status, err +} + + diff --git a/methods_api_test.go b/methods_api_test.go new file mode 100644 index 0000000..187ccdd --- /dev/null +++ b/methods_api_test.go @@ -0,0 +1,42 @@ +package retailcrm + +import ( + "testing" + "net/http" +) + +func TestVersions(t *testing.T) { + c := UnversionedClient() + + data, status, err := c.ApiVersions() + + if err != nil { + t.Fail() + } + + if status >= http.StatusBadRequest { + t.Fail() + } + + if data.Success != true { + t.Fail() + } +} + +func TestCredentials(t *testing.T) { + c := UnversionedClient() + + data, status, err := c.ApiCredentials() + + if err != nil { + t.Fail() + } + + if status >= http.StatusBadRequest { + t.Fail() + } + + if data.Success != true { + t.Fail() + } +} diff --git a/methods_customers.go b/methods_customers.go new file mode 100644 index 0000000..2160d61 --- /dev/null +++ b/methods_customers.go @@ -0,0 +1,60 @@ +package retailcrm + +import ( + "fmt" + "net/http" + "errors" + "encoding/json" + "github.com/google/go-querystring/query" +) + +// Customer get method +func (c *Client) Customer(id, by, site string) (*CustomerResponse, int, error) { + var resp CustomerResponse + var context = CheckBy(by) + + fw := CustomerGetFilter{context, site} + params, _ := query.Values(fw) + data, status, err := c.getRequest(fmt.Sprintf("/customers/%s?%s", id, params.Encode())) + if err != nil { + return &resp, status, err + } + + if status >= http.StatusBadRequest { + return &resp, status, errors.New(fmt.Sprintf("HTTP request error. Status code: %d.\n", status)) + } + + err = json.Unmarshal(data, &resp) + + return &resp, status, err +} + +// Customers list method +func (c *Client) Customers(filter CustomersFilter, limit, page int) (*CustomersResponse, int, error) { + var resp CustomersResponse + + if limit == 0 { + limit = 20 + } + + if page == 0 { + page = 1 + } + + fw := CustomersParameters{filter, limit, page} + params, _ := query.Values(fw) + + data, status, err := c.getRequest(fmt.Sprintf("/customers?%s", params.Encode())) + + if err != nil { + return &resp, status, err + } + + if status >= http.StatusBadRequest { + return &resp, status, errors.New(fmt.Sprintf("HTTP request error. Status code: %d.\n", status)) + } + + err = json.Unmarshal(data, &resp) + + return &resp, status, err +} diff --git a/methods_customers_test.go b/methods_customers_test.go new file mode 100644 index 0000000..53b8d0a --- /dev/null +++ b/methods_customers_test.go @@ -0,0 +1,54 @@ +package retailcrm + +import ( + "testing" + "net/http" +) + +func TestCustomer(t *testing.T) { + c := VersionedClient() + data, status, err := c.Customer("163", "id", "") + + if err != nil { + t.Errorf("%s", err) + t.Fail() + } + + if status >= http.StatusBadRequest { + t.Errorf("%s", err) + t.Fail() + } + + if data.Success != true { + t.Errorf("%s", err) + t.Fail() + } + + if data.Customer.Id != 163 { + t.Errorf("%s", err) + t.Fail() + } +} + +func TestCustomers(t *testing.T) { + c := VersionedClient() + f := CustomersFilter{} + f.City = "Москва" + + data, status, err := c.Customers(f, 20, 1) + + if err != nil { + t.Errorf("%s", err) + t.Fail() + } + + if status >= http.StatusBadRequest { + t.Errorf("%s", err) + t.Fail() + } + + if data.Success != true { + t.Errorf("%s", err) + t.Fail() + } +} diff --git a/types_api.go b/types_api.go new file mode 100644 index 0000000..c011969 --- /dev/null +++ b/types_api.go @@ -0,0 +1,15 @@ +package retailcrm + +// VersionResponse return available API versions +type VersionResponse struct { + Success bool `json:"success,omitempty"` + Versions []string `json:"versions,brackets,omitempty"` +} + +// CredentialResponse return available API methods +type CredentialResponse struct { + Success bool `json:"success,omitempty"` + Credentials []string `json:"credentials,brackets,omitempty"` + SiteAccess string `json:"siteAccess,omitempty"` + SitesAvailable []string `json:"sitesAvailable,brackets,omitempty"` +} diff --git a/types_common.go b/types_common.go new file mode 100644 index 0000000..8d59b52 --- /dev/null +++ b/types_common.go @@ -0,0 +1,60 @@ +package retailcrm + +// Pagination type +type Pagination struct { + Limit int `json:"limit,omitempty"` + TotalCount int `json:"totalCount,omitempty"` + CurrentPage int `json:"currentPage,omitempty"` + TotalPageCount int `json:"totalPageCount,omitempty"` +} + +// Address type +type Address struct { + Index string `json:"index,omitempty"` + CountryIso string `json:"countryIso,omitempty"` + Region string `json:"region,omitempty"` + RegionId int `json:"regionId,omitempty"` + City string `json:"city,omitempty"` + CityId int `json:"cityId,omitempty"` + CityType string `json:"cityType,omitempty"` + Street string `json:"street,omitempty"` + StreetId int `json:"streetId,omitempty"` + StreetType string `json:"streetType,omitempty"` + Building string `json:"building,omitempty"` + Flat string `json:"flat,omitempty"` + IntercomCode string `json:"intercomCode,omitempty"` + Floor int `json:"floor,omitempty"` + Block int `json:"block,omitempty"` + House string `json:"house,omitempty"` + Metro string `json:"metro,omitempty"` + Notes string `json:"notes,omitempty"` + Text string `json:"text,omitempty"` +} + +// Source type +type Source struct { + Source string `json:"source,omitempty"` + Medium string `json:"medium,omitempty"` + Campaign string `json:"campaign,omitempty"` + Keyword string `json:"keyword,omitempty"` + Content string `json:"content,omitempty"` +} + +// Contragent type +type Contragent struct { + ContragentType string `json:"contragentType,omitempty"` + LegalName string `json:"legalName,omitempty"` + LegalAddress string `json:"legalAddress,omitempty"` + INN string `json:"INN,omitempty"` + OKPO string `json:"OKPO,omitempty"` + KPP string `json:"KPP,omitempty"` + OGRN string `json:"OGRN,omitempty"` + OGRNIP string `json:"OGRNIP,omitempty"` + CertificateNumber string `json:"certificateNumber,omitempty"` + CertificateDate string `json:"certificateDate,omitempty"` + BIK string `json:"BIK,omitempty"` + Bank string `json:"bank,omitempty"` + BankAddress string `json:"bankAddress,omitempty"` + CorrAccount string `json:"corrAccount,omitempty"` + BankAccount string `json:"bankAccount,omitempty"` +} diff --git a/types_customer.go b/types_customer.go new file mode 100644 index 0000000..b06df5b --- /dev/null +++ b/types_customer.go @@ -0,0 +1,70 @@ +package retailcrm + +// Customer type +type Customer struct { + Id int `json:"id,omitempty"` + ExternalId string `json:"externalId,omitempty"` + FirstName string `json:"firstName,omitempty"` + LastName string `json:"lastName,omitempty"` + Patronymic string `json:"patronymic,omitempty"` + Sex string `json:"sex,omitempty"` + Email string `json:"email,omitempty"` + Phones []CustomerPhone `json:"phones,brackets,omitempty"` + Address *Address `json:"address,omitempty"` + CreatedAt string `json:"createdAt,omitempty"` + Birthday string `json:"birthday,omitempty"` + ManagerId int `json:"managerId,omitempty"` + Vip bool `json:"vip,omitempty"` + Bad bool `json:"bad,omitempty"` + Site string `json:"site,omitempty"` + Source *Source `json:"source,omitempty"` + Contragent *Contragent `json:"contragent,omitempty"` + PersonalDiscount float32 `json:"personalDiscount,omitempty"` + CumulativeDiscount float32 `json:"cumulativeDiscount,omitempty"` + DiscountCardNumber string `json:"discountCardNumber,omitempty"` + EmailMarketingUnsubscribedAt string `json:"emailMarketingUnsubscribedAt,omitempty"` + AvgMarginSumm float32 `json:"avgMarginSumm,omitempty"` + MarginSumm float32 `json:"marginSumm,omitempty"` + TotalSumm float32 `json:"totalSumm,omitempty"` + AverageSumm float32 `json:"averageSumm,omitempty"` + OrdersCount int `json:"ordersCount,omitempty"` + CostSumm float32 `json:"costSumm,omitempty"` + MaturationTime int `json:"maturationTime,omitempty"` + FirstClientId string `json:"firstClientId,omitempty"` + LastClientId string `json:"lastClientId,omitempty"` + //CustomFields map[string]string `json:"customFields,omitempty"` +} + +// CustomerPhone type +type CustomerPhone struct { + Number string `json:"number,omitempty"` +} + +// CustomerGetFilter for get customer request +type CustomerGetFilter struct { + By string `url:"by,omitempty"` + Site string `url:"site,omitempty"` +} + +// CustomerResponse type +type CustomerResponse struct { + Success bool `json:"success"` + Customer *Customer `json:"customer,omitempty,brackets"` +} + +type CustomersFilter struct { + ExternalIds []string `url:"externalIds,omitempty,brackets"` + City string `url:"city,omitempty"` +} + +type CustomersParameters struct { + Filter CustomersFilter `url:"filter,omitempty"` + Limit int `url:"limit,omitempty"` + Page int `url:"page,omitempty"` +} + +type CustomersResponse struct { + Success bool `json:"success"` + Pagination *Pagination `json:"pagination,omitempty"` + Customers []Customer `json:"customers,omitempty,brackets"` +}