├── 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 |
--------------------------------------------------------------------------------