├── .gitignore ├── internal ├── core │ ├── ports │ │ ├── driver │ │ │ └── user_web.go │ │ └── driven │ │ │ └── user_db.go │ ├── application │ │ ├── dto │ │ │ └── user_dto.go │ │ └── user_service.go │ └── domain │ │ └── user.go ├── adapters │ ├── driven │ │ ├── repository │ │ │ └── user_repository.go │ │ └── user_adapter.go │ └── driver │ │ └── user_handler.go └── platform │ ├── storage │ └── mysql │ │ ├── user.go │ │ └── user_repository.go │ └── server │ └── server.go ├── cmd ├── main.go └── bootstrap │ └── bootstrap.go ├── docker-compose.yml ├── pkg ├── uidgen │ └── uidgen.go ├── encryption │ └── bcrypt.go ├── helpers │ └── utils.go └── config │ └── config.go ├── go.mod └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | db 2 | .env -------------------------------------------------------------------------------- /internal/core/ports/driver/user_web.go: -------------------------------------------------------------------------------- 1 | package driverport 2 | 3 | type UserAPI interface { 4 | SignInHandler() error 5 | } 6 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/dany0814/go-hexagonal/cmd/bootstrap" 7 | ) 8 | 9 | func main() { 10 | if err := bootstrap.Run(); err != nil { 11 | log.Fatal(err) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /internal/core/ports/driven/user_db.go: -------------------------------------------------------------------------------- 1 | package drivenport 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/dany0814/go-hexagonal/internal/core/domain" 7 | ) 8 | 9 | type UserDB interface { 10 | Create(ctx context.Context, user domain.User) error 11 | } 12 | -------------------------------------------------------------------------------- /internal/adapters/driven/repository/user_repository.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/dany0814/go-hexagonal/internal/core/domain" 7 | ) 8 | 9 | type UserRepository interface { 10 | Save(ctx context.Context, sqluser domain.User) error 11 | } 12 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | mysql: 4 | image: mysql:8.0 5 | restart: always 6 | environment: 7 | - MYSQL_ROOT_PASSWORD=enviadoc 8 | - MYSQL_USER=enviadoc 9 | - MYSQL_PASSWORD=enviadoc 10 | - MYSQL_DATABASE=enviadoc 11 | ports: 12 | - '3306:3306' 13 | volumes: 14 | - db:/var/lib/mysql 15 | - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql 16 | volumes: 17 | db: 18 | driver: local -------------------------------------------------------------------------------- /pkg/uidgen/uidgen.go: -------------------------------------------------------------------------------- 1 | package uidgen 2 | 3 | import "github.com/google/uuid" 4 | 5 | type UIDGen interface { 6 | New() string 7 | } 8 | 9 | type uidgen struct{} 10 | 11 | func New() UIDGen { 12 | return &uidgen{} 13 | } 14 | 15 | func (u uidgen) New() string { 16 | return uuid.New().String() 17 | } 18 | 19 | func Parse(value string) (string, error) { 20 | v, err := uuid.Parse(value) 21 | if err != nil { 22 | return "", err 23 | } 24 | return v.String(), nil 25 | } 26 | -------------------------------------------------------------------------------- /internal/core/application/dto/user_dto.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | import "time" 4 | 5 | type User struct { 6 | ID string `json:"id"` 7 | Name string `json:"name"` 8 | Lastname string `json:"lastname"` 9 | Email string `json:"email"` 10 | Password string `json:"password"` 11 | CreatedAt time.Time `json:"created_at,omitempty"` 12 | UpdatedAt time.Time `json:"updated_at,omitempty"` 13 | DeletedAt *time.Time `json:"deleted_at,omitempty"` 14 | } 15 | -------------------------------------------------------------------------------- /pkg/encryption/bcrypt.go: -------------------------------------------------------------------------------- 1 | package encryption 2 | 3 | import ( 4 | "golang.org/x/crypto/bcrypt" 5 | ) 6 | 7 | func ComparePasswords(hashed, plain string) bool { 8 | err := bcrypt.CompareHashAndPassword([]byte(hashed), []byte(plain)) 9 | return err == nil 10 | } 11 | 12 | func HashAndSalt(password string) (string, error) { 13 | hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.MinCost) 14 | if err != nil { 15 | return "", err 16 | } 17 | return string(hash), nil 18 | } 19 | -------------------------------------------------------------------------------- /internal/platform/storage/mysql/user.go: -------------------------------------------------------------------------------- 1 | package mysqldb 2 | 3 | import "time" 4 | 5 | const ( 6 | sqlUserTable = "users" 7 | ) 8 | 9 | type SqlUser struct { 10 | ID string `db:"user_id"` 11 | Name string `db:"name"` 12 | Lastname string `db:"lastname"` 13 | Email string `db:"email"` 14 | Password string `db:"password"` 15 | CreatedAt time.Time `db:"created_at"` 16 | UpdatedAt time.Time `db:"updated_at"` 17 | DeletedAt *time.Time `db:"deleted_at"` 18 | } 19 | -------------------------------------------------------------------------------- /pkg/helpers/utils.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | // Message api 4 | func Message(code int, message string) map[string]interface{} { 5 | return map[string]interface{}{"code": code, "message": message} 6 | } 7 | 8 | // Message api error 9 | func MessageError(code int, err error) map[string]interface{} { 10 | return map[string]interface{}{"code": code, "message": err.Error()} 11 | } 12 | 13 | // DataResponse api 14 | func DataResponse(code int, message string, data interface{}) map[string]interface{} { 15 | return map[string]interface{}{"code": code, "message": message, "data": data} 16 | } 17 | -------------------------------------------------------------------------------- /internal/adapters/driven/user_adapter.go: -------------------------------------------------------------------------------- 1 | package drivenadapt 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/dany0814/go-hexagonal/internal/adapters/driven/repository" 7 | "github.com/dany0814/go-hexagonal/internal/core/domain" 8 | ) 9 | 10 | type UserAdapter struct { 11 | userRepository repository.UserRepository 12 | } 13 | 14 | func NewUserAdapter(usrep repository.UserRepository) UserAdapter { 15 | return UserAdapter{ 16 | userRepository: usrep, 17 | } 18 | } 19 | 20 | func (uadpt UserAdapter) Create(ctx context.Context, user domain.User) error { 21 | return uadpt.userRepository.Save(ctx, user) 22 | } 23 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dany0814/go-hexagonal 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/go-sql-driver/mysql v1.7.0 7 | github.com/google/uuid v1.3.0 8 | github.com/huandu/go-sqlbuilder v1.20.0 9 | github.com/kelseyhightower/envconfig v1.4.0 10 | github.com/labstack/echo/v4 v4.10.2 11 | golang.org/x/crypto v0.6.0 12 | ) 13 | 14 | require ( 15 | github.com/huandu/xstrings v1.3.2 // indirect 16 | github.com/labstack/gommon v0.4.0 // indirect 17 | github.com/mattn/go-colorable v0.1.13 // indirect 18 | github.com/mattn/go-isatty v0.0.17 // indirect 19 | github.com/valyala/bytebufferpool v1.0.0 // indirect 20 | github.com/valyala/fasttemplate v1.2.2 // indirect 21 | golang.org/x/net v0.7.0 // indirect 22 | golang.org/x/sys v0.5.0 // indirect 23 | golang.org/x/text v0.7.0 // indirect 24 | ) 25 | -------------------------------------------------------------------------------- /cmd/bootstrap/bootstrap.go: -------------------------------------------------------------------------------- 1 | package bootstrap 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | 8 | database "github.com/dany0814/go-hexagonal/internal/adapters/driven" 9 | "github.com/dany0814/go-hexagonal/internal/core/application" 10 | "github.com/dany0814/go-hexagonal/internal/platform/server" 11 | mysqldb "github.com/dany0814/go-hexagonal/internal/platform/storage/mysql" 12 | "github.com/dany0814/go-hexagonal/pkg/config" 13 | ) 14 | 15 | func Run() error { 16 | 17 | err := config.LoadConfig() 18 | if err != nil { 19 | return err 20 | } 21 | fmt.Println("Web server ready!") 22 | 23 | ctx := context.Background() 24 | db, err := config.ConfigDb(ctx) 25 | 26 | if err != nil { 27 | log.Fatalf("Database configuration failed: %v", err) 28 | } 29 | 30 | userRepository := mysqldb.NewUserRepository(db, config.Cfg.DbTimeout) 31 | userAdapter := database.NewUserAdapter(userRepository) 32 | userService := application.NewUserService(userAdapter) 33 | 34 | ctx, srv := server.NewServer(context.Background(), config.Cfg.Host, config.Cfg.Port, config.Cfg.ShutdownTimeout, server.AppService{ 35 | UserService: userService, 36 | }) 37 | 38 | return srv.Run(ctx) 39 | } 40 | -------------------------------------------------------------------------------- /internal/adapters/driver/user_handler.go: -------------------------------------------------------------------------------- 1 | package driveradapt 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | 7 | "github.com/dany0814/go-hexagonal/internal/core/application" 8 | "github.com/dany0814/go-hexagonal/internal/core/application/dto" 9 | "github.com/dany0814/go-hexagonal/internal/core/domain" 10 | "github.com/dany0814/go-hexagonal/pkg/helpers" 11 | "github.com/labstack/echo/v4" 12 | _ "github.com/labstack/echo/v4" 13 | ) 14 | 15 | type UserHandler struct { 16 | userService application.UserService 17 | Ctx echo.Context 18 | } 19 | 20 | func NewUserHandler(usrv application.UserService) UserHandler { 21 | return UserHandler{ 22 | userService: usrv, 23 | } 24 | } 25 | 26 | func (usrh UserHandler) SignInHandler() error { 27 | ctx := usrh.Ctx 28 | var req dto.User 29 | if err := ctx.Bind(&req); err != nil { 30 | ctx.JSON(http.StatusBadRequest, err.Error()) 31 | return nil 32 | } 33 | res, err := usrh.userService.Register(ctx.Request().Context(), req) 34 | 35 | if err != nil { 36 | switch { 37 | case errors.Is(err, domain.ErrUserConflict): 38 | ctx.JSON(http.StatusConflict, err.Error()) 39 | return nil 40 | default: 41 | ctx.JSON(http.StatusInternalServerError, err.Error()) 42 | return nil 43 | } 44 | } 45 | ctx.JSON(http.StatusCreated, helpers.DataResponse(0, "User created", res)) 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /internal/core/application/user_service.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/dany0814/go-hexagonal/internal/core/application/dto" 8 | "github.com/dany0814/go-hexagonal/internal/core/domain" 9 | outdb "github.com/dany0814/go-hexagonal/internal/core/ports/driven" 10 | "github.com/dany0814/go-hexagonal/pkg/encryption" 11 | "github.com/dany0814/go-hexagonal/pkg/uidgen" 12 | ) 13 | 14 | type UserService struct { 15 | userDB outdb.UserDB 16 | } 17 | 18 | func NewUserService(userDB outdb.UserDB) UserService { 19 | return UserService{ 20 | userDB: userDB, 21 | } 22 | } 23 | 24 | func (usrv UserService) Register(ctx context.Context, user dto.User) (*dto.User, error) { 25 | id := uidgen.New().New() 26 | 27 | newuser, err := domain.NewUser(id, user.Name, user.Lastname, user.Email, user.Password) 28 | 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | pass, err := encryption.HashAndSalt(user.Password) 34 | 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | passencrypted, _ := domain.NewUserPassword(pass) 40 | 41 | newuser.Password = passencrypted 42 | newuser.CreatedAt = time.Now() 43 | newuser.UpdatedAt = time.Now() 44 | 45 | err = usrv.userDB.Create(ctx, newuser) 46 | 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | user.ID = id 52 | return &user, nil 53 | } 54 | -------------------------------------------------------------------------------- /internal/platform/storage/mysql/user_repository.go: -------------------------------------------------------------------------------- 1 | package mysqldb 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/dany0814/go-hexagonal/internal/core/domain" 10 | "github.com/huandu/go-sqlbuilder" 11 | ) 12 | 13 | type UserRepository struct { 14 | db *sql.DB 15 | dbTimeout time.Duration 16 | } 17 | 18 | // NewUserRepository initializes a MySQL-based implementation of UserRepository. 19 | func NewUserRepository(db *sql.DB, dbTimeout time.Duration) *UserRepository { 20 | return &UserRepository{ 21 | db: db, 22 | dbTimeout: dbTimeout, 23 | } 24 | } 25 | 26 | // Save implements the adapter userRepository interface. 27 | func (r *UserRepository) Save(ctx context.Context, user domain.User) error { 28 | userSQLStruct := sqlbuilder.NewStruct(new(SqlUser)) 29 | query, args := userSQLStruct.InsertInto(sqlUserTable, SqlUser{ 30 | ID: user.ID.String(), 31 | Name: user.Name, 32 | Lastname: user.Lastname, 33 | Email: user.Email.String(), 34 | Password: user.Password.String(), 35 | CreatedAt: user.CreatedAt, 36 | UpdatedAt: user.UpdatedAt, 37 | DeletedAt: user.DeletedAt, 38 | }).Build() 39 | 40 | _, err := r.db.ExecContext(ctx, query, args...) 41 | if err != nil { 42 | return fmt.Errorf("Error trying to persist course on database: %v", err) 43 | } 44 | 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /pkg/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "fmt" 7 | "time" 8 | 9 | _ "github.com/go-sql-driver/mysql" 10 | "github.com/kelseyhightower/envconfig" 11 | ) 12 | 13 | type config struct { 14 | // Database config 15 | DbUser string `default:"enviadoc"` 16 | DbPass string `default:"enviadoc"` 17 | DbHost string `default:"0.0.0.0"` 18 | DbPort string `default:"3306"` 19 | DbName string `default:"enviadoc"` 20 | DbTimeout time.Duration `default:"10s"` 21 | // Server config 22 | Host string `default:"0.0.0.0"` 23 | Port uint `default:"8080"` 24 | ShutdownTimeout time.Duration `default:"20s"` 25 | // Security token 26 | // Secret string `env:"TK_SECRET,required"` 27 | // ExpiredHour int64 `env:"TK_EXPIREDya HOUR,required"` 28 | } 29 | 30 | var Cfg config 31 | 32 | func LoadConfig() error { 33 | err := envconfig.Process("IRIS", &Cfg) 34 | if err != nil { 35 | return err 36 | } 37 | return nil 38 | } 39 | 40 | func ConfigDb(ctx context.Context) (*sql.DB, error) { 41 | mysqlURI := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true", Cfg.DbUser, Cfg.DbPass, Cfg.DbHost, Cfg.DbPort, Cfg.DbName) 42 | fmt.Println("uri: ", mysqlURI) 43 | db, err := sql.Open("mysql", mysqlURI) 44 | if err != nil { 45 | fmt.Println("Failed database connection") 46 | panic(err) 47 | } 48 | 49 | fmt.Println("Successfully Connected to MySQL database") 50 | 51 | db.SetConnMaxLifetime(time.Minute * 4) 52 | db.SetMaxOpenConns(10) 53 | db.SetMaxIdleConns(10) 54 | 55 | err = db.Ping() 56 | if err != nil { 57 | return nil, err 58 | } 59 | return db, nil 60 | } 61 | -------------------------------------------------------------------------------- /internal/platform/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "os" 9 | "os/signal" 10 | "time" 11 | 12 | web "github.com/dany0814/go-hexagonal/internal/adapters/driver" 13 | "github.com/dany0814/go-hexagonal/internal/core/application" 14 | "github.com/labstack/echo/v4" 15 | ) 16 | 17 | type AppService struct { 18 | UserService application.UserService 19 | } 20 | 21 | type Server struct { 22 | engine *echo.Echo 23 | httpAddr string 24 | ShutdownTimeout time.Duration 25 | app AppService 26 | } 27 | 28 | func NewServer(ctx context.Context, host string, port uint, shutdownTimeout time.Duration, app AppService) (context.Context, Server) { 29 | srv := Server{ 30 | engine: echo.New(), 31 | httpAddr: fmt.Sprintf("%s:%d", host, port), 32 | ShutdownTimeout: shutdownTimeout, 33 | app: app, 34 | } 35 | srv.registerRoutes() 36 | return serverContext(ctx), srv 37 | } 38 | 39 | func (s *Server) Run(ctx context.Context) error { 40 | log.Println("Server running on", s.httpAddr) 41 | srv := &http.Server{ 42 | Addr: s.httpAddr, 43 | Handler: s.engine, 44 | } 45 | 46 | go func() { 47 | if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { 48 | log.Fatal("server shut down", err) 49 | } 50 | }() 51 | 52 | <-ctx.Done() 53 | ctxShutDown, cancel := context.WithTimeout(context.Background(), s.ShutdownTimeout) 54 | defer cancel() 55 | 56 | return srv.Shutdown(ctxShutDown) 57 | } 58 | 59 | func serverContext(ctx context.Context) context.Context { 60 | c := make(chan os.Signal, 1) 61 | signal.Notify(c, os.Interrupt) 62 | ctx, cancel := context.WithCancel(ctx) 63 | go func() { 64 | <-c 65 | cancel() 66 | }() 67 | 68 | return ctx 69 | } 70 | 71 | func (s *Server) registerRoutes() { 72 | // User Routes 73 | uh := web.NewUserHandler(s.app.UserService) 74 | s.engine.POST("/user/sigin", func(c echo.Context) error { 75 | uh.Ctx = c 76 | return uh.SignInHandler() 77 | }) 78 | } 79 | -------------------------------------------------------------------------------- /internal/core/domain/user.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/mail" 7 | "time" 8 | 9 | "github.com/dany0814/go-hexagonal/pkg/uidgen" 10 | ) 11 | 12 | var ErrUserConflict = errors.New("user already exists") 13 | var ErrInvalidUserID = errors.New("invalid User ID") 14 | var ErrInvalidUserEmail = errors.New("invalid Email") 15 | var ErrInvalidUserPassword = errors.New("invalid Password") 16 | var ErrEmptyName = errors.New("the field name is required") 17 | 18 | // NewUserID function to instantiate the initial value for UserID 19 | 20 | type UserID struct { 21 | value string 22 | } 23 | 24 | func NewUserID(value string) (UserID, error) { 25 | v, err := uidgen.Parse(value) 26 | if err != nil { 27 | return UserID{}, fmt.Errorf("%w: %s", ErrInvalidUserID, value) 28 | } 29 | return UserID{ 30 | value: v, 31 | }, nil 32 | } 33 | 34 | func (id UserID) String() string { 35 | return id.value 36 | } 37 | 38 | // NewUserEmail function to instantiate the initial value for UserEmail 39 | 40 | type UserEmail struct { 41 | value string 42 | } 43 | 44 | func NewUserEmail(value string) (UserEmail, error) { 45 | _, err := mail.ParseAddress(value) 46 | if err != nil { 47 | return UserEmail{}, fmt.Errorf("%w: %s", ErrInvalidUserEmail, value) 48 | } 49 | return UserEmail{ 50 | value: value, 51 | }, nil 52 | } 53 | 54 | func (email UserEmail) String() string { 55 | return email.value 56 | } 57 | 58 | // NewUserPassword function to instantiate the initial value for UserPassword 59 | 60 | type UserPassword struct { 61 | value string 62 | } 63 | 64 | func NewUserPassword(value string) (UserPassword, error) { 65 | if value == "" { 66 | return UserPassword{}, fmt.Errorf("%w: %s", ErrInvalidUserPassword, value) 67 | } 68 | return UserPassword{ 69 | value: value, 70 | }, nil 71 | } 72 | 73 | func (pass UserPassword) String() string { 74 | return pass.value 75 | } 76 | 77 | // NewUserUsername function to instantiate the initial value for UserUsername 78 | 79 | // NewUser function to instantiate the initial value for User 80 | 81 | type User struct { 82 | ID UserID 83 | Name string 84 | Lastname string 85 | Email UserEmail 86 | Password UserPassword 87 | CreatedAt time.Time 88 | UpdatedAt time.Time 89 | DeletedAt *time.Time 90 | } 91 | 92 | func NewUser(userID, name, lastname, email, password string) (User, error) { 93 | idVo, err := NewUserID(userID) 94 | if err != nil { 95 | return User{}, err 96 | } 97 | 98 | if name == "" { 99 | return User{}, fmt.Errorf("%w: %s", ErrEmptyName, name) 100 | } 101 | 102 | emailVo, err := NewUserEmail(email) 103 | if err != nil { 104 | return User{}, err 105 | } 106 | 107 | passwordVo, err := NewUserPassword(password) 108 | if err != nil { 109 | return User{}, err 110 | } 111 | 112 | return User{ 113 | ID: idVo, 114 | Name: name, 115 | Lastname: lastname, 116 | Email: emailVo, 117 | Password: passwordVo, 118 | }, nil 119 | } 120 | 121 | func (u User) UserID() UserID { 122 | return u.UserID() 123 | } 124 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= 5 | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= 6 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 7 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 8 | github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c= 9 | github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U= 10 | github.com/huandu/go-sqlbuilder v1.20.0 h1:q/XSlHhRT/eIrasrYQEbe2VJvGoou3WMGShEjNkGUS8= 11 | github.com/huandu/go-sqlbuilder v1.20.0/go.mod h1:nUVmMitjOmn/zacMLXT0d3Yd3RHoO2K+vy906JzqxMI= 12 | github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= 13 | github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 14 | github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= 15 | github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= 16 | github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M= 17 | github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k= 18 | github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= 19 | github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= 20 | github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 21 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 22 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 23 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 24 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 25 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= 26 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 27 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 28 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 29 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 30 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 31 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 32 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 33 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 34 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 35 | github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 36 | github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= 37 | github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 38 | golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= 39 | golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= 40 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= 41 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 42 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 43 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 44 | golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 45 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 46 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= 47 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 48 | golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= 49 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 50 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 51 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 52 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 53 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 54 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 55 | --------------------------------------------------------------------------------