├── jwt.png ├── internal ├── models │ ├── user_role.go │ ├── user.go │ └── product.go ├── entity │ ├── user_role.go │ ├── user.go │ └── product.go ├── api │ ├── dtos │ │ ├── login_user.go │ │ ├── add_product.go │ │ └── register_user.go │ ├── routes.go │ ├── api.go │ └── handlers.go ├── service │ ├── service.go │ ├── products.service_test.go │ ├── service_test.go │ ├── products.service.go │ ├── users.service.go │ ├── users.service_test.go │ └── mock_Service.go └── repository │ ├── repository.go │ ├── products.repository.go │ ├── users.repository.go │ └── mock_Repository.go ├── settings ├── settings.yaml └── settings.go ├── .vscode └── launch.json ├── database └── database.go ├── encryption ├── jwt.go └── encryption.go ├── main.go ├── schema.sql ├── index.html ├── go.mod └── go.sum /jwt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/disturb16/max-inventory/HEAD/jwt.png -------------------------------------------------------------------------------- /internal/models/user_role.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type UserRole struct { 4 | UserID int64 `json:"user_id"` 5 | RoleID int64 `json:"role_id"` 6 | } 7 | -------------------------------------------------------------------------------- /settings/settings.yaml: -------------------------------------------------------------------------------- 1 | port: 8080 2 | database: 3 | host: localhost 4 | port: 3306 5 | user: root 6 | password: rootroot 7 | name: max_inventory 8 | -------------------------------------------------------------------------------- /internal/models/user.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type User struct { 4 | ID int64 `json:"id"` 5 | Email string `json:"email"` 6 | Name string `json:"name"` 7 | } 8 | -------------------------------------------------------------------------------- /internal/entity/user_role.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | type UserRole struct { 4 | ID int64 `db:"id"` 5 | UserID int64 `db:"user_id"` 6 | RoleID int64 `db:"role_id"` 7 | } 8 | -------------------------------------------------------------------------------- /internal/api/dtos/login_user.go: -------------------------------------------------------------------------------- 1 | package dtos 2 | 3 | type LoginUser struct { 4 | Email string `json:"email" validate:"required,email"` 5 | Password string `json:"password" validate:"required"` 6 | } 7 | -------------------------------------------------------------------------------- /internal/entity/user.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | type User struct { 4 | ID int64 `db:"id"` 5 | Email string `db:"email"` 6 | Name string `db:"name"` 7 | Password string `db:"password"` 8 | } 9 | -------------------------------------------------------------------------------- /internal/models/product.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type Product struct { 4 | ID int64 `json:"id"` 5 | Name string `json:"name"` 6 | Description string `json:"description"` 7 | Price float32 `json:"price"` 8 | } 9 | -------------------------------------------------------------------------------- /internal/api/dtos/add_product.go: -------------------------------------------------------------------------------- 1 | package dtos 2 | 3 | type AddProduct struct { 4 | Name string `json:"name" validate:"required"` 5 | Description string `json:"description"` 6 | Price float32 `json:"price" validate:"required"` 7 | } 8 | -------------------------------------------------------------------------------- /internal/api/dtos/register_user.go: -------------------------------------------------------------------------------- 1 | package dtos 2 | 3 | type RegisterUser struct { 4 | Email string `json:"email" validate:"required,email"` 5 | Password string `json:"password" validate:"required,min=8"` 6 | Name string `json:"name" validate:"required"` 7 | } 8 | -------------------------------------------------------------------------------- /internal/entity/product.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | type Product struct { 4 | ID int64 `db:"id"` 5 | Name string `db:"name"` 6 | Description string `db:"description"` 7 | Price float32 `db:"price"` 8 | CreatedBy int64 `db:"created_by"` 9 | } 10 | -------------------------------------------------------------------------------- /internal/api/routes.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import "github.com/labstack/echo/v4" 4 | 5 | func (a *API) RegisterRoutes(e *echo.Echo) { 6 | 7 | users := e.Group("/users") 8 | products := e.Group("/products") 9 | 10 | users.POST("/register", a.RegisterUser) 11 | users.POST("/login", a.LoginUser) 12 | 13 | products.POST("", a.AddProduct) 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch Package", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "auto", 12 | "program": "${workspaceFolder}" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /database/database.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/disturb/max-inventory/settings" 8 | _ "github.com/go-sql-driver/mysql" 9 | "github.com/jmoiron/sqlx" 10 | ) 11 | 12 | func New(ctx context.Context, s *settings.Settings) (*sqlx.DB, error) { 13 | connectionString := fmt.Sprintf( 14 | "%s:%s@tcp(%s:%d)/%s?parseTime=true", 15 | s.DB.User, 16 | s.DB.Password, 17 | s.DB.Host, 18 | s.DB.Port, 19 | s.DB.Name, 20 | ) 21 | 22 | return sqlx.ConnectContext(ctx, "mysql", connectionString) 23 | } 24 | -------------------------------------------------------------------------------- /encryption/jwt.go: -------------------------------------------------------------------------------- 1 | package encryption 2 | 3 | import ( 4 | "github.com/disturb/max-inventory/internal/models" 5 | "github.com/golang-jwt/jwt/v4" 6 | ) 7 | 8 | func SignedLoginToken(u *models.User) (string, error) { 9 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ 10 | "email": u.Email, 11 | "name": u.Name, 12 | }) 13 | 14 | return token.SignedString([]byte(key)) 15 | } 16 | 17 | func ParseLoginJWT(value string) (jwt.MapClaims, error) { 18 | token, err := jwt.Parse(value, func(token *jwt.Token) (interface{}, error) { 19 | return []byte(key), nil 20 | }) 21 | 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | return token.Claims.(jwt.MapClaims), nil 27 | } 28 | -------------------------------------------------------------------------------- /settings/settings.go: -------------------------------------------------------------------------------- 1 | package settings 2 | 3 | import ( 4 | _ "embed" 5 | 6 | "gopkg.in/yaml.v3" 7 | ) 8 | 9 | //go:embed settings.yaml 10 | var settingsFile []byte 11 | 12 | type DatabaseConfig struct { 13 | Host string `yaml:"host"` 14 | Port int `yaml:"port"` 15 | User string `yaml:"user"` 16 | Password string `yaml:"password"` 17 | Name string `yaml:"name"` 18 | } 19 | 20 | type Settings struct { 21 | Port string `yaml:"port"` 22 | DB DatabaseConfig `yaml:"database"` 23 | } 24 | 25 | func New() (*Settings, error) { 26 | var s Settings 27 | 28 | err := yaml.Unmarshal(settingsFile, &s) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | return &s, nil 34 | } 35 | -------------------------------------------------------------------------------- /internal/api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/disturb/max-inventory/internal/service" 5 | "github.com/go-playground/validator/v10" 6 | "github.com/labstack/echo/v4" 7 | "github.com/labstack/echo/v4/middleware" 8 | ) 9 | 10 | type API struct { 11 | serv service.Service 12 | dataValidator *validator.Validate 13 | } 14 | 15 | func New(serv service.Service) *API { 16 | return &API{ 17 | serv: serv, 18 | dataValidator: validator.New(), 19 | } 20 | } 21 | 22 | func (a *API) Start(e *echo.Echo, address string) error { 23 | a.RegisterRoutes(e) 24 | 25 | e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ 26 | AllowOrigins: []string{"http://127.0.0.1:5500"}, 27 | AllowMethods: []string{echo.POST}, 28 | AllowHeaders: []string{echo.HeaderContentType}, 29 | AllowCredentials: true, 30 | })) 31 | 32 | return e.Start(address) 33 | } 34 | -------------------------------------------------------------------------------- /internal/service/service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/disturb/max-inventory/internal/models" 7 | "github.com/disturb/max-inventory/internal/repository" 8 | ) 9 | 10 | // Service is the business logic of the application. 11 | // 12 | //go:generate mockery --name=Service --output=service --inpackage 13 | type Service interface { 14 | RegisterUser(ctx context.Context, email, name, password string) error 15 | LoginUser(ctx context.Context, email, password string) (*models.User, error) 16 | AddUserRole(ctx context.Context, userID, roleID int64) error 17 | RemoveUserRole(ctx context.Context, userID, roleID int64) error 18 | GetProducts(ctx context.Context) ([]models.Product, error) 19 | GetProduct(ctx context.Context, id int64) (*models.Product, error) 20 | AddProdcut(ctx context.Context, product models.Product, userEmail string) error 21 | } 22 | 23 | type serv struct { 24 | repo repository.Repository 25 | } 26 | 27 | func New(repo repository.Repository) Service { 28 | return &serv{ 29 | repo: repo, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/disturb/max-inventory/database" 8 | "github.com/disturb/max-inventory/internal/api" 9 | "github.com/disturb/max-inventory/internal/repository" 10 | "github.com/disturb/max-inventory/internal/service" 11 | "github.com/disturb/max-inventory/settings" 12 | "github.com/labstack/echo/v4" 13 | "go.uber.org/fx" 14 | ) 15 | 16 | func main() { 17 | app := fx.New( 18 | fx.Provide( 19 | context.Background, 20 | settings.New, 21 | database.New, 22 | repository.New, 23 | service.New, 24 | api.New, 25 | echo.New, 26 | ), 27 | 28 | fx.Invoke( 29 | setLifeCycle, 30 | ), 31 | ) 32 | 33 | app.Run() 34 | } 35 | 36 | func setLifeCycle(lc fx.Lifecycle, a *api.API, s *settings.Settings, e *echo.Echo) { 37 | lc.Append(fx.Hook{ 38 | OnStart: func(ctx context.Context) error { 39 | address := fmt.Sprintf(":%s", s.Port) 40 | go a.Start(e, address) 41 | 42 | return nil 43 | }, 44 | OnStop: func(ctx context.Context) error { 45 | return nil 46 | }, 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /internal/repository/repository.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/disturb/max-inventory/internal/entity" 7 | "github.com/jmoiron/sqlx" 8 | ) 9 | 10 | // Repository is the interface that wraps the basic CRUD operations. 11 | // 12 | //go:generate mockery --name=Repository --output=repository --inpackage 13 | type Repository interface { 14 | SaveUser(ctx context.Context, email, name, password string) error 15 | GetUserByEmail(ctx context.Context, email string) (*entity.User, error) 16 | 17 | SaveUserRole(ctx context.Context, userID, roleID int64) error 18 | RemoveUserRole(ctx context.Context, userID, roleID int64) error 19 | GetUserRoles(ctx context.Context, userID int64) ([]entity.UserRole, error) 20 | 21 | SaveProduct(ctx context.Context, name, description string, price float32, createdBy int64) error 22 | GetProducts(ctx context.Context) ([]entity.Product, error) 23 | GetProduct(ctx context.Context, id int64) (*entity.Product, error) 24 | } 25 | 26 | type repo struct { 27 | db *sqlx.DB 28 | } 29 | 30 | func New(db *sqlx.DB) Repository { 31 | return &repo{ 32 | db: db, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /schema.sql: -------------------------------------------------------------------------------- 1 | create database max_inventory; 2 | use max_inventory; 3 | 4 | create table USERS ( 5 | id int not null auto_increment, 6 | email varchar(255) not null, 7 | name varchar(255) not null, 8 | password varchar(300) not null, 9 | primary key (id) 10 | ); 11 | 12 | create table PRODUCTS ( 13 | id int not null auto_increment, 14 | name varchar(255) not null, 15 | description varchar(255) not null, 16 | price float not null, 17 | created_by int not null, 18 | primary key (id), 19 | foreign key (created_by) references USERS(id) 20 | ); 21 | 22 | create table ROLES( 23 | id int not null auto_increment, 24 | name varchar(255) not null, 25 | primary key (id) 26 | ); 27 | 28 | create table USER_ROLES( 29 | id int not null auto_increment, 30 | user_id int not null, 31 | role_id int not null, 32 | primary key (id), 33 | foreign key (user_id) references USERS(id), 34 | foreign key (role_id) references ROLES(id) 35 | ); 36 | 37 | insert into ROLES (id, name) values (1, 'admin'); 38 | insert into ROLES (id, name) values (2, 'seller'); 39 | insert into ROLES (id, name) values (3, 'customer'); -------------------------------------------------------------------------------- /internal/service/products.service_test.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | context "context" 5 | "testing" 6 | 7 | models "github.com/disturb/max-inventory/internal/models" 8 | ) 9 | 10 | func TestAddProduct(t *testing.T) { 11 | testCases := []struct { 12 | Name string 13 | Product models.Product 14 | Email string 15 | ExpectedError error 16 | }{ 17 | { 18 | Name: "AddProduct_Success", 19 | Product: models.Product{ 20 | Name: "Test Product", 21 | Description: "Test Description", 22 | Price: 10.00, 23 | }, 24 | Email: "admin@email.com", 25 | ExpectedError: nil, 26 | }, 27 | { 28 | Name: "AddProduct_InvalidPermissions", 29 | Product: models.Product{ 30 | Name: "Test Product", 31 | Description: "Test Description", 32 | Price: 10.00, 33 | }, 34 | Email: "customer@email.com", 35 | ExpectedError: ErrInvalidPermissions, 36 | }, 37 | } 38 | 39 | ctx := context.Background() 40 | 41 | for i := range testCases { 42 | tc := testCases[i] 43 | 44 | t.Run(tc.Name, func(t *testing.T) { 45 | t.Parallel() 46 | repo.Mock.Test(t) 47 | 48 | err := s.AddProdcut(ctx, tc.Product, tc.Email) 49 | 50 | if err != tc.ExpectedError { 51 | t.Errorf("Expected error %v, got %v", tc.ExpectedError, err) 52 | } 53 | }) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /internal/repository/products.repository.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | context "context" 5 | 6 | entity "github.com/disturb/max-inventory/internal/entity" 7 | ) 8 | 9 | const ( 10 | qryInsertProduct = ` 11 | insert into PRODUCTS (name, description, price, created_by) values (?, ?, ?, ?);` 12 | 13 | qryGetAllProducts = ` 14 | select 15 | id 16 | name, 17 | description 18 | price 19 | created_by 20 | from PRODUCTS;` 21 | 22 | qryGetProductByID = ` 23 | select 24 | id 25 | name, 26 | description 27 | price 28 | created_by 29 | from PRODUCTS 30 | where id = ?;` 31 | ) 32 | 33 | func (r *repo) SaveProduct(ctx context.Context, name, description string, price float32, createdBy int64) error { 34 | _, err := r.db.ExecContext(ctx, qryInsertProduct, name, description, price, createdBy) 35 | return err 36 | } 37 | 38 | func (r *repo) GetProducts(ctx context.Context) ([]entity.Product, error) { 39 | pp := []entity.Product{} 40 | 41 | err := r.db.SelectContext(ctx, &pp, qryGetAllProducts) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | return pp, nil 47 | } 48 | 49 | func (r *repo) GetProduct(ctx context.Context, id int64) (*entity.Product, error) { 50 | p := &entity.Product{} 51 | 52 | err := r.db.GetContext(ctx, p, qryGetProductByID, id) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | return p, nil 58 | } 59 | -------------------------------------------------------------------------------- /encryption/encryption.go: -------------------------------------------------------------------------------- 1 | package encryption 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "crypto/rand" 7 | "encoding/base64" 8 | "errors" 9 | "io" 10 | ) 11 | 12 | const key = "01234567890123456789012345678901" 13 | 14 | func Encrypt(plaintext []byte) ([]byte, error) { 15 | c, err := aes.NewCipher([]byte(key)) 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | gcm, err := cipher.NewGCM(c) 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | nonce := make([]byte, gcm.NonceSize()) 26 | if _, err = io.ReadFull(rand.Reader, nonce); err != nil { 27 | return nil, err 28 | } 29 | 30 | return gcm.Seal(nonce, nonce, plaintext, nil), nil 31 | } 32 | 33 | func Decrypt(ciphertext []byte) ([]byte, error) { 34 | c, err := aes.NewCipher([]byte(key)) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | gcm, err := cipher.NewGCM(c) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | nonceSize := gcm.NonceSize() 45 | if len(ciphertext) < nonceSize { 46 | return nil, errors.New("ciphertext too short") 47 | } 48 | 49 | nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] 50 | return gcm.Open(nil, nonce, ciphertext, nil) 51 | } 52 | 53 | func ToBase64(plaintext []byte) string { 54 | return base64.RawStdEncoding.EncodeToString(plaintext) 55 | } 56 | 57 | func FromBase64(ciphertext string) ([]byte, error) { 58 | return base64.RawStdEncoding.DecodeString(ciphertext) 59 | } 60 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/disturb/max-inventory 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/go-sql-driver/mysql v1.6.0 7 | github.com/labstack/echo/v4 v4.9.0 8 | github.com/stretchr/testify v1.7.2 9 | gopkg.in/yaml.v3 v3.0.1 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/go-playground/locales v0.14.0 // indirect 15 | github.com/go-playground/universal-translator v0.18.0 // indirect 16 | github.com/golang-jwt/jwt v3.2.2+incompatible // indirect 17 | github.com/golang-jwt/jwt/v4 v4.4.2 // indirect 18 | github.com/kr/pretty v0.3.0 // indirect 19 | github.com/labstack/gommon v0.3.1 // indirect 20 | github.com/leodido/go-urn v1.2.1 // indirect 21 | github.com/mattn/go-colorable v0.1.11 // indirect 22 | github.com/mattn/go-isatty v0.0.14 // indirect 23 | github.com/pmezard/go-difflib v1.0.0 // indirect 24 | github.com/rogpeppe/go-internal v1.8.0 // indirect 25 | github.com/stretchr/objx v0.1.0 // indirect 26 | github.com/valyala/bytebufferpool v1.0.0 // indirect 27 | github.com/valyala/fasttemplate v1.2.1 // indirect 28 | golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect 29 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect 30 | golang.org/x/text v0.3.7 // indirect 31 | golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect 32 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 33 | ) 34 | 35 | require ( 36 | github.com/go-playground/validator/v10 v10.11.0 37 | github.com/jmoiron/sqlx v1.3.5 38 | go.uber.org/atomic v1.6.0 // indirect 39 | go.uber.org/dig v1.14.0 // indirect 40 | go.uber.org/fx v1.17.1 41 | go.uber.org/multierr v1.5.0 // indirect 42 | go.uber.org/zap v1.16.0 // indirect 43 | golang.org/x/sys v0.0.0-20211103235746-7861aae1554b // indirect 44 | ) 45 | -------------------------------------------------------------------------------- /internal/service/service_test.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/disturb/max-inventory/encryption" 8 | "github.com/disturb/max-inventory/internal/entity" 9 | "github.com/disturb/max-inventory/internal/repository" 10 | mock "github.com/stretchr/testify/mock" 11 | ) 12 | 13 | var repo *repository.MockRepository 14 | var s Service 15 | 16 | func TestMain(m *testing.M) { 17 | validPassword, _ := encryption.Encrypt([]byte("validPassword")) 18 | encryptedPassword := encryption.ToBase64(validPassword) 19 | u := &entity.User{Email: "test@exists.com", Password: encryptedPassword} 20 | adminUser := &entity.User{ID: 1, Email: "admin@email.com", Password: encryptedPassword} 21 | customerUser := &entity.User{ID: 2, Email: "customer@email.com", Password: encryptedPassword} 22 | 23 | repo = &repository.MockRepository{} 24 | repo.On("GetUserByEmail", mock.Anything, "test@test.com").Return(nil, nil) 25 | repo.On("GetUserByEmail", mock.Anything, "test@exists.com").Return(u, nil) 26 | repo.On("GetUserByEmail", mock.Anything, "admin@email.com").Return(adminUser, nil) 27 | repo.On("GetUserByEmail", mock.Anything, "customer@email.com").Return(customerUser, nil) 28 | 29 | repo.On("SaveUser", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) 30 | 31 | repo.On("GetUserRoles", mock.Anything, int64(1)).Return([]entity.UserRole{{UserID: 1, RoleID: 1}}, nil) 32 | repo.On("GetUserRoles", mock.Anything, int64(2)).Return([]entity.UserRole{{UserID: 2, RoleID: 3}}, nil) 33 | 34 | repo.On("SaveUserRole", mock.Anything, mock.Anything, mock.Anything).Return(nil) 35 | repo.On("RemoveUserRole", mock.Anything, mock.Anything, mock.Anything).Return(nil) 36 | 37 | repo.On("SaveProduct", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) 38 | 39 | s = New(repo) 40 | 41 | code := m.Run() 42 | os.Exit(code) 43 | } 44 | -------------------------------------------------------------------------------- /internal/service/products.service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | context "context" 5 | "errors" 6 | 7 | models "github.com/disturb/max-inventory/internal/models" 8 | ) 9 | 10 | var validRolesToAddProduct []int64 = []int64{1, 2} 11 | var ErrInvalidPermissions = errors.New("user does not have permission to add product") 12 | 13 | func (s *serv) GetProducts(ctx context.Context) ([]models.Product, error) { 14 | pp, err := s.repo.GetProducts(ctx) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | products := []models.Product{} 20 | 21 | for _, p := range pp { 22 | products = append(products, models.Product{ 23 | ID: p.ID, 24 | Name: p.Name, 25 | Description: p.Description, 26 | Price: p.Price, 27 | }) 28 | } 29 | 30 | return products, nil 31 | } 32 | 33 | func (s *serv) GetProduct(ctx context.Context, id int64) (*models.Product, error) { 34 | p, err := s.repo.GetProduct(ctx, id) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | product := &models.Product{ 40 | ID: p.ID, 41 | Name: p.Name, 42 | Description: p.Description, 43 | Price: p.Price, 44 | } 45 | 46 | return product, nil 47 | } 48 | 49 | func (s *serv) AddProdcut(ctx context.Context, product models.Product, email string) error { 50 | 51 | u, err := s.repo.GetUserByEmail(ctx, email) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | roles, err := s.repo.GetUserRoles(ctx, u.ID) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | userCanAdd := false 62 | 63 | for _, r := range roles { 64 | for _, vr := range validRolesToAddProduct { 65 | if vr == r.RoleID { 66 | userCanAdd = true 67 | } 68 | } 69 | } 70 | 71 | if !userCanAdd { 72 | return ErrInvalidPermissions 73 | } 74 | 75 | return s.repo.SaveProduct( 76 | ctx, 77 | product.Name, 78 | product.Description, 79 | product.Price, 80 | u.ID, 81 | ) 82 | } 83 | -------------------------------------------------------------------------------- /internal/repository/users.repository.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/disturb/max-inventory/internal/entity" 7 | ) 8 | 9 | const ( 10 | qryInsertUser = ` 11 | insert into USERS (email, name, password) 12 | values (?, ?, ?);` 13 | 14 | qryGetUserByEmail = ` 15 | select 16 | id, 17 | email, 18 | name, 19 | password 20 | from USERS 21 | where email = ?;` 22 | 23 | qryInsertUserRole = ` 24 | insert into USER_ROLES (user_id, role_id) values (:user_id, :role_id);` 25 | 26 | qryRemoveUserRole = ` 27 | delete from USER_ROLES where user_id = :user_id and role_id = :role_id;` 28 | ) 29 | 30 | func (r *repo) SaveUser(ctx context.Context, email, name, password string) error { 31 | _, err := r.db.ExecContext(ctx, qryInsertUser, email, name, password) 32 | return err 33 | } 34 | 35 | func (r *repo) GetUserByEmail(ctx context.Context, email string) (*entity.User, error) { 36 | u := &entity.User{} 37 | err := r.db.GetContext(ctx, u, qryGetUserByEmail, email) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | return u, nil 43 | } 44 | 45 | func (r *repo) SaveUserRole(ctx context.Context, userID, roleID int64) error { 46 | data := entity.UserRole{ 47 | UserID: userID, 48 | RoleID: roleID, 49 | } 50 | 51 | _, err := r.db.NamedExecContext(ctx, qryInsertUserRole, data) 52 | return err 53 | } 54 | 55 | func (r *repo) RemoveUserRole(ctx context.Context, userID, roleID int64) error { 56 | data := entity.UserRole{ 57 | UserID: userID, 58 | RoleID: roleID, 59 | } 60 | 61 | _, err := r.db.NamedExecContext(ctx, qryRemoveUserRole, data) 62 | 63 | return err 64 | } 65 | 66 | func (r *repo) GetUserRoles(ctx context.Context, userID int64) ([]entity.UserRole, error) { 67 | roles := []entity.UserRole{} 68 | 69 | err := r.db.SelectContext(ctx, &roles, "select user_id, role_id from USER_ROLES where user_id = ?", userID) 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | return roles, nil 75 | } 76 | -------------------------------------------------------------------------------- /internal/service/users.service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | "github.com/disturb/max-inventory/encryption" 8 | "github.com/disturb/max-inventory/internal/models" 9 | ) 10 | 11 | var ( 12 | ErrUserAlreadyExists = errors.New("user already exists") 13 | ErrInvalidCredentials = errors.New("invalid credentials") 14 | ErrRoleAlreadyAdded = errors.New("role was already added for this user") 15 | ErrRoleNotFound = errors.New("role not found") 16 | ) 17 | 18 | func (s *serv) RegisterUser(ctx context.Context, email, name, password string) error { 19 | 20 | u, _ := s.repo.GetUserByEmail(ctx, email) 21 | if u != nil { 22 | return ErrUserAlreadyExists 23 | } 24 | 25 | bb, err := encryption.Encrypt([]byte(password)) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | pass := encryption.ToBase64(bb) 31 | return s.repo.SaveUser(ctx, email, name, pass) 32 | } 33 | 34 | func (s *serv) LoginUser(ctx context.Context, email, password string) (*models.User, error) { 35 | u, err := s.repo.GetUserByEmail(ctx, email) 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | bb, err := encryption.FromBase64(u.Password) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | decryptedPassword, err := encryption.Decrypt(bb) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | if string(decryptedPassword) != password { 51 | return nil, ErrInvalidCredentials 52 | } 53 | 54 | return &models.User{ 55 | ID: u.ID, 56 | Email: u.Email, 57 | Name: u.Name, 58 | }, nil 59 | } 60 | 61 | func (s *serv) AddUserRole(ctx context.Context, userID, roleID int64) error { 62 | 63 | roles, err := s.repo.GetUserRoles(ctx, userID) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | for _, r := range roles { 69 | if r.RoleID == roleID { 70 | return ErrRoleAlreadyAdded 71 | } 72 | } 73 | 74 | return s.repo.SaveUserRole(ctx, userID, roleID) 75 | } 76 | 77 | func (s *serv) RemoveUserRole(ctx context.Context, userID, roleID int64) error { 78 | roles, err := s.repo.GetUserRoles(ctx, userID) 79 | if err != nil { 80 | return err 81 | } 82 | 83 | roleFound := false 84 | for _, r := range roles { 85 | if r.RoleID == roleID { 86 | roleFound = true 87 | break 88 | } 89 | } 90 | 91 | if !roleFound { 92 | return ErrRoleNotFound 93 | } 94 | 95 | return s.repo.RemoveUserRole(ctx, userID, roleID) 96 | } 97 | -------------------------------------------------------------------------------- /internal/api/handlers.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | 7 | "github.com/disturb/max-inventory/encryption" 8 | "github.com/disturb/max-inventory/internal/api/dtos" 9 | "github.com/disturb/max-inventory/internal/models" 10 | "github.com/disturb/max-inventory/internal/service" 11 | "github.com/labstack/echo/v4" 12 | ) 13 | 14 | type responseMessage struct { 15 | Message string `json:"message"` 16 | } 17 | 18 | func (a *API) RegisterUser(c echo.Context) error { 19 | ctx := c.Request().Context() 20 | params := dtos.RegisterUser{} 21 | 22 | err := c.Bind(¶ms) 23 | if err != nil { 24 | return c.JSON(http.StatusBadRequest, responseMessage{Message: "Invalid request"}) 25 | } 26 | 27 | err = a.dataValidator.Struct(params) 28 | if err != nil { 29 | return c.JSON(http.StatusBadRequest, responseMessage{Message: err.Error()}) 30 | } 31 | 32 | err = a.serv.RegisterUser(ctx, params.Email, params.Name, params.Password) 33 | if err != nil { 34 | if err == service.ErrUserAlreadyExists { 35 | return c.JSON(http.StatusConflict, responseMessage{Message: "User already exists"}) 36 | } 37 | 38 | return c.JSON(http.StatusInternalServerError, responseMessage{Message: "Internal server error"}) 39 | } 40 | 41 | return c.JSON(http.StatusCreated, nil) 42 | } 43 | 44 | func (a *API) LoginUser(c echo.Context) error { 45 | ctx := c.Request().Context() 46 | params := dtos.LoginUser{} 47 | 48 | err := c.Bind(¶ms) 49 | if err != nil { 50 | log.Println(err) 51 | return c.JSON(http.StatusBadRequest, responseMessage{Message: "Invalid request"}) 52 | } 53 | 54 | err = a.dataValidator.Struct(params) 55 | if err != nil { 56 | log.Println(err) 57 | return c.JSON(http.StatusBadRequest, responseMessage{Message: err.Error()}) 58 | } 59 | 60 | u, err := a.serv.LoginUser(ctx, params.Email, params.Password) 61 | if err != nil { 62 | log.Println(err) 63 | return c.JSON(http.StatusInternalServerError, responseMessage{Message: "Internal server error"}) 64 | } 65 | 66 | token, err := encryption.SignedLoginToken(u) 67 | if err != nil { 68 | log.Println(err) 69 | return c.JSON(http.StatusInternalServerError, responseMessage{Message: "Internal server error"}) 70 | } 71 | 72 | cookie := &http.Cookie{ 73 | Name: "Authorization", 74 | Value: token, 75 | Secure: true, 76 | SameSite: http.SameSiteNoneMode, 77 | HttpOnly: true, 78 | Path: "/", 79 | } 80 | 81 | c.SetCookie(cookie) 82 | return c.JSON(http.StatusOK, map[string]string{"success": "true"}) 83 | } 84 | 85 | func (a *API) AddProduct(c echo.Context) error { 86 | cookie, err := c.Cookie("Authorization") 87 | if err != nil { 88 | log.Println(err) 89 | return c.JSON(http.StatusUnauthorized, responseMessage{Message: "Unauthorized"}) 90 | } 91 | 92 | claims, err := encryption.ParseLoginJWT(cookie.Value) 93 | if err != nil { 94 | log.Println(err) 95 | return c.JSON(http.StatusUnauthorized, responseMessage{Message: "Unauthorized"}) 96 | } 97 | 98 | email := claims["email"].(string) 99 | 100 | ctx := c.Request().Context() 101 | params := dtos.AddProduct{} 102 | 103 | err = c.Bind(¶ms) 104 | if err != nil { 105 | log.Println(err) 106 | return c.JSON(http.StatusBadRequest, responseMessage{Message: "Invalid request"}) 107 | } 108 | 109 | err = a.dataValidator.Struct(params) 110 | if err != nil { 111 | log.Println(err) 112 | return c.JSON(http.StatusBadRequest, responseMessage{Message: err.Error()}) 113 | } 114 | 115 | p := models.Product{ 116 | Name: params.Name, 117 | Description: params.Description, 118 | Price: params.Price, 119 | } 120 | 121 | err = a.serv.AddProdcut(ctx, p, email) 122 | if err != nil { 123 | log.Println(err) 124 | 125 | if err == service.ErrInvalidPermissions { 126 | return c.JSON(http.StatusForbidden, responseMessage{Message: "Invalid permissions"}) 127 | } 128 | 129 | return c.JSON(http.StatusInternalServerError, responseMessage{Message: "Internal server error"}) 130 | } 131 | 132 | return c.JSON(http.StatusOK, nil) 133 | } 134 | -------------------------------------------------------------------------------- /internal/service/users.service_test.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | ) 7 | 8 | func TestRegisterUser(t *testing.T) { 9 | testCases := []struct { 10 | Name string 11 | Email string 12 | UserName string 13 | Password string 14 | ExpectedError error 15 | }{ 16 | { 17 | Name: "RegisterUser_Success", 18 | Email: "test@test.com", 19 | UserName: "test", 20 | Password: "validPassword", 21 | ExpectedError: nil, 22 | }, 23 | { 24 | Name: "RegisterUser_UserAlreadyExists", 25 | Email: "test@exists.com", 26 | UserName: "test", 27 | Password: "validPassword", 28 | ExpectedError: ErrUserAlreadyExists, 29 | }, 30 | } 31 | 32 | ctx := context.Background() 33 | 34 | for i := range testCases { 35 | tc := testCases[i] 36 | 37 | t.Run(tc.Name, func(t *testing.T) { 38 | t.Parallel() 39 | repo.Mock.Test(t) 40 | 41 | err := s.RegisterUser(ctx, tc.Email, tc.UserName, tc.Password) 42 | 43 | if err != tc.ExpectedError { 44 | t.Errorf("Expected error %v, got %v", tc.ExpectedError, err) 45 | } 46 | 47 | }) 48 | } 49 | } 50 | 51 | func TestLoginUser(t *testing.T) { 52 | testCases := []struct { 53 | Name string 54 | Email string 55 | Password string 56 | ExpectedError error 57 | }{ 58 | { 59 | Name: "LoginUser_Success", 60 | Email: "test@exists.com", 61 | Password: "validPassword", 62 | ExpectedError: nil, 63 | }, 64 | { 65 | Name: "LoginUser_InvalidPassword", 66 | Email: "test@exists.com", 67 | Password: "invalidPassword", 68 | ExpectedError: ErrInvalidCredentials, 69 | }, 70 | } 71 | 72 | ctx := context.Background() 73 | 74 | for i := range testCases { 75 | tc := testCases[i] 76 | 77 | t.Run(tc.Name, func(t *testing.T) { 78 | t.Parallel() 79 | repo.Mock.Test(t) 80 | 81 | _, errr := s.LoginUser(ctx, tc.Email, tc.Password) 82 | 83 | if errr != tc.ExpectedError { 84 | t.Errorf("Expected error %v, got %v", tc.ExpectedError, errr) 85 | } 86 | 87 | }) 88 | } 89 | } 90 | 91 | func TestAddUserRole(t *testing.T) { 92 | testCases := []struct { 93 | Name string 94 | UserID int64 95 | RoleID int64 96 | ExpectedError error 97 | }{ 98 | { 99 | Name: "AddUserRole_Success", 100 | UserID: 1, 101 | RoleID: 2, 102 | ExpectedError: nil, 103 | }, 104 | { 105 | Name: "AddUserRole_UserAlreadyHasRole", 106 | UserID: 1, 107 | RoleID: 1, 108 | ExpectedError: ErrRoleAlreadyAdded, 109 | }, 110 | } 111 | 112 | ctx := context.Background() 113 | 114 | for i := range testCases { 115 | tc := testCases[i] 116 | 117 | t.Run(tc.Name, func(t *testing.T) { 118 | t.Parallel() 119 | 120 | repo.Mock.Test(t) 121 | 122 | err := s.AddUserRole(ctx, tc.UserID, tc.RoleID) 123 | 124 | if err != tc.ExpectedError { 125 | t.Errorf("Expected error %v, got %v", tc.ExpectedError, err) 126 | } 127 | }) 128 | } 129 | } 130 | 131 | func TestRemoveUserRole(t *testing.T) { 132 | testCases := []struct { 133 | Name string 134 | UserID int64 135 | RoleID int64 136 | ExpectedError error 137 | }{ 138 | { 139 | Name: "RemoveUserRole_Success", 140 | UserID: 1, 141 | RoleID: 1, 142 | ExpectedError: nil, 143 | }, 144 | { 145 | Name: "RemoveUserRole_UserDoesNotHaveRole", 146 | UserID: 1, 147 | RoleID: 3, 148 | ExpectedError: ErrRoleNotFound, 149 | }, 150 | } 151 | 152 | ctx := context.Background() 153 | 154 | for i := range testCases { 155 | tc := testCases[i] 156 | 157 | t.Run(tc.Name, func(t *testing.T) { 158 | t.Parallel() 159 | 160 | repo.Mock.Test(t) 161 | 162 | err := s.RemoveUserRole(ctx, tc.UserID, tc.RoleID) 163 | 164 | if err != tc.ExpectedError { 165 | t.Errorf("Expected error %v, got %v", tc.ExpectedError, err) 166 | } 167 | }) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /internal/service/mock_Service.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.13.1. DO NOT EDIT. 2 | 3 | package service 4 | 5 | import ( 6 | context "context" 7 | 8 | models "github.com/disturb/max-inventory/internal/models" 9 | mock "github.com/stretchr/testify/mock" 10 | ) 11 | 12 | // MockService is an autogenerated mock type for the Service type 13 | type MockService struct { 14 | mock.Mock 15 | } 16 | 17 | // AddProdcut provides a mock function with given fields: ctx, product, userEmail 18 | func (_m *MockService) AddProdcut(ctx context.Context, product models.Product, userEmail string) error { 19 | ret := _m.Called(ctx, product, userEmail) 20 | 21 | var r0 error 22 | if rf, ok := ret.Get(0).(func(context.Context, models.Product, string) error); ok { 23 | r0 = rf(ctx, product, userEmail) 24 | } else { 25 | r0 = ret.Error(0) 26 | } 27 | 28 | return r0 29 | } 30 | 31 | // AddUserRole provides a mock function with given fields: ctx, userID, roleID 32 | func (_m *MockService) AddUserRole(ctx context.Context, userID int64, roleID int64) error { 33 | ret := _m.Called(ctx, userID, roleID) 34 | 35 | var r0 error 36 | if rf, ok := ret.Get(0).(func(context.Context, int64, int64) error); ok { 37 | r0 = rf(ctx, userID, roleID) 38 | } else { 39 | r0 = ret.Error(0) 40 | } 41 | 42 | return r0 43 | } 44 | 45 | // GetProduct provides a mock function with given fields: ctx, id 46 | func (_m *MockService) GetProduct(ctx context.Context, id int64) (*models.Product, error) { 47 | ret := _m.Called(ctx, id) 48 | 49 | var r0 *models.Product 50 | if rf, ok := ret.Get(0).(func(context.Context, int64) *models.Product); ok { 51 | r0 = rf(ctx, id) 52 | } else { 53 | if ret.Get(0) != nil { 54 | r0 = ret.Get(0).(*models.Product) 55 | } 56 | } 57 | 58 | var r1 error 59 | if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok { 60 | r1 = rf(ctx, id) 61 | } else { 62 | r1 = ret.Error(1) 63 | } 64 | 65 | return r0, r1 66 | } 67 | 68 | // GetProducts provides a mock function with given fields: ctx 69 | func (_m *MockService) GetProducts(ctx context.Context) ([]models.Product, error) { 70 | ret := _m.Called(ctx) 71 | 72 | var r0 []models.Product 73 | if rf, ok := ret.Get(0).(func(context.Context) []models.Product); ok { 74 | r0 = rf(ctx) 75 | } else { 76 | if ret.Get(0) != nil { 77 | r0 = ret.Get(0).([]models.Product) 78 | } 79 | } 80 | 81 | var r1 error 82 | if rf, ok := ret.Get(1).(func(context.Context) error); ok { 83 | r1 = rf(ctx) 84 | } else { 85 | r1 = ret.Error(1) 86 | } 87 | 88 | return r0, r1 89 | } 90 | 91 | // LoginUser provides a mock function with given fields: ctx, email, password 92 | func (_m *MockService) LoginUser(ctx context.Context, email string, password string) (*models.User, error) { 93 | ret := _m.Called(ctx, email, password) 94 | 95 | var r0 *models.User 96 | if rf, ok := ret.Get(0).(func(context.Context, string, string) *models.User); ok { 97 | r0 = rf(ctx, email, password) 98 | } else { 99 | if ret.Get(0) != nil { 100 | r0 = ret.Get(0).(*models.User) 101 | } 102 | } 103 | 104 | var r1 error 105 | if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { 106 | r1 = rf(ctx, email, password) 107 | } else { 108 | r1 = ret.Error(1) 109 | } 110 | 111 | return r0, r1 112 | } 113 | 114 | // RegisterUser provides a mock function with given fields: ctx, email, name, password 115 | func (_m *MockService) RegisterUser(ctx context.Context, email string, name string, password string) error { 116 | ret := _m.Called(ctx, email, name, password) 117 | 118 | var r0 error 119 | if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { 120 | r0 = rf(ctx, email, name, password) 121 | } else { 122 | r0 = ret.Error(0) 123 | } 124 | 125 | return r0 126 | } 127 | 128 | // RemoveUserRole provides a mock function with given fields: ctx, userID, roleID 129 | func (_m *MockService) RemoveUserRole(ctx context.Context, userID int64, roleID int64) error { 130 | ret := _m.Called(ctx, userID, roleID) 131 | 132 | var r0 error 133 | if rf, ok := ret.Get(0).(func(context.Context, int64, int64) error); ok { 134 | r0 = rf(ctx, userID, roleID) 135 | } else { 136 | r0 = ret.Error(0) 137 | } 138 | 139 | return r0 140 | } 141 | 142 | type mockConstructorTestingTNewMockService interface { 143 | mock.TestingT 144 | Cleanup(func()) 145 | } 146 | 147 | // NewMockService creates a new instance of MockService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 148 | func NewMockService(t mockConstructorTestingTNewMockService) *MockService { 149 | mock := &MockService{} 150 | mock.Mock.Test(t) 151 | 152 | t.Cleanup(func() { mock.AssertExpectations(t) }) 153 | 154 | return mock 155 | } 156 | -------------------------------------------------------------------------------- /internal/repository/mock_Repository.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.13.1. DO NOT EDIT. 2 | 3 | package repository 4 | 5 | import ( 6 | context "context" 7 | 8 | entity "github.com/disturb/max-inventory/internal/entity" 9 | mock "github.com/stretchr/testify/mock" 10 | ) 11 | 12 | // MockRepository is an autogenerated mock type for the Repository type 13 | type MockRepository struct { 14 | mock.Mock 15 | } 16 | 17 | // GetProduct provides a mock function with given fields: ctx, id 18 | func (_m *MockRepository) GetProduct(ctx context.Context, id int64) (*entity.Product, error) { 19 | ret := _m.Called(ctx, id) 20 | 21 | var r0 *entity.Product 22 | if rf, ok := ret.Get(0).(func(context.Context, int64) *entity.Product); ok { 23 | r0 = rf(ctx, id) 24 | } else { 25 | if ret.Get(0) != nil { 26 | r0 = ret.Get(0).(*entity.Product) 27 | } 28 | } 29 | 30 | var r1 error 31 | if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok { 32 | r1 = rf(ctx, id) 33 | } else { 34 | r1 = ret.Error(1) 35 | } 36 | 37 | return r0, r1 38 | } 39 | 40 | // GetProducts provides a mock function with given fields: ctx 41 | func (_m *MockRepository) GetProducts(ctx context.Context) ([]entity.Product, error) { 42 | ret := _m.Called(ctx) 43 | 44 | var r0 []entity.Product 45 | if rf, ok := ret.Get(0).(func(context.Context) []entity.Product); ok { 46 | r0 = rf(ctx) 47 | } else { 48 | if ret.Get(0) != nil { 49 | r0 = ret.Get(0).([]entity.Product) 50 | } 51 | } 52 | 53 | var r1 error 54 | if rf, ok := ret.Get(1).(func(context.Context) error); ok { 55 | r1 = rf(ctx) 56 | } else { 57 | r1 = ret.Error(1) 58 | } 59 | 60 | return r0, r1 61 | } 62 | 63 | // GetUserByEmail provides a mock function with given fields: ctx, email 64 | func (_m *MockRepository) GetUserByEmail(ctx context.Context, email string) (*entity.User, error) { 65 | ret := _m.Called(ctx, email) 66 | 67 | var r0 *entity.User 68 | if rf, ok := ret.Get(0).(func(context.Context, string) *entity.User); ok { 69 | r0 = rf(ctx, email) 70 | } else { 71 | if ret.Get(0) != nil { 72 | r0 = ret.Get(0).(*entity.User) 73 | } 74 | } 75 | 76 | var r1 error 77 | if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { 78 | r1 = rf(ctx, email) 79 | } else { 80 | r1 = ret.Error(1) 81 | } 82 | 83 | return r0, r1 84 | } 85 | 86 | // GetUserRoles provides a mock function with given fields: ctx, userID 87 | func (_m *MockRepository) GetUserRoles(ctx context.Context, userID int64) ([]entity.UserRole, error) { 88 | ret := _m.Called(ctx, userID) 89 | 90 | var r0 []entity.UserRole 91 | if rf, ok := ret.Get(0).(func(context.Context, int64) []entity.UserRole); ok { 92 | r0 = rf(ctx, userID) 93 | } else { 94 | if ret.Get(0) != nil { 95 | r0 = ret.Get(0).([]entity.UserRole) 96 | } 97 | } 98 | 99 | var r1 error 100 | if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok { 101 | r1 = rf(ctx, userID) 102 | } else { 103 | r1 = ret.Error(1) 104 | } 105 | 106 | return r0, r1 107 | } 108 | 109 | // RemoveUserRole provides a mock function with given fields: ctx, userID, roleID 110 | func (_m *MockRepository) RemoveUserRole(ctx context.Context, userID int64, roleID int64) error { 111 | ret := _m.Called(ctx, userID, roleID) 112 | 113 | var r0 error 114 | if rf, ok := ret.Get(0).(func(context.Context, int64, int64) error); ok { 115 | r0 = rf(ctx, userID, roleID) 116 | } else { 117 | r0 = ret.Error(0) 118 | } 119 | 120 | return r0 121 | } 122 | 123 | // SaveProduct provides a mock function with given fields: ctx, name, description, price, createdBy 124 | func (_m *MockRepository) SaveProduct(ctx context.Context, name string, description string, price float32, createdBy int64) error { 125 | ret := _m.Called(ctx, name, description, price, createdBy) 126 | 127 | var r0 error 128 | if rf, ok := ret.Get(0).(func(context.Context, string, string, float32, int64) error); ok { 129 | r0 = rf(ctx, name, description, price, createdBy) 130 | } else { 131 | r0 = ret.Error(0) 132 | } 133 | 134 | return r0 135 | } 136 | 137 | // SaveUser provides a mock function with given fields: ctx, email, name, password 138 | func (_m *MockRepository) SaveUser(ctx context.Context, email string, name string, password string) error { 139 | ret := _m.Called(ctx, email, name, password) 140 | 141 | var r0 error 142 | if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { 143 | r0 = rf(ctx, email, name, password) 144 | } else { 145 | r0 = ret.Error(0) 146 | } 147 | 148 | return r0 149 | } 150 | 151 | // SaveUserRole provides a mock function with given fields: ctx, userID, roleID 152 | func (_m *MockRepository) SaveUserRole(ctx context.Context, userID int64, roleID int64) error { 153 | ret := _m.Called(ctx, userID, roleID) 154 | 155 | var r0 error 156 | if rf, ok := ret.Get(0).(func(context.Context, int64, int64) error); ok { 157 | r0 = rf(ctx, userID, roleID) 158 | } else { 159 | r0 = ret.Error(0) 160 | } 161 | 162 | return r0 163 | } 164 | 165 | type mockConstructorTestingTNewMockRepository interface { 166 | mock.TestingT 167 | Cleanup(func()) 168 | } 169 | 170 | // NewMockRepository creates a new instance of MockRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 171 | func NewMockRepository(t mockConstructorTestingTNewMockRepository) *MockRepository { 172 | mock := &MockRepository{} 173 | mock.Mock.Test(t) 174 | 175 | t.Cleanup(func() { mock.AssertExpectations(t) }) 176 | 177 | return mock 178 | } 179 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= 4 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 9 | github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= 10 | github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= 11 | github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= 12 | github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= 13 | github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= 14 | github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= 15 | github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= 16 | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 17 | github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= 18 | github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= 19 | github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= 20 | github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= 21 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 22 | github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= 23 | github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= 24 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 25 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 26 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 27 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 28 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 29 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 30 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 31 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 32 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 33 | github.com/labstack/echo/v4 v4.8.0 h1:wdc6yKVaHxkNOEdz4cRZs1pQkwSXPiRjq69yWP4QQS8= 34 | github.com/labstack/echo/v4 v4.8.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks= 35 | github.com/labstack/echo/v4 v4.9.0 h1:wPOF1CE6gvt/kmbMR4dGzWvHMPT+sAEUJOwOTtvITVY= 36 | github.com/labstack/echo/v4 v4.9.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks= 37 | github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o= 38 | github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= 39 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= 40 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 41 | github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= 42 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 43 | github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= 44 | github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 45 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 46 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 47 | github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= 48 | github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= 49 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 50 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 51 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 52 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 53 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 54 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 55 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 56 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= 57 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= 58 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 59 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 60 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 61 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 62 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 63 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 64 | github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= 65 | github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= 66 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 67 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 68 | github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= 69 | github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 70 | go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= 71 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 72 | go.uber.org/dig v1.14.0 h1:VmGvIH45/aapXPQkaOrK5u4B5B7jxZB98HM/utx0eME= 73 | go.uber.org/dig v1.14.0/go.mod h1:jHAn/z1Ld1luVVyGKOAIFYz/uBFqKjjEEdIqVAqfQ2o= 74 | go.uber.org/fx v1.17.1 h1:S42dZ6Pok8hQ3jxKwo6ZMYcCgHQA/wAS/gnpRa1Pksg= 75 | go.uber.org/fx v1.17.1/go.mod h1:yO7KN5rhlARljyo4LR047AjaV6J+KFzd/Z7rnTbEn0A= 76 | go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= 77 | go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= 78 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 79 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= 80 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 81 | go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM= 82 | go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= 83 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 84 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 85 | golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= 86 | golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 87 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= 88 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 89 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 90 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 91 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 92 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 93 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= 94 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 95 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 96 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 97 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 98 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 99 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 100 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 101 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 102 | golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 103 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 104 | golang.org/x/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4= 105 | golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 106 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 107 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 108 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 109 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 110 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 111 | golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE= 112 | golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 113 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 114 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 115 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 116 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 117 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= 118 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 119 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 120 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 121 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 122 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 123 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 124 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 125 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 126 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 127 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 128 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 129 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 130 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 131 | honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= 132 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 133 | --------------------------------------------------------------------------------