updated gin, better code

This commit is contained in:
Pavel 2019-12-12 09:35:05 +03:00
parent bf689e15b0
commit 6290301815
21 changed files with 275 additions and 124 deletions

View File

@ -7,6 +7,7 @@ import (
"github.com/retailcrm/mg-transport-core/core" "github.com/retailcrm/mg-transport-core/core"
) )
// Options for tool command
type Options struct{} type Options struct{}
var ( var (

View File

@ -16,7 +16,8 @@ var (
"/api/integration-modules/{code}/edit", "/api/integration-modules/{code}/edit",
} }
markdownSymbols = []string{"*", "_", "`", "["} markdownSymbols = []string{"*", "_", "`", "["}
regCommandName = regexp.MustCompile(`^https://?[\da-z.-]+\.(retailcrm\.(ru|pro|es)|ecomlogic\.com|simlachat\.(com|ru))/?$`) regCommandName = regexp.MustCompile(
`^https://?[\da-z.-]+\.(retailcrm\.(ru|pro|es)|ecomlogic\.com|simlachat\.(com|ru))/?$`)
slashRegex = regexp.MustCompile(`/+$`) slashRegex = regexp.MustCompile(`/+$`)
) )
@ -29,7 +30,7 @@ type ConfigInterface interface {
GetDBConfig() DatabaseConfig GetDBConfig() DatabaseConfig
GetAWSConfig() ConfigAWS GetAWSConfig() ConfigAWS
GetTransportInfo() InfoInterface GetTransportInfo() InfoInterface
GetHTTPClientConfig() *HTTPClientConfig GetHTTPClientConfig() HTTPClientConfigInterface
GetUpdateInterval() int GetUpdateInterval() int
IsDebug() bool IsDebug() bool
} }
@ -41,6 +42,14 @@ type InfoInterface interface {
GetLogoPath() string GetLogoPath() string
} }
// HTTPClientConfigInterface can be used to provide alternative way for configuring HTTP server
type HTTPClientConfigInterface interface {
GetTimeout() time.Duration
IsSSLVerificationEnabled() bool
GetMockAddress() string
GetMockedDomains() []string
}
// Config struct // Config struct
type Config struct { type Config struct {
Version string `yaml:"version"` Version string `yaml:"version"`
@ -52,7 +61,7 @@ type Config struct {
UpdateInterval int `yaml:"update_interval"` UpdateInterval int `yaml:"update_interval"`
ConfigAWS ConfigAWS `yaml:"config_aws"` ConfigAWS ConfigAWS `yaml:"config_aws"`
TransportInfo Info `yaml:"transport_info"` TransportInfo Info `yaml:"transport_info"`
HTTPClientConfig *HTTPClientConfig `yaml:"http_client"` HTTPClientConfig HTTPClientConfigInterface `yaml:"http_client"`
} }
// Info struct // Info struct
@ -85,7 +94,7 @@ type DatabaseConfig struct {
// HTTPClientConfig struct // HTTPClientConfig struct
type HTTPClientConfig struct { type HTTPClientConfig struct {
Timeout time.Duration `yaml:"timeout"` Timeout time.Duration `yaml:"timeout"`
SSLVerification bool `yaml:"ssl_verification"` SSLVerification *bool `yaml:"ssl_verification"`
MockAddress string `yaml:"mock_address"` MockAddress string `yaml:"mock_address"`
MockedDomains []string `yaml:"mocked_domains"` MockedDomains []string `yaml:"mocked_domains"`
} }
@ -180,7 +189,7 @@ func (c Config) GetUpdateInterval() int {
} }
// GetHTTPClientConfig returns http client config // GetHTTPClientConfig returns http client config
func (c Config) GetHTTPClientConfig() *HTTPClientConfig { func (c Config) GetHTTPClientConfig() HTTPClientConfigInterface {
return c.HTTPClientConfig return c.HTTPClientConfig
} }
@ -198,3 +207,31 @@ func (t Info) GetCode() string {
func (t Info) GetLogoPath() string { func (t Info) GetLogoPath() string {
return t.LogoPath return t.LogoPath
} }
// GetTimeout returns timeout for HTTP client (default is 30 seconds)
func (h *HTTPClientConfig) GetTimeout() time.Duration {
if h.Timeout <= 0 {
h.Timeout = 30 * time.Second
}
return h.Timeout
}
// IsSSLVerificationEnabled returns SSL verification flag (default is true)
func (h *HTTPClientConfig) IsSSLVerificationEnabled() bool {
if h.SSLVerification == nil {
return true
}
return *h.SSLVerification
}
// GetMockAddress returns mock address
func (h *HTTPClientConfig) GetMockAddress() string {
return h.MockAddress
}
// GetMockedDomains returns mocked domains list
func (h *HTTPClientConfig) GetMockedDomains() []string {
return h.MockedDomains
}

View File

@ -2,6 +2,7 @@ package core
import ( import (
"bytes" "bytes"
// nolint:gosec
"crypto/sha1" "crypto/sha1"
"encoding/base64" "encoding/base64"
"io" "io"
@ -44,6 +45,7 @@ var DefaultCSRFTokenGetter = func(c *gin.Context) string {
// DefaultIgnoredMethods ignored methods for CSRF verifier middleware // DefaultIgnoredMethods ignored methods for CSRF verifier middleware
var DefaultIgnoredMethods = []string{"GET", "HEAD", "OPTIONS"} var DefaultIgnoredMethods = []string{"GET", "HEAD", "OPTIONS"}
// CSRF struct. Provides CSRF token verification.
type CSRF struct { type CSRF struct {
salt string salt string
secret string secret string
@ -51,7 +53,6 @@ type CSRF struct {
abortFunc gin.HandlerFunc abortFunc gin.HandlerFunc
csrfTokenGetter CSRFTokenGetter csrfTokenGetter CSRFTokenGetter
store sessions.Store store sessions.Store
locale *Localizer
} }
// NewCSRF creates CSRF struct with specified configuration and session store. // NewCSRF creates CSRF struct with specified configuration and session store.
@ -115,8 +116,12 @@ func (x *CSRF) strInSlice(slice []string, v string) bool {
// generateCSRFToken generates new CSRF token // generateCSRFToken generates new CSRF token
func (x *CSRF) generateCSRFToken() string { func (x *CSRF) generateCSRFToken() string {
// nolint:gosec
h := sha1.New() h := sha1.New()
io.WriteString(h, x.salt+"#"+x.secret) // Fallback to less secure method - token must be always filled even if we cannot properly generate it
if _, err := io.WriteString(h, x.salt+"#"+x.secret); err != nil {
return base64.URLEncoding.EncodeToString([]byte(time.Now().String()))
}
hash := base64.URLEncoding.EncodeToString(h.Sum(nil)) hash := base64.URLEncoding.EncodeToString(h.Sum(nil))
return hash return hash
@ -155,12 +160,10 @@ func (x *CSRF) CSRFFromContext(c *gin.Context) string {
if i, ok := c.Get("csrf_token"); ok { if i, ok := c.Get("csrf_token"); ok {
if token, ok := i.(string); ok { if token, ok := i.(string); ok {
return token return token
} else {
return x.generateCSRFToken()
} }
} else {
return x.generateCSRFToken()
} }
return x.generateCSRFToken()
} }
// GenerateCSRFMiddleware returns gin.HandlerFunc which will generate CSRF token // GenerateCSRFMiddleware returns gin.HandlerFunc which will generate CSRF token
@ -211,13 +214,14 @@ func (x *CSRF) VerifyCSRFMiddleware(ignoredMethods []string) gin.HandlerFunc {
session, _ := x.store.Get(c.Request, x.sessionName) session, _ := x.store.Get(c.Request, x.sessionName)
if i, ok := session.Values["csrf_token"]; ok { if i, ok := session.Values["csrf_token"]; ok {
if i, ok := i.(string); !ok || i == "" { var v string
if v, ok = i.(string); !ok || v == "" {
x.abortFunc(c) x.abortFunc(c)
c.Abort() c.Abort()
return return
} else {
token = i
} }
token = v
} else { } else {
x.abortFunc(c) x.abortFunc(c)
c.Abort() c.Abort()

View File

@ -92,7 +92,7 @@ func (e *Engine) Prepare() *Engine {
return e return e
} }
// templateFuncMap combines func map for templates // TemplateFuncMap combines func map for templates
func (e *Engine) TemplateFuncMap(functions template.FuncMap) template.FuncMap { func (e *Engine) TemplateFuncMap(functions template.FuncMap) template.FuncMap {
funcMap := e.LocalizationFuncMap() funcMap := e.LocalizationFuncMap()
@ -100,6 +100,10 @@ func (e *Engine) TemplateFuncMap(functions template.FuncMap) template.FuncMap {
funcMap[name] = fn funcMap[name] = fn
} }
funcMap["version"] = func() string {
return e.Config.GetVersion()
}
return funcMap return funcMap
} }
@ -156,9 +160,9 @@ func (e *Engine) SetHTTPClient(client *http.Client) *Engine {
func (e *Engine) HTTPClient() *http.Client { func (e *Engine) HTTPClient() *http.Client {
if e.httpClient == nil { if e.httpClient == nil {
return http.DefaultClient return http.DefaultClient
} else {
return e.httpClient
} }
return e.httpClient
} }
// WithCookieSessions generates new CookieStore with optional key length. // WithCookieSessions generates new CookieStore with optional key length.
@ -174,7 +178,7 @@ func (e *Engine) WithCookieSessions(keyLength ...int) *Engine {
return e return e
} }
// WithCookieSessions generates new FilesystemStore with optional key length. // WithFilesystemSessions generates new FilesystemStore with optional key length.
// Default key length is 32 bytes. // Default key length is 32 bytes.
func (e *Engine) WithFilesystemSessions(path string, keyLength ...int) *Engine { func (e *Engine) WithFilesystemSessions(path string, keyLength ...int) *Engine {
length := 32 length := 32

View File

@ -154,7 +154,7 @@ func (e *EngineTest) Test_BuildHTTPClient() {
e.engine.Config = &Config{ e.engine.Config = &Config{
HTTPClientConfig: &HTTPClientConfig{ HTTPClientConfig: &HTTPClientConfig{
Timeout: 30, Timeout: 30,
SSLVerification: true, SSLVerification: boolPtr(true),
}, },
} }
e.engine.BuildHTTPClient() e.engine.BuildHTTPClient()
@ -300,3 +300,8 @@ func (e *EngineTest) Test_Run_Fail() {
func TestEngine_Suite(t *testing.T) { func TestEngine_Suite(t *testing.T) {
suite.Run(t, new(EngineTest)) suite.Run(t, new(EngineTest))
} }
func boolPtr(val bool) *bool {
b := val
return &b
}

View File

@ -11,10 +11,11 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
var ( // DefaultClient stores original http.DefaultClient
DefaultClient = http.DefaultClient var DefaultClient = http.DefaultClient
DefaultTransport = http.DefaultTransport
) // DefaultTransport stores original http.DefaultTransport
var DefaultTransport = http.DefaultTransport
// HTTPClientBuilder builds http client with mocks (if necessary) and timeout. // HTTPClientBuilder builds http client with mocks (if necessary) and timeout.
// Example: // Example:
@ -111,21 +112,21 @@ func (b *HTTPClientBuilder) EnableLogging() *HTTPClientBuilder {
} }
// FromConfig fulfills mock configuration from HTTPClientConfig // FromConfig fulfills mock configuration from HTTPClientConfig
func (b *HTTPClientBuilder) FromConfig(config *HTTPClientConfig) *HTTPClientBuilder { func (b *HTTPClientBuilder) FromConfig(config HTTPClientConfigInterface) *HTTPClientBuilder {
if config == nil { if config == nil {
return b return b
} }
if config.MockAddress != "" { if config.GetMockAddress() != "" {
b.SetMockAddress(config.MockAddress) b.SetMockAddress(config.GetMockAddress())
b.SetMockedDomains(config.MockedDomains) b.SetMockedDomains(config.GetMockedDomains())
} }
if config.Timeout > 0 { if config.GetTimeout() > 0 {
b.SetTimeout(config.Timeout) b.SetTimeout(config.GetTimeout())
} }
b.SetSSLVerification(config.SSLVerification) b.SetSSLVerification(config.IsSSLVerificationEnabled())
return b return b
} }
@ -156,10 +157,11 @@ func (b *HTTPClientBuilder) parseAddress() error {
if host, port, err := net.SplitHostPort(b.mockAddress); err == nil { if host, port, err := net.SplitHostPort(b.mockAddress); err == nil {
b.mockHost = host b.mockHost = host
b.mockPort = port b.mockPort = port
return nil
} else { } else {
return errors.Errorf("cannot split host and port: %s", err.Error()) return errors.Errorf("cannot split host and port: %s", err.Error())
} }
return nil
} }
// buildMocks builds mocks for http client // buildMocks builds mocks for http client
@ -177,9 +179,15 @@ func (b *HTTPClientBuilder) buildMocks() error {
} }
b.httpTransport.DialContext = func(ctx context.Context, network, addr string) (conn net.Conn, e error) { b.httpTransport.DialContext = func(ctx context.Context, network, addr string) (conn net.Conn, e error) {
if host, port, err := net.SplitHostPort(addr); err != nil { var (
host string
port string
err error
)
if host, port, err = net.SplitHostPort(addr); err != nil {
return b.dialer.DialContext(ctx, network, addr) return b.dialer.DialContext(ctx, network, addr)
} else { }
for _, mock := range b.mockedDomains { for _, mock := range b.mockedDomains {
if mock == host { if mock == host {
oldAddr := addr oldAddr := addr
@ -193,7 +201,6 @@ func (b *HTTPClientBuilder) buildMocks() error {
b.logf("Mocking \"%s\" with \"%s\"\n", oldAddr, addr) b.logf("Mocking \"%s\" with \"%s\"\n", oldAddr, addr)
} }
} }
}
return b.dialer.DialContext(ctx, network, addr) return b.dialer.DialContext(ctx, network, addr)
} }

View File

@ -58,14 +58,14 @@ func (t *HTTPClientBuilderTest) Test_SetSSLVerification() {
func (t *HTTPClientBuilderTest) Test_FromConfig() { func (t *HTTPClientBuilderTest) Test_FromConfig() {
config := &HTTPClientConfig{ config := &HTTPClientConfig{
SSLVerification: true, SSLVerification: boolPtr(true),
MockAddress: "anothermock.local:3004", MockAddress: "anothermock.local:3004",
MockedDomains: []string{"example.gov"}, MockedDomains: []string{"example.gov"},
Timeout: 60, Timeout: 60,
} }
t.builder.FromConfig(config) t.builder.FromConfig(config)
assert.Equal(t.T(), !config.SSLVerification, t.builder.httpTransport.TLSClientConfig.InsecureSkipVerify) assert.Equal(t.T(), !config.IsSSLVerificationEnabled(), t.builder.httpTransport.TLSClientConfig.InsecureSkipVerify)
assert.Equal(t.T(), config.MockAddress, t.builder.mockAddress) assert.Equal(t.T(), config.MockAddress, t.builder.mockAddress)
assert.Equal(t.T(), config.MockedDomains[0], t.builder.mockedDomains[0]) assert.Equal(t.T(), config.MockedDomains[0], t.builder.mockedDomains[0])
assert.Equal(t.T(), config.Timeout*time.Second, t.builder.timeout) assert.Equal(t.T(), config.Timeout*time.Second, t.builder.timeout)
@ -76,7 +76,7 @@ func (t *HTTPClientBuilderTest) Test_FromEngine() {
engine := &Engine{ engine := &Engine{
Config: Config{ Config: Config{
HTTPClientConfig: &HTTPClientConfig{ HTTPClientConfig: &HTTPClientConfig{
SSLVerification: true, SSLVerification: boolPtr(true),
MockAddress: "anothermock.local:3004", MockAddress: "anothermock.local:3004",
MockedDomains: []string{"example.gov"}, MockedDomains: []string{"example.gov"},
}, },

View File

@ -145,9 +145,9 @@ func (l *Localizer) loadFromFS() error {
if err != nil { if err != nil {
return err return err
} else {
return nil
} }
return nil
} }
// SetLocale will change language for current localizer // SetLocale will change language for current localizer

View File

@ -54,7 +54,7 @@ func (m *Migrate) Add(migration *gormigrate.Migration) {
m.migrations[migration.ID] = migration m.migrations[migration.ID] = migration
} }
// SetORM to migrate // SetDB to migrate
func (m *Migrate) SetDB(db *gorm.DB) *Migrate { func (m *Migrate) SetDB(db *gorm.DB) *Migrate {
m.db = db m.db = db
return m return m
@ -86,9 +86,9 @@ func (m *Migrate) Rollback() error {
if err := m.GORMigrate.RollbackTo(m.first.ID); err == nil { if err := m.GORMigrate.RollbackTo(m.first.ID); err == nil {
if err := m.GORMigrate.RollbackMigration(m.first); err == nil { if err := m.GORMigrate.RollbackMigration(m.first); err == nil {
return nil return nil
} else {
return err
} }
return err
} else { } else {
return err return err
} }
@ -185,10 +185,10 @@ func (m *Migrate) Current() string {
if err := m.db.Last(&migrationInfo).Error; err == nil { if err := m.db.Last(&migrationInfo).Error; err == nil {
return migrationInfo.ID return migrationInfo.ID
} else { }
fmt.Printf("warning => cannot fetch migration version: %s\n", err.Error()) fmt.Printf("warning => cannot fetch migration version: %s\n", err.Error())
return "0" return "0"
}
} }
// NextFrom returns next version from passed version // NextFrom returns next version from passed version
@ -197,9 +197,9 @@ func (m *Migrate) NextFrom(version string) (string, error) {
if ver == version { if ver == version {
if key < (len(m.versions) - 1) { if key < (len(m.versions) - 1) {
return m.versions[key+1], nil return m.versions[key+1], nil
} else {
return "", errors.New("this is last migration")
} }
return "", errors.New("this is last migration")
} }
} }
@ -212,9 +212,9 @@ func (m *Migrate) PreviousFrom(version string) (string, error) {
if ver == version { if ver == version {
if key > 0 { if key > 0 {
return m.versions[key-1], nil return m.versions[key-1], nil
} else {
return "0", nil
} }
return "0", nil
} }
} }

View File

@ -54,7 +54,7 @@ func (m *MigrateTest) RefreshMigrate() {
} }
} }
func (m *MigrateTest) Migration_TestModelFirst() *gormigrate.Migration { func (m *MigrateTest) MigrationTestModelFirst() *gormigrate.Migration {
return &gormigrate.Migration{ return &gormigrate.Migration{
ID: "1", ID: "1",
Migrate: func(db *gorm.DB) error { Migrate: func(db *gorm.DB) error {
@ -66,7 +66,7 @@ func (m *MigrateTest) Migration_TestModelFirst() *gormigrate.Migration {
} }
} }
func (m *MigrateTest) Migration_TestModelSecond() *gormigrate.Migration { func (m *MigrateTest) MigrationTestModelSecond() *gormigrate.Migration {
return &gormigrate.Migration{ return &gormigrate.Migration{
ID: "2", ID: "2",
Migrate: func(db *gorm.DB) error { Migrate: func(db *gorm.DB) error {
@ -81,7 +81,7 @@ func (m *MigrateTest) Migration_TestModelSecond() *gormigrate.Migration {
func (m *MigrateTest) Test_Add() { func (m *MigrateTest) Test_Add() {
m.RefreshMigrate() m.RefreshMigrate()
m.Migrate.Add(nil) m.Migrate.Add(nil)
m.Migrate.Add(m.Migration_TestModelFirst()) m.Migrate.Add(m.MigrationTestModelFirst())
assert.Equal(m.T(), 1, len(m.Migrate.migrations)) assert.Equal(m.T(), 1, len(m.Migrate.migrations))
i, ok := m.Migrate.migrations["1"] i, ok := m.Migrate.migrations["1"]
@ -112,7 +112,7 @@ func (m *MigrateTest) Test_prepareMigrations_AlreadyPrepared() {
func (m *MigrateTest) Test_prepareMigrations_OK() { func (m *MigrateTest) Test_prepareMigrations_OK() {
m.RefreshMigrate() m.RefreshMigrate()
m.Migrate.Add(m.Migration_TestModelFirst()) m.Migrate.Add(m.MigrationTestModelFirst())
err := m.Migrate.prepareMigrations() err := m.Migrate.prepareMigrations()
require.NoError(m.T(), err) require.NoError(m.T(), err)
@ -124,7 +124,7 @@ func (m *MigrateTest) Test_prepareMigrations_OK() {
func (m *MigrateTest) Test_Migrate_Fail_NilDB() { func (m *MigrateTest) Test_Migrate_Fail_NilDB() {
m.RefreshMigrate() m.RefreshMigrate()
m.Migrate.SetDB(nil) m.Migrate.SetDB(nil)
m.Migrate.Add(m.Migration_TestModelFirst()) m.Migrate.Add(m.MigrationTestModelFirst())
err := m.Migrate.Migrate() err := m.Migrate.Migrate()
@ -134,7 +134,7 @@ func (m *MigrateTest) Test_Migrate_Fail_NilDB() {
func (m *MigrateTest) Test_Migrate_Success_NoMigrations() { func (m *MigrateTest) Test_Migrate_Success_NoMigrations() {
m.RefreshMigrate() m.RefreshMigrate()
m.Migrate.Add(m.Migration_TestModelFirst()) m.Migrate.Add(m.MigrationTestModelFirst())
m.mock.ExpectBegin() m.mock.ExpectBegin()
m.mock. m.mock.
@ -157,7 +157,7 @@ func (m *MigrateTest) Test_Migrate_Success_NoMigrations() {
func (m *MigrateTest) Test_Migrate_Success() { func (m *MigrateTest) Test_Migrate_Success() {
m.RefreshMigrate() m.RefreshMigrate()
m.Migrate.Add(m.Migration_TestModelFirst()) m.Migrate.Add(m.MigrationTestModelFirst())
m.mock.ExpectBegin() m.mock.ExpectBegin()
m.mock. m.mock.
@ -188,7 +188,7 @@ func (m *MigrateTest) Test_Migrate_Success() {
func (m *MigrateTest) Test_Rollback_Fail_NilDB() { func (m *MigrateTest) Test_Rollback_Fail_NilDB() {
m.RefreshMigrate() m.RefreshMigrate()
m.Migrate.SetDB(nil) m.Migrate.SetDB(nil)
m.Migrate.Add(m.Migration_TestModelFirst()) m.Migrate.Add(m.MigrationTestModelFirst())
err := m.Migrate.Rollback() err := m.Migrate.Rollback()
@ -198,7 +198,7 @@ func (m *MigrateTest) Test_Rollback_Fail_NilDB() {
func (m *MigrateTest) Test_Rollback_Fail_NoMigrations() { func (m *MigrateTest) Test_Rollback_Fail_NoMigrations() {
m.RefreshMigrate() m.RefreshMigrate()
m.Migrate.first = m.Migration_TestModelFirst() m.Migrate.first = m.MigrationTestModelFirst()
err := m.Migrate.Rollback() err := m.Migrate.Rollback()
@ -208,7 +208,7 @@ func (m *MigrateTest) Test_Rollback_Fail_NoMigrations() {
func (m *MigrateTest) Test_Rollback_Fail_NoFirstMigration() { func (m *MigrateTest) Test_Rollback_Fail_NoFirstMigration() {
m.RefreshMigrate() m.RefreshMigrate()
m.Migrate.Add(m.Migration_TestModelFirst()) m.Migrate.Add(m.MigrationTestModelFirst())
m.Migrate.first = nil m.Migrate.first = nil
err := m.Migrate.Rollback() err := m.Migrate.Rollback()
@ -229,7 +229,7 @@ func (m *MigrateTest) Test_MigrateTo_Fail_NilDB() {
func (m *MigrateTest) Test_MigrateTo_DoNothing() { func (m *MigrateTest) Test_MigrateTo_DoNothing() {
m.RefreshMigrate() m.RefreshMigrate()
m.Migrate.Add(m.Migration_TestModelFirst()) m.Migrate.Add(m.MigrationTestModelFirst())
m.mock. m.mock.
ExpectExec(regexp.QuoteMeta(`CREATE TABLE "migrations" ("id" varchar(255) , PRIMARY KEY ("id"))`)). ExpectExec(regexp.QuoteMeta(`CREATE TABLE "migrations" ("id" varchar(255) , PRIMARY KEY ("id"))`)).
@ -247,7 +247,7 @@ func (m *MigrateTest) Test_MigrateTo_DoNothing() {
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1)) WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
m.mock.ExpectCommit() m.mock.ExpectCommit()
err := m.Migrate.MigrateTo(m.Migration_TestModelFirst().ID) err := m.Migrate.MigrateTo(m.MigrationTestModelFirst().ID)
assert.NoError(m.T(), err) assert.NoError(m.T(), err)
assert.NoError(m.T(), m.mock.ExpectationsWereMet()) assert.NoError(m.T(), m.mock.ExpectationsWereMet())
@ -255,7 +255,7 @@ func (m *MigrateTest) Test_MigrateTo_DoNothing() {
func (m *MigrateTest) Test_MigrateTo() { func (m *MigrateTest) Test_MigrateTo() {
m.RefreshMigrate() m.RefreshMigrate()
m.Migrate.Add(m.Migration_TestModelFirst()) m.Migrate.Add(m.MigrationTestModelFirst())
m.mock. m.mock.
ExpectExec(regexp.QuoteMeta(`CREATE TABLE "migrations" ("id" varchar(255) , PRIMARY KEY ("id"))`)). ExpectExec(regexp.QuoteMeta(`CREATE TABLE "migrations" ("id" varchar(255) , PRIMARY KEY ("id"))`)).
@ -280,7 +280,7 @@ func (m *MigrateTest) Test_MigrateTo() {
WillReturnResult(sqlmock.NewResult(1, 1)) WillReturnResult(sqlmock.NewResult(1, 1))
m.mock.ExpectCommit() m.mock.ExpectCommit()
err := m.Migrate.MigrateTo(m.Migration_TestModelFirst().ID) err := m.Migrate.MigrateTo(m.MigrationTestModelFirst().ID)
assert.NoError(m.T(), err) assert.NoError(m.T(), err)
assert.NoError(m.T(), m.mock.ExpectationsWereMet()) assert.NoError(m.T(), m.mock.ExpectationsWereMet())
@ -288,13 +288,13 @@ func (m *MigrateTest) Test_MigrateTo() {
func (m *MigrateTest) Test_RollbackTo() { func (m *MigrateTest) Test_RollbackTo() {
m.RefreshMigrate() m.RefreshMigrate()
m.Migrate.Add(m.Migration_TestModelFirst()) m.Migrate.Add(m.MigrationTestModelFirst())
m.Migrate.Add(m.Migration_TestModelSecond()) m.Migrate.Add(m.MigrationTestModelSecond())
m.mock.ExpectBegin() m.mock.ExpectBegin()
m.mock.ExpectCommit() m.mock.ExpectCommit()
err := m.Migrate.RollbackTo(m.Migration_TestModelSecond().ID) err := m.Migrate.RollbackTo(m.MigrationTestModelSecond().ID)
assert.NoError(m.T(), err) assert.NoError(m.T(), err)
assert.NoError(m.T(), m.mock.ExpectationsWereMet()) assert.NoError(m.T(), m.mock.ExpectationsWereMet())
@ -302,8 +302,8 @@ func (m *MigrateTest) Test_RollbackTo() {
func (m *MigrateTest) Test_MigrateNextTo() { func (m *MigrateTest) Test_MigrateNextTo() {
m.RefreshMigrate() m.RefreshMigrate()
m.Migrate.Add(m.Migration_TestModelFirst()) m.Migrate.Add(m.MigrationTestModelFirst())
m.Migrate.Add(m.Migration_TestModelSecond()) m.Migrate.Add(m.MigrationTestModelSecond())
m.mock. m.mock.
ExpectExec(regexp.QuoteMeta(`CREATE TABLE "migrations" ("id" varchar(255) , PRIMARY KEY ("id"))`)). ExpectExec(regexp.QuoteMeta(`CREATE TABLE "migrations" ("id" varchar(255) , PRIMARY KEY ("id"))`)).
@ -332,7 +332,7 @@ func (m *MigrateTest) Test_MigrateNextTo() {
WillReturnResult(sqlmock.NewResult(1, 1)) WillReturnResult(sqlmock.NewResult(1, 1))
m.mock.ExpectCommit() m.mock.ExpectCommit()
err := m.Migrate.MigrateNextTo(m.Migration_TestModelFirst().ID) err := m.Migrate.MigrateNextTo(m.MigrationTestModelFirst().ID)
assert.NoError(m.T(), err) assert.NoError(m.T(), err)
assert.NoError(m.T(), m.mock.ExpectationsWereMet()) assert.NoError(m.T(), m.mock.ExpectationsWereMet())
@ -340,14 +340,14 @@ func (m *MigrateTest) Test_MigrateNextTo() {
func (m *MigrateTest) Test_MigratePreviousTo() { func (m *MigrateTest) Test_MigratePreviousTo() {
m.RefreshMigrate() m.RefreshMigrate()
m.Migrate.Add(m.Migration_TestModelFirst()) m.Migrate.Add(m.MigrationTestModelFirst())
m.Migrate.Add(m.Migration_TestModelSecond()) m.Migrate.Add(m.MigrationTestModelSecond())
m.mock. m.mock.
ExpectExec(regexp.QuoteMeta(`CREATE TABLE "migrations" ("id" varchar(255) , PRIMARY KEY ("id"))`)). ExpectExec(regexp.QuoteMeta(`CREATE TABLE "migrations" ("id" varchar(255) , PRIMARY KEY ("id"))`)).
WillReturnResult(sqlmock.NewResult(1, 1)) WillReturnResult(sqlmock.NewResult(1, 1))
err := m.Migrate.MigratePreviousTo(m.Migration_TestModelSecond().ID) err := m.Migrate.MigratePreviousTo(m.MigrationTestModelSecond().ID)
assert.Error(m.T(), err) assert.Error(m.T(), err)
assert.NoError(m.T(), m.mock.ExpectationsWereMet()) assert.NoError(m.T(), m.mock.ExpectationsWereMet())

View File

@ -35,6 +35,7 @@ type NewMigrationCommand struct {
Directory string `short:"d" long:"directory" default:"./migrations" description:"Directory where migration will be created"` Directory string `short:"d" long:"directory" default:"./migrations" description:"Directory where migration will be created"`
} }
// FileExists returns true if provided file exist and it's not directory
func (x *NewMigrationCommand) FileExists(filename string) bool { func (x *NewMigrationCommand) FileExists(filename string) bool {
info, err := os.Stat(filename) info, err := os.Stat(filename)
if os.IsNotExist(err) { if os.IsNotExist(err) {
@ -43,6 +44,7 @@ func (x *NewMigrationCommand) FileExists(filename string) bool {
return !info.IsDir() return !info.IsDir()
} }
// Execute migration generator command
func (x *NewMigrationCommand) Execute(args []string) error { func (x *NewMigrationCommand) Execute(args []string) error {
version := strconv.FormatInt(time.Now().Unix(), 10) version := strconv.FormatInt(time.Now().Unix(), 10)
directory := path.Clean(x.Directory) directory := path.Clean(x.Directory)

View File

@ -42,7 +42,7 @@ func (s *MigrationGeneratorSuite) Test_Execute() {
} }
for _, f := range files { for _, f := range files {
if strings.Index(f.Name(), "_app.go") != -1 { if strings.Contains(f.Name(), "_app.go") {
found = true found = true
assert.NoError(s.T(), os.Remove(path.Join(s.command.Directory, f.Name()))) assert.NoError(s.T(), os.Remove(path.Join(s.command.Directory, f.Name())))
} }

View File

@ -4,7 +4,6 @@ import (
"html/template" "html/template"
"github.com/gin-contrib/multitemplate" "github.com/gin-contrib/multitemplate"
"github.com/gobuffalo/packr/v2"
) )
// Renderer wraps multitemplate.Renderer in order to make it easier to use // Renderer wraps multitemplate.Renderer in order to make it easier to use
@ -25,7 +24,7 @@ func NewStaticRenderer(funcMap template.FuncMap) Renderer {
return newRendererWithMultitemplate(funcMap, multitemplate.New()) return newRendererWithMultitemplate(funcMap, multitemplate.New())
} }
// NewStaticRenderer is a Renderer constructor with multitemplate.DynamicRender // NewDynamicRenderer is a Renderer constructor with multitemplate.DynamicRender
func NewDynamicRenderer(funcMap template.FuncMap) Renderer { func NewDynamicRenderer(funcMap template.FuncMap) Renderer {
return newRendererWithMultitemplate(funcMap, multitemplate.NewDynamic()) return newRendererWithMultitemplate(funcMap, multitemplate.NewDynamic())
} }
@ -47,9 +46,9 @@ func (r *Renderer) Push(name string, files ...string) *template.Template {
if r.TemplatesBox == nil { if r.TemplatesBox == nil {
return r.storeTemplate(name, r.AddFromFilesFuncs(name, r.FuncMap, files...)) return r.storeTemplate(name, r.AddFromFilesFuncs(name, r.FuncMap, files...))
} else {
return r.storeTemplate(name, r.addFromBox(name, r.FuncMap, files...))
} }
return r.storeTemplate(name, r.addFromBox(name, r.FuncMap, files...))
} }
// addFromBox adds embedded template // addFromBox adds embedded template
@ -67,7 +66,7 @@ func (r *Renderer) addFromBox(name string, funcMap template.FuncMap, files ...st
// storeTemplate stores built template if multitemplate.DynamicRender is used. // storeTemplate stores built template if multitemplate.DynamicRender is used.
// Dynamic render doesn't store templates - it stores builders, that's why we can't just extract them. // Dynamic render doesn't store templates - it stores builders, that's why we can't just extract them.
// It possibly can cause data inconsistency in developer enviroments where return value from Renderer.Push is used. // It possibly can cause data inconsistency in developer environments where return value from Renderer.Push is used.
func (r *Renderer) storeTemplate(name string, tpl *template.Template) *template.Template { func (r *Renderer) storeTemplate(name string, tpl *template.Template) *template.Template {
if _, ok := r.Renderer.(multitemplate.DynamicRender); ok { if _, ok := r.Renderer.(multitemplate.DynamicRender); ok {
r.alreadyAdded[name] = tpl r.alreadyAdded[name] = tpl

View File

@ -23,7 +23,7 @@ type TranslationsExtractor struct {
TranslationsPath string TranslationsPath string
} }
// TranslationsExtractor constructor. Use "translate.{}.yml" as template if your translations are named like "translate.en.yml" // NewTranslationsExtractor constructor. Use "translate.{}.yml" as template if your translations are named like "translate.en.yml"
func NewTranslationsExtractor(fileNameTemplate string) *TranslationsExtractor { func NewTranslationsExtractor(fileNameTemplate string) *TranslationsExtractor {
return &TranslationsExtractor{fileNameTemplate: fileNameTemplate} return &TranslationsExtractor{fileNameTemplate: fileNameTemplate}
} }
@ -43,10 +43,10 @@ func (t *TranslationsExtractor) unmarshalToMap(in []byte) (map[string]interface{
func (t *TranslationsExtractor) loadYAMLBox(fileName string) (map[string]interface{}, error) { func (t *TranslationsExtractor) loadYAMLBox(fileName string) (map[string]interface{}, error) {
var dataMap map[string]interface{} var dataMap map[string]interface{}
if data, err := t.TranslationsBox.Find(fileName); err == nil { if data, err := t.TranslationsBox.Find(fileName); err != nil {
return t.unmarshalToMap(data)
} else {
return dataMap, err return dataMap, err
} else {
return t.unmarshalToMap(data)
} }
} }
@ -57,10 +57,10 @@ func (t *TranslationsExtractor) loadYAMLFile(fileName string) (map[string]interf
if info, err := os.Stat(fileName); err == nil { if info, err := os.Stat(fileName); err == nil {
if !info.IsDir() { if !info.IsDir() {
if path, err := filepath.Abs(fileName); err == nil { if path, err := filepath.Abs(fileName); err == nil {
if source, err := ioutil.ReadFile(path); err == nil { if source, err := ioutil.ReadFile(path); err != nil {
return t.unmarshalToMap(source)
} else {
return dataMap, err return dataMap, err
} else {
return t.unmarshalToMap(source)
} }
} else { } else {
return dataMap, err return dataMap, err
@ -106,9 +106,9 @@ func (t *TranslationsExtractor) LoadLocale(locale string) (map[string]interface{
// LoadLocaleKeys returns only sorted keys from translation file // LoadLocaleKeys returns only sorted keys from translation file
func (t *TranslationsExtractor) LoadLocaleKeys(locale string) ([]string, error) { func (t *TranslationsExtractor) LoadLocaleKeys(locale string) ([]string, error) {
if data, err := t.LoadLocale(locale); err == nil { if data, err := t.LoadLocale(locale); err != nil {
return t.GetMapKeys(data), nil
} else {
return []string{}, err return []string{}, err
} }
return t.GetMapKeys(data), nil
} }

View File

@ -38,6 +38,8 @@ func (t *TranslationsExtractorTest) SetupSuite() {
"another": "second", "another": "second",
} }
data, _ := yaml.Marshal(translation) data, _ := yaml.Marshal(translation)
// It's not regular temporary file. Little hack in order to test translations extractor.
// nolint:gosec
errWrite := ioutil.WriteFile("/tmp/translate.en.yml", data, os.ModePerm) errWrite := ioutil.WriteFile("/tmp/translate.en.yml", data, os.ModePerm)
require.NoError(t.T(), errWrite) require.NoError(t.T(), errWrite)

View File

@ -1,6 +1,7 @@
package core package core
import ( import (
// nolint:gosec
"crypto/sha1" "crypto/sha1"
"crypto/sha256" "crypto/sha256"
"encoding/json" "encoding/json"
@ -127,6 +128,7 @@ func (u *Utils) UploadUserAvatar(url string) (picURLs3 string, err error) {
s := session.Must(session.NewSession(s3Config)) s := session.Must(session.NewSession(s3Config))
uploader := s3manager.NewUploader(s) uploader := s3manager.NewUploader(s)
// nolint:gosec
resp, err := http.Get(url) resp, err := http.Get(url)
if err != nil { if err != nil {
return return
@ -181,6 +183,7 @@ func GetMGItemData(client *v1.MgClient, url string, caption string) (v1.Item, in
func GetEntitySHA1(v interface{}) (hash string, err error) { func GetEntitySHA1(v interface{}) (hash string, err error) {
res, _ := json.Marshal(v) res, _ := json.Marshal(v)
// nolint:gosec
h := sha1.New() h := sha1.New()
_, err = h.Write(res) _, err = h.Write(res)
hash = fmt.Sprintf("%x", h.Sum(nil)) hash = fmt.Sprintf("%x", h.Sum(nil))

View File

@ -1,12 +1,11 @@
package core package core
import ( import (
"reflect"
"github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/binding"
"gopkg.in/go-playground/validator.v8" "gopkg.in/go-playground/validator.v9"
) )
// init here will register `validatecrmurl` function for gin validator
func init() { func init() {
if v, ok := binding.Validator.Engine().(*validator.Validate); ok { if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
if err := v.RegisterValidation("validatecrmurl", validateCrmURL); err != nil { if err := v.RegisterValidation("validatecrmurl", validateCrmURL); err != nil {
@ -15,9 +14,7 @@ func init() {
} }
} }
func validateCrmURL( // validateCrmURL will validate CRM URL
v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value, func validateCrmURL(fl validator.FieldLevel) bool {
field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string, return regCommandName.Match([]byte(fl.Field().String()))
) bool {
return regCommandName.Match([]byte(field.Interface().(string)))
} }

63
core/validator_test.go Normal file
View File

@ -0,0 +1,63 @@
package core
import (
"testing"
"github.com/gin-gonic/gin/binding"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"gopkg.in/go-playground/validator.v9"
)
type ValidatorSuite struct {
suite.Suite
engine *validator.Validate
}
func Test_Validator(t *testing.T) {
suite.Run(t, new(ValidatorSuite))
}
func (s *ValidatorSuite) SetupSuite() {
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
s.engine = v
} else {
s.T().Fatal("cannot obtain validation engine")
}
}
func (s *ValidatorSuite) getError(err error) string {
if err == nil {
return ""
}
return err.Error()
}
func (s *ValidatorSuite) Test_ValidationInvalidType() {
assert.IsType(s.T(), &validator.InvalidValidationError{}, s.engine.Struct(nil))
}
func (s *ValidatorSuite) Test_ValidationFails() {
conn := Connection{
Key: "key",
URL: "url",
}
err := s.engine.Struct(conn)
require.IsType(s.T(), validator.ValidationErrors{}, err)
validatorErrors := err.(validator.ValidationErrors)
assert.Equal(
s.T(),
"Key: 'Connection.URL' Error:Field validation for 'URL' failed on the 'validatecrmurl' tag",
validatorErrors.Error())
}
func (s *ValidatorSuite) Test_ValidationSuccess() {
conn := Connection{
Key: "key",
URL: "https://test.retailcrm.pro",
}
err := s.engine.Struct(conn)
assert.NoError(s.T(), err, s.getError(err))
}

17
go.mod
View File

@ -9,20 +9,20 @@ require (
github.com/denisenkom/go-mssqldb v0.0.0-20190830225923-3302f0226fbd // indirect github.com/denisenkom/go-mssqldb v0.0.0-20190830225923-3302f0226fbd // indirect
github.com/getsentry/raven-go v0.2.0 github.com/getsentry/raven-go v0.2.0
github.com/gin-contrib/multitemplate v0.0.0-20190914010127-bba2ccfe37ec github.com/gin-contrib/multitemplate v0.0.0-20190914010127-bba2ccfe37ec
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-gonic/gin v1.5.0
github.com/gin-gonic/gin v1.4.0 github.com/go-playground/universal-translator v0.17.0 // indirect
github.com/gobuffalo/packd v0.3.0 github.com/gobuffalo/packd v0.3.0
github.com/gobuffalo/packr/v2 v2.7.1 github.com/gobuffalo/packr/v2 v2.7.1
github.com/golang/protobuf v1.3.2 // indirect
github.com/google/go-querystring v1.0.0 // indirect github.com/google/go-querystring v1.0.0 // indirect
github.com/gorilla/securecookie v1.1.1 github.com/gorilla/securecookie v1.1.1
github.com/gorilla/sessions v1.2.0 github.com/gorilla/sessions v1.2.0
github.com/h2non/gock v1.0.10 github.com/h2non/gock v1.0.10
github.com/jessevdk/go-flags v1.4.0 github.com/jessevdk/go-flags v1.4.0
github.com/jinzhu/gorm v1.9.11 github.com/jinzhu/gorm v1.9.11
github.com/json-iterator/go v1.1.7 // indirect github.com/json-iterator/go v1.1.8 // indirect
github.com/leodido/go-urn v1.2.0 // indirect
github.com/lib/pq v1.2.0 // indirect github.com/lib/pq v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.10 // indirect github.com/mattn/go-isatty v0.0.11 // indirect
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 // indirect github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 // indirect
github.com/nicksnyder/go-i18n/v2 v2.0.2 github.com/nicksnyder/go-i18n/v2 v2.0.2
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
@ -31,12 +31,11 @@ require (
github.com/retailcrm/mg-transport-api-client-go v1.1.32 github.com/retailcrm/mg-transport-api-client-go v1.1.32
github.com/rogpeppe/go-internal v1.5.0 // indirect github.com/rogpeppe/go-internal v1.5.0 // indirect
github.com/stretchr/testify v1.4.0 github.com/stretchr/testify v1.4.0
github.com/ugorji/go v1.1.7 // indirect
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 // indirect golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 // indirect
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 // indirect golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 // indirect
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 // indirect
golang.org/x/text v0.3.2 golang.org/x/text v0.3.2
gopkg.in/go-playground/validator.v8 v8.18.2 gopkg.in/go-playground/validator.v9 v9.30.2
gopkg.in/gormigrate.v1 v1.6.0 gopkg.in/gormigrate.v1 v1.6.0
gopkg.in/yaml.v2 v2.2.4 gopkg.in/yaml.v2 v2.2.7
) )

30
go.sum
View File

@ -48,8 +48,18 @@ 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-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ= github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ=
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
github.com/gin-gonic/gin v1.5.0 h1:fi+bqFAx/oLK54somfCtEZs9HeH1LHVoEPUgARpTqyc=
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 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-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc=
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM=
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= 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-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/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
@ -114,6 +124,8 @@ github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqx
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 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/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/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@ -126,14 +138,21 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8=
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= 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/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= 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/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
@ -259,9 +278,13 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 h1:gSbV7h1NRL2G1xTg/owz62CST1oJBmxy4QpMMregXVQ=
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 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.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 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
@ -296,8 +319,11 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
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/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc=
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/go-playground/validator.v9 v9.30.2 h1:icxYLlYflpazIV3ufMoNB9h9SYMQ37DZ8CTwkU4pnOs=
gopkg.in/go-playground/validator.v9 v9.30.2/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/gormigrate.v1 v1.6.0 h1:XpYM6RHQPmzwY7Uyu+t+xxMXc86JYFJn4nEc9HzQjsI= gopkg.in/gormigrate.v1 v1.6.0 h1:XpYM6RHQPmzwY7Uyu+t+xxMXc86JYFJn4nEc9HzQjsI=
gopkg.in/gormigrate.v1 v1.6.0/go.mod h1:Lf00lQrHqfSYWiTtPcyQabsDdM6ejZaMgV0OU6JMSlw= gopkg.in/gormigrate.v1 v1.6.0/go.mod h1:Lf00lQrHqfSYWiTtPcyQabsDdM6ejZaMgV0OU6JMSlw=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
@ -306,6 +332,8 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v2 v2.2.7/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-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-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=