mirror of
https://github.com/retailcrm/mg-transport-core.git
synced 2024-11-25 06:36:03 +03:00
init
This commit is contained in:
commit
34cba277b9
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.idea
|
3
README.md
Normal file
3
README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
## MG Transport Library
|
||||||
|
|
||||||
|
This library provides different functions like error-reporting, loggingm localization, etc. in order to make it easier to create transports
|
169
core/config.go
Normal file
169
core/config.go
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/op/go-logging"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigInterface settings data structure
|
||||||
|
type ConfigInterface interface {
|
||||||
|
GetVersion() string
|
||||||
|
GetSentryDSN() string
|
||||||
|
GetLogLevel() logging.Level
|
||||||
|
GetDebug() bool
|
||||||
|
GetHTTPConfig() HTTPServerConfig
|
||||||
|
GetDBConfig() DatabaseConfig
|
||||||
|
GetAWSConfig() ConfigAWS
|
||||||
|
GetTransportInfo() InfoInterface
|
||||||
|
GetUpdateInterval() int
|
||||||
|
IsDebug() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// InfoInterface transport settings data structure
|
||||||
|
type InfoInterface interface {
|
||||||
|
GetName() string
|
||||||
|
GetCode() string
|
||||||
|
GetLogoPath() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config struct
|
||||||
|
type Config struct {
|
||||||
|
Version string `yaml:"version"`
|
||||||
|
LogLevel logging.Level `yaml:"log_level"`
|
||||||
|
Database DatabaseConfig `yaml:"database"`
|
||||||
|
SentryDSN string `yaml:"sentry_dsn"`
|
||||||
|
HTTPServer HTTPServerConfig `yaml:"http_server"`
|
||||||
|
Debug bool `yaml:"debug"`
|
||||||
|
UpdateInterval int `yaml:"update_interval"`
|
||||||
|
ConfigAWS ConfigAWS `yaml:"config_aws"`
|
||||||
|
TransportInfo Info `yaml:"transport_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info struct
|
||||||
|
type Info struct {
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
Code string `yaml:"code"`
|
||||||
|
LogoPath string `yaml:"logo_path"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigAWS struct
|
||||||
|
type ConfigAWS struct {
|
||||||
|
AccessKeyID string `yaml:"access_key_id"`
|
||||||
|
SecretAccessKey string `yaml:"secret_access_key"`
|
||||||
|
Region string `yaml:"region"`
|
||||||
|
Bucket string `yaml:"bucket"`
|
||||||
|
FolderName string `yaml:"folder_name"`
|
||||||
|
ContentType string `yaml:"content_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DatabaseConfig struct
|
||||||
|
type DatabaseConfig struct {
|
||||||
|
Connection string `yaml:"connection"`
|
||||||
|
Logging bool `yaml:"logging"`
|
||||||
|
TablePrefix string `yaml:"table_prefix"`
|
||||||
|
MaxOpenConnections int `yaml:"max_open_connections"`
|
||||||
|
MaxIdleConnections int `yaml:"max_idle_connections"`
|
||||||
|
ConnectionLifetime int `yaml:"connection_lifetime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPServerConfig struct
|
||||||
|
type HTTPServerConfig struct {
|
||||||
|
Host string `yaml:"host"`
|
||||||
|
Listen string `yaml:"listen"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConfig reads configuration file and returns config instance
|
||||||
|
// Usage:
|
||||||
|
// NewConfig("config.yml")
|
||||||
|
func NewConfig(path string) *Config {
|
||||||
|
return (&Config{}).LoadConfig(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadConfig read & load configuration file
|
||||||
|
func (c *Config) LoadConfig(path string) *Config {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
path, err = filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
source, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = yaml.Unmarshal(source, c); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSentryDSN sentry connection dsn
|
||||||
|
func (c Config) GetSentryDSN() string {
|
||||||
|
return c.SentryDSN
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVersion transport version
|
||||||
|
func (c Config) GetVersion() string {
|
||||||
|
return c.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogLevel log level
|
||||||
|
func (c Config) GetLogLevel() logging.Level {
|
||||||
|
return c.LogLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransportInfo transport basic data
|
||||||
|
func (c Config) GetTransportInfo() InfoInterface {
|
||||||
|
return c.TransportInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDebug debug flag
|
||||||
|
func (c Config) GetDebug() bool {
|
||||||
|
return c.Debug
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAWSConfig AWS configuration
|
||||||
|
func (c Config) GetAWSConfig() ConfigAWS {
|
||||||
|
return c.ConfigAWS
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDBConfig database configuration
|
||||||
|
func (c Config) GetDBConfig() DatabaseConfig {
|
||||||
|
return c.Database
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHTTPConfig server configuration
|
||||||
|
func (c Config) GetHTTPConfig() HTTPServerConfig {
|
||||||
|
return c.HTTPServer
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUpdateInterval user data update interval
|
||||||
|
func (c Config) GetUpdateInterval() int {
|
||||||
|
return c.UpdateInterval
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDebug debug state
|
||||||
|
func (c Config) IsDebug() bool {
|
||||||
|
return c.Debug
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetName transport name
|
||||||
|
func (t Info) GetName() string {
|
||||||
|
return t.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCode transport code
|
||||||
|
func (t Info) GetCode() string {
|
||||||
|
return t.Code
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogoPath transport logo
|
||||||
|
func (t Info) GetLogoPath() string {
|
||||||
|
return t.LogoPath
|
||||||
|
}
|
115
core/engine.go
Normal file
115
core/engine.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/op/go-logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Engine struct
|
||||||
|
type Engine struct {
|
||||||
|
Localizer
|
||||||
|
ORM
|
||||||
|
Sentry
|
||||||
|
Utils
|
||||||
|
ginEngine *gin.Engine
|
||||||
|
Logger *logging.Logger
|
||||||
|
Config ConfigInterface
|
||||||
|
LogFormatter logging.Formatter
|
||||||
|
prepared bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// New Engine instance (must be configured manually, gin can be accessed via engine.Router() directly or engine.ConfigureRouter(...) with callback)
|
||||||
|
func New() *Engine {
|
||||||
|
return &Engine{
|
||||||
|
Config: nil,
|
||||||
|
Localizer: Localizer{},
|
||||||
|
ORM: ORM{},
|
||||||
|
Sentry: Sentry{},
|
||||||
|
Utils: Utils{},
|
||||||
|
ginEngine: nil,
|
||||||
|
Logger: nil,
|
||||||
|
prepared: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Engine) initGin() {
|
||||||
|
if !e.Config.IsDebug() {
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
r := gin.New()
|
||||||
|
r.Use(gin.Recovery())
|
||||||
|
|
||||||
|
if e.Config.IsDebug() {
|
||||||
|
r.Use(gin.Logger())
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Use(e.LocalizationMiddleware(), e.ErrorMiddleware())
|
||||||
|
e.ginEngine = r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare engine for start
|
||||||
|
func (e *Engine) Prepare() *Engine {
|
||||||
|
if e.prepared {
|
||||||
|
panic("engine already initialized")
|
||||||
|
}
|
||||||
|
if e.Config == nil {
|
||||||
|
panic("engine.Config must be loaded before initializing")
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.DefaultError == "" {
|
||||||
|
e.DefaultError = "error"
|
||||||
|
}
|
||||||
|
if e.LogFormatter == nil {
|
||||||
|
e.LogFormatter = DefaultLogFormatter()
|
||||||
|
}
|
||||||
|
if e.LocaleBundle == nil {
|
||||||
|
e.LocaleBundle = DefaultLocalizerBundle()
|
||||||
|
}
|
||||||
|
if e.LocaleMatcher == nil {
|
||||||
|
e.LocaleMatcher = DefaultLocalizerMatcher()
|
||||||
|
}
|
||||||
|
|
||||||
|
e.LoadTranslations()
|
||||||
|
e.createDB(e.Config.GetDBConfig())
|
||||||
|
e.createRavenClient(e.Config.GetSentryDSN())
|
||||||
|
e.resetUtils(e.Config.GetAWSConfig(), e.Config.IsDebug(), 0)
|
||||||
|
e.Logger = NewLogger(e.Config.GetTransportInfo().GetCode(), e.Config.GetLogLevel(), e.LogFormatter)
|
||||||
|
e.Utils.Localizer = &e.Localizer
|
||||||
|
e.Sentry.Localizer = &e.Localizer
|
||||||
|
e.Utils.Logger = e.Logger
|
||||||
|
e.Sentry.Logger = e.Logger
|
||||||
|
e.prepared = true
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateRenderer with translation function
|
||||||
|
func (e *Engine) CreateRenderer(callback func(*Renderer)) Renderer {
|
||||||
|
renderer := NewRenderer(e.LocalizationFuncMap())
|
||||||
|
callback(&renderer)
|
||||||
|
return renderer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Router will return current gin.Engine or panic if it's not present
|
||||||
|
func (e *Engine) Router() *gin.Engine {
|
||||||
|
if !e.prepared {
|
||||||
|
panic("prepare engine first")
|
||||||
|
}
|
||||||
|
if e.ginEngine == nil {
|
||||||
|
e.initGin()
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.ginEngine
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigureRouter will call provided callback with current gin.Engine, or panic if engine is not present
|
||||||
|
func (e *Engine) ConfigureRouter(callback func(*gin.Engine)) *Engine {
|
||||||
|
callback(e.Router())
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run gin.Engine loop, or panic if engine is not present
|
||||||
|
func (e *Engine) Run() error {
|
||||||
|
return e.Router().Run(e.Config.GetHTTPConfig().Listen)
|
||||||
|
}
|
17
core/error.go
Normal file
17
core/error.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// ErrorResponse struct
|
||||||
|
type ErrorResponse struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BadRequest returns ErrorResponse with code 400
|
||||||
|
// Usage (with gin):
|
||||||
|
// context.JSON(BadRequest("invalid data"))
|
||||||
|
func BadRequest(error string) (int, interface{}) {
|
||||||
|
return http.StatusBadRequest, ErrorResponse{
|
||||||
|
Error: error,
|
||||||
|
}
|
||||||
|
}
|
137
core/localizer.go
Normal file
137
core/localizer.go
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"io/ioutil"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Localizer struct
|
||||||
|
type Localizer struct {
|
||||||
|
i18n *i18n.Localizer
|
||||||
|
LocaleBundle *i18n.Bundle
|
||||||
|
LocaleMatcher language.Matcher
|
||||||
|
LanguageTag language.Tag
|
||||||
|
TranslationsPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLocalizer returns localizer instance with specified parameters.
|
||||||
|
// Usage:
|
||||||
|
// NewLocalizer(language.English, DefaultLocalizerBundle(), DefaultLocalizerMatcher(), "translations")
|
||||||
|
func NewLocalizer(locale language.Tag, bundle *i18n.Bundle, matcher language.Matcher, translationsPath string) *Localizer {
|
||||||
|
localizer := &Localizer{
|
||||||
|
i18n: nil,
|
||||||
|
LocaleBundle: bundle,
|
||||||
|
LocaleMatcher: matcher,
|
||||||
|
TranslationsPath: translationsPath,
|
||||||
|
}
|
||||||
|
localizer.SetLanguage(locale)
|
||||||
|
localizer.LoadTranslations()
|
||||||
|
|
||||||
|
return localizer
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultLocalizerBundle returns new localizer bundle with English as default language
|
||||||
|
func DefaultLocalizerBundle() *i18n.Bundle {
|
||||||
|
return &i18n.Bundle{DefaultLanguage: language.English}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultLocalizerMatcher returns matcher with English, Russian and Spanish tags
|
||||||
|
func DefaultLocalizerMatcher() language.Matcher {
|
||||||
|
return language.NewMatcher([]language.Tag{
|
||||||
|
language.English,
|
||||||
|
language.Russian,
|
||||||
|
language.Spanish,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalizationMiddleware returns gin.HandlerFunc which will set localizer language by Accept-Language header
|
||||||
|
// Usage:
|
||||||
|
// engine := gin.New()
|
||||||
|
// localizer := NewLocalizer("en", DefaultLocalizerBundle(), DefaultLocalizerMatcher(), "translations")
|
||||||
|
// engine.Use(localizer.LocalizationMiddleware())
|
||||||
|
func (l *Localizer) LocalizationMiddleware() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
l.SetLocale(c.GetHeader("Accept-Language"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalizationFuncMap returns template.FuncMap (html template is used) with one method - trans
|
||||||
|
// Usage in code:
|
||||||
|
// engine := gin.New()
|
||||||
|
// engine.FuncMap = localizer.LocalizationFuncMap()
|
||||||
|
// or (with multitemplate)
|
||||||
|
// renderer := multitemplate.NewRenderer()
|
||||||
|
// funcMap := localizer.LocalizationFuncMap()
|
||||||
|
// renderer.AddFromFilesFuncs("index", funcMap, "template/index.html")
|
||||||
|
// funcMap must be passed for every .AddFromFilesFuncs call
|
||||||
|
// Usage in templates:
|
||||||
|
// <p class="info">{{"need_login_msg" | trans}}
|
||||||
|
// You can borrow FuncMap from this method and add your functions to it.
|
||||||
|
func (l *Localizer) LocalizationFuncMap() template.FuncMap {
|
||||||
|
return template.FuncMap{
|
||||||
|
"trans": l.GetLocalizedMessage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Localizer) getLocaleBundle() *i18n.Bundle {
|
||||||
|
if l.LocaleBundle == nil {
|
||||||
|
l.LocaleBundle = DefaultLocalizerBundle()
|
||||||
|
}
|
||||||
|
return l.LocaleBundle
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadTranslations will load all translation files from translations directory
|
||||||
|
func (l *Localizer) LoadTranslations() {
|
||||||
|
l.getLocaleBundle().RegisterUnmarshalFunc("yml", yaml.Unmarshal)
|
||||||
|
files, err := ioutil.ReadDir(l.TranslationsPath)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
for _, f := range files {
|
||||||
|
if !f.IsDir() {
|
||||||
|
l.getLocaleBundle().MustLoadMessageFile(path.Join(l.TranslationsPath, f.Name()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLocale will change language for current localizer
|
||||||
|
func (l *Localizer) SetLocale(al string) {
|
||||||
|
tag, _ := language.MatchStrings(l.LocaleMatcher, al)
|
||||||
|
l.SetLanguage(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLanguage will change language using language tag
|
||||||
|
func (l *Localizer) SetLanguage(tag language.Tag) {
|
||||||
|
l.LanguageTag = tag
|
||||||
|
l.FetchLanguage()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchLanguage will load language from tag
|
||||||
|
func (l *Localizer) FetchLanguage() {
|
||||||
|
l.i18n = i18n.NewLocalizer(l.getLocaleBundle(), l.LanguageTag.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLocalizedMessage will return localized message by it's ID
|
||||||
|
func (l *Localizer) GetLocalizedMessage(messageID string) string {
|
||||||
|
return l.i18n.MustLocalize(&i18n.LocalizeConfig{MessageID: messageID})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLocalizedTemplateMessage will return localized message with specified data
|
||||||
|
// It uses text/template syntax: https://golang.org/pkg/text/template/
|
||||||
|
func (l *Localizer) GetLocalizedTemplateMessage(messageID string, templateData map[string]interface{}) string {
|
||||||
|
return l.i18n.MustLocalize(&i18n.LocalizeConfig{
|
||||||
|
MessageID: messageID,
|
||||||
|
TemplateData: templateData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// BadRequestLocalized is same as BadRequest(string), but passed string will be localized
|
||||||
|
func (l *Localizer) BadRequestLocalized(err string) (int, interface{}) {
|
||||||
|
return BadRequest(l.GetLocalizedMessage(err))
|
||||||
|
}
|
28
core/logger.go
Normal file
28
core/logger.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/op/go-logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewLogger will create new logger with specified formatter.
|
||||||
|
// Usage:
|
||||||
|
// logger := NewLogger(config, DefaultLogFormatter())
|
||||||
|
func NewLogger(transportCode string, logLevel logging.Level, logFormat logging.Formatter) *logging.Logger {
|
||||||
|
logger := logging.MustGetLogger(transportCode)
|
||||||
|
logBackend := logging.NewLogBackend(os.Stdout, "", 0)
|
||||||
|
formatBackend := logging.NewBackendFormatter(logBackend, logFormat)
|
||||||
|
backend1Leveled := logging.AddModuleLevel(logBackend)
|
||||||
|
backend1Leveled.SetLevel(logLevel, "")
|
||||||
|
logging.SetBackend(formatBackend)
|
||||||
|
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultLogFormatter will return default formatter for logs
|
||||||
|
func DefaultLogFormatter() logging.Formatter {
|
||||||
|
return logging.MustStringFormatter(
|
||||||
|
`%{time:2006-01-02 15:04:05.000} %{level:.4s} => %{message}`,
|
||||||
|
)
|
||||||
|
}
|
53
core/models.go
Normal file
53
core/models.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Connection model
|
||||||
|
type Connection struct {
|
||||||
|
ID int `gorm:"primary_key"`
|
||||||
|
ClientID string `gorm:"column:client_id;type:varchar(70);not null;unique" json:"clientId,omitempty"`
|
||||||
|
Key string `gorm:"column:api_key;type:varchar(100);not null" json:"api_key,omitempty" binding:"required,max=100"`
|
||||||
|
URL string `gorm:"column:api_url;type:varchar(255);not null" json:"api_url,omitempty" binding:"required,validatecrmurl,max=255"`
|
||||||
|
GateURL string `gorm:"column:mg_url;type:varchar(255);not null;" json:"mg_url,omitempty" binding:"max=255"`
|
||||||
|
GateToken string `gorm:"column:mg_token;type:varchar(100);not null;unique" json:"mg_token,omitempty" binding:"max=100"`
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
Active bool `json:"active,omitempty"`
|
||||||
|
Accounts []Account `gorm:"foreignkey:ConnectionID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Account model
|
||||||
|
type Account struct {
|
||||||
|
ID int `gorm:"primary_key"`
|
||||||
|
ConnectionID int `gorm:"column:connection_id" json:"connectionId,omitempty"`
|
||||||
|
Channel uint64 `gorm:"column:channel;not null;unique" json:"channel,omitempty"`
|
||||||
|
ChannelSettingsHash string `gorm:"column:channel_settings_hash type:varchar(70)" binding:"max=70"`
|
||||||
|
Name string `gorm:"column:name type:varchar(40)" json:"name,omitempty" binding:"max=40"`
|
||||||
|
Lang string `gorm:"column:lang type:varchar(2)" json:"lang,omitempty" binding:"max=2"`
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// User model
|
||||||
|
type User struct {
|
||||||
|
ID int `gorm:"primary_key"`
|
||||||
|
ExternalID int `gorm:"column:external_id;not null;unique"`
|
||||||
|
UserPhotoURL string `gorm:"column:user_photo_url type:varchar(255)" binding:"max=255"`
|
||||||
|
UserPhotoID string `gorm:"column:user_photo_id type:varchar(100)" binding:"max=100"`
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName will return table name for User
|
||||||
|
// It will not work if User is not embedded, but mapped as another type
|
||||||
|
// type MyUser User // will not work
|
||||||
|
// but
|
||||||
|
// type MyUser struct { // will work
|
||||||
|
// User
|
||||||
|
// }
|
||||||
|
func (User) TableName() string {
|
||||||
|
return "mg_user"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accounts list
|
||||||
|
type Accounts []Account
|
42
core/orm.go
Normal file
42
core/orm.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
|
// PostgreSQL is an default
|
||||||
|
_ "github.com/jinzhu/gorm/dialects/postgres"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ORM struct
|
||||||
|
type ORM struct {
|
||||||
|
DB *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewORM will init new database connection
|
||||||
|
func NewORM(config DatabaseConfig) *ORM {
|
||||||
|
orm := &ORM{}
|
||||||
|
orm.createDB(config)
|
||||||
|
return orm
|
||||||
|
}
|
||||||
|
|
||||||
|
func (orm *ORM) createDB(config DatabaseConfig) {
|
||||||
|
db, err := gorm.Open("postgres", config.Connection)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.DB().SetConnMaxLifetime(time.Duration(config.ConnectionLifetime) * time.Second)
|
||||||
|
db.DB().SetMaxOpenConns(config.MaxOpenConnections)
|
||||||
|
db.DB().SetMaxIdleConns(config.MaxIdleConnections)
|
||||||
|
|
||||||
|
db.SingularTable(true)
|
||||||
|
db.LogMode(config.Logging)
|
||||||
|
|
||||||
|
orm.DB = db
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseDB close database connection
|
||||||
|
func (orm *ORM) CloseDB() {
|
||||||
|
_ = orm.DB.Close()
|
||||||
|
}
|
382
core/sentry.go
Normal file
382
core/sentry.go
Normal file
@ -0,0 +1,382 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"runtime/debug"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/Neur0toxine/mg-transport-lib/internal"
|
||||||
|
"github.com/getsentry/raven-go"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/op/go-logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrorHandlerFunc will handle errors
|
||||||
|
type ErrorHandlerFunc func(recovery interface{}, c *gin.Context)
|
||||||
|
|
||||||
|
// SentryTaggedTypes list
|
||||||
|
type SentryTaggedTypes []SentryTagged
|
||||||
|
|
||||||
|
// SentryTags list for SentryTaggedStruct. Format: name => property name
|
||||||
|
type SentryTags map[string]string
|
||||||
|
|
||||||
|
// SentryTagged interface for both tagged scalar and struct
|
||||||
|
type SentryTagged interface {
|
||||||
|
BuildTags(interface{}) (map[string]string, error)
|
||||||
|
GetContextKey() string
|
||||||
|
GetTags() SentryTags
|
||||||
|
GetName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sentry struct. Holds SentryTaggedStruct list
|
||||||
|
type Sentry struct {
|
||||||
|
TaggedTypes SentryTaggedTypes
|
||||||
|
Stacktrace bool
|
||||||
|
DefaultError string
|
||||||
|
Localizer *Localizer
|
||||||
|
Logger *logging.Logger
|
||||||
|
Client *raven.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// SentryTaggedStruct holds information about type, it's key in gin.Context (for middleware), and it's properties
|
||||||
|
type SentryTaggedStruct struct {
|
||||||
|
Type reflect.Type
|
||||||
|
GinContextKey string
|
||||||
|
Tags SentryTags
|
||||||
|
}
|
||||||
|
|
||||||
|
// SentryTaggedScalar variable from context
|
||||||
|
type SentryTaggedScalar struct {
|
||||||
|
SentryTaggedStruct
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSentry constructor
|
||||||
|
func NewSentry(sentryDSN string, defaultError string, taggedTypes SentryTaggedTypes, logger *logging.Logger, localizer *Localizer) *Sentry {
|
||||||
|
sentry := &Sentry{
|
||||||
|
DefaultError: defaultError,
|
||||||
|
TaggedTypes: taggedTypes,
|
||||||
|
Localizer: localizer,
|
||||||
|
Logger: logger,
|
||||||
|
Stacktrace: true,
|
||||||
|
}
|
||||||
|
sentry.createRavenClient(sentryDSN)
|
||||||
|
return sentry
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTaggedStruct constructor
|
||||||
|
func NewTaggedStruct(sample interface{}, ginCtxKey string, tags map[string]string) *SentryTaggedStruct {
|
||||||
|
n := make(map[string]string)
|
||||||
|
for k, v := range tags {
|
||||||
|
n[v] = k
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SentryTaggedStruct{
|
||||||
|
Type: reflect.TypeOf(sample),
|
||||||
|
GinContextKey: ginCtxKey,
|
||||||
|
Tags: n,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTaggedScalar constructor
|
||||||
|
func NewTaggedScalar(sample interface{}, ginCtxKey string, name string) *SentryTaggedScalar {
|
||||||
|
return &SentryTaggedScalar{
|
||||||
|
SentryTaggedStruct: SentryTaggedStruct{
|
||||||
|
Type: reflect.TypeOf(sample),
|
||||||
|
GinContextKey: ginCtxKey,
|
||||||
|
Tags: SentryTags{},
|
||||||
|
},
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// createRavenClient will init raven.Client
|
||||||
|
func (s *Sentry) createRavenClient(sentryDSN string) {
|
||||||
|
client, _ := raven.New(sentryDSN)
|
||||||
|
s.Client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
// combineGinErrorHandlers calls several error handlers simultaneously
|
||||||
|
func (s *Sentry) combineGinErrorHandlers(handlers ...ErrorHandlerFunc) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
defer func() {
|
||||||
|
rec := recover()
|
||||||
|
for _, handler := range handlers {
|
||||||
|
handler(rec, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rec != nil || len(c.Errors) > 0 {
|
||||||
|
c.Abort()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorMiddleware returns error handlers, attachable to gin.Engine
|
||||||
|
func (s *Sentry) ErrorMiddleware() gin.HandlerFunc {
|
||||||
|
defaultHandlers := []ErrorHandlerFunc{
|
||||||
|
s.ErrorResponseHandler(),
|
||||||
|
s.PanicLogger(),
|
||||||
|
s.ErrorLogger(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Client != nil {
|
||||||
|
defaultHandlers = append(defaultHandlers, s.ErrorCaptureHandler())
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.combineGinErrorHandlers(defaultHandlers...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PanicLogger logs panic
|
||||||
|
func (s *Sentry) PanicLogger() ErrorHandlerFunc {
|
||||||
|
return func(recovery interface{}, c *gin.Context) {
|
||||||
|
if recovery != nil {
|
||||||
|
if s.Logger != nil {
|
||||||
|
s.Logger.Error(c.Request.RequestURI, recovery)
|
||||||
|
} else {
|
||||||
|
fmt.Print("ERROR =>", c.Request.RequestURI, recovery)
|
||||||
|
}
|
||||||
|
debug.PrintStack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorLogger logs basic errors
|
||||||
|
func (s *Sentry) ErrorLogger() ErrorHandlerFunc {
|
||||||
|
return func(recovery interface{}, c *gin.Context) {
|
||||||
|
for _, err := range c.Errors {
|
||||||
|
if s.Logger != nil {
|
||||||
|
s.Logger.Error(c.Request.RequestURI, err.Err)
|
||||||
|
} else {
|
||||||
|
fmt.Print("ERROR =>", c.Request.RequestURI, err.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorResponseHandler will be executed in case of any unexpected error
|
||||||
|
func (s *Sentry) ErrorResponseHandler() ErrorHandlerFunc {
|
||||||
|
return func(recovery interface{}, c *gin.Context) {
|
||||||
|
publicErrors := c.Errors.ByType(gin.ErrorTypePublic)
|
||||||
|
privateLen := len(c.Errors.ByType(gin.ErrorTypePrivate))
|
||||||
|
publicLen := len(publicErrors)
|
||||||
|
|
||||||
|
if privateLen == 0 && publicLen == 0 && recovery == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
messagesLen := publicLen
|
||||||
|
if privateLen > 0 || recovery != nil {
|
||||||
|
messagesLen++
|
||||||
|
}
|
||||||
|
|
||||||
|
messages := make([]string, messagesLen)
|
||||||
|
index := 0
|
||||||
|
for _, err := range publicErrors {
|
||||||
|
messages[index] = err.Error()
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
|
||||||
|
if privateLen > 0 || recovery != nil {
|
||||||
|
if s.Localizer == nil {
|
||||||
|
messages[index] = s.DefaultError
|
||||||
|
} else {
|
||||||
|
messages[index] = s.Localizer.GetLocalizedMessage(s.DefaultError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": messages})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorCaptureHandler will generate error data and send it to sentry
|
||||||
|
func (s *Sentry) ErrorCaptureHandler() ErrorHandlerFunc {
|
||||||
|
return func(recovery interface{}, c *gin.Context) {
|
||||||
|
tags := map[string]string{
|
||||||
|
"endpoint": c.Request.RequestURI,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(s.TaggedTypes) > 0 {
|
||||||
|
for _, tagged := range s.TaggedTypes {
|
||||||
|
if item, ok := c.Get(tagged.GetContextKey()); ok && item != nil {
|
||||||
|
if itemTags, err := tagged.BuildTags(item); err == nil {
|
||||||
|
for tagName, tagValue := range itemTags {
|
||||||
|
tags[tagName] = tagValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if recovery != nil {
|
||||||
|
stacktrace := raven.NewStacktrace(4, 3, nil)
|
||||||
|
recStr := fmt.Sprint(recovery)
|
||||||
|
err := errors.New(recStr)
|
||||||
|
go s.Client.CaptureMessageAndWait(
|
||||||
|
recStr,
|
||||||
|
tags,
|
||||||
|
raven.NewException(err, stacktrace),
|
||||||
|
raven.NewHttp(c.Request),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, err := range c.Errors {
|
||||||
|
if s.Stacktrace {
|
||||||
|
stacktrace := internal.NewRavenStackTrace(s.Client, err.Err, 0)
|
||||||
|
go s.Client.CaptureMessageAndWait(
|
||||||
|
err.Error(),
|
||||||
|
tags,
|
||||||
|
raven.NewException(err.Err, stacktrace),
|
||||||
|
raven.NewHttp(c.Request),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
go s.Client.CaptureErrorAndWait(err.Err, tags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTag will add tag with property name which holds tag in object
|
||||||
|
func (t *SentryTaggedStruct) AddTag(name string, property string) *SentryTaggedStruct {
|
||||||
|
t.Tags[property] = name
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTags is Tags getter
|
||||||
|
func (t *SentryTaggedStruct) GetTags() SentryTags {
|
||||||
|
return t.Tags
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetContextKey is GinContextKey getter
|
||||||
|
func (t *SentryTaggedStruct) GetContextKey() string {
|
||||||
|
return t.GinContextKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetName is useless for SentryTaggedStruct
|
||||||
|
func (t *SentryTaggedStruct) GetName() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProperty will extract property string representation from specified object. It will be properly formatted.
|
||||||
|
func (t *SentryTaggedStruct) GetProperty(v interface{}, property string) (name string, value string, err error) {
|
||||||
|
val := reflect.Indirect(reflect.ValueOf(v))
|
||||||
|
if !val.IsValid() {
|
||||||
|
err = errors.New("invalid value provided")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if val.Kind() != reflect.Struct {
|
||||||
|
err = fmt.Errorf("passed value must be struct, %s provided", val.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if val.Type().Name() != t.Type.Name() {
|
||||||
|
err = fmt.Errorf("passed value should be of type `%s`, got `%s` instead", t.Type.String(), val.Type().String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if i, ok := t.Tags[property]; ok {
|
||||||
|
name = i
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("cannot find property `%s`", property)
|
||||||
|
}
|
||||||
|
|
||||||
|
field := val.FieldByName(property)
|
||||||
|
if !field.IsValid() {
|
||||||
|
err = fmt.Errorf("invalid property, got %s", field.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
value = t.valueToString(field)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildTags will extract tags for Sentry from specified object
|
||||||
|
func (t *SentryTaggedStruct) BuildTags(v interface{}) (tags map[string]string, err error) {
|
||||||
|
items := make(map[string]string)
|
||||||
|
for prop, name := range t.Tags {
|
||||||
|
if _, value, e := t.GetProperty(v, prop); e == nil {
|
||||||
|
items[name] = value
|
||||||
|
} else {
|
||||||
|
err = e
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tags = items
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// valueToString convert reflect.Value to string representation
|
||||||
|
func (t *SentryTaggedStruct) valueToString(field reflect.Value) string {
|
||||||
|
k := field.Kind()
|
||||||
|
switch {
|
||||||
|
case k == reflect.Bool:
|
||||||
|
return strconv.FormatBool(field.Bool())
|
||||||
|
case k >= reflect.Int && k <= reflect.Int64:
|
||||||
|
return strconv.FormatInt(field.Int(), 10)
|
||||||
|
case k >= reflect.Uint && k <= reflect.Uintptr:
|
||||||
|
return strconv.FormatUint(field.Uint(), 10)
|
||||||
|
case k == reflect.Float32 || k == reflect.Float64:
|
||||||
|
bitSize := 32
|
||||||
|
if k == reflect.Float64 {
|
||||||
|
bitSize = 64
|
||||||
|
}
|
||||||
|
return strconv.FormatFloat(field.Float(), 'f', 12, bitSize)
|
||||||
|
default:
|
||||||
|
return field.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTags is useless for SentryTaggedScalar
|
||||||
|
func (t *SentryTaggedScalar) GetTags() SentryTags {
|
||||||
|
return SentryTags{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetContextKey is getter for GinContextKey
|
||||||
|
func (t *SentryTaggedScalar) GetContextKey() string {
|
||||||
|
return t.GinContextKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetName is getter for Name (tag name for scalar)
|
||||||
|
func (t *SentryTaggedScalar) GetName() string {
|
||||||
|
return t.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get will extract property string representation from specified object. It will be properly formatted.
|
||||||
|
func (t *SentryTaggedScalar) Get(v interface{}) (value string, err error) {
|
||||||
|
val := reflect.Indirect(reflect.ValueOf(v))
|
||||||
|
if !val.IsValid() {
|
||||||
|
err = errors.New("invalid value provided")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if val.Kind() == reflect.Struct {
|
||||||
|
err = errors.New("passed value must not be struct")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if val.Type().Name() != t.Type.Name() {
|
||||||
|
err = fmt.Errorf("passed value should be of type `%s`, got `%s` instead", t.Type.String(), val.Type().String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
value = t.valueToString(val)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildTags returns map with single item in this format: <tag name> => <scalar value>
|
||||||
|
func (t *SentryTaggedScalar) BuildTags(v interface{}) (items map[string]string, err error) {
|
||||||
|
items = make(map[string]string)
|
||||||
|
if value, e := t.Get(v); e == nil {
|
||||||
|
items[t.Name] = value
|
||||||
|
} else {
|
||||||
|
err = e
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
26
core/template.go
Normal file
26
core/template.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
|
||||||
|
"github.com/gin-contrib/multitemplate"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Renderer wraps multitemplate.Renderer in order to make it easier to use
|
||||||
|
type Renderer struct {
|
||||||
|
multitemplate.Renderer
|
||||||
|
FuncMap template.FuncMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRenderer is a Renderer constructor
|
||||||
|
func NewRenderer(funcMap template.FuncMap) Renderer {
|
||||||
|
return Renderer{
|
||||||
|
Renderer: multitemplate.NewRenderer(),
|
||||||
|
FuncMap: funcMap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push is an AddFromFilesFuncs wrapper
|
||||||
|
func (r *Renderer) Push(name string, files ...string) *template.Template {
|
||||||
|
return r.AddFromFilesFuncs(name, r.FuncMap, files...)
|
||||||
|
}
|
208
core/utils.go
Normal file
208
core/utils.go
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Neur0toxine/mg-transport-lib/internal"
|
||||||
|
"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/op/go-logging"
|
||||||
|
v5 "github.com/retailcrm/api-client-go/v5"
|
||||||
|
v1 "github.com/retailcrm/mg-transport-api-client-go/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Utils service object
|
||||||
|
type Utils struct {
|
||||||
|
IsDebug bool
|
||||||
|
ConfigAWS ConfigAWS
|
||||||
|
Localizer *Localizer
|
||||||
|
Logger *logging.Logger
|
||||||
|
TokenCounter uint32
|
||||||
|
slashRegex *regexp.Regexp
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUtils will create new Utils instance
|
||||||
|
func NewUtils(awsConfig ConfigAWS, localizer *Localizer, logger *logging.Logger, debug bool) *Utils {
|
||||||
|
return &Utils{
|
||||||
|
IsDebug: debug,
|
||||||
|
ConfigAWS: awsConfig,
|
||||||
|
Localizer: localizer,
|
||||||
|
Logger: logger,
|
||||||
|
TokenCounter: 0,
|
||||||
|
slashRegex: internal.SlashRegex,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// resetUtils
|
||||||
|
func (u *Utils) resetUtils(awsConfig ConfigAWS, debug bool, tokenCounter uint32) {
|
||||||
|
u.TokenCounter = tokenCounter
|
||||||
|
u.ConfigAWS = awsConfig
|
||||||
|
u.IsDebug = debug
|
||||||
|
u.slashRegex = internal.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
|
||||||
|
func (u *Utils) GetAPIClient(url, key string) (*v5.Client, int, error) {
|
||||||
|
client := v5.New(url, key)
|
||||||
|
client.Debug = u.IsDebug
|
||||||
|
|
||||||
|
cr, status, e := client.APICredentials()
|
||||||
|
if e.RuntimeErr != nil {
|
||||||
|
u.Logger.Error(url, status, e.RuntimeErr, cr)
|
||||||
|
return nil, http.StatusInternalServerError, e.RuntimeErr
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cr.Success {
|
||||||
|
u.Logger.Error(url, status, e.ApiErr, cr)
|
||||||
|
return nil, http.StatusBadRequest, errors.New(u.Localizer.GetLocalizedMessage("incorrect_url_key"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if res := u.checkCredentials(cr.Credentials); len(res) != 0 {
|
||||||
|
u.Logger.Error(url, status, res)
|
||||||
|
return nil,
|
||||||
|
http.StatusBadRequest,
|
||||||
|
errors.New(
|
||||||
|
u.Localizer.GetLocalizedTemplateMessage(
|
||||||
|
"missing_credentials",
|
||||||
|
map[string]interface{}{
|
||||||
|
"Credentials": strings.Join(res, ", "),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Utils) checkCredentials(credential []string) []string {
|
||||||
|
rc := make([]string, len(internal.CredentialsTransport))
|
||||||
|
copy(rc, internal.CredentialsTransport)
|
||||||
|
|
||||||
|
for _, vc := range credential {
|
||||||
|
for kn, vn := range rc {
|
||||||
|
if vn == vc {
|
||||||
|
if len(rc) == 1 {
|
||||||
|
rc = rc[:0]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
rc = append(rc[:kn], rc[kn+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadUserAvatar will upload avatar for user
|
||||||
|
func (u *Utils) UploadUserAvatar(url string) (picURLs3 string, err error) {
|
||||||
|
s3Config := &aws.Config{
|
||||||
|
Credentials: credentials.NewStaticCredentials(
|
||||||
|
u.ConfigAWS.AccessKeyID,
|
||||||
|
u.ConfigAWS.SecretAccessKey,
|
||||||
|
""),
|
||||||
|
Region: aws.String(u.ConfigAWS.Region),
|
||||||
|
}
|
||||||
|
|
||||||
|
s := session.Must(session.NewSession(s3Config))
|
||||||
|
uploader := s3manager.NewUploader(s)
|
||||||
|
|
||||||
|
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.ConfigAWS.Bucket),
|
||||||
|
Key: aws.String(fmt.Sprintf("%v/%v.jpg", u.ConfigAWS.FolderName, u.GenerateToken())),
|
||||||
|
Body: resp.Body,
|
||||||
|
ContentType: aws.String(u.ConfigAWS.ContentType),
|
||||||
|
ACL: aws.String("public-read"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
picURLs3 = result.Location
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveTrailingSlash will remove slash at the end of any string
|
||||||
|
func (u *Utils) RemoveTrailingSlash(crmURL string) string {
|
||||||
|
return u.slashRegex.ReplaceAllString(crmURL, ``)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
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 internal.MarkdownSymbols {
|
||||||
|
s = strings.Replace(s, v, "\\"+v, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultCurrencies will return default currencies list for all bots
|
||||||
|
func DefaultCurrencies() map[string]string {
|
||||||
|
return map[string]string{
|
||||||
|
"rub": "₽",
|
||||||
|
"uah": "₴",
|
||||||
|
"byr": "Br",
|
||||||
|
"kzt": "₸",
|
||||||
|
"usd": "$",
|
||||||
|
"eur": "€",
|
||||||
|
}
|
||||||
|
}
|
24
core/validator.go
Normal file
24
core/validator.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/binding"
|
||||||
|
"github.com/Neur0toxine/mg-transport-lib/internal"
|
||||||
|
"gopkg.in/go-playground/validator.v8"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
|
||||||
|
if err := v.RegisterValidation("validatecrmurl", validateCrmURL); err != nil {
|
||||||
|
panic("cannot register crm url validator: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateCrmURL(
|
||||||
|
v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
|
||||||
|
field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,
|
||||||
|
) bool {
|
||||||
|
return internal.RegCommandName.Match([]byte(field.Interface().(string)))
|
||||||
|
}
|
35
go.mod
Normal file
35
go.mod
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
module github.com/retailcrm/mg-transport-core
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/aws/aws-sdk-go v1.23.9
|
||||||
|
github.com/certifi/gocertifi v0.0.0-20190506164543-d2eda7129713 // indirect
|
||||||
|
github.com/denisenkom/go-mssqldb v0.0.0-20190830225923-3302f0226fbd // indirect
|
||||||
|
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect
|
||||||
|
github.com/getsentry/raven-go v0.0.0-20180903072508-084a9de9eb03
|
||||||
|
github.com/gin-contrib/multitemplate v0.0.0-20180827023943-5799bbbb6dce
|
||||||
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
|
github.com/gin-gonic/gin v1.3.0
|
||||||
|
github.com/go-sql-driver/mysql v1.4.1 // indirect
|
||||||
|
github.com/golang/protobuf v1.3.2 // indirect
|
||||||
|
github.com/google/go-querystring v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/gorm v1.9.1
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.0.1 // indirect
|
||||||
|
github.com/lib/pq v1.2.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.9 // indirect
|
||||||
|
github.com/mattn/go-sqlite3 v1.11.0 // indirect
|
||||||
|
github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.5
|
||||||
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||||
|
github.com/pkg/errors v0.8.1
|
||||||
|
github.com/retailcrm/api-client-go v1.1.1
|
||||||
|
github.com/retailcrm/mg-transport-api-client-go v1.1.31
|
||||||
|
github.com/stretchr/testify v1.4.0 // indirect
|
||||||
|
github.com/ugorji/go v1.1.7 // indirect
|
||||||
|
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 // indirect
|
||||||
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2
|
||||||
|
golang.org/x/tools v0.0.0-20190830082254-f340ed3ae274 // indirect
|
||||||
|
gopkg.in/go-playground/validator.v8 v8.18.2
|
||||||
|
gopkg.in/yaml.v2 v2.2.2
|
||||||
|
)
|
200
go.sum
Normal file
200
go.sum
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU=
|
||||||
|
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
|
||||||
|
github.com/BurntSushi/toml v0.3.0 h1:e1/Ivsx3Z0FVTV0NSOv/aVgbUWyQuzj7DDnFblkRvsY=
|
||||||
|
github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||||
|
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||||
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||||
|
github.com/aws/aws-sdk-go v1.23.9 h1:UYWPGrBMlrW5VCYeWMbog1T/kqZzkvvheUDQaaUAhqI=
|
||||||
|
github.com/aws/aws-sdk-go v1.23.9/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||||
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
|
github.com/certifi/gocertifi v0.0.0-20190506164543-d2eda7129713 h1:UNOqI3EKhvbqV8f1Vm3NIwkrhq388sGCeAH2Op7w0rc=
|
||||||
|
github.com/certifi/gocertifi v0.0.0-20190506164543-d2eda7129713/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
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/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/denisenkom/go-mssqldb v0.0.0-20190830225923-3302f0226fbd h1:DoaaxHqzWPQCWKSTmsi8UDSiFqxbfue+Xt+qi/BFKb8=
|
||||||
|
github.com/denisenkom/go-mssqldb v0.0.0-20190830225923-3302f0226fbd/go.mod h1:uU0N10vx1abI4qeVe79CxepBP6PPREVTgMS5Gx6/mOk=
|
||||||
|
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||||
|
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||||
|
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||||
|
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
|
||||||
|
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||||
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/getsentry/raven-go v0.0.0-20180903072508-084a9de9eb03 h1:G/9fPivTr5EiyqE9OlW65iMRUxFXMGRHgZFGo50uG8Q=
|
||||||
|
github.com/getsentry/raven-go v0.0.0-20180903072508-084a9de9eb03/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
||||||
|
github.com/gin-contrib/multitemplate v0.0.0-20180827023943-5799bbbb6dce h1:KqeVCdb+M2iwyF6GzdYxTazfE1cE+133RXuGaZ5Sc1E=
|
||||||
|
github.com/gin-contrib/multitemplate v0.0.0-20180827023943-5799bbbb6dce/go.mod h1:62qM8p4crGvNKE413gTzn4eMFin1VOJfMDWMRzHdvqM=
|
||||||
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/gin-gonic/gin v1.3.0 h1:kCmZyPklC0gVdL728E6Aj20uYBJV93nj/TkwBTKhFbs=
|
||||||
|
github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
|
||||||
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
|
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
||||||
|
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||||
|
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/subcommands v1.0.1 h1:/eqq+otEXm5vhfBrbREPCSVQbvofip6kIz+mX5TUH7k=
|
||||||
|
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||||
|
github.com/google/wire v0.3.0 h1:imGQZGEVEHpje5056+K+cgdO72p0LQv2xIIFXNGUf60=
|
||||||
|
github.com/google/wire v0.3.0/go.mod h1:i1DMg/Lu8Sz5yYl25iOdmc5CT5qusaa+zmRWs16741s=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
|
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
github.com/jinzhu/gorm v1.9.1 h1:lDSDtsCt5AGGSKTs8AHlSDbbgif4G4+CKJ8ETBDVHTA=
|
||||||
|
github.com/jinzhu/gorm v1.9.1/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo=
|
||||||
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
|
||||||
|
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
|
||||||
|
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||||
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
|
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
||||||
|
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
|
||||||
|
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||||
|
github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
|
||||||
|
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
|
github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.5 h1:/TjjTS4kg7vC+05gD0LE4+97f/+PRFICnK/7wJPk7kE=
|
||||||
|
github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.5/go.mod h1:4Opqa6/HIv0lhG3WRAkqzO0afezkRhxXI0P8EJkqeRU=
|
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||||
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||||
|
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||||
|
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||||
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
|
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
|
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||||
|
github.com/retailcrm/api-client-go v1.1.1 h1:yqsyYjBDdmDwExVlTdGucY9/IpEokXpkfTfA6z5AZ7M=
|
||||||
|
github.com/retailcrm/api-client-go v1.1.1/go.mod h1:QRoPE2SM6ST7i2g0yEdqm7Iw98y7cYuq3q14Ot+6N8c=
|
||||||
|
github.com/retailcrm/mg-transport-api-client-go v1.1.31 h1:21pE1JhT49rvbMLDYJa0iiqbb/roz+eSp27fPck4uUw=
|
||||||
|
github.com/retailcrm/mg-transport-api-client-go v1.1.31/go.mod h1:AWV6BueE28/6SCoyfKURTo4lF0oXYoOKmHTzehd5vAI=
|
||||||
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||||
|
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||||
|
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||||
|
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||||
|
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||||
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA=
|
||||||
|
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
|
||||||
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.0.0-20171214130843-f21a4dfb5e38/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
|
||||||
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190830082254-f340ed3ae274 h1:3LEbAKuShoQDlrpbepJOeKph85ROShka+GypY1YNQYQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190830082254-f340ed3ae274/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
|
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/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
|
||||||
|
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
105
internal/stacktrace.go
Normal file
105
internal/stacktrace.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/getsentry/raven-go"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewRavenStackTrace generate stacktrace compatible with raven-go format
|
||||||
|
// It tries to extract better stacktrace from error from package "github.com/pkg/errors"
|
||||||
|
// In case of fail it will fallback to default stacktrace generation from raven-go.
|
||||||
|
// Default stacktrace highly likely will be useless, because it will not include call
|
||||||
|
// which returned error. This occurs because default stacktrace doesn't include any call
|
||||||
|
// before stacktrace generation, and raven-go will generate stacktrace here, which will end
|
||||||
|
// in trace to this file. But errors from "github.com/pkg/errors" will generate stacktrace
|
||||||
|
// immediately, it will include call which returned error, and we can fetch this trace.
|
||||||
|
// Also we can wrap default errors with error from this package, like this:
|
||||||
|
// errors.Wrap(err, err.Error)
|
||||||
|
func NewRavenStackTrace(client *raven.Client, myerr error, skip int) *raven.Stacktrace {
|
||||||
|
st := getErrorStackTraceConverted(myerr, 3, client.IncludePaths())
|
||||||
|
if st == nil {
|
||||||
|
st = raven.NewStacktrace(skip, 3, client.IncludePaths())
|
||||||
|
}
|
||||||
|
return st
|
||||||
|
}
|
||||||
|
|
||||||
|
// getErrorStackTraceConverted will return converted stacktrace from custom error, or nil in case of default error
|
||||||
|
func getErrorStackTraceConverted(err error, context int, appPackagePrefixes []string) *raven.Stacktrace {
|
||||||
|
st := getErrorCauseStackTrace(err)
|
||||||
|
if st == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return convertStackTrace(st, context, appPackagePrefixes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getErrorCauseStackTrace tries to extract stacktrace from custom error, returns nil in case of failure
|
||||||
|
func getErrorCauseStackTrace(err error) errors.StackTrace {
|
||||||
|
// This code is inspired by github.com/pkg/errors.Cause().
|
||||||
|
var st errors.StackTrace
|
||||||
|
for err != nil {
|
||||||
|
s := getErrorStackTrace(err)
|
||||||
|
if s != nil {
|
||||||
|
st = s
|
||||||
|
}
|
||||||
|
err = getErrorCause(err)
|
||||||
|
}
|
||||||
|
return st
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertStackTrace converts github.com/pkg/errors.StackTrace to github.com/getsentry/raven-go.Stacktrace
|
||||||
|
func convertStackTrace(st errors.StackTrace, context int, appPackagePrefixes []string) *raven.Stacktrace {
|
||||||
|
// This code is borrowed from github.com/getsentry/raven-go.NewStacktrace().
|
||||||
|
var frames []*raven.StacktraceFrame
|
||||||
|
for _, f := range st {
|
||||||
|
frame := convertFrame(f, context, appPackagePrefixes)
|
||||||
|
if frame != nil {
|
||||||
|
frames = append(frames, frame)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(frames) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for i, j := 0, len(frames)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
frames[i], frames[j] = frames[j], frames[i]
|
||||||
|
}
|
||||||
|
return &raven.Stacktrace{Frames: frames}
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertFrame converts single frame from github.com/pkg/errors.Frame to github.com/pkg/errors.Frame
|
||||||
|
func convertFrame(f errors.Frame, context int, appPackagePrefixes []string) *raven.StacktraceFrame {
|
||||||
|
// This code is borrowed from github.com/pkg/errors.Frame.
|
||||||
|
pc := uintptr(f) - 1
|
||||||
|
fn := runtime.FuncForPC(pc)
|
||||||
|
var file string
|
||||||
|
var line int
|
||||||
|
if fn != nil {
|
||||||
|
file, line = fn.FileLine(pc)
|
||||||
|
} else {
|
||||||
|
file = "unknown"
|
||||||
|
}
|
||||||
|
return raven.NewStacktraceFrame(pc, file, line, context, appPackagePrefixes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getErrorStackTrace will try to extract stacktrace from error using StackTrace method (default errors doesn't have it)
|
||||||
|
func getErrorStackTrace(err error) errors.StackTrace {
|
||||||
|
ster, ok := err.(interface {
|
||||||
|
StackTrace() errors.StackTrace
|
||||||
|
})
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ster.StackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
// getErrorCause will try to extract original error from wrapper - it is used only if stacktrace is not present
|
||||||
|
func getErrorCause(err error) error {
|
||||||
|
cer, ok := err.(interface {
|
||||||
|
Cause() error
|
||||||
|
})
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return cer.Cause()
|
||||||
|
}
|
14
internal/variables.go
Normal file
14
internal/variables.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import "regexp"
|
||||||
|
|
||||||
|
// CredentialsTransport set of API methods for transport registration
|
||||||
|
var (
|
||||||
|
CredentialsTransport = []string{
|
||||||
|
"/api/integration-modules/{code}",
|
||||||
|
"/api/integration-modules/{code}/edit",
|
||||||
|
}
|
||||||
|
MarkdownSymbols = []string{"*", "_", "`", "["}
|
||||||
|
RegCommandName = regexp.MustCompile(`^https://?[\da-z.-]+\.(retailcrm\.(ru|pro|es)|ecomlogic\.com|simlachat\.(com|ru))/?$`)
|
||||||
|
SlashRegex = regexp.MustCompile(`/+$`)
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user