docxTemplater/amo/api.go
2023-09-06 15:13:15 +05:00

391 lines
9.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package amo
import (
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/pkg/errors"
"golang.org/x/oauth2"
"penahub.gitlab.yandexcloud.net/backend/templategen/tools"
)
const (
OauthURL = "https://www.amocrm.ru/oauth"
)
type Client struct {
App *ClientApp
Config *oauth2.Config // Индивидуальный конфиг клиента
HTTPClient *http.Client
Subdomain string // Субдомен с которым работаем
Token *oauth2.Token
}
type transport struct {
underlyingTransport http.RoundTripper
}
func (t *transport) RoundTrip(request *http.Request) (*http.Response, error) {
request.Header.Set("User-Agent", "amoCRM-oAuth-client/1.0")
request.Header.Set("Content-Type", "application/json")
return t.underlyingTransport.RoundTrip(request)
}
type ClientApp struct {
Config *oauth2.Config
}
/*
TODO: Вероятно стоит вынести аргументы в отдельную структуру типа Deps.
Аргументация Кирилла:
Я думаю лучше все аргументы для инициализации модуля выносить в структуру,
потому что количество аргументов может в дальнейшем увеличиваться.
И чтобы избежать большого количества аргументов в количестве +3 штук, лучше выносить в структуру.
*/
func NewClientApp(clientID, clientSecret string, redirectURI string) *ClientApp {
return &ClientApp{
Config: &oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
Endpoint: oauth2.Endpoint{
AuthURL: OauthURL,
TokenURL: OauthURL + "2/access_token",
},
RedirectURL: redirectURI,
Scopes: nil,
},
}
}
func (ca *ClientApp) GenerateOAuthURL(amoID, penaID, redirectURL string) (string, error) {
state, err := tools.EncryptTokenAES(tools.StateToken{
AmoID: amoID,
PenaID: penaID,
Service: "amo",
RedirectURL: redirectURL,
})
if err != nil {
return "", err
}
return ca.Config.AuthCodeURL(
state,
oauth2.SetAuthURLParam("mode", "popup"),
), nil
}
func (ca *ClientApp) DecodeJwt(r *http.Request) (*XAuthToken, error) {
tokenHeader := r.Header.Get("x-auth-token")
if tokenHeader == "" {
cookie, err := r.Cookie("x-auth-token")
if err != nil {
return nil, errors.New("empty jwt")
}
tokenHeader = cookie.Value
}
token, err := jwt.ParseWithClaims(tokenHeader, &XAuthToken{}, func(token *jwt.Token) (interface{}, error) {
return []byte(ca.Config.ClientSecret), nil
})
if err != nil {
return nil, err
}
claims, ok := token.Claims.(*XAuthToken)
if !ok || !token.Valid {
fmt.Println("token:", token)
fmt.Println("claims:", claims)
fmt.Println("valid:", token.Valid)
return nil, errors.New("invalid token")
}
return claims, nil
}
func (ca *ClientApp) NewClient(ctx context.Context, referer string, token *oauth2.Token, code string) (*Client,
error) {
var err error
client := &Client{
App: ca,
Config: &oauth2.Config{
ClientID: ca.Config.ClientID,
ClientSecret: ca.Config.ClientSecret,
Endpoint: oauth2.Endpoint{
AuthURL: ca.Config.Endpoint.AuthURL,
TokenURL: "https://" + referer + "/oauth2/access_token",
},
RedirectURL: ca.Config.RedirectURL,
Scopes: ca.Config.Scopes,
},
HTTPClient: nil,
Subdomain: referer,
Token: token,
}
if code != "" {
token, err = client.Config.Exchange(ctx, code)
}
if err != nil {
return nil, err
}
client.Token = token
client.HTTPClient = client.Config.Client(ctx, token)
client.HTTPClient.Transport = &transport{underlyingTransport: client.HTTPClient.Transport}
return client, nil
}
func (ca *ClientApp) RefreshToken(ctx context.Context, oldToken *oauth2.Token, referer string) (*oauth2.Token,
error) {
referer = "https://" + referer
config := &oauth2.Config{
ClientID: ca.Config.ClientID,
ClientSecret: ca.Config.ClientSecret,
Endpoint: oauth2.Endpoint{
AuthURL: ca.Config.Endpoint.AuthURL,
TokenURL: referer + "/oauth2/access_token",
},
RedirectURL: ca.Config.RedirectURL,
Scopes: ca.Config.Scopes,
}
token, err := config.TokenSource(ctx, oldToken).Token()
if err != nil {
return nil, err
}
return token, nil
}
func (c *Client) GetAccount(ctx context.Context) (*Account, error) {
requestURL := url.URL{
Scheme: "https",
Host: c.Subdomain,
Path: "api/v4/account",
}
request, err := http.NewRequestWithContext(ctx, http.MethodGet, requestURL.String(), http.NoBody)
if err != nil {
return nil, errors.Wrap(err, "GetAccount.NewRequest")
}
response, err := c.HTTPClient.Do(request)
if err != nil {
return nil, errors.Wrap(err, "GetAccount.DoRequest")
}
defer func() {
if err = response.Body.Close(); err != nil {
log.Println("ERROR", errors.Wrap(err, "GetAccount.BodyClose"))
}
}()
var (
result Account
bobo []byte
)
bobo, _ = io.ReadAll(response.Body)
fmt.Println("getarrr", string(bobo))
// TODO: Проверить в ssa сокращенную запись if err = operation(); err != nil {...}
err = json.Unmarshal(bobo, &result)
if err != nil {
return nil, err
}
return &result, nil
}
func (c *Client) GetLeadByID(ctx context.Context, id string) (*Lead, error) {
requestURLQueries := make(url.Values)
requestURLQueries.Add("with", "contacts,catalog_elements,is_price_modified_by_robot,loss_reason")
requestURL := url.URL{
Scheme: "https",
Host: c.Subdomain,
Path: fmt.Sprintf("api/v4/leads/%v", id),
RawQuery: requestURLQueries.Encode(),
}
request, err := http.NewRequestWithContext(ctx, http.MethodGet, requestURL.String(), http.NoBody)
fmt.Println("URL:", requestURL.String())
if err != nil {
return nil, errors.Wrap(err, "GetLeadByID.NewRequest")
}
response, err := c.HTTPClient.Do(request)
if err != nil {
return nil, errors.Wrap(err, "GetLeadByID.DoRequest")
}
defer func() {
if err = response.Body.Close(); err != nil {
log.Println("ERROR", errors.Wrap(err, "GetLeadByID.BodyClose"))
}
}()
for response.StatusCode == http.StatusTooManyRequests {
time.Sleep(time.Second)
response, err = c.HTTPClient.Do(request)
if err != nil {
return nil, errors.Wrap(err, "GetLeadByID.DoRequest")
}
func() {
defer func() {
if err = response.Body.Close(); err != nil {
log.Println("ERROR", errors.Wrap(err, "GetLeadByID.BodyClose"))
}
}()
}()
}
var result Lead
responseData, err := io.ReadAll(response.Body)
if err != nil {
return nil, errors.Wrap(err, "GetLeadByID.ReadAll")
}
err = json.Unmarshal(responseData, &result)
if err != nil {
return nil, errors.Wrap(err, "GetLeadByID.Unmarshal")
}
return &result, nil
}
func (c *Client) GetContactByID(ctx context.Context, id string) (*Contact, error) {
requestURLQueries := make(url.Values)
requestURLQueries.Add("with", "catalog_elements,leads,customers")
requestURL := url.URL{
Scheme: "https",
Host: c.Subdomain,
Path: fmt.Sprintf("api/v4/contacts/%v", id),
RawQuery: requestURLQueries.Encode(),
}
request, err := http.NewRequestWithContext(ctx, http.MethodGet, requestURL.String(), http.NoBody)
if err != nil {
return nil, errors.Wrap(err, "GetContactByID.NewRequest")
}
response, err := c.HTTPClient.Do(request)
if err != nil {
return nil, errors.Wrap(err, "GetContactByID.DoRequest")
}
defer func() {
if err = response.Body.Close(); err != nil {
log.Println("ERROR", errors.Wrap(err, "GetContactByID.BodyClose"))
}
}()
// TODO: переписать на отдельную сущность-клиент, которая сама внутри себя будет мониторить вопрос rate-limit
for response.StatusCode == http.StatusTooManyRequests {
time.Sleep(time.Second)
response, err = c.HTTPClient.Do(request)
if err != nil {
return nil, errors.Wrap(err, "GetContactByID.ForDoRequest")
}
func() {
defer func() {
if err = response.Body.Close(); err != nil {
log.Println("ERROR", errors.Wrap(err, "GetContactByID.BodyClose"))
}
}()
}()
}
var result Contact
err = json.NewDecoder(response.Body).Decode(&result)
if err != nil {
return nil, errors.Wrap(err, "GetContactByID.Decode")
}
return &result, nil
}
func (c *Client) GetCompanyByID(ctx context.Context, id string) (*Company, error) {
requestURLQueries := make(url.Values)
requestURLQueries.Add("with", "contacts,leads,catalog_elements,customers")
requestURL := url.URL{
Scheme: "https",
Host: c.Subdomain,
Path: fmt.Sprintf("api/v4/companies/%v", id),
RawQuery: requestURLQueries.Encode(),
}
request, err := http.NewRequestWithContext(ctx, http.MethodGet, requestURL.String(), http.NoBody)
if err != nil {
return nil, errors.Wrap(err, "GetCompanyByID.NewRequest")
}
response, err := c.HTTPClient.Do(request)
if err != nil {
return nil, errors.Wrap(err, "GetCompanyByID.DoRequest")
}
defer func() {
if err = response.Body.Close(); err != nil {
log.Println("ERROR", errors.Wrap(err, "GetCompanyByID.BodyClose"))
}
}()
for response.StatusCode == http.StatusTooManyRequests {
time.Sleep(time.Second)
response, err = c.HTTPClient.Do(request)
if err != nil {
return nil, errors.Wrap(err, "GetCompanyByID.ForDoRequest")
}
func() {
defer func() {
if err = response.Body.Close(); err != nil {
log.Println("ERROR", errors.Wrap(err, "GetCompanyByID.BodyClose"))
}
}()
}()
}
var result Company
err = json.NewDecoder(response.Body).Decode(&result)
if err != nil {
return nil, errors.Wrap(err, "GetCompanyByID.Decode")
}
return &result, nil
}