Добавил файлы проекта
This commit is contained in:
parent
6aee0ebb07
commit
383a830275
9 changed files with 459 additions and 0 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -13,3 +13,4 @@
|
|||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
.idea
|
39
amocrm.go
Normal file
39
amocrm.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
package go_amo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/qdimka/go-amo/api"
|
||||
"github.com/qdimka/go-amo/models"
|
||||
)
|
||||
|
||||
type AmoClient struct {
|
||||
api *api.Client
|
||||
}
|
||||
|
||||
func NewAmoClient(options *api.ClientOptions) (*AmoClient, error) {
|
||||
apiClient, err := api.NewClient(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &AmoClient{
|
||||
api: apiClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (client *AmoClient) GetLead(leadId string) (*models.Lead, error) {
|
||||
deal := new(models.Lead)
|
||||
err := client.api.Get(fmt.Sprintf("/api/v4/leads/%s", leadId), deal)
|
||||
return deal, err
|
||||
}
|
||||
|
||||
func (client *AmoClient) GetUser(userId string) (*models.User, error) {
|
||||
user := new(models.User)
|
||||
err := client.api.Get(fmt.Sprintf("/api/v4/users/%s", userId), user)
|
||||
return user, err
|
||||
}
|
||||
|
||||
func (client *AmoClient) UpdateLead(lead *models.Lead) error {
|
||||
err := client.api.Patch(fmt.Sprintf("/api/v4/leads/%d", lead.ID), lead, nil)
|
||||
return err
|
||||
}
|
162
api/api.go
Normal file
162
api/api.go
Normal file
|
@ -0,0 +1,162 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type ClientOptions struct {
|
||||
Url string
|
||||
AccessToken string
|
||||
RefreshToken string
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
options *ClientOptions
|
||||
BaseUrl *url.URL
|
||||
}
|
||||
|
||||
type requestOptions struct {
|
||||
HttpMethod string
|
||||
Body interface{}
|
||||
Headers map[string]string
|
||||
}
|
||||
|
||||
type OauthTokenResponse struct {
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
func NewClient(options *ClientOptions) (*Client, error) {
|
||||
if len(options.AccessToken) == 0 || len(options.RefreshToken) == 0 || len(options.Url) == 0 {
|
||||
return nil, errors.New("AmoCrm: Invalid options")
|
||||
}
|
||||
|
||||
resolvedUrl, err := url.Parse(options.Url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Client{
|
||||
options: options,
|
||||
BaseUrl: resolvedUrl,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (api *Client) doRequest(resourceUrl string, requestParams requestOptions, result interface{}) error {
|
||||
resolvedUrl, err := url.Parse(resourceUrl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
requestUrl := api.BaseUrl.ResolveReference(resolvedUrl)
|
||||
|
||||
requestBody := new(bytes.Buffer)
|
||||
if requestParams.Body != nil {
|
||||
encoder := json.NewEncoder(requestBody)
|
||||
if err := encoder.Encode(requestParams.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
request, err := http.NewRequest(requestParams.HttpMethod, requestUrl.String(), requestBody)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if requestParams.Headers != nil {
|
||||
for k, v := range requestParams.Headers {
|
||||
request.Header.Set(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
response, err := http.DefaultClient.Do(request)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Request error: %s %d %s %s", err.Error(), response.StatusCode, requestParams.HttpMethod, resourceUrl))
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode >= 400 {
|
||||
bodyBytes, _ := ioutil.ReadAll(response.Body)
|
||||
return errors.New(fmt.Sprintf("%s %d %s %s", string(bodyBytes), response.StatusCode, requestParams.HttpMethod, resourceUrl))
|
||||
}
|
||||
|
||||
if result != nil {
|
||||
decoder := json.NewDecoder(response.Body)
|
||||
err = decoder.Decode(result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *Client) RefreshToken() (*map[string]interface{}, error) {
|
||||
result := new(map[string]interface{})
|
||||
request := map[string]string{
|
||||
"client_id": "7c08f8a9-c49d-4378-890c-a6dba79f88f9",
|
||||
"client_secret": "SDqfNBxxk98zq6CRjLN7tHeyVHS0EAwlQkirAx0i71s81N9fXGPlTlkoxIOZd6Rg",
|
||||
"grant_type": "refresh_token",
|
||||
"refresh_token": api.options.RefreshToken,
|
||||
"redirect_uri": "https://ddamosmartheadru.amocrm.ru",
|
||||
}
|
||||
|
||||
err := api.doRequest("/oauth2/access_token", requestOptions{
|
||||
HttpMethod: http.MethodPost,
|
||||
Body: request,
|
||||
Headers: map[string]string{
|
||||
"Accept": "application/json",
|
||||
"Cache-Control": "no-cache",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}, result)
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (api *Client) Get(resource string, result interface{}) error {
|
||||
return api.doRequest(resource, requestOptions{
|
||||
HttpMethod: http.MethodGet,
|
||||
Body: nil,
|
||||
Headers: map[string]string{
|
||||
"Accept": "application/json",
|
||||
"Cache-Control": "no-cache",
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": fmt.Sprintf("Bearer %s", api.options.AccessToken),
|
||||
},
|
||||
}, result)
|
||||
}
|
||||
|
||||
func (api *Client) Post(resource string, request interface{}, result interface{}) error {
|
||||
return api.doRequest(resource, requestOptions{
|
||||
HttpMethod: http.MethodPost,
|
||||
Body: request,
|
||||
Headers: map[string]string{
|
||||
"Accept": "application/json",
|
||||
"Cache-Control": "no-cache",
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": fmt.Sprintf("Bearer %s", api.options.AccessToken),
|
||||
},
|
||||
}, result)
|
||||
}
|
||||
|
||||
func (api *Client) Patch(resource string, request interface{}, result interface{}) error {
|
||||
return api.doRequest(resource, requestOptions{
|
||||
HttpMethod: http.MethodPatch,
|
||||
Body: request,
|
||||
Headers: map[string]string{
|
||||
"Accept": "application/json",
|
||||
"Cache-Control": "no-cache",
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": fmt.Sprintf("Bearer %s", api.options.AccessToken),
|
||||
},
|
||||
}, result)
|
||||
}
|
8
go.mod
Normal file
8
go.mod
Normal file
|
@ -0,0 +1,8 @@
|
|||
module github.com/qdimka/go-amo
|
||||
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/gorilla/schema v1.2.0
|
||||
github.com/stretchr/testify v1.6.1
|
||||
)
|
13
go.sum
Normal file
13
go.sum
Normal file
|
@ -0,0 +1,13 @@
|
|||
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/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc=
|
||||
github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
|
||||
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/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
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.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
87
models/leads.go
Normal file
87
models/leads.go
Normal file
|
@ -0,0 +1,87 @@
|
|||
package models
|
||||
|
||||
type Lead struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Price int `json:"price"`
|
||||
ResponsibleUserID int `json:"responsible_user_id"`
|
||||
GroupID int `json:"group_id"`
|
||||
StatusID int `json:"status_id"`
|
||||
PipelineID int `json:"pipeline_id"`
|
||||
LossReasonID int `json:"loss_reason_id,omitempty"`
|
||||
SourceID interface{} `json:"source_id"`
|
||||
CreatedBy int `json:"created_by"`
|
||||
UpdatedBy int `json:"updated_by"`
|
||||
CreatedAt int `json:"created_at"`
|
||||
UpdatedAt int `json:"updated_at"`
|
||||
ClosedAt int `json:"closed_at"`
|
||||
ClosestTaskAt interface{} `json:"closest_task_at"`
|
||||
IsDeleted bool `json:"is_deleted"`
|
||||
CustomFieldsValues []*CustomFieldsValue `json:"custom_fields_values"`
|
||||
Score interface{} `json:"score"`
|
||||
AccountID int `json:"account_id"`
|
||||
IsPriceModifiedByRobot bool `json:"is_price_modified_by_robot"`
|
||||
Links Links `json:"_links"`
|
||||
Embedded Embedded `json:"_embedded"`
|
||||
}
|
||||
|
||||
type Self struct {
|
||||
Href string `json:"href"`
|
||||
}
|
||||
|
||||
type Links struct {
|
||||
Self Self `json:"self"`
|
||||
}
|
||||
|
||||
type Tags struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type Metadata struct {
|
||||
Quantity int `json:"quantity"`
|
||||
CatalogID int `json:"catalog_id"`
|
||||
}
|
||||
|
||||
type CatalogElements struct {
|
||||
ID int `json:"id"`
|
||||
Metadata Metadata `json:"metadata"`
|
||||
}
|
||||
|
||||
type LossReason struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Sort int `json:"sort"`
|
||||
CreatedAt int `json:"created_at"`
|
||||
UpdatedAt int `json:"updated_at"`
|
||||
Links Links `json:"_links"`
|
||||
}
|
||||
|
||||
type Companies struct {
|
||||
ID int `json:"id"`
|
||||
Links Links `json:"_links"`
|
||||
}
|
||||
|
||||
type Contacts struct {
|
||||
ID int `json:"id"`
|
||||
IsMain bool `json:"is_main"`
|
||||
Links Links `json:"_links"`
|
||||
}
|
||||
|
||||
type Embedded struct {
|
||||
Tags []*Tags `json:"tags"`
|
||||
CatalogElements []*CatalogElements `json:"catalog_elements"`
|
||||
LossReason []*LossReason `json:"loss_reason"`
|
||||
Companies []*Companies `json:"companies"`
|
||||
Contacts []*Contacts `json:"contacts"`
|
||||
}
|
||||
|
||||
type Value struct {
|
||||
Value interface{} `json:"value"`
|
||||
}
|
||||
|
||||
type CustomFieldsValue struct {
|
||||
FieldID int `json:"field_id"`
|
||||
FieldCode string `json:"field_code"`
|
||||
Values []*Value `json:"values"`
|
||||
}
|
10
models/users.go
Normal file
10
models/users.go
Normal file
|
@ -0,0 +1,10 @@
|
|||
package models
|
||||
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Lang string `json:"lang"`
|
||||
Rights interface{} `json:"rights"`
|
||||
Links interface{} `json:"_links"`
|
||||
}
|
62
webhooks/webhook.go
Normal file
62
webhooks/webhook.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
package webhooks
|
||||
|
||||
import (
|
||||
"github.com/gorilla/schema"
|
||||
"log"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type WebhookRequest struct {
|
||||
Leads Leads `schema:"leads"`
|
||||
Account Account `schema:"account"`
|
||||
}
|
||||
|
||||
type Leads struct {
|
||||
Status []Status `schema:"status"`
|
||||
Add []Status `schema:"add"`
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
Id string `schema:"id"`
|
||||
StatusId string `schema:"status_id"`
|
||||
PipelineId string `schema:"pipeline_id"`
|
||||
OldStatusId string `schema:"old_status_id"`
|
||||
OldPipelineId string `schema:"old_pipeline_id"`
|
||||
}
|
||||
|
||||
type Account struct {
|
||||
Id string `schema:"id"`
|
||||
SubDomain string `schema:"subdomain"`
|
||||
}
|
||||
|
||||
func NewFromString(body string) (*WebhookRequest, error) {
|
||||
decodedBody, err := url.QueryUnescape(body)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
replacer := strings.NewReplacer("][", ".", "[", ".", "]", "")
|
||||
decodedBody = replacer.Replace(decodedBody)
|
||||
|
||||
bodyMap := make(map[string][]string)
|
||||
|
||||
for _, value := range strings.Split(decodedBody, "&") {
|
||||
parameter := strings.Split(value, "=")
|
||||
bodyMap[parameter[0]] = []string{parameter[1]}
|
||||
}
|
||||
|
||||
webhook := new(WebhookRequest)
|
||||
|
||||
err = schema.NewDecoder().Decode(webhook, bodyMap)
|
||||
|
||||
return webhook, err
|
||||
}
|
||||
|
||||
func (hook *WebhookRequest) GetLeadId() string {
|
||||
if hook.Leads.Status != nil {
|
||||
return hook.Leads.Status[0].Id
|
||||
}
|
||||
return hook.Leads.Add[0].Id
|
||||
}
|
77
webhooks/webhook_test.go
Normal file
77
webhooks/webhook_test.go
Normal file
|
@ -0,0 +1,77 @@
|
|||
package webhooks
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_Webhook_MoveIntoStage(t *testing.T) {
|
||||
requestString := "leads[status][0][id]=2050297&" +
|
||||
"leads[status][0][status_id]=35573056&" +
|
||||
"leads[status][0][pipeline_id]=3643927&" +
|
||||
"leads[status][0][old_status_id]=35572897&" +
|
||||
"leads[status][0][old_pipeline_id]=3643927&" +
|
||||
"account[id]=29085955&" +
|
||||
"account[subdomain]=domain"
|
||||
|
||||
expected := &WebhookRequest{
|
||||
Leads: Leads{
|
||||
Status: []Status{
|
||||
{
|
||||
Id: "2050297",
|
||||
StatusId: "35573056",
|
||||
PipelineId: "3643927",
|
||||
OldStatusId: "35572897",
|
||||
OldPipelineId: "3643927",
|
||||
},
|
||||
},
|
||||
},
|
||||
Account: Account{
|
||||
Id: "29085955",
|
||||
SubDomain: "domain",
|
||||
},
|
||||
}
|
||||
|
||||
webhook, err := NewFromString(requestString)
|
||||
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if assert.NotNil(t, webhook) {
|
||||
assert.Equal(t, webhook, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Webhook_CreateIntoStage(t *testing.T) {
|
||||
requestString := "leads[add][0][id]=2232929&" +
|
||||
"leads[add][0][status_id]=35573056&" +
|
||||
"leads[add][0][pipeline_id]=3643927&" +
|
||||
"account[id]=29085955&account[subdomain]=domain"
|
||||
|
||||
expected := &WebhookRequest{
|
||||
Leads: Leads{
|
||||
Add: []Status{
|
||||
{
|
||||
Id: "2232929",
|
||||
StatusId: "35573056",
|
||||
PipelineId: "3643927",
|
||||
},
|
||||
},
|
||||
},
|
||||
Account: Account{
|
||||
Id: "29085955",
|
||||
SubDomain: "domain",
|
||||
},
|
||||
}
|
||||
|
||||
webhook, err := NewFromString(requestString)
|
||||
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if assert.NotNil(t, webhook) {
|
||||
assert.Equal(t, webhook, expected)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue