Commit 68d67e83 authored by Faizal Aziz's avatar Faizal Aziz

init ulfssar

parent a76afc5e
Pipeline #424 failed with stages
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
ulfsaar
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/ulfsaar.iml" filepath="$PROJECT_DIR$/.idea/ulfsaar.iml" />
</modules>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
\ No newline at end of file
package config
import (
"os"
"github.com/joho/godotenv"
"github.com/kelseyhightower/envconfig"
)
func New(target interface{}) error {
var (
filename = os.Getenv("CONFIG_FILE")
)
if filename == "" {
filename = ".env"
}
if _, err := os.Stat(filename); os.IsNotExist(err) {
if err := envconfig.Process("", target); err != nil {
return err
}
return nil
}
if err := godotenv.Load(filename); err != nil {
return err
}
if err := envconfig.Process("", target); err != nil {
return err
}
return nil
}
package context
import (
"github.com/labstack/echo/v4"
"ulfsaar/errors"
"ulfsaar/session"
)
type (
UlfsaarContext struct {
echo.Context
Session *session.Session
}
Success struct {
Code string `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data"`
}
Failed struct {
Code string `json:"code"`
Message string `json:"message"`
Error string `json:"error"`
}
FailedWithData struct {
Code string `json:"code"`
Message string `json:"message"`
Error string `json:"error"`
ErrorData interface{} `json:"error_data"`
}
)
func (sc *UlfsaarContext) Success(data interface{}) error {
return sc.JSON(200, Success{
Code: "200",
Message: "success",
Data: data,
})
}
func (sc *UlfsaarContext) SuccessWithMeta(data, meta interface{}) error {
return sc.JSON(200, Success{
Code: "200",
Message: "success",
Data: data,
})
}
func (sc *UlfsaarContext) Fail(err error) error {
var (
ed = errors.ExtractError(err)
)
return sc.JSON(ed.HttpCode, Failed{
Code: ed.Code,
Message: "failed",
Error: ed.Message,
})
}
func (sc *UlfsaarContext) FailWithData(err error, data interface{}) error {
var (
ed = errors.ExtractError(err)
)
return sc.JSON(ed.HttpCode, FailedWithData{
Code: ed.Code,
Message: "failed",
Error: ed.Message,
ErrorData: data,
})
}
func NewEmptyUlfsaarContext(parent echo.Context) *UlfsaarContext {
return &UlfsaarContext{parent, nil}
}
func NewUlfsaarContext(parent echo.Context) (*UlfsaarContext, error) {
pctx, ok := parent.(*UlfsaarContext)
if !ok {
return nil, errors.ErrSession
}
if pctx.Session == nil {
return nil, errors.ErrSession
}
return pctx, nil
}
package crypto
import "github.com/golang-jwt/jwt"
type (
Crypto interface {
Encrypt(claims jwt.Claims) ([]byte, error)
Decrypt(claims jwt.Claims, text string) (jwt.Claims, error)
}
impl struct {
secret []byte
}
)
func (i *impl) Encrypt(claims jwt.Claims) ([]byte, error) {
token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), claims)
ciphertext, err := token.SignedString(i.secret)
if err != nil {
return nil, err
}
return []byte(ciphertext), nil
}
func (i *impl) Decrypt(claims jwt.Claims, tokenString string) (jwt.Claims, error) {
keyFunc := func(token *jwt.Token) (interface{}, error) {
return i.secret, nil
}
token, err := jwt.ParseWithClaims(tokenString, claims, keyFunc)
if err != nil {
return nil, err
}
return token.Claims, nil
}
func New(secret string) (Crypto, error) {
return &impl{[]byte(secret)}, nil
}
package dbcon
import (
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
const (
MySQL string = "mysql"
SQLite string = "sqlite"
PostgreSQL string = "postgresql"
)
type DatabaseJSONType struct {
}
func (t DatabaseJSONType) GormDBDataType(db *gorm.DB, field *schema.Field) string {
switch db.Dialector.Name() {
case MySQL, SQLite:
return "JSON"
case PostgreSQL:
return "JSONB"
}
return ""
}
package dbcon
import (
"context"
"gorm.io/gorm"
)
type ORM interface {
Error() error
Close() error
Begin() ORM
Commit() error
Rollback() error
Offset(offset int64) ORM
Limit(limit int64) ORM
First(object interface{}) error
Last(object interface{}) error
Find(object interface{}) error
Model(value interface{}) ORM
Select(query interface{}, args ...interface{}) ORM
OmitAssoc() ORM
Table(name string, args ...interface{}) ORM
Where(query interface{}, args ...interface{}) ORM
Order(value interface{}) ORM
Create(args interface{}) error
Update(args interface{}) error
UpdateColumns(args interface{}) error
Delete(model interface{}, args ...interface{}) error
WithContext(ctx context.Context) ORM
Raw(query string, args ...interface{}) ORM
Exec(query string, args ...interface{}) ORM
Scan(object interface{}) error
Preload(assoc string, args ...interface{}) ORM
Joins(assoc string) ORM
GetGormInstance() *gorm.DB
Count(count *int64) error
Association(column string) ORMAssociation
Or(query interface{}, args ...interface{}) ORM
Save(data interface{}) error
}
type ORMAssociation interface {
Replace(values ...interface{}) error
Find(out interface{}, conds ...interface{}) error
Clear() error
}
package dbcon
import (
"context"
"time"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/logger"
log "ulfsaar/logger"
)
type (
postgresqldb struct {
db *gorm.DB
err error
}
PostgreSqlOption struct {
ConnectionString string
MaxLifeTimeConnection time.Duration
MaxIdleConnection, MaxOpenConnection int
Logger log.Logger
}
)
func (d *postgresqldb) Error() error {
return d.err
}
func (d *postgresqldb) Close() error {
sql, err := d.db.DB()
if err != nil {
return err
}
if err := sql.Close(); err != nil {
return err
}
return nil
}
func (d *postgresqldb) Begin() ORM {
var (
db = d.db.Begin()
err = db.Error
)
return &postgresqldb{db, err}
}
func (d *postgresqldb) Commit() error {
return d.db.Commit().Error
}
func (d *postgresqldb) Rollback() error {
return d.db.Rollback().Error
}
func (d *postgresqldb) Offset(offset int64) ORM {
var (
db = d.db.Offset(int(offset))
err = d.db.Error
)
return &postgresqldb{db, err}
}
func (d *postgresqldb) Limit(limit int64) ORM {
var (
db = d.db.Limit(int(limit))
err = d.db.Error
)
return &postgresqldb{db, err}
}
func (d *postgresqldb) First(object interface{}) error {
var (
res = d.db.First(object)
)
if res.Error != nil {
return res.Error
}
return nil
}
func (d *postgresqldb) Last(object interface{}) error {
var (
res = d.db.Last(object)
)
if res.Error != nil {
return res.Error
}
return nil
}
func (d *postgresqldb) Find(object interface{}) error {
var (
res = d.db.Find(object)
)
if res.Error != nil {
return res.Error
}
return nil
}
func (d *postgresqldb) Model(value interface{}) ORM {
var (
db = d.db.Model(value)
err = db.Error
)
return &postgresqldb{db, err}
}
func (d *postgresqldb) OmitAssoc() ORM {
var (
db = d.db.Omit(clause.Associations)
err = db.Error
)
return &postgresqldb{db, err}
}
func (d *postgresqldb) Select(query interface{}, args ...interface{}) ORM {
var (
db = d.db.Select(query, args...)
err = db.Error
)
return &postgresqldb{db, err}
}
func (d *postgresqldb) Table(name string, args ...interface{}) ORM {
var (
db = d.db.Table(name, args...)
err = db.Error
)
return &postgresqldb{db, err}
}
func (d *postgresqldb) Where(query interface{}, args ...interface{}) ORM {
var (
db = d.db.Where(query, args...)
err = db.Error
)
return &postgresqldb{db, err}
}
func (d *postgresqldb) Order(value interface{}) ORM {
var (
db = d.db.Order(value)
err = d.db.Error
)
return &postgresqldb{db, err}
}
func (d *postgresqldb) Create(args interface{}) error {
return d.db.Create(args).Error
}
func (d *postgresqldb) Update(args interface{}) error {
return d.db.Updates(args).Error
}
func (d *postgresqldb) UpdateColumns(args interface{}) error {
return d.db.UpdateColumns(args).Error
}
func (d *postgresqldb) Delete(model interface{}, args ...interface{}) error {
return d.db.Delete(model, args...).Error
}
func (d *postgresqldb) WithContext(ctx context.Context) ORM {
var (
db = d.db.WithContext(ctx)
)
return &postgresqldb{db: db, err: nil}
}
func (d *postgresqldb) Raw(query string, args ...interface{}) ORM {
var (
db = d.db.Raw(query, args...)
err = db.Error
)
return &postgresqldb{db, err}
}
func (d *postgresqldb) Exec(query string, args ...interface{}) ORM {
var (
db = d.db.Exec(query, args...)
err = db.Error
)
return &postgresqldb{db, err}
}
func (d *postgresqldb) Scan(object interface{}) error {
var (
db = d.db.Scan(object)
)
return db.Error
}
func (d *postgresqldb) Preload(assoc string, args ...interface{}) ORM {
var (
db = d.db.Preload(assoc, args)
err = db.Error
)
return &postgresqldb{db, err}
}
func (d *postgresqldb) Joins(assoc string) ORM {
var (
db = d.db.Joins(assoc)
err = db.Error
)
return &postgresqldb{db, err}
}
func (d *postgresqldb) GetGormInstance() *gorm.DB {
return d.db
}
func (d *postgresqldb) Count(count *int64) error {
var (
res = d.db.Count(count)
)
if res.Error != nil {
return res.Error
}
return nil
}
func (d *postgresqldb) Association(column string) ORMAssociation {
return d.db.Association(column)
}
func (d *postgresqldb) Or(query interface{}, args ...interface{}) ORM {
var (
db = d.db.Or(query, args...)
err = db.Error
)
return &postgresqldb{db, err}
}
func (d *postgresqldb) Save(data interface{}) error {
var (
db = d.db.Save(data)
err = db.Error
)
return err
}
func NewPostgreSql(option *PostgreSqlOption) (ORM, error) {
var (
opts = &gorm.Config{
QueryFields: true,
}
)
if option.Logger != nil {
opts.Logger = logger.New(option.Logger, logger.Config{
SlowThreshold: time.Second,
LogLevel: logger.Info,
Colorful: false,
IgnoreRecordNotFoundError: false,
})
}
db, err := gorm.Open(postgres.Open(option.ConnectionString), opts)
if err != nil {
return nil, err
}
sql, err := db.DB()
if err != nil {
return nil, err
}
sql.SetConnMaxLifetime(option.MaxLifeTimeConnection)
sql.SetMaxOpenConns(option.MaxOpenConnection)
sql.SetMaxIdleConns(option.MaxIdleConnection)
return &postgresqldb{db: db}, nil
}
package errors
import "github.com/joomcode/errorx"
type (
ErrorDescription struct {
Code string
HttpCode int
Message, FullMessage, Source string
}
)
var (
ErrCodeProperty = errorx.RegisterProperty("code")
ErrHttpCodeProperty = errorx.RegisterProperty("httpcode")
ErrSourceProperty = errorx.RegisterProperty("source")
ErrMessage = errorx.RegisterProperty("message")
ErrNamespace = errorx.NewNamespace("nobu")
ErrBase = errorx.NewType(ErrNamespace, "base")
ErrSessionHeader = ErrBase.New("Authorization header is empty").WithProperty(ErrCodeProperty, "401").WithProperty(ErrHttpCodeProperty, 401)
// - session
ErrExpiredSession = ErrBase.New("session is already expired").WithProperty(ErrCodeProperty, "1000").WithProperty(ErrHttpCodeProperty, 401)
ErrSession = ErrBase.New("unauthorized").WithProperty(ErrCodeProperty, "1002").WithProperty(ErrHttpCodeProperty, 401)
// - json
ErrJsonMarshal = ErrBase.New("failed marshal to json").WithProperty(ErrCodeProperty, "1003").WithProperty(ErrHttpCodeProperty, 400)
ErrJsonUnmarshal = ErrBase.New("failed unmarshal from json").WithProperty(ErrCodeProperty, "1003").WithProperty(ErrHttpCodeProperty, 400)
// - validation
ErrValidation = ErrBase.New("failed to validate request body").WithProperty(ErrCodeProperty, "1004").WithProperty(ErrHttpCodeProperty, 400)
)
func WrapErr(err error, message string) *errorx.Error {
return errorx.Decorate(err, message)
}
func ExtractError(err error) ErrorDescription {
var (
e, ok = err.(*errorx.Error)
)
if ok {
if ErrNamespace.IsNamespaceOf(e.Type()) {
code, source, httpcode := "0", "internal", 0
c, ok := errorx.ExtractProperty(e, ErrCodeProperty)
if ok {
code = c.(string)
} else {
code = "500"
}
hc, ok := errorx.ExtractProperty(e, ErrHttpCodeProperty)
if ok {
httpcode = hc.(int)
} else {
httpcode = 500
}
s, ok := errorx.ExtractProperty(e, ErrSourceProperty)
if ok {
source = s.(string)
}
return ErrorDescription{code, httpcode, e.Message(), e.Error(), source}
}
}
return ErrorDescription{
Code: "500",
HttpCode: 500,
Message: "internal server error",
FullMessage: err.Error(),
Source: "internal",
}
}
func New(hc int, code, message string) *errorx.Error {
return ErrBase.New(message).
WithProperty(ErrCodeProperty, code).
WithProperty(ErrHttpCodeProperty, hc)
}
func NewWithSource(hc int, code, message, source string) *errorx.Error {
return ErrBase.New(message).
WithProperty(ErrCodeProperty, code).
WithProperty(ErrHttpCodeProperty, hc).
WithProperty(ErrSourceProperty, source)
}
module gitlab.ursabyte.com/faizal.aziz/ulfssar-go
go 1.19
require (
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5
github.com/go-playground/validator/v10 v10.10.0
github.com/joho/godotenv v1.4.0
github.com/joomcode/errorx v1.1.0
github.com/kelseyhightower/envconfig v1.4.0
github.com/labstack/echo/v4 v4.10.2
github.com/labstack/gommon v0.4.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/crypto v0.6.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.7.0 // indirect
gorm.io/driver/postgres v1.3.8
gorm.io/gorm v1.23.8
)
require (
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/google/uuid v1.3.0
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.4.2
go.uber.org/zap v1.21.0
gopkg.in/natefinch/lumberjack.v2 v2.0.0
)
require (
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-resty/resty/v2 v2.2.0
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.12.1 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.3.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
github.com/jackc/pgtype v1.11.0 // indirect
github.com/jackc/pgx/v4 v4.16.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.4 // indirect
github.com/jonboulle/clockwork v0.4.0 // indirect
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/lestrrat-go/strftime v1.0.6 // indirect
github.com/smartystreets/goconvey v1.8.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
)
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 h1:rFw4nCn9iMW+Vajsk51NtYIcwSTkXr+JGrMd36kTDJw=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/go-kit/log v0.1.0 h1:DGJh0Sm43HbOeYDNnVZFl8BvcYVvjD5bqYJvp0REbwQ=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-resty/resty/v2 v2.2.0 h1:vgZ1cdblp8Aw4jZj3ZsKh6yKAlMg3CHMrqFSFFd+jgY=
github.com/go-resty/resty/v2 v2.2.0/go.mod h1:nYW/8rxqQCmI3bPz9Fsmjbr2FBjGuR2Mzt6kDh3zZ7w=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgconn v1.12.1 h1:rsDFzIpRk7xT4B8FufgpCCeyjdNpKyghZeSefViE5W8=
github.com/jackc/pgconn v1.12.1/go.mod h1:ZkhRC59Llhrq3oSfrikvwQ5NaxYExr6twkdkMLaKono=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.3.0 h1:brH0pCGBDkBW07HWlN/oSBXrmo3WB0UvZd1pIuDcL8Y=
github.com/jackc/pgproto3/v2 v2.3.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
github.com/jackc/pgtype v1.11.0 h1:u4uiGPz/1hryuXzyaBhSk6dnIyyG2683olG2OV+UUgs=
github.com/jackc/pgtype v1.11.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
github.com/jackc/pgx/v4 v4.16.1 h1:JzTglcal01DrghUqt+PmzWsZx/Yh7SC/CTQmSBMTd0Y=
github.com/jackc/pgx/v4 v4.16.1/go.mod h1:SIhx0D5hoADaiXZVyv+3gSm3LCIIINTVO0PficsvWGQ=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.2.1 h1:gI8os0wpRXFd4FiAY2dWiqRK037tjj3t7rKFeO4X5iw=
github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
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.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
github.com/joomcode/errorx v1.1.0 h1:dizuSG6yHzlvXOOGHW00gwsmM4Sb9x/yWEfdtPztqcs=
github.com/joomcode/errorx v1.1.0/go.mod h1:eQzdtdlNyN7etw6YCS4W4+lu442waxZYw5yvz0ULrRo=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
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/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M=
github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k=
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8=
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is=
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkLibYKgg+SwmyFU9dF2hn6MdTj4=
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA=
github.com/lestrrat-go/strftime v1.0.6 h1:CFGsDEt1pOpFNU+TJB0nhz9jl+K0hZSLE205AhTIGQQ=
github.com/lestrrat-go/strftime v1.0.6/go.mod h1:f7jQKgV5nnJpYgdEasS+/y7EsTb8ykN2z68n3TtcTaw=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.5/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.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.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/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0 h1:uPRuwkWF4J6fGsJ2R0Gn2jB1EQiav9k3S6CSdygQJXY=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v1.13.1 h1:Ef7KhSmjZcK6AVf9YbJdvPYG9avaF0ZxudX+ThRdWfU=
github.com/smartystreets/assertions v1.13.1/go.mod h1:cXr/IwVfSo/RbCSPhoAPv73p3hlSdrBH/b3SdnW/LMY=
github.com/smartystreets/goconvey v1.8.0 h1:Oi49ha/2MURE0WexF052Z0m+BNSGirfjg5RL+JXWq3w=
github.com/smartystreets/goconvey v1.8.0/go.mod h1:EdX8jtrTIj26jmjCOVNMVSIYAtgexqXKHOXW2Dx9JLg=
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/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
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/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/yuin/goldmark v1.3.5 h1:dPmz1Snjq0kmkz159iL7S6WzdahUTHnHB5M56WFVifs=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/zenazn/goji v0.9.0 h1:RSQQAbXGArQ0dIDEq+PI6WqN6if+5KHu6x2Cx/GXLTQ=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/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/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec h1:RlWgLqCMMIYYEVcAR5MDsuHlVkaIPDAF+5Dehzg8L5A=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.3.8 h1:8bEphSAB69t3odsCR4NDzt581iZEWQuRM27Cg6KgfPY=
gorm.io/driver/postgres v1.3.8/go.mod h1:qB98Aj6AhRO/oyu/jmZsi/YM9g6UzVCjMxO/6frFvcA=
gorm.io/gorm v1.23.6/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
gorm.io/gorm v1.23.8 h1:h8sGJ+biDgBA1AD1Ha9gFCx7h8npU7AsLdlkX0n2TpE=
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
package http
import (
"context"
"errors"
"fmt"
"net"
"net/http"
"net/url"
"sync"
"time"
)
const (
// This is the default timeout set into the default HTTP client (if a client is not supplied)
defaultTimeout = 3 * time.Second
// This is the default connect timeout set into the
defaultConnectTimeout = 1 * time.Second
)
var (
// ErrConnectTimeout indicates that we were unable to connect to the destination host and by extension the destination host cannot have
// processed this request in any way
ErrConnectTimeout = errors.New("connection timeout")
// ErrConnection indicates that there were errors (other than timeout) connecting to the destination host
ErrConnection = errors.New("error initiating connection")
// ErrTimeout indicates that we succeeded to connect to the destination host but failed to receive the response before the Timeout or
// context timeout expired.
// By extension this error implies that the destination received the request and may have partial processed it.
ErrTimeout = errors.New("timeout")
)
// Client is a drop-in replacement for the standard http.Client that provides additional features.
// Please note, as with the http.Client it is strongly recommended that a single instance of this client is created and then
// shared amongst the goroutines that make this request type. Allowing for connection pooling and other performance optimizations.
type Client struct {
// Name is the unique name for this client.
// This name is used to track errors, emit telemetry, etc.
// It is recommended to use an identifiable name link the service or endpoint being called.
Name string
// Client is the underlying HTTP client that will be used to make the requests.
// User are encouraged to populate this and explicitly set timeouts.
// If users do not populate this field, it will be automatically populated with this package's default settings.
// Users must not change or access this client after initial creation and a data race may result.
Client *http.Client
clientInitOnce sync.Once
// Timeout is the total timeout (including connection and read timeout) of a particular request
Timeout time.Duration
// ConnectTimeout is the timeout for the connection initiation phase.
// Note: ConnectTimeout should be lesser than timeout. Else, ErrConnectTimeout cannot be caught
ConnectTimeout time.Duration
// Instrumentation allows reporting and logging of internal events and statistics
Instrumentation Instrumentation
// CircuitBreaker defines the (optional) circuit breaker configuration for this client.
CircuitBreaker CircuitBreaker
}
// Do performs the HTTP request provided.
//
// Note: This method does not take a context as it uses the context inside the Request parameter.
// Note: Timeouts should be set using the context.Context in the Request.
// For more information see https://godoc.org/net/http#Client.Do
// nolint:funlen
func (c *Client) Do(req *http.Request) (*http.Response, error) {
start := time.Now()
path := c.getInstrumentation().SanitizePath(req.URL.Path)
endpointTag := generateEndpointTag(req.Method, path)
defer c.getInstrumentation().DoDuration(start, endpointTag)
// base request
doRequestFunc := func(req *http.Request) (*http.Response, error) {
resp, err := c.getClient().Do(req)
if err != nil {
c.getInstrumentation().BaseDoDuration(start, 0, endpointTag)
var urlErr *url.Error
switch {
case errors.As(err, &urlErr) && urlErr.Timeout():
c.getInstrumentation().BaseDoErr(err, endpointTag, "timeout")
return resp, err
case errors.Is(err, context.DeadlineExceeded):
c.getInstrumentation().BaseDoErr(err, endpointTag, "ctxTimeout")
return resp, err
case errors.Is(err, context.Canceled):
c.getInstrumentation().BaseDoErr(err, endpointTag, "ctxCanceled")
return resp, err
default:
c.getInstrumentation().BaseDoErr(err, endpointTag, "na")
return resp, err
}
}
c.getInstrumentation().BaseDoDuration(start, resp.StatusCode, endpointTag)
return resp, nil
}
// add middleware (note: be wary of the ordering here)
// retries are inside the circuit; this means the circuit only see complete failure
doRequestFunc = (&c.CircuitBreaker).addMiddleware(doRequestFunc)
// perform request + middleware
resp, err := doRequestFunc(req)
if err != nil {
return resp, err
}
return resp, nil
}
// all access to the http.Client by this struct should be via this method.
func (c *Client) getClient() *http.Client {
c.clientInitOnce.Do(c.doInitOnce)
return c.Client
}
// all access to the Instrumentation by this struct should be via this method.
func (c *Client) getInstrumentation() Instrumentation {
c.clientInitOnce.Do(c.doInitOnce)
return c.Instrumentation
}
func (c *Client) doInitOnce() {
if c.Instrumentation == nil {
c.Instrumentation = &noopInstrumentation{}
}
if c.Timeout == 0 {
c.Timeout = defaultTimeout
}
if c.ConnectTimeout == 0 {
c.ConnectTimeout = defaultConnectTimeout
}
if c.Client == nil {
c.Client = buildClient(c.Timeout, c.ConnectTimeout)
}
if c.Name == "" {
c.Instrumentation.InitWarning("name was not supplied. Use of unique and informative names is strongly recommended")
c.Name = fmt.Sprintf("smart-http-%d", time.Now().UnixNano())
}
c.Instrumentation.Init(c.Name)
(&c.CircuitBreaker).doInitOnce(c.Instrumentation, c.Name)
}
// GetTransportWithCustomDialer is used internally to assist with detecting connection timeouts during Dial().
// It is provided here so others can use it with their own http.Transport.
func GetTransportWithCustomDialer(connectionTimeout time.Duration) *http.Transport {
return &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (conn net.Conn, err error) {
dialer := net.Dialer{
Timeout: connectionTimeout,
}
conn, err = dialer.DialContext(ctx, network, addr)
if err != nil {
if netError, ok := err.(net.Error); ok {
if netError.Timeout() {
return nil, ErrConnectTimeout
}
return nil, fmt.Errorf("%w %v", ErrConnection, err)
}
return nil, err
}
return conn, nil
},
}
}
func buildClient(timeout, connectTimeout time.Duration) *http.Client {
return &http.Client{
Timeout: timeout,
Transport: GetTransportWithCustomDialer(connectTimeout),
}
}
func generateEndpointTag(method, path string) string {
return method + "::" + path
}
type requestClosure func(*http.Request) (*http.Response, error)
package http
import (
"github.com/afex/hystrix-go/hystrix"
"github.com/pkg/errors"
"net/http"
"time"
)
const (
defaultErrorThreshold = 80
minErrorThreshold = 50
)
var (
defaultMaxConcurrentRequests = hystrix.DefaultMaxConcurrent
// see `getTimeout()` for more details
defaultCircuitBreakerTimeout = 1 * time.Hour
// This indicates a HTTP response code that should be tracked by the circuit
errTrackableStatusCodeError = errors.New("response code is tracked by the circuit")
// ErrCircuitIsOpen indicates that the circuit is open and any available fallback should be used
ErrCircuitIsOpen = errors.New("the circuit is open")
// ErrCircuitMaxConcurrencyReached indicates that there are more concurrent requests than configured going through
// the circuit
ErrCircuitMaxConcurrencyReached = errors.New("the circuit's max concurrency is reached")
// ErrCircuitTimeout indicates that the circuit timed-out the request
ErrCircuitTimeout = errors.New("the circuit timed out the request")
)
// CircuitBreaker defines the circuit breaker configuration
type CircuitBreaker struct {
// Default value is 80 (cannot be set below 50)
ErrorPercentThreshold int
// Default value is 10 (setting above 100 is not advisable)
MaxConcurrentRequests int
name string
instrumentation Instrumentation
// used for testing only
trackError func(cb *CircuitBreaker)
totalTrackedErrors int
}
func (b *CircuitBreaker) getTimeout() int {
// Set a timeout that is so long that all other timeouts will trigger first
// We are essentially disabling this timeout
return int(defaultCircuitBreakerTimeout.Milliseconds())
}
func (b *CircuitBreaker) getMaxConcurrent() int {
if b.MaxConcurrentRequests > 0 {
return b.MaxConcurrentRequests
}
b.instrumentation.InitWarning("using default 'max concurrent requests' setting for circuit breaker")
return defaultMaxConcurrentRequests
}
func (b *CircuitBreaker) getErrorPercent() int {
if b.ErrorPercentThreshold > minErrorThreshold {
return b.ErrorPercentThreshold
}
b.instrumentation.InitWarning("using default 'error threshold' setting for circuit breaker")
return defaultErrorThreshold
}
//nolint:bodyclose
func (b *CircuitBreaker) buildMiddleware(doFunc requestClosure) requestClosure {
return func(req *http.Request) (*http.Response, error) {
var resp *http.Response
err := hystrix.Do(b.name, func() error {
var innerErr error
resp, innerErr = doFunc(req)
if innerErr != nil {
return innerErr
}
return b.outErrorBasedOnResponseCode(req, resp)
}, nil)
switch err {
case hystrix.ErrCircuitOpen:
b.instrumentation.CBCircuitOpen(req)
return resp, ErrCircuitIsOpen
case hystrix.ErrMaxConcurrency:
return resp, ErrCircuitMaxConcurrencyReached
case hystrix.ErrTimeout:
return resp, ErrCircuitTimeout
case nil, errTrackableStatusCodeError:
return resp, nil
default:
return resp, err
}
}
}
func (b *CircuitBreaker) outErrorBasedOnResponseCode(req *http.Request, resp *http.Response) error {
// process HTTP response codes (and throw errors that we should track)
switch resp.StatusCode {
case http.StatusRequestTimeout,
http.StatusTooManyRequests,
http.StatusInternalServerError,
http.StatusNotImplemented,
http.StatusBadGateway,
http.StatusServiceUnavailable,
http.StatusGatewayTimeout,
http.StatusHTTPVersionNotSupported,
http.StatusVariantAlsoNegotiates,
http.StatusInsufficientStorage,
http.StatusLoopDetected,
http.StatusNotExtended,
http.StatusNetworkAuthenticationRequired:
// these HTTP response codes should be tracked by the circuit breaker
b.trackError(b)
b.instrumentation.CBTrackedStatusCode(req, resp.StatusCode)
return errTrackableStatusCodeError
default:
// do not track these HTTP response codes (they are success codes or user errors)
return nil
}
}
func (b *CircuitBreaker) addMiddleware(doFunc requestClosure) requestClosure {
if b == nil {
return doFunc
}
return b.buildMiddleware(doFunc)
}
func (b *CircuitBreaker) doInitOnce(instrumentation Instrumentation, name string) {
if b == nil {
instrumentation.InitWarning("no circuit breaker has been configured. CB use is strongly recommended")
return
}
b.name = name
b.instrumentation = instrumentation
hystrix.ConfigureCommand(b.name, hystrix.CommandConfig{
Timeout: b.getTimeout(),
MaxConcurrentRequests: b.getMaxConcurrent(),
ErrorPercentThreshold: b.getErrorPercent(),
})
if b.trackError == nil {
b.trackError = func(_ *CircuitBreaker) {
// noop
}
}
}
package http
import (
"net/http"
"time"
)
type Instrumentation interface {
// Init is called once during initialization
Init(name string)
// InitWarning is called during init for warnings
InitWarning(message string)
// SanitizePath sanitizes the url path that can be sent to DataDog as a tag
SanitizePath(urlPath string) string
// DoDuration is the total time taken to complete the request (includes retries)
DoDuration(start time.Time, endpointTag string)
// BaseDoDuration is the time taken to make a single http.Client.Do() request
BaseDoDuration(start time.Time, statusCode int, endpointTag string)
// BaseDoErr is called when the underlying http.Client.Do() request returns an error
BaseDoErr(err error, endpointTag, errTag string)
// CBCircuitOpen is called when the circuit breaker circuit is open
CBCircuitOpen(req *http.Request)
// CBTrackedStatusCode is called when the response code is tracked by the circuit breaker as an error
CBTrackedStatusCode(req *http.Request, code int)
// RetryNonRetriable is called when a non-retriable HTTP status code or error has been returned
RetryNonRetriable(req *http.Request, code int)
// RetryRetriable is called when a retriable HTTP status code or error has been returned
// NOTE: when errors occur status code is set to 666
RetryRetriable(req *http.Request, code int)
// SingleflightErr is called when singleflight returns an error
SingleflightErr(req *http.Request, err error)
}
type noopInstrumentation struct{}
func (n *noopInstrumentation) Init(_ string) {}
func (n *noopInstrumentation) InitWarning(_ string) {}
func (n *noopInstrumentation) SanitizePath(_ string) string { return "" }
func (n *noopInstrumentation) DoDuration(_ time.Time, _ string) {}
func (n *noopInstrumentation) BaseDoDuration(_ time.Time, _ int, _ string) {}
func (n *noopInstrumentation) BaseDoErr(_ error, _, _ string) {}
func (n *noopInstrumentation) CBCircuitOpen(_ *http.Request) {}
func (n *noopInstrumentation) CBTrackedStatusCode(_ *http.Request, _ int) {}
func (n *noopInstrumentation) RetryNonRetriable(_ *http.Request, _ int) {}
func (n *noopInstrumentation) RetryRetriable(_ *http.Request, _ int) {}
func (n *noopInstrumentation) SingleflightErr(_ *http.Request, _ error) {}
package logger
import (
"context"
"github.com/sirupsen/logrus"
"gopkg.in/natefinch/lumberjack.v2"
)
type (
Logger interface {
Info(...interface{})
Infof(string, ...interface{})
Debug(...interface{})
Debugf(string, ...interface{})
Error(...interface{})
Errorf(string, ...interface{})
Warning(...interface{})
Warningf(string, ...interface{})
Fatal(...interface{})
Fatalf(string, ...interface{})
Print(...interface{})
Printf(string, ...interface{})
Println(...interface{})
Instance() interface{}
DebugWithCtx(context.Context, string, ...Field)
DebugfWithCtx(context.Context, string, ...interface{})
InfoWithCtx(context.Context, string, ...Field)
InfofWithCtx(context.Context, string, ...interface{})
WarnWithCtx(context.Context, string, ...Field)
WarnfWithCtx(context.Context, string, ...interface{})
ErrorWithCtx(context.Context, string, ...Field)
ErrorfWithCtx(context.Context, string, ...interface{})
FatalWithCtx(context.Context, string, ...Field)
FatalfWithCtx(context.Context, string, ...interface{})
Summary(tdr LogSummary)
}
LogSummary struct {
ExternalID string `json:"external_id"`
JourneyID string `json:"journey_id"`
ChainID string `json:"chain_id"`
RespTime int64 `json:"rt"`
Error string `json:"error"`
URI string `json:"uri"`
Header interface{} `json:"header"`
Request interface{} `json:"req"`
Response interface{} `json:"resp"`
AdditionalData interface{} `json:"additional_data"`
}
Field struct {
Key string
Val interface{}
}
Level string
Formatter string
Option struct {
Level Level
LogFilePath string
Formatter Formatter
MaxSize, MaxBackups, MaxAge int
Compress bool
}
lumberjackHook struct {
lbj *lumberjack.Logger
logrus *logrus.Logger
}
impl struct {
instance *logrus.Logger
}
)
const (
Info Level = "INFO"
Debug Level = "DEBUG"
Error Level = "ERROR"
JSONFormatter Formatter = "JSON"
)
func (l *impl) Info(args ...interface{}) {
l.instance.Info(args...)
}
func (l *impl) Infof(format string, args ...interface{}) {
l.instance.Infof(format, args...)
}
func (l *impl) Debug(args ...interface{}) {
l.instance.Debug(args...)
}
func (l *impl) Debugf(format string, args ...interface{}) {
l.instance.Debugf(format, args...)
}
func (l *impl) Error(args ...interface{}) {
l.instance.Error(args...)
}
func (l *impl) Errorf(format string, args ...interface{}) {
l.instance.Errorf(format, args...)
}
func (l *impl) Warning(args ...interface{}) {
l.instance.Warning(args...)
}
func (l *impl) Warningf(format string, args ...interface{}) {
l.instance.Warningf(format, args...)
}
func (l *impl) Fatal(args ...interface{}) {
l.instance.Fatal(args...)
}
func (l *impl) Fatalf(format string, args ...interface{}) {
l.instance.Fatalf(format, args...)
}
func (l *impl) Print(args ...interface{}) {
l.instance.Print(args...)
}
func (l *impl) Println(args ...interface{}) {
l.instance.Println(args...)
}
func (l *impl) Printf(format string, args ...interface{}) {
l.instance.Printf(format, args...)
}
func (l *impl) Instance() interface{} {
return l.instance
}
func (l *impl) DebugWithCtx(ctx context.Context, message string, field ...Field) {
l.instance.Debug(message, field)
}
func (l *impl) DebugfWithCtx(ctx context.Context, format string, args ...interface{}) {
l.instance.Debugf(format, args...)
}
func (l *impl) InfoWithCtx(ctx context.Context, message string, field ...Field) {
l.instance.Info(message, field)
}
func (l *impl) InfofWithCtx(ctx context.Context, format string, args ...interface{}) {
l.instance.Infof(format, args...)
}
func (l *impl) WarnWithCtx(ctx context.Context, message string, field ...Field) {
l.instance.Warning(message, field)
}
func (l *impl) WarnfWithCtx(ctx context.Context, format string, args ...interface{}) {
l.instance.Warnf(format, args...)
}
func (l *impl) ErrorWithCtx(ctx context.Context, message string, field ...Field) {
l.instance.Error(message, field)
}
func (l *impl) ErrorfWithCtx(ctx context.Context, format string, args ...interface{}) {
l.instance.Errorf(format, args...)
}
func (l *impl) FatalWithCtx(ctx context.Context, message string, field ...Field) {
l.instance.Fatal(message, field)
}
func (l *impl) FatalfWithCtx(ctx context.Context, format string, args ...interface{}) {
l.instance.Fatalf(format, args...)
}
func (l *impl) Summary(tdr LogSummary) {
l.instance.Info(tdr)
}
func New(option *Option) (Logger, error) {
instance := logrus.New()
if option.Level == Info {
instance.Level = logrus.InfoLevel
}
if option.Level == Debug {
instance.Level = logrus.DebugLevel
}
if option.Level == Error {
instance.Level = logrus.ErrorLevel
}
var formatter logrus.Formatter
if option.Formatter == JSONFormatter {
formatter = &logrus.JSONFormatter{}
} else {
formatter = &logrus.TextFormatter{}
}
instance.Formatter = formatter
// - check if log file path does exists
if option.LogFilePath != "" {
lbj := &lumberjack.Logger{
Filename: option.LogFilePath,
MaxSize: option.MaxSize,
MaxAge: option.MaxAge,
MaxBackups: option.MaxBackups,
LocalTime: true,
Compress: option.Compress,
}
instance.Hooks.Add(&lumberjackHook{
lbj: lbj,
logrus: instance,
})
}
return &impl{instance}, nil
}
func (l *lumberjackHook) Levels() []logrus.Level {
return []logrus.Level{logrus.InfoLevel, logrus.DebugLevel, logrus.ErrorLevel}
}
func (l *lumberjackHook) Fire(entry *logrus.Entry) error {
b, err := l.logrus.Formatter.Format(entry)
if err != nil {
return err
}
if _, err := l.lbj.Write(b); err != nil {
return err
}
return nil
}
package middlewares
import (
"github.com/labstack/echo/v4"
"strings"
"ulfsaar/context"
"ulfsaar/crypto"
"ulfsaar/logger"
"ulfsaar/session"
)
const (
AuthorizationHeader = "authorization"
)
type (
SessionMiddleware interface {
AuthenticateSession(next echo.HandlerFunc) echo.HandlerFunc
}
impl struct {
secret string
crypto crypto.Crypto
logger logger.Logger
prefixSkip []string
}
)
func (i *impl) AuthenticateSession(next echo.HandlerFunc) echo.HandlerFunc {
return func(ctx echo.Context) error {
var (
sctx = context.NewEmptyUlfsaarContext(ctx)
token = ctx.Request().Header.Get(AuthorizationHeader)
)
if i.skipper(ctx) {
return next(sctx)
}
NewSession, err := session.NewSession(i.crypto, token)
if err != nil {
return err
}
sctx.Session = NewSession
sctx.Set("Session", NewSession)
return next(sctx)
}
}
func (i *impl) skipper(c echo.Context) (skip bool) {
url := c.Request().URL.String()
if url == "/" {
skip = true
return
}
for _, urlSkip := range i.prefixSkip {
if strings.HasPrefix(url, urlSkip) {
skip = true
return
}
}
return
}
func NewSessionMiddleware(secret string, crypto crypto.Crypto, logger logger.Logger, prefixSkip ...string) (SessionMiddleware, error) {
return &impl{secret, crypto, logger, prefixSkip}, nil
}
package middlewares
import (
"github.com/labstack/echo/v4"
"ulfsaar/errors"
)
type (
BasicAuthorizationMiddleware interface {
BasicAuthenticate(next echo.HandlerFunc) echo.HandlerFunc
}
basicAuthorizationMiddleware struct {
username, password string
}
)
func (i *basicAuthorizationMiddleware) BasicAuthenticate(next echo.HandlerFunc) echo.HandlerFunc {
return func(ctx echo.Context) error {
if i.skipper(ctx) {
return next(ctx)
}
username, password, ok := ctx.Request().BasicAuth()
if !ok {
return errors.ErrSession
}
isValid := (username == i.username) && (password == i.password)
if !isValid {
return errors.ErrSession
}
return next(ctx)
}
}
func (i *basicAuthorizationMiddleware) skipper(c echo.Context) (skip bool) {
url := c.Request().URL.String()
if url == "/" {
skip = true
return
}
return
}
func NewBasicAuthorizationMiddleware(username, password string) (BasicAuthorizationMiddleware, error) {
return &basicAuthorizationMiddleware{username, password}, nil
}
package context
import (
"context"
"time"
"ulfsaar/logger"
pkgLogger "ulfsaar/pkg/logger"
)
type UlfsaarContext struct {
Context context.Context
additionalData map[string]interface{}
Logger logger.Logger
RequestTime time.Time
ExternalID string
JourneyID string
ChainID string
URI string
Header interface{}
Request interface{}
Response interface{}
ErrorCode string
ErrorMessage string
}
func NewNUlfsaarContext(logger logger.Logger, xid, jid, cid string) *UlfsaarContext {
return &UlfsaarContext{
Context: context.Background(),
additionalData: map[string]interface{}{},
Logger: logger,
RequestTime: time.Now(),
ExternalID: xid,
JourneyID: jid,
ChainID: cid,
Header: map[string]interface{}{},
Request: struct{}{},
Response: struct{}{},
}
}
func (c *UlfsaarContext) Get(key string) (data interface{}, ok bool) {
data, ok = c.additionalData[key]
return
}
func (c *UlfsaarContext) Put(key string, data interface{}) {
c.additionalData[key] = data
}
func (c *UlfsaarContext) ToContextLogger() (ctx context.Context) {
ctxVal := pkgLogger.Context{
ExternalID: c.ExternalID,
JourneyID: c.JourneyID,
ChainID: c.ChainID,
AdditionalData: c.additionalData,
}
ctx = pkgLogger.InjectCtx(context.Background(), ctxVal)
return
}
func (c *UlfsaarContext) Debug(message string, field ...logger.Field) {
c.Logger.DebugWithCtx(c.ToContextLogger(), message, field...)
}
func (c *UlfsaarContext) Debugf(format string, arg ...interface{}) {
c.Logger.DebugfWithCtx(c.ToContextLogger(), format, arg...)
}
func (c *UlfsaarContext) Info(message string, field ...logger.Field) {
c.Logger.InfoWithCtx(c.ToContextLogger(), message, field...)
}
func (c *UlfsaarContext) Infof(format string, arg ...interface{}) {
c.Logger.InfofWithCtx(c.ToContextLogger(), format, arg...)
}
func (c *UlfsaarContext) Warn(message string, field ...logger.Field) {
c.Logger.WarnWithCtx(c.ToContextLogger(), message, field...)
}
func (c *UlfsaarContext) Warnf(format string, arg ...interface{}) {
c.Logger.WarnfWithCtx(c.ToContextLogger(), format, arg...)
}
func (c *UlfsaarContext) Error(message string, field ...logger.Field) {
c.Logger.ErrorWithCtx(c.ToContextLogger(), message, field...)
}
func (c *UlfsaarContext) Errorf(format string, arg ...interface{}) {
c.Logger.ErrorfWithCtx(c.ToContextLogger(), format, arg...)
}
func (c *UlfsaarContext) Fatal(message string, field ...logger.Field) {
c.Logger.FatalWithCtx(c.ToContextLogger(), message, field...)
}
func (c *UlfsaarContext) Fatalf(format string, arg ...interface{}) {
c.Logger.FatalfWithCtx(c.ToContextLogger(), format, arg...)
}
func (c *UlfsaarContext) Summary() {
model := logger.LogSummary{
ExternalID: c.ExternalID,
JourneyID: c.JourneyID,
ChainID: c.ChainID,
RespTime: c.ResponseTime(),
Error: c.ErrorMessage,
URI: c.URI,
Header: c.Header,
Request: c.Request,
Response: c.Response,
AdditionalData: c.additionalData,
}
c.Logger.Summary(model)
}
func (c *UlfsaarContext) ResponseTime() int64 {
return time.Since(c.RequestTime).Milliseconds()
}
func (c *UlfsaarContext) GetAdditionalData() map[string]interface{} {
return c.additionalData
}
package echo
import (
"github.com/labstack/echo/v4"
"net/http"
"ulfsaar/errors"
"ulfsaar/pkg/context"
"ulfsaar/pkg/logger"
"ulfsaar/session"
)
const (
Ulfsaar = "UlfsaarContext"
Session = "Session"
)
type (
ApplicationContext struct {
echo.Context
Session *session.Session
UlfsaarContext *context.UlfsaarContext
}
Success struct {
Code string `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data"`
}
Failed struct {
Code string `json:"code"`
Message string `json:"message"`
Error string `json:"error"`
Data interface{} `json:"data"`
}
)
func (sc *ApplicationContext) Success(data interface{}) error {
hc := http.StatusOK
if data == nil {
data = struct{}{}
}
res := Success{
Code: "00",
Message: "success",
Data: data,
}
sc.UlfsaarContext.Response = res
sc.UlfsaarContext.Info("Outgoing",
logger.ToField("rt", sc.UlfsaarContext.ResponseTime()),
logger.ToField("response", res),
logger.ToField("http_code", hc))
sc.UlfsaarContext.Summary()
sc.UlfsaarContext.ErrorCode = res.Code
sc.UlfsaarContext.ErrorMessage = res.Message
return sc.JSON(hc, res)
}
func (sc *ApplicationContext) SuccessWithMeta(data, meta interface{}) error {
hc := http.StatusOK
res := Success{
Code: "00",
Message: "success",
Data: data,
}
sc.UlfsaarContext.Response = res
sc.UlfsaarContext.Info("Outgoing",
logger.ToField("rt", sc.UlfsaarContext.ResponseTime()),
logger.ToField("response", res),
logger.ToField("http_code", hc))
sc.UlfsaarContext.Summary()
sc.UlfsaarContext.ErrorCode = res.Code
sc.UlfsaarContext.ErrorMessage = res.Message
return sc.JSON(hc, res)
}
func (sc *ApplicationContext) Fail(err error) error {
return sc.FailWithData(err, nil)
}
func (sc *ApplicationContext) FailWithData(err error, data interface{}) error {
var (
ed = errors.ExtractError(err)
)
if data == nil {
data = struct{}{}
}
res := Failed{
Code: ed.Code,
Message: ed.Message,
Error: ed.FullMessage,
Data: data,
}
sc.UlfsaarContext.Response = res
sc.UlfsaarContext.Info("Outgoing",
logger.ToField("rt", sc.UlfsaarContext.ResponseTime()),
logger.ToField("response", res),
logger.ToField("http_code", ed.HttpCode))
sc.UlfsaarContext.Summary()
sc.UlfsaarContext.ErrorCode = res.Code
sc.UlfsaarContext.ErrorMessage = res.Message
return sc.JSON(ed.HttpCode, res)
}
func (sc *ApplicationContext) Raw(hc int, data interface{}) error {
if data == nil {
data = struct{}{}
}
sc.UlfsaarContext.Response = data
sc.UlfsaarContext.Info("Outgoing",
logger.ToField("rt", sc.UlfsaarContext.ResponseTime()),
logger.ToField("response", data),
logger.ToField("http_code", hc))
sc.UlfsaarContext.Summary()
return sc.JSON(hc, data)
}
func (sc *ApplicationContext) AddSession(session *session.Session) *ApplicationContext {
sc.Set(Session, session)
sc.Session = session
return sc
}
func (sc *ApplicationContext) AddNobuContext(rc *context.UlfsaarContext) *ApplicationContext {
sc.Set(Ulfsaar, rc)
sc.UlfsaarContext = rc
return sc
}
func ParseApplicationContext(c echo.Context) *ApplicationContext {
var (
nc = c.Get(Ulfsaar)
ss = c.Get(Session)
sess *session.Session
ctx *context.UlfsaarContext
)
// request context is mandatory on application context
// force casting
ctx = nc.(*context.UlfsaarContext)
if ss != nil {
sess, _ = ss.(*session.Session)
}
return &ApplicationContext{Context: c, UlfsaarContext: ctx, Session: sess}
}
func NewEmptyApplicationContext(parent echo.Context) *ApplicationContext {
return &ApplicationContext{parent, nil, nil}
}
func NewApplicationContext(parent echo.Context) (*ApplicationContext, error) {
pctx, ok := parent.(*ApplicationContext)
if !ok {
return nil, errors.ErrSession
}
if pctx.Session == nil {
return nil, errors.ErrSession
}
return pctx, nil
}
package middlewares
import (
"bytes"
"github.com/labstack/echo/v4"
"io/ioutil"
"strings"
"ulfsaar/logger"
ucontext "ulfsaar/pkg/context"
uecho "ulfsaar/pkg/echo"
pkgLogger "ulfsaar/pkg/logger"
"ulfsaar/utilities"
)
const (
ExternalID = "X-EXTERNAL-ID"
JourneyID = "X-JOURNEY-ID"
ChainID = "X-CHAIN-ID"
)
type (
ContextInjectorMiddleware interface {
Injector(next echo.HandlerFunc) echo.HandlerFunc
}
contextInjectorMiddleware struct {
logger logger.Logger
prefixSkip []string
}
)
func (i *contextInjectorMiddleware) Injector(h echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
var (
tid = c.Request().Header.Get(ExternalID)
jid = c.Request().Header.Get(JourneyID)
cid = c.Request().Header.Get(ChainID)
)
if len(tid) == 0 {
tid, _ = utilities.NewUtils().GenerateUUID()
}
// - Set session to context
ctx := ucontext.NewNUlfsaarContext(i.logger, tid, jid, cid)
ctx.Context = c.Request().Context()
ctx.Header = c.Request().Header
ctx.URI = c.Request().URL.String()
c.Set(uecho.Ulfsaar, ctx)
// print request time
var bodyBytes []byte
if c.Request().Body != nil {
bodyBytes, _ = ioutil.ReadAll(c.Request().Body)
// Restore the io.ReadCloser to its original state
c.Request().Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
}
ctx.Request = string(bodyBytes)
if !i.skipper(c) {
ctx.Info("Incoming",
pkgLogger.ToField("url", c.Request().URL.String()),
pkgLogger.ToField("header", ctx.Header),
pkgLogger.ToField("request", ctx.Request))
}
return h(c)
}
}
func (i *contextInjectorMiddleware) skipper(c echo.Context) (skip bool) {
url := c.Request().URL.String()
if url == "/" {
skip = true
return
}
for _, urlSkip := range i.prefixSkip {
if strings.HasPrefix(url, urlSkip) {
skip = true
return
}
}
return
}
func NewContextInjectorMiddleware(logger logger.Logger, prefixSkip ...string) (ContextInjectorMiddleware, error) {
return &contextInjectorMiddleware{logger: logger, prefixSkip: prefixSkip}, nil
}
package http_client
import (
"bytes"
"crypto/tls"
"github.com/go-resty/resty/v2"
"net/http"
"time"
"ulfsaar/pkg/context"
"ulfsaar/pkg/logger"
)
func Setup(options Options) HttpClient {
httpClient := resty.New()
if options.SkipTLS {
httpClient.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
}
if options.SkipCheckRedirect {
httpClient.SetRedirectPolicy(resty.RedirectPolicyFunc(func(request *http.Request, requests []*http.Request) error {
return http.ErrUseLastResponse
}))
}
if options.WithProxy {
httpClient.SetProxy(options.ProxyAddress)
} else {
httpClient.RemoveProxy()
}
httpClient.SetTimeout(options.Timeout)
httpClient.SetDebug(options.DebugMode)
return &client{
options: options,
httpClient: httpClient,
}
}
type (
HttpClient interface {
SetTimeout(timeout time.Duration)
Post(ctx *context.UlfsaarContext, path string, headers http.Header, payload interface{}) (respHeader http.Header, statusCode int, body []byte, err error)
PostFormData(ctx *context.UlfsaarContext, path string, headers http.Header, payload map[string]string) (respHeader http.Header, statusCode int, body []byte, err error)
PostMultipartFormFilesAndData(ctx *context.UlfsaarContext, path string, headers http.Header, formData []*MultipartField, formFiles []MultipartFileRequest) (respHeader http.Header, statusCode int, body []byte, err error)
Put(ctx *context.UlfsaarContext, path string, headers http.Header, payload interface{}) (respHeader http.Header, statusCode int, body []byte, err error)
Get(ctx *context.UlfsaarContext, path string, headers http.Header) (respHeader http.Header, statusCode int, body []byte, err error)
GetWithQueryParam(ctx *context.UlfsaarContext, path string, headers http.Header, queryParam map[string]string) (respHeader http.Header, statusCode int, body []byte, err error)
Delete(ctx *context.UlfsaarContext, path string, headers http.Header, payload interface{}) (respHeader http.Header, statusCode int, body []byte, err error)
Patch(ctx *context.UlfsaarContext, path string, headers http.Header, payload interface{}) (respHeader http.Header, statusCode int, body []byte, err error)
}
client struct {
options Options
httpClient *resty.Client
}
)
func (c *client) SetTimeout(timeout time.Duration) {
c.httpClient.SetTimeout(timeout)
return
}
func (c *client) Post(ctx *context.UlfsaarContext, path string, headers http.Header, payload interface{}) (respHeader http.Header, statusCode int, body []byte, err error) {
url := c.options.Address + path
startTime := time.Now()
request := c.httpClient.R()
request.SetBody(payload)
for h, val := range headers {
request.Header[h] = val
}
if headers[ContentType] == nil {
request.Header.Set(ContentType, ApplicationJSON)
}
request.Header.Set(UserAgent, UserAgentValue)
httpResp, httpErr := request.Post(url)
if httpResp != nil {
body = httpResp.Body()
respHeader = httpResp.Header()
statusCode = httpResp.StatusCode()
}
ctx.Info("Post",
logger.ToField(urlKey, url),
logger.ToField(requestKey, toRequest(request.Header, payload)),
logger.ToField(responseKey, toResponse(statusCode, respHeader, body)),
logger.ToField(startProcessingTimeKey, startProcessingTime(startTime)),
logger.ToField(processingTimeKey, processingTime(startTime)),
)
if statusCode == http.StatusOK {
return respHeader, statusCode, body, nil
}
return respHeader, statusCode, body, httpErr
}
func (c *client) PostFormData(ctx *context.UlfsaarContext, path string, headers http.Header, payload map[string]string) (respHeader http.Header, statusCode int, body []byte, err error) {
url := c.options.Address + path
startTime := time.Now()
request := c.httpClient.R()
request.SetFormData(payload)
for h, val := range headers {
request.Header[h] = val
}
if headers[ContentType] == nil {
request.Header.Set(ContentType, ApplicationJSON)
}
request.Header.Set(UserAgent, UserAgentValue)
httpResp, httpErr := request.Post(url)
if httpResp != nil {
body = httpResp.Body()
respHeader = httpResp.Header()
statusCode = httpResp.StatusCode()
}
ctx.Info("PostFormData",
logger.ToField(urlKey, url),
logger.ToField(requestKey, toRequest(request.Header, payload)),
logger.ToField(responseKey, toResponse(statusCode, respHeader, body)),
logger.ToField(startProcessingTimeKey, startProcessingTime(startTime)),
logger.ToField(processingTimeKey, processingTime(startTime)),
)
if statusCode == http.StatusOK {
return respHeader, statusCode, body, nil
}
return respHeader, statusCode, body, httpErr
}
func (c *client) PostMultipartFormFilesAndData(ctx *context.UlfsaarContext, path string, headers http.Header, formData []*MultipartField, formFiles []MultipartFileRequest) (respHeader http.Header, statusCode int, body []byte, err error) {
url := c.options.Address + path
startTime := time.Now()
request := c.httpClient.R()
request.SetMultipartFields(formData...)
for _, val := range formFiles {
request.SetFileReader(val.FieldName, val.FileName, bytes.NewReader(val.File))
}
for h, val := range headers {
request.Header[h] = val
}
request.Header.Set(UserAgent, UserAgentValue)
httpResp, httpErr := request.Post(url)
if httpResp != nil {
body = httpResp.Body()
respHeader = httpResp.Header()
statusCode = httpResp.StatusCode()
}
ctx.Info("PostMultipartFormFilesAndData",
logger.ToField(urlKey, url),
logger.ToField(requestKey, toRequest(request.Header, nil)),
logger.ToField("formData", formData),
logger.ToField("formFiles", formFiles),
logger.ToField(responseKey, toResponse(statusCode, respHeader, body)),
logger.ToField(startProcessingTimeKey, startProcessingTime(startTime)),
logger.ToField(processingTimeKey, processingTime(startTime)),
)
if statusCode == http.StatusOK {
return respHeader, statusCode, body, nil
}
return respHeader, statusCode, body, httpErr
}
func (c *client) Put(ctx *context.UlfsaarContext, path string, headers http.Header, payload interface{}) (respHeader http.Header, statusCode int, body []byte, err error) {
url := c.options.Address + path
startTime := time.Now()
request := c.httpClient.R()
for h, val := range headers {
request.Header[h] = val
}
if headers[ContentType] == nil {
request.Header.Set(ContentType, ApplicationJSON)
}
request.Header.Set(UserAgent, UserAgentValue)
request.SetBody(payload)
httpResp, httpErr := request.Put(url)
if httpResp != nil {
body = httpResp.Body()
respHeader = httpResp.Header()
statusCode = httpResp.StatusCode()
}
ctx.Info("Put",
logger.ToField(urlKey, url),
logger.ToField(requestKey, toRequest(request.Header, payload)),
logger.ToField(responseKey, toResponse(statusCode, respHeader, body)),
logger.ToField(startProcessingTimeKey, startProcessingTime(startTime)),
logger.ToField(processingTimeKey, processingTime(startTime)),
)
if statusCode == http.StatusOK {
return respHeader, statusCode, body, nil
}
return respHeader, statusCode, body, httpErr
}
func (c *client) Get(ctx *context.UlfsaarContext, path string, headers http.Header) (respHeader http.Header, statusCode int, body []byte, err error) {
url := c.options.Address + path
startTime := time.Now()
request := c.httpClient.R()
for h, val := range headers {
request.Header[h] = val
}
request.Header.Set(UserAgent, UserAgentValue)
httpResp, httpErr := request.Get(url)
if httpResp != nil {
body = httpResp.Body()
respHeader = httpResp.Header()
statusCode = httpResp.StatusCode()
}
ctx.Info("Get",
logger.ToField(urlKey, url),
logger.ToField(requestKey, toRequest(request.Header, nil)),
logger.ToField(responseKey, toResponse(statusCode, respHeader, body)),
logger.ToField(startProcessingTimeKey, startProcessingTime(startTime)),
logger.ToField(processingTimeKey, processingTime(startTime)),
)
if statusCode == http.StatusOK {
return respHeader, statusCode, body, nil
}
return respHeader, statusCode, body, httpErr
}
func (c *client) GetWithQueryParam(ctx *context.UlfsaarContext, path string, headers http.Header, queryParam map[string]string) (respHeader http.Header, statusCode int, body []byte, err error) {
url := c.options.Address + path
startTime := time.Now()
request := c.httpClient.R()
for h, val := range headers {
request.Header[h] = val
}
request.Header.Set(UserAgent, UserAgentValue)
request.SetQueryParams(queryParam)
httpResp, httpErr := request.Get(url)
if httpResp != nil {
body = httpResp.Body()
respHeader = httpResp.Header()
statusCode = httpResp.StatusCode()
}
ctx.Info("GetWithQueryParam",
logger.ToField(urlKey, url),
logger.ToField(requestKey, toRequest(request.Header, queryParam)),
logger.ToField(responseKey, toResponse(statusCode, respHeader, body)),
logger.ToField(startProcessingTimeKey, startProcessingTime(startTime)),
logger.ToField(processingTimeKey, processingTime(startTime)),
)
if statusCode == http.StatusOK {
return respHeader, statusCode, body, nil
}
return respHeader, statusCode, body, httpErr
}
func (c *client) Delete(ctx *context.UlfsaarContext, path string, headers http.Header, payload interface{}) (respHeader http.Header, statusCode int, body []byte, err error) {
url := c.options.Address + path
startTime := time.Now()
request := c.httpClient.R()
for h, val := range headers {
request.Header[h] = val
}
request.Header.Set(UserAgent, UserAgentValue)
request.SetBody(payload)
httpResp, httpErr := request.Delete(url)
if httpResp != nil {
body = httpResp.Body()
respHeader = httpResp.Header()
statusCode = httpResp.StatusCode()
}
ctx.Info("Delete",
logger.ToField(urlKey, url),
logger.ToField(requestKey, toRequest(request.Header, payload)),
logger.ToField(responseKey, toResponse(statusCode, respHeader, body)),
logger.ToField(startProcessingTimeKey, startProcessingTime(startTime)),
logger.ToField(processingTimeKey, processingTime(startTime)),
)
if statusCode == http.StatusOK {
return respHeader, statusCode, body, nil
}
return respHeader, statusCode, body, httpErr
}
func (c *client) Patch(ctx *context.UlfsaarContext, path string, headers http.Header, payload interface{}) (respHeader http.Header, statusCode int, body []byte, err error) {
url := c.options.Address + path
startTime := time.Now()
request := c.httpClient.R()
request.SetBody(payload)
for h, val := range headers {
request.Header[h] = val
}
if headers[ContentType] == nil {
request.Header.Set(ContentType, ApplicationJSON)
}
request.Header.Set(UserAgent, UserAgentValue)
httpResp, httpErr := request.Patch(url)
if httpResp != nil {
body = httpResp.Body()
respHeader = httpResp.Header()
statusCode = httpResp.StatusCode()
}
ctx.Info("Patch",
logger.ToField(urlKey, url),
logger.ToField(requestKey, toRequest(request.Header, payload)),
logger.ToField(responseKey, toResponse(statusCode, respHeader, body)),
logger.ToField(startProcessingTimeKey, startProcessingTime(startTime)),
logger.ToField(processingTimeKey, processingTime(startTime)),
)
if statusCode == http.StatusOK {
return respHeader, statusCode, body, nil
}
return respHeader, statusCode, body, httpErr
}
package http_client
const (
ContentType = "Content-Type"
ApplicationJSON = "application/json"
UserAgent = "User-Agent"
UserAgentValue = "https://digdayatech.id"
)
const (
startProcessingTimeKey = "start_processing_time"
processingTimeKey = "processing_time"
urlKey = "url"
requestKey = "request"
responseKey = "response"
)
package http_client
import "github.com/go-resty/resty/v2"
type MultipartFileRequest struct {
FieldName string
FileName string
File []byte
}
type MultipartField = resty.MultipartField
package http_client
import "time"
type Options struct {
Address string `json:"address"`
Timeout time.Duration `json:"timeout"`
DebugMode bool `json:"debug_mode"`
WithProxy bool `json:"with_proxy"`
ProxyAddress string `json:"proxy_address"`
SkipTLS bool `json:"skip_tls"`
SkipCheckRedirect bool `json:"skip_check_redirect"`
}
package http_client
import (
"encoding/json"
"time"
)
type (
logRequest struct {
Header interface{} `json:"header"`
Body interface{} `json:"body"`
}
logResponse struct {
StatusCode int `json:"status_code"`
Header interface{} `json:"header"`
Body interface{} `json:"body"`
}
)
func startProcessingTime(start time.Time) string {
return start.Format("2006-01-02 15:04:05.000")
}
func processingTime(start time.Time) int64 {
return time.Since(start).Milliseconds()
}
func toRequest(header, body interface{}) *logRequest {
if body == nil {
body = struct{}{}
}
return &logRequest{
Header: header,
Body: body,
}
}
func toResponse(statusCode int, header interface{}, body []byte) *logResponse {
var data interface{}
if body != nil {
if _err := json.Unmarshal(body, &data); _err != nil {
data = body
}
}
return &logResponse{
StatusCode: statusCode,
Header: header,
Body: data,
}
}
package logger
import (
"context"
"fmt"
"ulfsaar/logger"
)
type (
fileLogger struct {
defaultLogger *zapLogger
}
Option struct {
Stdout bool `json:"stdout"`
FileLocation string `json:"file_location"`
FileMaxAge int `json:"file_max_age"`
Level int8 `json:"level"`
}
)
func NewLogger(config *Option) logger.Logger {
fmt.Println("Try NewLogger File...")
if config == nil {
panic("logger file config is nil")
}
log := &fileLogger{
defaultLogger: createLogger(config.Stdout, config.Level, config.FileLocation, config.FileMaxAge),
}
return log
}
func (c *fileLogger) Info(args ...interface{}) {
c.InfoWithCtx(context.Background(), fmt.Sprint(args...))
}
func (c *fileLogger) Infof(format string, args ...interface{}) {
c.InfofWithCtx(context.Background(), format, args...)
}
func (c *fileLogger) Debug(args ...interface{}) {
c.DebugWithCtx(context.Background(), fmt.Sprint(args...))
}
func (c *fileLogger) Debugf(format string, args ...interface{}) {
c.DebugfWithCtx(context.Background(), format, args...)
}
func (c *fileLogger) Error(args ...interface{}) {
c.ErrorWithCtx(context.Background(), fmt.Sprint(args...))
}
func (c *fileLogger) Errorf(format string, args ...interface{}) {
c.ErrorfWithCtx(context.Background(), format, args...)
}
func (c *fileLogger) Warning(args ...interface{}) {
c.WarnWithCtx(context.Background(), fmt.Sprint(args...))
}
func (c *fileLogger) Warningf(format string, args ...interface{}) {
c.WarnfWithCtx(context.Background(), format, args...)
}
func (c *fileLogger) Fatal(args ...interface{}) {
c.FatalWithCtx(context.Background(), fmt.Sprint(args...))
}
func (c *fileLogger) Fatalf(format string, args ...interface{}) {
c.FatalfWithCtx(context.Background(), format, args...)
}
func (c *fileLogger) Print(args ...interface{}) {
c.defaultLogger.Print(args...)
}
func (c *fileLogger) Printf(format string, args ...interface{}) {
c.defaultLogger.Printf(format, args...)
}
func (c *fileLogger) Println(args ...interface{}) {
c.defaultLogger.Println(args...)
}
func (c *fileLogger) Instance() interface{} {
return c.defaultLogger
}
func (c *fileLogger) DebugWithCtx(ctx context.Context, message string, fields ...logger.Field) {
c.defaultLogger.Debug(ctx, message, fields...)
}
func (c *fileLogger) DebugfWithCtx(ctx context.Context, format string, args ...interface{}) {
c.defaultLogger.Debugf(ctx, format, args...)
}
func (c *fileLogger) InfoWithCtx(ctx context.Context, message string, fields ...logger.Field) {
c.defaultLogger.Info(ctx, message, fields...)
}
func (c *fileLogger) InfofWithCtx(ctx context.Context, format string, args ...interface{}) {
c.defaultLogger.Infof(ctx, format, args...)
}
func (c *fileLogger) WarnWithCtx(ctx context.Context, message string, fields ...logger.Field) {
c.defaultLogger.Warn(ctx, message, fields...)
}
func (c *fileLogger) WarnfWithCtx(ctx context.Context, format string, args ...interface{}) {
c.defaultLogger.Warnf(ctx, format, args...)
}
func (c *fileLogger) ErrorWithCtx(ctx context.Context, message string, fields ...logger.Field) {
c.defaultLogger.Error(ctx, message, fields...)
}
func (c *fileLogger) ErrorfWithCtx(ctx context.Context, format string, args ...interface{}) {
c.defaultLogger.Errorf(ctx, format, args...)
}
func (c *fileLogger) FatalWithCtx(ctx context.Context, message string, fields ...logger.Field) {
c.defaultLogger.Fatal(ctx, message, fields...)
}
func (c *fileLogger) FatalfWithCtx(ctx context.Context, format string, args ...interface{}) {
c.defaultLogger.Fatalf(ctx, format, args...)
}
func (c *fileLogger) Summary(tdr logger.LogSummary) {
c.defaultLogger.Summary(tdr)
}
package logger
type (
Context struct {
ExternalID string `json:"external_id"`
JourneyID string `json:"journey_id"`
ChainID string `json:"chain_id"`
AdditionalData map[string]interface{} `json:"additional_data,omitempty"`
}
ctxKeyLogger struct{}
)
var ctxKey = ctxKeyLogger{}
package logger
import (
"context"
"encoding/json"
"fmt"
"go.uber.org/zap"
"time"
"ulfsaar/logger"
)
func ToField(key string, val interface{}) (field logger.Field) {
field = logger.Field{
Key: key,
Val: val,
}
return
}
func InjectCtx(parent context.Context, ctx Context) context.Context {
if parent == nil {
return InjectCtx(context.Background(), ctx)
}
return context.WithValue(parent, ctxKey, ctx)
}
func ExtractCtx(ctx context.Context) Context {
if ctx == nil {
return Context{}
}
val, ok := ctx.Value(ctxKey).(Context)
if !ok {
return Context{}
}
return val
}
func ctxToLog(ctx context.Context, logTime time.Time) (logRecord []zap.Field) {
ctxVal := ExtractCtx(ctx)
logRecord = append(logRecord, zap.String(logTimeKey, logTime.Format("2006-01-02 15:04:05.000")))
logRecord = append(logRecord, zap.String(externalIDKey, ctxVal.ExternalID))
if len(ctxVal.JourneyID) != 0 {
logRecord = append(logRecord, zap.String(journeyIDKey, ctxVal.JourneyID))
}
if len(ctxVal.ChainID) != 0 {
logRecord = append(logRecord, zap.String(chainIDKey, ctxVal.ChainID))
}
return
}
func appendToLog(fields ...logger.Field) (logRecord []zap.Field) {
for _, field := range fields {
logRecord = append(logRecord, formatLog(field.Key, field.Val))
}
return
}
func formatLog(key string, msg interface{}) (logRecord zap.Field) {
if msg == nil {
logRecord = zap.Any(key, struct{}{})
return
}
// handle string, string is cannot be masked, just write it
// but try to parse as json object if possible
if str, ok := msg.(string); ok {
var data interface{}
if _err := json.Unmarshal([]byte(str), &data); _err != nil {
logRecord = zap.String(key, str)
return
}
logRecord = zap.Any(key, data)
return
}
// not masked since it failed to convert to reflect.Value above
logRecord = zap.Any(key, msg)
return
}
func createLogger(stdout bool, level int8, location string, age int) *zapLogger {
var opt = make([]ZapOption, 0)
if stdout {
opt = append(opt, WithStdout())
} else {
opt = append(opt, WithFileOutput(location, age))
}
opt = append(opt, WithLevel(level))
log, err := NewZapLogger(opt...)
if err != nil {
panic(fmt.Errorf("init logger with error: %w", err))
}
return log
}
package logger
import (
"context"
"fmt"
rotateLogs "github.com/lestrrat-go/file-rotatelogs"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"io"
"os"
"time"
"ulfsaar/logger"
)
const (
logTimeKey = "log_time"
externalIDKey = "external_id"
journeyIDKey = "journey_id"
chainIDKey = "chain_id"
responseTimeKey = "rt"
responseURI = "uri"
headerKey = "header"
requestKey = "req"
responseKey = "resp"
errorKey = "error"
additionalDataKey = "additional_data"
levelKey = "level"
)
type logLevel string
const (
debugLevel = logLevel("Debug")
infoLevel = logLevel("Info")
warnLevel = logLevel("Warn")
errorLevel = logLevel("Error")
panicLevel = logLevel("Panic")
fatalLevel = logLevel("Fatal")
summaryLevel = logLevel("Summary")
)
type (
ZapOption func(*zapLogger) error
zapLogger struct {
writers []io.Writer
closer []io.Closer
zapLog *zap.Logger
level int8
loggerChannel chan logItem
}
logItem struct {
level logLevel
logTime time.Time
ctx context.Context
message string
args []logger.Field
summary logger.LogSummary
}
)
func WithStdout() ZapOption {
return func(logger *zapLogger) error {
// Wire STD output for both type
logger.writers = append(logger.writers, os.Stdout)
return nil
}
}
func WithFileOutput(location string, maxAge int) ZapOption {
return func(logger *zapLogger) error {
output, err := rotateLogs.New(
location+".%Y%m%d",
rotateLogs.WithLinkName(location),
rotateLogs.WithMaxAge(time.Duration(maxAge)*24*time.Hour),
rotateLogs.WithRotationTime(time.Hour),
)
if err != nil {
return fmt.Errorf("sys file error: %w", err)
}
// Wire SYS config only in sys
logger.writers = append(logger.writers, output)
logger.closer = append(logger.closer, output)
return nil
}
}
// WithCustomWriter add custom writer, so you can write using any storage method
// without waiting this package to be updated.
func WithCustomWriter(writer io.WriteCloser) ZapOption {
return func(logger *zapLogger) error {
if writer == nil {
return fmt.Errorf("writer is nil")
}
// wire custom writer to log
logger.writers = append(logger.writers, writer)
logger.closer = append(logger.closer, writer)
return nil
}
}
// WithLevel set level of logger
func WithLevel(level int8) ZapOption {
return func(logger *zapLogger) error {
logger.level = level
return nil
}
}
func NewZapLogger(opts ...ZapOption) (*zapLogger, error) {
defaultLogger := &zapLogger{
writers: make([]io.Writer, 0),
loggerChannel: make(chan logItem, 51200),
}
for _, o := range opts {
if err := o(defaultLogger); err != nil {
return nil, err
}
}
// use stdout only when writer is not specified
if len(defaultLogger.writers) <= 0 {
defaultLogger.writers = append(defaultLogger.writers, zapcore.AddSync(os.Stdout))
}
encoderConfig := zapcore.EncoderConfig{
TimeKey: "time",
MessageKey: "message",
LineEnding: zapcore.DefaultLineEnding,
EncodeDuration: func(d time.Duration, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendInt64(d.Nanoseconds() / 1000000)
},
EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(t.Format("2006-01-02 15:04:05.999"))
},
}
encoding := zapcore.NewJSONEncoder(encoderConfig)
// set logger here instead in options to make easy and consistent initiation
// set multiple writer as already set in options
zapWriters := make([]zapcore.WriteSyncer, 0)
for _, writer := range defaultLogger.writers {
if writer == nil {
continue
}
zapWriters = append(zapWriters, zapcore.AddSync(writer))
}
core := zapcore.NewCore(encoding, zapcore.NewMultiWriteSyncer(zapWriters...), zapcore.Level(defaultLogger.level))
defaultLogger.zapLog = zap.New(core)
// single thread for logging
go func() {
for {
select {
case log := <-defaultLogger.loggerChannel:
defaultLogger.logByLevel(log.level, log.logTime, log.ctx, log.message, log.summary, log.args...)
}
}
}()
return defaultLogger, nil
}
func (d *zapLogger) Close() error {
if d.closer == nil {
return nil
}
var err error
for _, closer := range d.closer {
if closer == nil {
continue
}
if e := closer.Close(); e != nil {
err = fmt.Errorf("%w: %q", e, err)
}
}
return err
}
func (d *zapLogger) Debug(ctx context.Context, message string, fields ...logger.Field) {
d.loggerChannel <- logItem{level: debugLevel, logTime: time.Now(), ctx: ctx, message: message, args: fields}
}
func (d *zapLogger) Debugf(ctx context.Context, format string, args ...interface{}) {
d.loggerChannel <- logItem{level: debugLevel, logTime: time.Now(), ctx: ctx, message: fmt.Sprintf(format, args...), args: []logger.Field{}}
}
func (d *zapLogger) Info(ctx context.Context, message string, fields ...logger.Field) {
d.loggerChannel <- logItem{level: infoLevel, logTime: time.Now(), ctx: ctx, message: message, args: fields}
}
func (d *zapLogger) Infof(ctx context.Context, format string, args ...interface{}) {
d.loggerChannel <- logItem{level: infoLevel, logTime: time.Now(), ctx: ctx, message: fmt.Sprintf(format, args...), args: []logger.Field{}}
}
func (d *zapLogger) Warn(ctx context.Context, message string, fields ...logger.Field) {
d.loggerChannel <- logItem{level: warnLevel, logTime: time.Now(), ctx: ctx, message: message, args: fields}
}
func (d *zapLogger) Warnf(ctx context.Context, format string, args ...interface{}) {
d.loggerChannel <- logItem{level: warnLevel, logTime: time.Now(), ctx: ctx, message: fmt.Sprintf(format, args...), args: []logger.Field{}}
}
func (d *zapLogger) Error(ctx context.Context, message string, fields ...logger.Field) {
d.loggerChannel <- logItem{level: errorLevel, logTime: time.Now(), ctx: ctx, message: message, args: fields}
}
func (d *zapLogger) Errorf(ctx context.Context, format string, args ...interface{}) {
d.loggerChannel <- logItem{level: errorLevel, logTime: time.Now(), ctx: ctx, message: fmt.Sprintf(format, args...), args: []logger.Field{}}
}
func (d *zapLogger) Fatal(ctx context.Context, message string, fields ...logger.Field) {
d.loggerChannel <- logItem{level: fatalLevel, logTime: time.Now(), ctx: ctx, message: message, args: fields}
}
func (d *zapLogger) Fatalf(ctx context.Context, format string, args ...interface{}) {
d.loggerChannel <- logItem{level: fatalLevel, logTime: time.Now(), ctx: ctx, message: fmt.Sprintf(format, args...), args: []logger.Field{}}
}
func (d *zapLogger) Print(args ...interface{}) {
d.loggerChannel <- logItem{level: infoLevel, logTime: time.Now(), ctx: nil, message: fmt.Sprint(args...), args: []logger.Field{}}
}
func (d *zapLogger) Printf(format string, args ...interface{}) {
d.loggerChannel <- logItem{level: infoLevel, logTime: time.Now(), ctx: nil, message: fmt.Sprintf(format, args...), args: []logger.Field{}}
}
func (d *zapLogger) Println(args ...interface{}) {
d.loggerChannel <- logItem{level: infoLevel, logTime: time.Now(), ctx: nil, message: fmt.Sprint(args...), args: []logger.Field{}}
}
func (d *zapLogger) Summary(summary logger.LogSummary) {
d.loggerChannel <- logItem{level: summaryLevel, logTime: time.Now(), ctx: nil, message: "Summary", args: nil, summary: summary}
}
func (d *zapLogger) logByLevel(level logLevel, logTime time.Time, ctx context.Context, message string, summary logger.LogSummary, fields ...logger.Field) {
switch level {
case debugLevel:
zapLogs := []zap.Field{
zap.String(levelKey, "debug"),
}
zapLogs = append(zapLogs, ctxToLog(ctx, logTime)...)
zapLogs = append(zapLogs, appendToLog(fields...)...)
d.zapLog.Debug(message, zapLogs...)
case infoLevel:
zapLogs := []zap.Field{
zap.String(levelKey, "info"),
}
zapLogs = append(zapLogs, ctxToLog(ctx, logTime)...)
zapLogs = append(zapLogs, appendToLog(fields...)...)
d.zapLog.Info(message, zapLogs...)
case warnLevel:
zapLogs := []zap.Field{
zap.String(levelKey, "warn"),
}
zapLogs = append(zapLogs, ctxToLog(ctx, logTime)...)
zapLogs = append(zapLogs, appendToLog(fields...)...)
d.zapLog.Warn(message, zapLogs...)
case errorLevel:
zapLogs := []zap.Field{
zap.String(levelKey, "error"),
}
zapLogs = append(zapLogs, ctxToLog(ctx, logTime)...)
zapLogs = append(zapLogs, appendToLog(fields...)...)
d.zapLog.Error(message, zapLogs...)
case panicLevel:
zapLogs := []zap.Field{
zap.String(levelKey, "panic"),
}
zapLogs = append(zapLogs, ctxToLog(ctx, logTime)...)
zapLogs = append(zapLogs, appendToLog(fields...)...)
d.zapLog.Panic(message, zapLogs...)
case fatalLevel:
zapLogs := []zap.Field{
zap.String(levelKey, "fatal"),
}
zapLogs = append(zapLogs, ctxToLog(ctx, logTime)...)
zapLogs = append(zapLogs, appendToLog(fields...)...)
d.zapLog.Fatal(message, zapLogs...)
case summaryLevel:
zapLogs := []zap.Field{
zap.String(levelKey, "summary"),
zap.String(logTimeKey, logTime.Format("2006-01-02 15:04:05.000")),
zap.String(externalIDKey, summary.ExternalID),
zap.String(journeyIDKey, summary.JourneyID),
zap.String(chainIDKey, summary.ChainID),
zap.Int64(responseTimeKey, summary.RespTime),
zap.String(responseURI, summary.URI),
formatLog(headerKey, summary.Header),
formatLog(requestKey, summary.Request),
formatLog(responseKey, summary.Response),
zap.String(errorKey, summary.Error),
formatLog(additionalDataKey, summary.AdditionalData),
}
d.zapLog.Info(message, zapLogs...)
}
}
package session
import (
"github.com/go-playground/validator/v10"
"ulfsaar/crypto"
"ulfsaar/errors"
)
type (
MediaSession struct {
Role string `json:"user_type" validate:"required"`
Directory string `json:"directory" validate:"required"`
Filenames []string `json:"filenames" validate:"required"`
UserID string `json:"user_id" validate:"required"`
}
)
func (ss *MediaSession) Encrypt(cr crypto.Crypto) (string, error) {
enc, _ := cr.Encrypt(ss)
return string(enc), nil
}
func (ss *MediaSession) Valid() error {
return nil
}
func NewMediaSession(cr crypto.Crypto, session string) (*MediaSession, error) {
var (
ss = &MediaSession{}
dec, err = cr.Decrypt(ss, session)
)
if err != nil {
return nil, errors.ErrSession.WithUnderlyingErrors(err)
}
if err := validator.New().Struct(dec); err != nil {
return nil, errors.ErrSession.WithUnderlyingErrors(err)
}
return ss, nil
}
package session
import (
"time"
"github.com/go-playground/validator/v10"
"ulfsaar/crypto"
"ulfsaar/errors"
)
type (
Session struct {
UserId string `json:"user_id" validate:"required"`
Email string `json:"email" validate:"required"`
Name string `json:"name" validate:"required"`
Role string `json:"role" validate:"required"`
Iat int64 `json:"iat" validate:"required"`
Expired int64 `json:"exp" validate:"required"`
}
)
func (ss *Session) IsSessionExpired() error {
if time.Now().After(time.Unix(ss.Expired, 0)) {
return errors.ErrExpiredSession
}
return nil
}
func (ss *Session) ExtendSession(cr crypto.Crypto, duration int64) (string, error) {
ss.Expired = time.Now().Add(time.Duration(duration) * time.Second).Unix()
return ss.Encrypt(cr)
}
func (ss *Session) Encrypt(cr crypto.Crypto) (string, error) {
enc, _ := cr.Encrypt(ss)
return string(enc), nil
}
func (ss *Session) Valid() error {
return nil
}
func NewSession(cr crypto.Crypto, session string) (*Session, error) {
var (
ss = &Session{}
dec, err = cr.Decrypt(ss, session)
)
if err != nil {
return nil, errors.ErrSession.WithUnderlyingErrors(err)
}
if err := validator.New().Struct(dec); err != nil {
return nil, errors.ErrSession.WithUnderlyingErrors(err)
}
if err := ss.IsSessionExpired(); err != nil {
return nil, err
}
return ss, nil
}
package utilities
import "github.com/google/uuid"
type (
Utils interface {
GenerateUUID() (string, error)
}
utilsImpl struct {
}
)
func NewUtils() Utils {
return &utilsImpl{}
}
// GenerateUUID produces random ID based on UUID
func (h *utilsImpl) GenerateUUID() (string, error) {
_uuid, err := uuid.NewRandom()
if err != nil {
return "", err
}
return _uuid.String(), nil
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment