package tnc import ( "fmt" "net/http" "net/url" "time" "git.selly.red/Selly-Modules/logger" "git.selly.red/Selly-Modules/natsio" "git.selly.red/Selly-Modules/natsio/model" "git.selly.red/Selly-Modules/natsio/subject" "github.com/nats-io/nats.go" "git.selly.red/Selly-Modules/3pl/util/httputil" "git.selly.red/Selly-Modules/3pl/util/pjson" ) // Client ... type Client struct { // Auth info realm string clientID string clientSecret string env ENV natsClient natsio.Server token string tokenExpireAt time.Time } // NewClient ... func NewClient(env ENV, clientID, clientSecret, realm string, natsClient natsio.Server) (*Client, error) { if env != EnvProd && env != EnvStaging { return nil, fmt.Errorf("tnc.NewClient: invalid_env %s", env) } return &Client{ realm: realm, clientID: clientID, clientSecret: clientSecret, token: "", env: env, natsClient: natsClient, }, nil } // CreateOutboundRequest ... func (c *Client) CreateOutboundRequest(p OutboundRequestPayload) (*OutboundRequestRes, error) { apiURL := c.getBaseURL() + apiPathCreateOutboundRequest natsPayload := model.CommunicationRequestHttp{ ResponseImmediately: true, Payload: model.HttpRequest{ URL: apiURL, Method: http.MethodPost, Data: pjson.ToJSONString([]OutboundRequestPayload{p}), Header: c.getRequestHeader(), }, } msg, err := c.requestHttpViaNats(natsPayload) if err != nil { logger.Error("tnc.Client.CreateOutboundRequest - requestHttpViaNats", logger.LogData{ "err": err.Error(), "payload": natsPayload, }) return nil, err } var ( r model.CommunicationHttpResponse dataRes []OutboundRequestRes ) if err = pjson.Unmarshal(msg.Data, &r); err != nil { return nil, fmt.Errorf("tnc.Client.CreateOutboundRequest: parse_data %v", err) } res := r.Response if res == nil { return nil, fmt.Errorf("tnc.Client.CreateOutboundRequest: empty_response") } if res.StatusCode >= http.StatusBadRequest { var errRes Error if err = r.ParseResponseData(&errRes); err != nil { return nil, fmt.Errorf("tnc.Client.CreateOutboundRequest: parse_response_err: %v", err) } return nil, errRes } if err = r.ParseResponseData(&dataRes); err != nil { return nil, fmt.Errorf("tnc.Client.CreateOutboundRequest: parse_response_data: %v", err) } if len(dataRes) == 0 { return nil, fmt.Errorf("tnc.Client.CreateOutboundRequest: empty_result") } item := &dataRes[0] e := item.Error if e != nil { return nil, e } return item, err } // UpdateOutboundRequestLogisticInfo ... func (c *Client) UpdateOutboundRequestLogisticInfo(p UpdateORLogisticInfoPayload) error { apiURL := c.getBaseURL() + fmt.Sprintf(apiPathUpdateLogisticInfoOutboundRequest, p.OrID) natsPayload := model.CommunicationRequestHttp{ ResponseImmediately: true, Payload: model.HttpRequest{ URL: apiURL, Method: http.MethodPost, Header: c.getRequestHeader(), Data: pjson.ToJSONString(p), }, } msg, err := c.requestHttpViaNats(natsPayload) if err != nil { logger.Error("tnc.Client.UpdateOutboundRequestLogisticInfo - requestHttpViaNats", logger.LogData{ "err": err.Error(), "payload": natsPayload, }) return err } var ( r model.CommunicationHttpResponse errRes Error ) if err = pjson.Unmarshal(msg.Data, &r); err != nil { return fmt.Errorf("tnc.Client.UpdateOutboundRequestLogisticInfo: parse_data %v", err) } res := r.Response if res == nil { return fmt.Errorf("tnc.Client.UpdateOutboundRequestLogisticInfo: empty_response") } if res.StatusCode >= http.StatusBadRequest { if err = r.ParseResponseData(&errRes); err != nil { return fmt.Errorf("tnc.Client.UpdateOutboundRequestLogisticInfo: parse_response_err: %v", err) } return errRes } return nil } // GetOutboundRequestByID ... func (c *Client) GetOutboundRequestByID(requestID int) (*OutboundRequestInfo, error) { apiURL := c.getBaseURL() + fmt.Sprintf(apiPathGetOutboundRequest, requestID) natsPayload := model.CommunicationRequestHttp{ ResponseImmediately: true, Payload: model.HttpRequest{ URL: apiURL, Method: http.MethodGet, Header: c.getRequestHeader(), }, } msg, err := c.requestHttpViaNats(natsPayload) if err != nil { logger.Error("tnc.Client.GetOutboundRequestByID - requestHttpViaNats", logger.LogData{ "err": err.Error(), "payload": natsPayload, }) return nil, err } var ( r model.CommunicationHttpResponse errRes Error outboundRequest OutboundRequestInfo ) if err = pjson.Unmarshal(msg.Data, &r); err != nil { return nil, fmt.Errorf("tnc.Client.GetOutboundRequestByID: parse_data %v", err) } res := r.Response if res == nil { return nil, fmt.Errorf("tnc.Client.GetOutboundRequestByID: empty_response") } if res.StatusCode >= http.StatusBadRequest { if err = r.ParseResponseData(&errRes); err != nil { return nil, fmt.Errorf("tnc.Client.GetOutboundRequestByID: parse_response_err: %v", err) } return nil, errRes } if err = r.ParseResponseData(&outboundRequest); err != nil { return nil, fmt.Errorf("tnc.Client.GetOutboundRequestByID: parse_response_data: %v", err) } return &outboundRequest, nil } // CancelOutboundRequest ... func (c *Client) CancelOutboundRequest(requestID int, note string) error { apiURL := c.getBaseURL() + fmt.Sprintf(apiPathCancelOutboundRequest, requestID) data := map[string]string{"note": note} natsPayload := model.CommunicationRequestHttp{ ResponseImmediately: true, Payload: model.HttpRequest{ URL: apiURL, Method: http.MethodPost, Header: c.getRequestHeader(), Data: pjson.ToJSONString(data), }, } msg, err := c.requestHttpViaNats(natsPayload) if err != nil { logger.Error("tnc.Client.CancelOutboundRequest - requestHttpViaNats", logger.LogData{ "err": err.Error(), "payload": natsPayload, }) return err } var ( r model.CommunicationHttpResponse errRes Error ) if err = pjson.Unmarshal(msg.Data, &r); err != nil { return fmt.Errorf("tnc.Client.CancelOutboundRequest: parse_data %v", err) } res := r.Response if res == nil { return fmt.Errorf("tnc.Client.CancelOutboundRequest: empty_response") } if res.StatusCode >= http.StatusBadRequest { if err = r.ParseResponseData(&errRes); err != nil { return fmt.Errorf("tnc.Client.CancelOutboundRequest: parse_response_err: %v", err) } return errRes } return nil } func (c *Client) auth() (*authRes, error) { v := url.Values{} v.Add("realm", c.realm) v.Add("grant_type", "client_credentials") v.Add("client_id", c.clientID) v.Add("client_secret", c.clientSecret) body := v.Encode() header := map[string]string{ httputil.HeaderKeyContentType: httputil.HeaderValueApplicationURLEncoded, } apiURL := baseURLAuthENVMapping[c.env] + fmt.Sprintf(apiPathAuth, c.realm) natsPayload := model.CommunicationRequestHttp{ ResponseImmediately: true, Payload: model.HttpRequest{ URL: apiURL, Method: http.MethodPost, Data: body, Header: header, }, } msg, err := c.requestHttpViaNats(natsPayload) if err != nil { logger.Error("tnc.Client.auth - requestHttpViaNats", logger.LogData{ "err": err.Error(), "payload": natsPayload, }) return nil, err } var ( r model.CommunicationHttpResponse errRes Error data authRes ) if err = pjson.Unmarshal(msg.Data, &r); err != nil { return nil, fmt.Errorf("tnc.Client.auth: parse_data %v", err) } res := r.Response if res == nil { return nil, fmt.Errorf("tnc.Client.auth: empty_response") } if res.StatusCode >= http.StatusBadRequest { if err = r.ParseResponseData(&errRes); err != nil { return nil, fmt.Errorf("tnc.Client.auth: parse_response_err: %v", err) } return nil, errRes } if err = r.ParseResponseData(&data); err != nil { return nil, fmt.Errorf("tnc.Client.auth: parse_response_data: %v", err) } return &data, nil } func (c *Client) getRequestHeader() map[string]string { m := map[string]string{ httputil.HeaderKeyContentType: httputil.HeaderValueApplicationJSON, } token, err := c.getToken() if err != nil { logger.Error("tnc.Client.getToken", logger.LogData{"err": err.Error()}) } else { m["Authorization"] = fmt.Sprintf("Bearer %s", token) } return m } func (c *Client) requestHttpViaNats(data model.CommunicationRequestHttp) (*nats.Msg, error) { b := pjson.ToBytes(data) return c.natsClient.Request(subject.Communication.RequestHTTP, b) } func (c *Client) getBaseURL() string { return baseURLENVMapping[c.env] } func (c *Client) getToken() (string, error) { if c.token != "" || c.tokenExpireAt.After(time.Now()) { return c.token, nil } data, err := c.auth() if err != nil { return "", err } c.token = data.AccessToken d := time.Duration(data.ExpiresIn) * time.Second if d.Minutes() > 30 { d -= 30 * time.Minute } c.tokenExpireAt = time.Now().Add(d) return c.token, nil }