mg-transport-core/core/util/utils.go
2024-06-07 17:56:49 +03:00

298 lines
6.9 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 util
import (
"bytes"
// nolint:gosec
"crypto/sha1"
"crypto/sha256"
"encoding/json"
"fmt"
"io"
"net/http"
"regexp"
"strings"
"sync/atomic"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/gin-gonic/gin"
retailcrm "github.com/retailcrm/api-client-go/v2"
v1 "github.com/retailcrm/mg-transport-api-client-go/v1"
"github.com/retailcrm/mg-transport-core/v2/core/config"
"github.com/retailcrm/mg-transport-core/v2/core/logger"
"github.com/retailcrm/mg-transport-core/v2/core/util/errorutil"
)
var (
markdownSymbols = []string{"*", "_", "`", "["}
slashRegex = regexp.MustCompile(`/+$`)
)
var (
DefaultScopes = []string{
"integration_read",
"integration_write",
}
DefaultCredentials = []string{
"/api/integration-modules/{code}",
"/api/integration-modules/{code}/edit",
}
)
var defaultCurrencies = map[string]string{
"rub": "₽",
"uah": "₴",
"byn": "Br",
"kzt": "₸",
"usd": "$",
"eur": "€",
"prb": "PRB",
"mdl": "L",
"kgs": "с",
"pln": "zł",
"azn": "₼",
"amd": "֏",
"thb": "฿",
"aed": "AED",
"nok": "kr",
"cad": "C$",
"czk": "Kč",
"sek": "kr",
"dkk": "kr",
"ron": "lei",
"uzs": "So'm",
"aud": "$",
"chf": "₣",
"inr": "₹",
"bgn": "лв",
"ngn": "₦",
"huf": "ƒ",
"ils": "₪",
"try": "₺",
"stn": "₡",
"ars": "$",
"bob": "Bs",
"ves": "Bs",
"gtq": "Q",
"hnl": "L",
"dop": "RD$",
"cop": "COL$",
"crc": "₡",
"cup": "$MN",
"nio": "C$",
"pab": "B/",
"pyg": "₲",
"pen": "S/",
"svc": "₡",
"uyu": "$U",
"gel": "₾",
"gbp": "£",
}
// Utils service object.
type Utils struct {
Logger logger.Logger
slashRegex *regexp.Regexp
AWS config.AWS
TokenCounter uint32
IsDebug bool
}
// NewUtils will create new Utils instance.
func NewUtils(awsConfig config.AWS, logger logger.Logger, debug bool) *Utils {
return &Utils{
IsDebug: debug,
AWS: awsConfig,
Logger: logger,
TokenCounter: 0,
slashRegex: slashRegex,
}
}
// ResetUtils resets the utils inner state.
func (u *Utils) ResetUtils(awsConfig config.AWS, debug bool, tokenCounter uint32) {
u.TokenCounter = tokenCounter
u.AWS = awsConfig
u.IsDebug = debug
u.slashRegex = slashRegex
}
// GenerateToken will generate long pseudo-random string.
func (u *Utils) GenerateToken() string {
c := atomic.AddUint32(&u.TokenCounter, 1)
return fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprintf("%d%d", time.Now().UnixNano(), c))))
}
// GetAPIClient will initialize RetailCRM api client from url and key.
// Scopes will be used to determine if client is valid. If there are no scopes - credentials will be used instead.
func (u *Utils) GetAPIClient(
url, key string, scopes []string, credentials ...[]string) (*retailcrm.Client, int, error) {
client := retailcrm.New(url, key).
WithLogger(logger.APIClientAdapter(u.Logger))
client.Debug = u.IsDebug
cr, status, err := client.APICredentials()
if err != nil {
return nil, status, err
}
if res := u.checkScopes(cr.Scopes, scopes); len(res) != 0 {
if len(credentials) == 0 || len(cr.Scopes) > 0 {
u.Logger.Error(url, logger.HTTPStatusCode(status), logger.Body(res))
return nil, http.StatusBadRequest, errorutil.NewInsufficientScopesErr(res)
}
if res := u.checkScopes(cr.Credentials, credentials[0]); len(res) != 0 {
u.Logger.Error(url, logger.HTTPStatusCode(status), logger.Body(res))
return nil, http.StatusBadRequest, errorutil.NewInsufficientScopesErr(res)
}
}
return client, 0, nil
}
func (u *Utils) checkScopes(scopes []string, scopesRequired []string) []string {
rs := make([]string, len(scopesRequired))
copy(rs, scopesRequired)
for _, vs := range scopes {
for kn, vn := range rs {
if vn == vs {
if len(rs) == 1 {
rs = rs[:0]
break
}
rs = append(rs[:kn], rs[kn+1:]...)
}
}
}
return rs
}
// UploadUserAvatar will upload avatar for user.
func (u *Utils) UploadUserAvatar(url string) (picURLs3 string, err error) {
s3Config := &aws.Config{
Credentials: credentials.NewStaticCredentials(
u.AWS.AccessKeyID,
u.AWS.SecretAccessKey,
""),
Region: aws.String(u.AWS.Region),
}
if u.AWS.Endpoint != "" {
s3Config.Endpoint = aws.String(u.AWS.Endpoint)
}
s := session.Must(session.NewSession(s3Config))
uploader := s3manager.NewUploader(s)
// nolint:gosec
resp, err := http.Get(url)
if err != nil {
return
}
defer resp.Body.Close()
if resp.StatusCode >= http.StatusBadRequest {
return "", fmt.Errorf("get: %v code: %v", url, resp.StatusCode)
}
result, err := uploader.Upload(&s3manager.UploadInput{
Bucket: aws.String(u.AWS.Bucket),
Key: aws.String(fmt.Sprintf("%v/%v.jpg", u.AWS.FolderName, u.GenerateToken())),
Body: resp.Body,
ContentType: aws.String(u.AWS.ContentType),
ACL: aws.String("public-read"),
})
if err != nil {
return
}
picURLs3 = result.Location
return
}
// RemoveTrailingSlash will remove slash at the end of any string.
func (u *Utils) RemoveTrailingSlash(crmURL string) string {
return u.slashRegex.ReplaceAllString(crmURL, ``)
}
// GetMGItemData will upload file to MG by URL and return information about attachable item.
func GetMGItemData(client *v1.MgClient, url string, caption string) (v1.Item, int, error) {
item := v1.Item{}
data, st, err := client.UploadFileByURL(
v1.UploadFileByUrlRequest{
Url: url,
},
)
if err != nil {
return item, st, err
}
item.ID = data.ID
item.Caption = caption
return item, st, err
}
// GetEntitySHA1 will serialize any value to JSON and return SHA1 hash of this JSON.
func GetEntitySHA1(v interface{}) (hash string, err error) {
res, _ := json.Marshal(v)
// nolint:gosec
h := sha1.New()
_, err = h.Write(res)
hash = fmt.Sprintf("%x", h.Sum(nil))
return
}
// ReplaceMarkdownSymbols will remove markdown symbols from text.
func ReplaceMarkdownSymbols(s string) string {
for _, v := range markdownSymbols {
s = strings.ReplaceAll(s, v, "\\"+v)
}
return s
}
// DefaultCurrencies will return default currencies list for all bots.
func DefaultCurrencies() map[string]string {
return defaultCurrencies
}
// GetCurrencySymbol returns currency symbol by it's ISO 4127 code.
// It returns provided currency code in uppercase if currency symbol cannot be found.
func GetCurrencySymbol(code string) string {
if i, ok := DefaultCurrencies()[strings.ToLower(code)]; ok {
return i
}
return strings.ToUpper(code)
}
func FormatCurrencyValue(value float32) string {
return fmt.Sprintf("%.2f", value)
}
// BindJSONWithRaw will perform usual ShouldBindJSON and will return the original body data.
func BindJSONWithRaw(c *gin.Context, obj any) ([]byte, error) {
closer := c.Request.Body
defer func() { _ = closer.Close() }()
data, err := io.ReadAll(closer)
if err != nil {
return []byte{}, err
}
c.Request.Body = io.NopCloser(bytes.NewReader(data))
return data, c.ShouldBindJSON(obj)
}