Commit 53365c46 authored by Abiyan Bagus Baskoro's avatar Abiyan Bagus Baskoro

Initialize Ulfsaar Base Go

parent 95d58c82
Pipeline #427 failed with stages
[url "ssh://git@github.com/"]
insteadOf = https://github.com/
\ No newline at end of file
# These are some examples of commonly ignored file patterns.
# You should customize this list as applicable to your project.
# Learn more about .gitignore:
# https://www.atlassian.com/git/tutorials/saving-changes/gitignore
# Node artifact files
node_modules/
dist/
# Compiled Java class files
*.class
# Compiled Python bytecode
*.py[cod]
# Log files
*.log
# Package files
*.jar
# Maven
target/
dist/
# JetBrains IDE
.idea/
# Unit test reports
TEST*.xml
# Generated by MacOS
.DS_Store
# Generated by Windows
Thumbs.db
# Applications
*.app
*.exe
*.war
# Large media files
*.mp4
*.tiff
*.avi
*.flv
*.mov
*.wmv
# ulfsaar-go
## Getting started
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
## Add your files
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
```
cd existing_repo
git remote add origin https://gitlab.ursabyte.com/abiyan89/ulfsaar-base-go.git
git branch -M main
git push -uf origin main
```
## Integrate with your tools
- [ ] [Set up project integrations](https://gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/-/settings/integrations)
## Collaborate with your team
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
## Test and Deploy
Use the built-in continuous integration in GitLab.
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
***
# Editing this README
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
## Suggestions for a good README
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
## Name
Choose a self-explaining name for your project.
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.
## Contributing
State if you are open to contributions and what your requirements are for accepting them.
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.
## License
For open source projects, say how it is licensed.
## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
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"
"gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/errors"
"gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/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 "gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/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("ursa")
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/abiyan89/ulfsaar-base-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/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
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/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
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/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/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/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/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/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/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/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/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
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/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/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/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
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/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
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/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/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/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/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
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/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/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/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/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/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/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/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/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/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
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=
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/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)
SummaryInfo(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 (l *impl) SummaryInfo(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"
"gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/context"
"gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/crypto"
"gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/logger"
"gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/session"
"strings"
)
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"
"gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/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"
"gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/logger"
pkgLogger "gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/pkg/logger"
"time"
)
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.SummaryInfo(model)
//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"
"gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/errors"
"gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/pkg/context"
"gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/pkg/logger"
"gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/session"
"gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/utilities"
"net/http"
)
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) SuccessWithBodyMasking(parameterBody []string, data any) error {
hc := http.StatusOK
if data == nil {
data = struct{}{}
}
maskDataBody := utilities.ConvertToFieldMask(parameterBody, data)
res := Success{
Code: "00",
Message: "success",
Data: maskDataBody,
}
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) SuccessWithAllMasking(parameterBody, parameterHeader []string, data any) error {
hc := http.StatusOK
if data == nil {
data = struct{}{}
}
maskDataBody := utilities.ConvertToFieldMask(parameterBody, data)
utilities.CheckFieldMaskHeader(parameterHeader, sc.UlfsaarContext)
res := Success{
Code: "00",
Message: "success",
Data: maskDataBody,
}
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) SuccessWithHeaderMasking(parameterHeader []string, data interface{}) error {
hc := http.StatusOK
if data == nil {
data = struct{}{}
}
utilities.CheckFieldMaskHeader(parameterHeader, sc.UlfsaarContext)
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) AddUlfsaarContext(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"
"gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/logger"
ucontext "gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/pkg/context"
uecho "gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/pkg/echo"
pkgLogger "gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/pkg/logger"
"gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/utilities"
"io/ioutil"
"strings"
)
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"
"gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/pkg/context"
"gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/pkg/logger"
"net/http"
"time"
)
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"
"gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/pkg/logger"
"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 logger.ConvertLogTime(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"
"gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/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)
}
func (c *fileLogger) SummaryInfo(tdr logger.LogSummary) {
c.defaultLogger.SummaryInfo(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"
"gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/logger"
"go.uber.org/zap"
"time"
)
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)
ctxLogTime := ConvertLogTime(logTime.Format("2006-01-02 15:04:05.000"))
logRecord = append(logRecord, zap.String(logTimeKey, ctxLogTime))
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 ConvertLogTime(date string) string {
t, err := time.Parse("2006-01-02 15:04:05.000", date)
if err != nil {
fmt.Println("Error parsing timestamp:", err)
return ""
}
// Format the timestamp in the desired format
return t.Format("2006-01-02T15:04:05.000Z")
}
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"
"gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/logger"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"io"
"os"
"time"
)
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")
infoWithSummaryLevel = logLevel("InfoSummary")
)
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(ConvertLogTime(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) SummaryInfo(summary logger.LogSummary) {
d.loggerChannel <- logItem{level: infoWithSummaryLevel, 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:
convertLogTime := ConvertLogTime(logTime.Format("2006-01-02 15:04:05.000"))
zapLogs := []zap.Field{
zap.String(levelKey, "summary"),
zap.String(logTimeKey, convertLogTime),
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...)
case infoWithSummaryLevel:
convertLogTime := ConvertLogTime(logTime.Format("2006-01-02 15:04:05.000"))
zapLogs := []zap.Field{
zap.String(levelKey, "info"),
zap.String(logTimeKey, convertLogTime),
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"
"gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/crypto"
"gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/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"
"gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/crypto"
"gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/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 (
"encoding/json"
"gitlab.ursabyte.com/abiyan89/ulfsaar-base-go/pkg/context"
"net/http"
"reflect"
"slices"
)
type Masking struct {
ParameterData []string `json:"parameter_data"`
ParameterHeader []string `json:"parameter_header"`
}
func ConvertToFieldMask(config []string, s any) interface{} {
valueOf := reflect.ValueOf(s)
if valueOf.Kind() == reflect.Ptr {
valueOf = valueOf.Elem()
}
if valueOf.Kind() == reflect.Slice || valueOf.Kind() == reflect.Array {
// array
return handlingArray(config, s, valueOf)
} else {
if valueOf.Kind() == reflect.String {
return "***"
} else {
myMap := make(map[string]interface{})
byteArr, err := json.Marshal(s)
if err == nil {
err = json.Unmarshal(byteArr, &myMap)
if err == nil {
maskingItems(config, myMap)
return myMap
}
}
return s
}
}
}
func handlingArray(config []string, originalValue any, valueOf reflect.Value) any {
_, ok := valueOf.Interface().([]string)
if !ok {
items := convertAnyToArrayOfInterface(valueOf.Interface())
for _, data := range items {
maskingItems(config, data)
}
if items == nil {
return originalValue
}
return items
} else {
// Masking For Array String
//var items []string
//for i := 0; i < valueOf.Len(); i++ {
// items = append(items, "***")
//}
return originalValue
}
}
func handlingObject(config []string, originalValue any, valueOf reflect.Value) any {
if valueOf.Kind() == reflect.String {
return "***"
} else {
myMap := make(map[string]interface{})
byteArr, err := json.Marshal(originalValue)
if err == nil {
err = json.Unmarshal(byteArr, &myMap)
if err == nil {
maskingItems(config, myMap)
return myMap
}
}
return originalValue
}
}
func checkFieldMask(config []string, s interface{}) interface{} {
valueOf := reflect.ValueOf(s)
if valueOf.Kind() == reflect.Ptr {
valueOf = valueOf.Elem()
}
if valueOf.Kind() != reflect.Struct && valueOf.Kind() != reflect.Map {
return nil
}
if valueOf.CanSet() {
for _, data := range config {
fieldMask := valueOf.FieldByName(data)
if fieldMask.IsValid() && fieldMask.Kind() == reflect.String {
fieldMask.SetString("***")
}
}
}
return valueOf.Interface()
}
func CheckFieldMaskHeader(config []string, ctx *context.UlfsaarContext) {
for _, data := range config {
ctx.Header.(http.Header).Set(data, "***")
}
}
func maskingItems(config []string, item map[string]interface{}) {
for key, _ := range item {
if reflect.Struct == reflect.TypeOf(item[key]).Kind() || reflect.Map == reflect.TypeOf(item[key]).Kind() {
maskingItems(config, item[key].(map[string]interface{}))
} else if reflect.Array == reflect.TypeOf(item[key]).Kind() || reflect.Slice == reflect.TypeOf(item[key]).Kind() {
items := convertAnyToArrayOfInterface(item[key])
if items != nil {
item[key] = items
for _, data := range item[key].([]map[string]interface{}) {
maskingItems(config, data)
}
}
} else {
exist := slices.Contains(config, key)
if exist {
item[key] = "***"
}
}
}
}
func convertAnyToArrayOfInterface(a any) []map[string]interface{} {
var res []map[string]interface{}
value := unPackArray(a)
for i := 0; i < len(value); i++ {
item := value[i]
t := reflect.TypeOf(item).Kind()
if t == reflect.Struct || t == reflect.Map {
var inInterface map[string]interface{}
inRec, _ := json.Marshal(item)
err := json.Unmarshal(inRec, &inInterface)
if err != nil {
continue
}
res = append(res, inInterface)
}
}
return res
}
func unPackArray(a any) []any {
v := reflect.ValueOf(a)
data := make([]any, v.Len())
for i := 0; i < v.Len(); i++ {
data[i] = v.Index(i).Interface()
}
return data
}
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