From 663c1f999c4aef603d57e18cd4c5b4fe41d31ebd Mon Sep 17 00:00:00 2001 From: Alex Lushpai Date: Fri, 3 Aug 2018 18:11:37 +0300 Subject: [PATCH] initial client structure --- .gitignore | 29 ++++ .travis.yml | 9 ++ v1/client.go | 381 ++++++++++++++++++++++++++++++++++++++++++++++ v1/client_test.go | 14 ++ v1/request.go | 108 +++++++++++++ v1/types.go | 275 +++++++++++++++++++++++++++++++++ 6 files changed, 816 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 v1/client.go create mode 100644 v1/client_test.go create mode 100644 v1/request.go create mode 100644 v1/types.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d70a5fb --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + +# IDE's files +.idea +*.iml + +# Project ignores diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..f39aae2 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,9 @@ +language: go +go: + - '1.8' + - '1.9' + - '1.10' +before_install: + - go get -v github.com/google/go-querystring/query + - go get -v github.com/h2non/gock +script: go test -v ./... diff --git a/v1/client.go b/v1/client.go new file mode 100644 index 0000000..17509e7 --- /dev/null +++ b/v1/client.go @@ -0,0 +1,381 @@ +package v1 + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "time" + + "github.com/google/go-querystring/query" +) + +// New initialize client +func New(url string, token string) *MgClient { + return &MgClient{ + URL: url, + Token: token, + httpClient: &http.Client{Timeout: 20 * time.Second}, + } +} + +func (c *MgClient) Bots(request BotsRequest) (BotsResponse, int, error) { + var resp BotsResponse + var b []byte + outgoing, _ := query.Values(request) + + data, status, err := c.GetRequest(fmt.Sprintf("/bots?%s", outgoing.Encode()), b) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +func (c *MgClient) Channels(request ChannelsRequest) (ChannelsResponse, int, error) { + var resp ChannelsResponse + var b []byte + outgoing, _ := query.Values(request) + + data, status, err := c.GetRequest(fmt.Sprintf("/channels?%s", outgoing.Encode()), b) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +func (c *MgClient) Managers(request ManagersRequest) (ManagersResponse, int, error) { + var resp ManagersResponse + var b []byte + outgoing, _ := query.Values(request) + + data, status, err := c.GetRequest(fmt.Sprintf("/managers?%s", outgoing.Encode()), b) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +func (c *MgClient) Customers(request CustomersRequest) (CustomersResponse, int, error) { + var resp CustomersResponse + var b []byte + outgoing, _ := query.Values(request) + + data, status, err := c.GetRequest(fmt.Sprintf("/customers?%s", outgoing.Encode()), b) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +func (c *MgClient) Chats(request ChatsRequest) (ChatsResponse, int, error) { + var resp ChatsResponse + var b []byte + outgoing, _ := query.Values(request) + + data, status, err := c.GetRequest(fmt.Sprintf("/chats?%s", outgoing.Encode()), b) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +func (c *MgClient) Members(request MembersRequest) (MembersResponse, int, error) { + var resp MembersResponse + var b []byte + outgoing, _ := query.Values(request) + + data, status, err := c.GetRequest(fmt.Sprintf("/members?%s", outgoing.Encode()), b) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +func (c *MgClient) Dialogs(request DialogsRequest) (DialogsResponse, int, error) { + var resp DialogsResponse + var b []byte + outgoing, _ := query.Values(request) + + data, status, err := c.GetRequest(fmt.Sprintf("/dialogs?%s", outgoing.Encode()), b) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +func (c *MgClient) DialogAssign(request DialogAssignRequest) (DialogAssignResponse, int, error) { + var resp DialogAssignResponse + outgoing, _ := json.Marshal(&request) + + data, status, err := c.PostRequest(fmt.Sprintf("/dialogs/%s/assign", request.ID), []byte(outgoing)) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +func (c *MgClient) DialogClose(request DialogCloseRequest) (DialogCloseResponse, int, error) { + var resp DialogCloseResponse + outgoing, _ := json.Marshal(&request) + + data, status, err := c.PostRequest(fmt.Sprintf("/dialogs/%s/close", request.ID), []byte(outgoing)) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +func (c *MgClient) Messages(request MessagesRequest) (MessagesResponse, int, error) { + var resp MessagesResponse + var b []byte + outgoing, _ := query.Values(request) + + data, status, err := c.GetRequest(fmt.Sprintf("/messages?%s", outgoing.Encode()), b) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +func (c *MgClient) MessageSend(request MessageSendRequest) (MessageResponse, int, error) { + var resp MessageResponse + outgoing, _ := json.Marshal(&request) + + data, status, err := c.PostRequest("/messages", []byte(outgoing)) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +func (c *MgClient) MessageEdit(request MessageEditRequest) (MessageResponse, int, error) { + var resp MessageResponse + outgoing, _ := json.Marshal(&request) + + data, status, err := c.PatchRequest(fmt.Sprintf("/messages/%s", request.ID), []byte(outgoing)) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +func (c *MgClient) MessageDelete(request MessageDeleteRequest) (MessageResponse, int, error) { + var resp MessageResponse + outgoing, _ := json.Marshal(&request) + + data, status, err := c.DeleteRequest(fmt.Sprintf("/messages/%s", request.ID), []byte(outgoing)) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +func (c *MgClient) Info(request InfoRequest) (InfoResponse, int, error) { + var resp InfoResponse + outgoing, _ := json.Marshal(&request) + + data, status, err := c.PatchRequest("/messages/info", []byte(outgoing)) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +func (c *MgClient) Commands(request CommandsRequest) (CommandsResponse, int, error) { + var resp CommandsResponse + var b []byte + outgoing, _ := query.Values(request) + + data, status, err := c.GetRequest(fmt.Sprintf("/my/commands?%s", outgoing.Encode()), b) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +func (c *MgClient) CommandEdit(request CommandEditRequest) (CommandEditResponse, int, error) { + var resp CommandEditResponse + outgoing, _ := json.Marshal(&request) + + data, status, err := c.PutRequest(fmt.Sprintf("/my/commands/%s", request.Name), []byte(outgoing)) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +func (c *MgClient) CommandDelete(request CommandDeleteRequest) (CommandDeleteResponse, int, error) { + var resp CommandDeleteResponse + outgoing, _ := json.Marshal(&request) + + data, status, err := c.DeleteRequest(fmt.Sprintf("/my/commands/%s", request.Name), []byte(outgoing)) + if err != nil { + return resp, status, err + } + + if err := json.Unmarshal(data, &resp); err != nil { + return resp, status, err + } + + if status > http.StatusCreated || status < http.StatusOK { + return resp, status, c.Error(data) + } + + return resp, status, err +} + +func (c *MgClient) Error(info []byte) error { + var data map[string]interface{} + + if err := json.Unmarshal(info, &data); err != nil { + return err + } + + values := data["errors"].([]interface{}) + + return errors.New(values[0].(string)) +} diff --git a/v1/client_test.go b/v1/client_test.go new file mode 100644 index 0000000..ef7d324 --- /dev/null +++ b/v1/client_test.go @@ -0,0 +1,14 @@ +package v1 + +import ( + "os" +) + +var ( + mgURL = os.Getenv("MG_URL") + mgToken = os.Getenv("MG_TOKEN") +) + +func client() *MgClient { + return New(mgURL, mgToken) +} diff --git a/v1/request.go b/v1/request.go new file mode 100644 index 0000000..9712d96 --- /dev/null +++ b/v1/request.go @@ -0,0 +1,108 @@ +package v1 + +import ( + "bytes" + "fmt" + "io/ioutil" + "log" + "net/http" +) + +var prefix = "/api/v1" + +// GetRequest implements GET Request +func (c *MgClient) GetRequest(url string, parameters []byte) ([]byte, int, error) { + return makeRequest( + "GET", + fmt.Sprintf("%s%s%s", c.URL, prefix, url), + bytes.NewBuffer(parameters), + c, + ) +} + +// PostRequest implements POST Request +func (c *MgClient) PostRequest(url string, parameters []byte) ([]byte, int, error) { + return makeRequest( + "POST", + fmt.Sprintf("%s%s%s", c.URL, prefix, url), + bytes.NewBuffer(parameters), + c, + ) +} + +// PatchRequest implements PATCH Request +func (c *MgClient) PatchRequest(url string, parameters []byte) ([]byte, int, error) { + return makeRequest( + "PATCH", + fmt.Sprintf("%s%s%s", c.URL, prefix, url), + bytes.NewBuffer(parameters), + c, + ) +} + +// PutRequest implements PUT Request +func (c *MgClient) PutRequest(url string, parameters []byte) ([]byte, int, error) { + return makeRequest( + "PUT", + fmt.Sprintf("%s%s%s", c.URL, prefix, url), + bytes.NewBuffer(parameters), + c, + ) +} + +// DeleteRequest implements DELETE Request +func (c *MgClient) DeleteRequest(url string, parameters []byte) ([]byte, int, error) { + return makeRequest( + "DELETE", + fmt.Sprintf("%s%s%s", c.URL, prefix, url), + bytes.NewBuffer(parameters), + c, + ) +} + +func makeRequest(reqType, url string, buf *bytes.Buffer, c *MgClient) ([]byte, int, error) { + var res []byte + req, err := http.NewRequest(reqType, url, buf) + if err != nil { + return res, 0, err + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Bot-Token", c.Token) + + if c.Debug { + log.Printf("MG BOT API Request: %s %s %s %s", reqType, url, c.Token, buf.String()) + } + + resp, err := c.httpClient.Do(req) + if err != nil { + return res, 0, err + } + + if resp.StatusCode >= http.StatusInternalServerError { + err = fmt.Errorf("http request error. Status code: %d", resp.StatusCode) + return res, resp.StatusCode, err + } + + res, err = buildRawResponse(resp) + if err != nil { + return res, 0, err + } + + if c.Debug { + log.Printf("MG BOT API Response: %s", res) + } + + return res, resp.StatusCode, err +} + +func buildRawResponse(resp *http.Response) ([]byte, error) { + defer resp.Body.Close() + + res, err := ioutil.ReadAll(resp.Body) + if err != nil { + return res, err + } + + return res, nil +} diff --git a/v1/types.go b/v1/types.go new file mode 100644 index 0000000..76d7aa4 --- /dev/null +++ b/v1/types.go @@ -0,0 +1,275 @@ +package v1 + +import ( + "net/http" +) + +// MgClient type +type MgClient struct { + URL string `json:"url"` + Token string `json:"token"` + Debug bool `json:"debug"` + httpClient *http.Client +} + +type BotsRequest struct { + ID uint64 + Self string + Active string + Since string + Until string +} + +type ChannelsRequest struct { + ID uint64 + Types string + Active string + Since string + Until string +} + +type ManagersRequest struct { + ID uint64 + ExternalID string `json:"external_id"` + Online string + Active string + Since string + Until string +} + +type CustomersRequest struct { + ID uint64 + ExternalID string `json:"external_id"` + Since string + Until string +} + +type ChatsRequest struct { + ID uint64 + ChannelID string `json:"channel_id"` + ChannelType string `json:"channel_type"` + Since string + Until string +} + +type MembersRequest struct { + ChatID string `json:"chat_id"` + ManagerID string `json:"manager_id"` + CustomerID string `json:"customer_id"` + Status string + Since string + Until string +} + +type DialogsRequest struct { + ID uint64 + ChatID string `json:"chat_id"` + ManagerID string `json:"manager_id"` + BotID string `json:"bot_id"` + Active string + Assigned string + Since string + Until string +} + +type MessagesRequest struct { + ID uint64 + ChatID string `json:"chat_id"` + DialogID string `json:"dialog_id"` + ManagerID string `json:"manager_id"` + CustomerID string `json:"customer_id"` + BotID string `json:"bot_id"` + ChannelID string `json:"channel_id"` + ChannelType string `json:"channel_type"` + Scope string + Since string + Until string +} + +type MessageSendRequest struct { + Content string `json:"content"` + Scope uint8 `json:"scope"` + ChatID uint64 `json:"chat_id"` + QuoteMessageId uint64 `json:"omitempty,quote_message_id"` +} + +type MessageEditRequest struct { + ID uint64 + Content string `json:"content"` +} + +type CommandsRequest struct { + ID string + Name string + Since string + Until string +} + +type BotsResponse struct { + Bots []BotListItem +} + +type BotListItem struct { + ID uint64 `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Events []string `json:"events,omitempty,brackets"` + ClientID string `json:"client_id"` + AvatarUrl string `json:"avatar_url"` + CreatedAt string `json:"created_at"` + UpdatedAt *string `json:"updated_at"` + DeactivatedAt *string `json:"deactivated_at"` + IsActive bool `json:"is_active"` + IsSelf bool `json:"is_self"` +} + +type ChannelsResponse struct { + Channels []ChannelListItem +} + +type ChannelListItem struct { + ID uint64 `json:"id"` + Type string `json:"type"` + Events []string `json:"events,omitempty,brackets"` + CreatedAt string `json:"created_at"` + UpdatedAt *string `json:"updated_at"` + ActivatedAt string `json:"activated_at"` + DeactivatedAt *string `json:"deactivated_at"` + IsActive bool `json:"is_active"` +} + +type ManagersResponse struct { + Managers []ManagersListItem +} + +type ManagersListItem struct { + ID uint64 `json:"id"` + ExternalID *string `json:"external_id,omitempty"` + Username *string `json:"username,omitempty"` + FirstName *string `json:"first_name,omitempty"` + LastName *string `json:"last_name,omitempty"` + CreatedAt string `json:"created_at"` + UpdatedAt *string `json:"updated_at,omitempty"` + RevokedAt *string `json:"revoked_at,omitempty"` + IsOnline bool `json:"is_online"` + IsActive bool `json:"is_active"` + Avatar *string `json:"avatar_url,omitempty"` +} + +type CustomersResponse struct { + Customers []CustomersListItem +} + +type CustomersListItem struct { + ID uint64 `json:"id"` + ExternalID *string `json:"external_id,omitempty"` + ChannelId *uint64 `json:"channel_id,omitempty"` + Username *string `json:"username,omitempty"` + FirstName *string `json:"first_name,omitempty"` + LastName *string `json:"last_name,omitempty"` + CreatedAt string `json:"created_at"` + UpdatedAt *string `json:"updated_at,omitempty"` + RevokedAt *string `json:"revoked_at,omitempty"` + Avatar *string `json:"avatar_url,omitempty"` + ProfileURL *string `json:"profile_url,omitempty"` + Country *string `json:"country,omitempty"` + Language *string `json:"language,omitempty"` + Phone *string `json:"phone,omitempty"` +} + +type ChatsResponse struct { + Chats []ChatListItem +} + +type ChatListItem struct { + ID uint64 `json:"id"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + Channel *string `json:"channel,omitempty"` + ChannelId *uint64 `json:"channel_id,omitempty"` +} + +type MembersResponse struct { + Members []MemberListItem +} + +type MemberListItem struct { + ID uint64 `json:"id"` + CreatedAt string `json:"created_at"` + UpdatedAt *string `json:"updated_at,omitempty"` + IsAuthor bool `json:"is_author"` + State string `json:"state"` + ChatID uint64 `json:"chat_id"` + UserID uint64 `json:"user_id"` +} + +type DialogsResponse struct { + Dialogs []DialogListItem +} + +type DialogListItem struct { + ID uint64 `json:"id"` + ChatID uint64 `json:"chat_id"` + BotID *uint64 `json:"bot_id,omitempty"` + BeginMessageID *uint64 `json:"begin_message_id,omitempty"` + EndingMessageID *uint64 `json:"ending_message_id,omitempty"` + CreatedAt string `json:"created_at"` + UpdatedAt *string `json:"updated_at,omitempty"` + ClosedAt *string `json:"closed_at,omitempty"` + IsAssign bool `json:"is_assign"` + Responsible *Responsible `json:"responsible,omitempty"` + IsActive bool `json:"is_active"` +} + +type MessagesResponse struct { + Messages []MessagesListItem +} + +type MessagesListItem struct { + ID uint64 `json:"id"` + Content string `json:"content"` + CreatedAt string `json:"created_at"` + UpdatedAt *string `json:"updated_at"` + Scope uint8 `json:"scope"` + ChatID uint64 `json:"chat_id"` + Sender Sender `json:"sender"` + ChannelID *uint64 `json:"channel_id,omitempty"` + ChannelSentAt *string `json:"channel_sent_at,omitempty"` + QuoteMessageId *uint64 `json:"quote_message_id,omitempty"` + EditedAt *string `json:"edited_at,omitempty"` + DeletedAt *string `json:"deleted_at,omitempty"` +} + +type MessageResponse struct { + ID uint64 `json:"id"` + Time string `json:"content"` +} + +type UpdateBotRequest struct { + Name string `json:"name,omitempty"` + Avatar *string `json:"avatar_url"` + Events []string `json:"events,omitempty,brackets"` +} + +type Responsible struct { + Type string `json:"type"` + ID int64 `json:"id"` + AssignAt string `json:"assign_at"` +} + +type DialogResponsibleRequest struct { + ManagerID int64 `json:"manager_id"` + BotID int64 `json:"bot_id"` +} + +type AssignResponse struct { + Responsible Responsible `json:"responsible"` + IsReAssign bool `json:"is_reassign"` + PreviousResponsible *Responsible `json:"previous_responsible,omitempty"` + LeftManagerID *uint64 `json:"left_manager_id,omitempty"` +} + +type Sender struct { + ID int64 + Type string +}