Compare commits

...

23 Commits

Author SHA1 Message Date
c7422da71a add product image 2023-04-05 14:04:49 +07:00
9ebacf85aa refactor transaction 2023-03-22 15:14:54 +07:00
f6877687c6 add product type id 2023-03-22 15:14:28 +07:00
46236aa71b add product type in products 2023-03-22 13:40:18 +07:00
5604f34e97 add sale order 2023-03-22 12:06:09 +07:00
2de83644b8 add custom message for error response 2023-03-22 12:05:43 +07:00
8f78dd25cf add created by params for sale and purchase 2023-03-21 13:15:14 +07:00
f77288369a refactor login json response 2023-03-19 18:55:55 +07:00
cb14cd40a4 add create stock logs 2023-03-19 18:17:17 +07:00
edddee0cb4 adjust response to new model 2023-03-19 18:17:01 +07:00
3c8a613222 set token inital duration to 16h 2023-03-19 18:16:45 +07:00
9a22afe984 adjust with product category 2023-03-16 20:37:20 +07:00
caf41e81e0 add product category 2023-03-16 16:55:45 +07:00
03743711bc remove unused command 2023-03-16 16:55:23 +07:00
2794491872 update gitignore 2023-03-16 14:33:29 +07:00
736d0215a8 accept migratedown step 2023-03-16 14:17:18 +07:00
2c21198412 add refresh token 2023-03-16 12:21:41 +07:00
61ed16163d add middlweare and authorization 2023-03-15 15:00:36 +07:00
4738c8c590 add user authentication 2023-03-14 17:39:40 +07:00
93f570f332 test create user merchant 2023-03-14 12:04:03 +07:00
d05b501360 add user and password hash 2023-03-13 09:24:59 +07:00
85dd2a3134 mock test 2023-03-12 11:01:43 +07:00
a2c799cb29 adjust with env 2023-03-08 15:22:57 +07:00
84 changed files with 4565 additions and 216 deletions

3
.gitignore vendored
View File

@ -1 +1,2 @@
.env
*.env
public

View File

@ -1,18 +1,21 @@
include .env
include dev.env
migrateup:
migrate -path db/migrations -database "${DB_TYPE}://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=disable" -verbose up
migratedown:
migrate -path db/migrations -database "${DB_TYPE}://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=disable" -verbose down
migrate -path db/migrations -database "${DB_TYPE}://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=disable" -verbose down $N
mock-generate:
mockgen -package mockdb -destination db/mock/store.go git.nochill.in/nochill/naice_pos/db/sqlc Store
sqlc:
sqlc generate
sqlc generate && make mock-generate
test:
go test -v -cover ./...
server:
go run main.go
go run main.go
.PHONY: server migrateup migratedown sqlc

View File

@ -2,6 +2,9 @@ package api
import "github.com/gin-gonic/gin"
func errorResponse(err error) gin.H {
return gin.H{"error": err.Error()}
func errorResponse(err error, msg string) gin.H {
return gin.H{
"error": err.Error(),
"message": msg,
}
}

31
api/main_test.go Normal file
View File

@ -0,0 +1,31 @@
package api
import (
"os"
"testing"
"time"
db "git.nochill.in/nochill/naice_pos/db/sqlc"
"git.nochill.in/nochill/naice_pos/util"
"github.com/gin-gonic/gin"
_ "github.com/lib/pq"
"github.com/stretchr/testify/require"
)
func newTestServer(t *testing.T, store db.Store) *Server {
config := util.Config{
TokenSymmetricKey: util.RandomString(32),
TokenDuration: time.Minute,
}
server, err := NewServer(config, store)
require.NoError(t, err)
return server
}
func TestMain(m *testing.M) {
gin.SetMode(gin.TestMode)
os.Exit(m.Run())
}

51
api/middleware.go Normal file
View File

@ -0,0 +1,51 @@
package api
import (
"errors"
"fmt"
"net/http"
"strings"
"git.nochill.in/nochill/naice_pos/token"
"github.com/gin-gonic/gin"
)
const (
authorizationHeaderKey = "authorization"
authorizationTypeBearer = "bearer"
authorizationPayloadKey = "authorization_payload"
)
func authMiddleware(tokenMaker token.Maker) gin.HandlerFunc {
return func(ctx *gin.Context) {
authorizationHeader := ctx.GetHeader(authorizationHeaderKey)
if len(authorizationHeader) == 0 {
err := errors.New("authorization header is not provided")
ctx.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse(err, ""))
return
}
fields := strings.Fields(authorizationHeader)
if len(fields) < 2 {
err := errors.New("Invalid authorization header format")
ctx.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse(err, ""))
return
}
authorizationType := strings.ToLower(fields[0])
if authorizationType != authorizationTypeBearer {
err := fmt.Errorf("Authorization only accept bearer type")
ctx.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse(err, ""))
}
accessToken := fields[1]
payload, err := tokenMaker.VerifyToken(accessToken)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse(err, ""))
return
}
ctx.Set(authorizationPayloadKey, payload)
ctx.Next()
}
}

81
api/middleware_test.go Normal file
View File

@ -0,0 +1,81 @@
package api
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
"git.nochill.in/nochill/naice_pos/token"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
)
func addAuthorization(
t *testing.T,
request *http.Request,
tokenMaker token.Maker,
authorizationType string,
email string,
merchantID string,
duration time.Duration,
) {
token, payload, err := tokenMaker.CreateToken(email, merchantID, duration)
require.NoError(t, err)
require.NotEmpty(t, token)
require.NotEmpty(t, payload)
authorizationHeader := fmt.Sprintf("%s %s", authorizationType, token)
request.Header.Set(authorizationHeaderKey, authorizationHeader)
}
func TestAuthMiddleware(t *testing.T) {
testCases := []struct {
name string
setupAuth func(t *testing.T, request *http.Request, tokenMaker token.Maker)
checkResponse func(t *testing.T, recorder *httptest.ResponseRecorder)
}{
{
name: "OK",
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, "email", uuid.New().String(), time.Minute)
},
checkResponse: func(t *testing.T, recorder *httptest.ResponseRecorder) {
require.Equal(t, http.StatusOK, recorder.Code)
},
},
{
name: "NoAuthorization",
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {},
checkResponse: func(t *testing.T, recorder *httptest.ResponseRecorder) {
require.Equal(t, http.StatusUnauthorized, recorder.Code)
},
},
}
for i := range testCases {
tc := testCases[i]
t.Run(tc.name, func(t *testing.T) {
server := newTestServer(t, nil)
authPath := "/user/login"
server.router.GET(
authPath,
authMiddleware(server.tokenMaker),
func(ctx *gin.Context) {
ctx.JSON(http.StatusOK, gin.H{})
},
)
recorder := httptest.NewRecorder()
request, err := http.NewRequest(http.MethodGet, authPath, nil)
require.NoError(t, err)
tc.setupAuth(t, request, server.tokenMaker)
server.router.ServeHTTP(recorder, request)
tc.checkResponse(t, recorder)
})
}
}

View File

@ -1,40 +1,188 @@
package api
import (
"database/sql"
"errors"
"fmt"
"net/http"
"os"
"path/filepath"
"time"
db "git.nochill.in/nochill/naice_pos/db/sqlc"
"git.nochill.in/nochill/naice_pos/token"
"git.nochill.in/nochill/naice_pos/util"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type createProductRequest struct {
MerchantID uuid.UUID `json:"merchant_id" binding:"required"`
Name string `json:"name" binding:"required"`
SellingPrice float64 `json:"selling_price" binding:"required"`
PurchasePrice float64 `json:"purchase_price" binding:"required"`
Stock float64 `json:"stock" binding:"required"`
Name string `form:"name" binding:"required"`
SellingPrice float64 `form:"selling_price" binding:"required"`
ProductTypeID int16 `form:"product_type_id" binding:"required"`
PurchasePrice float64 `form:"purchase_price" binding:"required"`
ProductCategoryID string `form:"product_category_id" binding:"required"`
Stock float64 `form:"stock" binding:"number"`
}
func (server *Server) createProduct(ctx *gin.Context) {
var req createProductRequest
var imagePath string
if err := ctx.ShouldBindJSON(&req); err != nil {
ctx.JSON(http.StatusBadRequest, errorResponse(err))
file, _ := ctx.FormFile("image")
if err := ctx.Bind(&req); err != nil {
ctx.JSON(http.StatusBadRequest, errorResponse(err, ""))
return
}
authPayload := ctx.MustGet(authorizationPayloadKey).(*token.Payload)
if file != nil {
file := file
fileExt := filepath.Ext(file.Filename)
now := time.Now()
dir := fmt.Sprintf("public/upload/images/%s/products", authPayload.MerchantID)
filename := fmt.Sprintf("%s%s%s", util.RandomString(5), fmt.Sprintf("%v", now.Unix()), fileExt)
imagePath = fmt.Sprintf("%s/%s", dir, filename)
if _, err := os.Stat(dir); os.IsNotExist(err) {
os.Mkdir(dir, 0775)
}
if err := ctx.SaveUploadedFile(file, fmt.Sprintf("%s", imagePath)); err != nil {
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
return
}
}
arg := db.CreateProductParams{
MerchantID: req.MerchantID,
Name: req.Name,
SellingPrice: req.SellingPrice,
PurchasePrice: req.PurchasePrice,
Stock: req.Stock,
MerchantID: authPayload.MerchantID,
Name: req.Name,
ProductTypeID: req.ProductTypeID,
SellingPrice: req.SellingPrice,
PurchasePrice: req.PurchasePrice,
ProductCategoryID: uuid.MustParse(req.ProductCategoryID),
Stock: req.Stock,
Image: sql.NullString{Valid: len(imagePath) > 0, String: imagePath},
}
product, err := server.store.CreateProduct(ctx, arg)
if err != nil {
ctx.JSON(http.StatusInternalServerError, errorResponse(err))
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
return
}
ctx.JSON(http.StatusOK, product)
}
type getProductRequest struct {
ID string `uri:"id" binding:"required,uuid"`
}
func (server *Server) getProduct(ctx *gin.Context) {
var req getProductRequest
if err := ctx.ShouldBindUri(&req); err != nil {
ctx.JSON(http.StatusBadRequest, errorResponse(err, ""))
return
}
product, err := server.store.GetProduct(ctx, uuid.MustParse(req.ID))
if err != nil {
if err == sql.ErrNoRows {
ctx.JSON(http.StatusNotFound, errorResponse(err, ""))
return
}
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
return
}
authPayload := ctx.MustGet(authorizationPayloadKey).(*token.Payload)
if product.MerchantID != authPayload.MerchantID {
err := errors.New("Product doesn't belong to the user")
ctx.JSON(http.StatusUnauthorized, errorResponse(err, ""))
return
}
ctx.JSON(http.StatusOK, product)
}
type listProductsRequest struct {
PageID int32 `form:"page_id" binding:"required,min=1"`
PageSize int32 `form:"page_size" binding:"required,min=5"`
}
func (server *Server) listProducts(ctx *gin.Context) {
var req listProductsRequest
if err := ctx.ShouldBindQuery(&req); err != nil {
ctx.JSON(http.StatusBadRequest, errorResponse(err, ""))
return
}
authPayload := ctx.MustGet(authorizationPayloadKey).(*token.Payload)
arg := db.ListProductsParams{
MerchantID: authPayload.MerchantID,
Limit: req.PageSize,
Offset: (req.PageID - 1) * req.PageSize,
}
products, err := server.store.ListProducts(ctx, arg)
if err != nil {
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
return
}
ctx.JSON(http.StatusOK, products)
}
type updateProductRequest struct {
ProductID uuid.UUID `form:"product_id" binding:"required"`
Name string `form:"name" binding:"required"`
SellingPrice float64 `form:"selling_price" binding:"required"`
PurchasePrice float64 `form:"purchase_price" binding:"required"`
}
func (server *Server) updateProduct(ctx *gin.Context) {
var req updateProductRequest
var imagePath string
file, _ := ctx.FormFile("image")
if err := ctx.Bind(&req); err != nil {
ctx.JSON(http.StatusBadRequest, errorResponse(err, ""))
return
}
authPayload := ctx.MustGet(authorizationPayloadKey).(*token.Payload)
if file != nil {
file := file
fileExt := filepath.Ext(file.Filename)
now := time.Now()
dir := fmt.Sprintf("public/upload/images/%s/products", authPayload.MerchantID)
filename := fmt.Sprintf("%s%s%s", util.RandomString(5), fmt.Sprintf("%v", now.Unix()), fileExt)
imagePath = fmt.Sprintf("%s/%s", dir, filename)
if err := ctx.SaveUploadedFile(file, fmt.Sprintf("%s", imagePath)); err != nil {
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
return
}
}
arg := db.UpdateProductParams{
ID: req.ProductID,
Name: req.Name,
SellingPrice: req.SellingPrice,
PurchasePrice: req.PurchasePrice,
UpdatedAt: sql.NullTime{Time: time.Now(), Valid: true},
}
product, err := server.store.UpdateProduct(ctx, arg)
if err != nil {
if err == sql.ErrNoRows {
ctx.JSON(http.StatusNotFound, errorResponse(err, ""))
return
}
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
return
}

97
api/product_category.go Normal file
View File

@ -0,0 +1,97 @@
package api
import (
"database/sql"
"net/http"
db "git.nochill.in/nochill/naice_pos/db/sqlc"
"git.nochill.in/nochill/naice_pos/token"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type createProductCategoryRequest struct {
MerchantID uuid.UUID `json:"merchant_id" binding:"required"`
Name string `json:"name" binding:"required"`
}
func (server *Server) createProductCategory(ctx *gin.Context) {
var req createProductCategoryRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
ctx.JSON(http.StatusBadRequest, errorResponse(err, ""))
return
}
authPayload := ctx.MustGet(authorizationPayloadKey).(*token.Payload)
arg := db.CreateProductCategoryParams{
MerchantID: authPayload.MerchantID,
Name: req.Name,
}
ProductCategory, err := server.store.CreateProductCategory(ctx, arg)
if err != nil {
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
return
}
ctx.JSON(http.StatusOK, ProductCategory)
}
type listProductCategoriesRequest struct {
PageID int32 `form:"page_id" binding:"required,min=1"`
PageSize int32 `form:"page_size" binding:"required,min=5"`
}
func (server *Server) listProductCategories(ctx *gin.Context) {
var req listProductCategoriesRequest
if err := ctx.ShouldBindQuery(&req); err != nil {
ctx.JSON(http.StatusBadRequest, errorResponse(err, ""))
return
}
authPayload := ctx.MustGet(authorizationPayloadKey).(*token.Payload)
arg := db.ListProductCategoriesByMerchantIDParams{
MerchantID: authPayload.MerchantID,
Limit: req.PageSize,
Offset: (req.PageID - 1) * req.PageSize,
}
ProductCategories, err := server.store.ListProductCategoriesByMerchantID(ctx, arg)
if err != nil {
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
return
}
ctx.JSON(http.StatusOK, ProductCategories)
}
type updateProductCategoryRequest struct {
ProductCategoryID uuid.UUID `json:"ProductCategory_id" binding:"required"`
Name string `json:"name" binding:"required"`
}
func (server *Server) updateProductCategory(ctx *gin.Context) {
var req updateProductCategoryRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
ctx.JSON(http.StatusBadRequest, errorResponse(err, ""))
return
}
arg := db.UpdateProductCategoryParams{
ID: req.ProductCategoryID,
Name: req.Name,
}
ProductCategory, err := server.store.UpdateProductCategory(ctx, arg)
if err != nil {
if err == sql.ErrNoRows {
ctx.JSON(http.StatusNotFound, errorResponse(err, ""))
return
}
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
return
}
ctx.JSON(http.StatusOK, ProductCategory)
}

View File

@ -0,0 +1,89 @@
package api
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
mockdb "git.nochill.in/nochill/naice_pos/db/mock"
db "git.nochill.in/nochill/naice_pos/db/sqlc"
"git.nochill.in/nochill/naice_pos/token"
"git.nochill.in/nochill/naice_pos/util"
"github.com/gin-gonic/gin"
"github.com/golang/mock/gomock"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
)
func TestCreateProductCategory(t *testing.T) {
productCategory := createRandomProductCategory()
testCases := []struct {
name string
body gin.H
setupAuth func(t *testing.T, request *http.Request, tokenMaker token.Maker)
buildStubs func(store *mockdb.MockStore)
checkResponse func(recorder *httptest.ResponseRecorder)
}{
{
name: "OK",
body: gin.H{
"name": productCategory.Name,
"merchant_id": productCategory.MerchantID,
},
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, util.RandomEmail(), "7e525a4b-4208-4f05-99a1-a75df475dd9b", time.Minute)
},
buildStubs: func(store *mockdb.MockStore) {
arg := db.CreateProductCategoryParams{
MerchantID: uuid.MustParse("7e525a4b-4208-4f05-99a1-a75df475dd9b"),
Name: productCategory.Name,
}
store.EXPECT().
CreateProductCategory(gomock.Any(), gomock.Eq(arg)).
Times(1).
Return(productCategory, nil)
},
checkResponse: func(recorder *httptest.ResponseRecorder) {
require.Equal(t, http.StatusOK, recorder.Code)
},
},
}
for i := range testCases {
tc := testCases[i]
t.Run(tc.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
data, err := json.Marshal(tc.body)
require.NoError(t, err)
store := mockdb.NewMockStore(ctrl)
tc.buildStubs(store)
server := newTestServer(t, store)
recorder := httptest.NewRecorder()
url := "/api/product/category"
request, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data))
require.NoError(t, err)
tc.setupAuth(t, request, server.tokenMaker)
server.router.ServeHTTP(recorder, request)
tc.checkResponse(recorder)
})
}
}
func createRandomProductCategory() db.ProductCategory {
return db.ProductCategory{
ID: uuid.New(),
MerchantID: uuid.MustParse("7e525a4b-4208-4f05-99a1-a75df475dd9b"),
Name: util.RandomString(5),
}
}

252
api/product_test.go Normal file
View File

@ -0,0 +1,252 @@
package api
import (
"bytes"
"database/sql"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"time"
mockdb "git.nochill.in/nochill/naice_pos/db/mock"
db "git.nochill.in/nochill/naice_pos/db/sqlc"
"git.nochill.in/nochill/naice_pos/token"
"git.nochill.in/nochill/naice_pos/util"
"github.com/gin-gonic/gin"
"github.com/golang/mock/gomock"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
)
var MERCHANTID = "7e525a4b-4208-4f05-99a1-a75df475dd9b"
func TestGetProductApi(t *testing.T) {
product := randomProduct(MERCHANTID)
testCases := []struct {
name string
productID string
buildStubs func(store *mockdb.MockStore)
setupAuth func(t *testing.T, request *http.Request, tokenMaker token.Maker)
checkResponse func(t *testing.T, recorder *httptest.ResponseRecorder)
}{
{
name: "OK",
productID: product.ID.String(),
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().
GetProduct(gomock.Any(), gomock.Eq(product.ID)).
Times(1).
Return(product, nil)
},
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, "email", MERCHANTID, time.Minute)
},
checkResponse: func(t *testing.T, recorder *httptest.ResponseRecorder) {
require.Equal(t, http.StatusOK, recorder.Code)
},
},
{
name: "Not found",
productID: product.ID.String(),
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().
GetProduct(gomock.Any(), gomock.Eq(product.ID)).
Times(1).
Return(db.Product{}, sql.ErrNoRows)
},
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, "email", MERCHANTID, time.Minute)
},
checkResponse: func(t *testing.T, recorder *httptest.ResponseRecorder) {
require.Equal(t, http.StatusNotFound, recorder.Code)
},
},
{
name: "Unauthorized",
productID: product.ID.String(),
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().
GetProduct(gomock.Any(), gomock.Eq(product.ID)).
Times(1).
Return(db.Product{}, sql.ErrNoRows)
},
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, "email", uuid.New().String(), time.Minute)
},
checkResponse: func(t *testing.T, recorder *httptest.ResponseRecorder) {
require.Equal(t, http.StatusNotFound, recorder.Code)
},
},
{
name: "NoAuthorization",
productID: product.ID.String(),
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().
GetProduct(gomock.Any(), gomock.Any()).
Times(0)
},
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
},
checkResponse: func(t *testing.T, recorder *httptest.ResponseRecorder) {
require.Equal(t, http.StatusUnauthorized, recorder.Code)
},
},
{
name: "Internal Error",
productID: product.ID.String(),
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().
GetProduct(gomock.Any(), gomock.Eq(product.ID)).
Times(1).
Return(db.Product{}, sql.ErrConnDone)
},
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, "email", MERCHANTID, time.Minute)
},
checkResponse: func(t *testing.T, recorder *httptest.ResponseRecorder) {
require.Equal(t, http.StatusInternalServerError, recorder.Code)
},
},
{
name: "Bad request",
productID: "0",
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().
GetProduct(gomock.Any(), gomock.Any()).
Times(0)
},
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, "email", MERCHANTID, time.Minute)
},
checkResponse: func(t *testing.T, recorder *httptest.ResponseRecorder) {
require.Equal(t, http.StatusBadRequest, recorder.Code)
},
},
}
for i := range testCases {
tc := testCases[i]
t.Run(tc.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
store := mockdb.NewMockStore(ctrl)
tc.buildStubs(store)
server := newTestServer(t, store)
recorder := httptest.NewRecorder()
url := fmt.Sprintf("/api/product/%s", tc.productID)
request, err := http.NewRequest(http.MethodGet, url, nil)
require.NoError(t, err)
tc.setupAuth(t, request, server.tokenMaker)
server.router.ServeHTTP(recorder, request)
tc.checkResponse(t, recorder)
})
}
}
func TestCreateProductApi(t *testing.T) {
product := randomProduct(MERCHANTID)
testCases := []struct {
name string
body gin.H
buildStubs func(store *mockdb.MockStore)
setupAuth func(t *testing.T, request *http.Request, tokenMaker token.Maker)
checkResponse func(t *testing.T, recorder *httptest.ResponseRecorder)
}{
{
name: "OK",
body: gin.H{
"name": product.Name,
"selling_price": product.SellingPrice,
"purchase_price": product.PurchasePrice,
"product_type_id": product.ProductTypeID,
"product_category_id": product.ProductCategoryID,
"stock": product.Stock,
},
buildStubs: func(store *mockdb.MockStore) {
arg := db.CreateProductParams{
MerchantID: product.MerchantID,
Name: product.Name,
ProductTypeID: product.ProductTypeID,
SellingPrice: product.SellingPrice,
PurchasePrice: product.PurchasePrice,
ProductCategoryID: product.ProductCategoryID,
Stock: product.Stock,
}
store.EXPECT().
CreateProduct(gomock.Any(), gomock.Eq(arg)).
Times(1).
Return(product, nil)
},
setupAuth: func(t *testing.T, request *http.Request, tokenMaker token.Maker) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, "email", MERCHANTID, time.Minute)
},
checkResponse: func(t *testing.T, recorder *httptest.ResponseRecorder) {
require.Equal(t, http.StatusOK, recorder.Code)
},
},
}
for i := range testCases {
tc := testCases[i]
t.Run(tc.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
store := mockdb.NewMockStore(ctrl)
tc.buildStubs(store)
data, err := json.Marshal(tc.body)
require.NoError(t, err)
server := newTestServer(t, store)
recorder := httptest.NewRecorder()
url := "/api/products"
request, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data))
require.NoError(t, err)
tc.setupAuth(t, request, server.tokenMaker)
server.router.ServeHTTP(recorder, request)
tc.checkResponse(t, recorder)
})
}
}
func randomProduct(merchantID string) db.Product {
productCategory := createRandomProductCategory()
return db.Product{
ID: uuid.New(),
MerchantID: uuid.MustParse(MERCHANTID),
ProductTypeID: 1,
Name: util.RandomString(5),
SellingPrice: util.RandomFloat(1000, 99999),
PurchasePrice: util.RandomFloat(999, 9999),
ProductCategoryID: productCategory.ID,
Stock: util.RandomFloat(100, 99999),
}
}
func requireBodyMatchAccount(t *testing.T, body *bytes.Buffer, product db.Product) {
data, err := ioutil.ReadAll(body)
require.NoError(t, err)
var gotProduct db.Product
err = json.Unmarshal(data, &gotProduct)
require.NoError(t, err)
require.Equal(t, product, gotProduct)
}

View File

@ -1 +1,60 @@
package api
import (
"database/sql"
"net/http"
db "git.nochill.in/nochill/naice_pos/db/sqlc"
"git.nochill.in/nochill/naice_pos/util"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type CreatePurchaseOrderRequest struct {
MerchantID uuid.UUID `json:"merchant_id" binding:"required"`
MerchantIdx int64 `json:"merchant_index" binding:"required,number"`
CreatedBy uuid.UUID `json:"created_by" binding:"required"`
SupplierID uuid.UUID `json:"supplier_id" binding:"required"`
Code string `json:"code"`
IsPaid bool `json:"is_paid" binding:"required"`
Total float64 `json:"total" binding:"required"`
PaidNominal float64 `json:"paid_nominal" binding:"required"`
Note string `json:"note"`
Products []db.PurchaseOrderProduct `json:"products" binding:"required"`
}
func (server Server) createPurchase(ctx *gin.Context) {
var req CreatePurchaseOrderRequest
var code sql.NullString
if len(req.Code) > 0 {
code = sql.NullString{Valid: true, String: req.Code}
} else {
code = sql.NullString{Valid: true, String: util.RandomTransactionCode("P", req.MerchantIdx)}
}
if err := ctx.ShouldBindJSON(&req); err != nil {
ctx.JSON(http.StatusBadRequest, errorResponse(err, ""))
return
}
arg := db.PurchasoOrderTxParams{
MerchantID: req.MerchantID,
CreatedBy: req.CreatedBy,
SupplierID: req.SupplierID,
Code: code,
IsPaid: req.IsPaid,
Total: req.Total,
PaidNominal: req.PaidNominal,
Note: sql.NullString{String: req.Note, Valid: len(req.Code) > 0},
Products: req.Products,
}
result, err := server.store.PurchaseOrderTx(ctx, arg)
if err != nil {
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
return
}
ctx.JSON(http.StatusOK, result)
}

78
api/sale_order.go Normal file
View File

@ -0,0 +1,78 @@
package api
import (
"database/sql"
"net/http"
db "git.nochill.in/nochill/naice_pos/db/sqlc"
"git.nochill.in/nochill/naice_pos/util"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/lib/pq"
)
type createSaleOrderRequest struct {
MerchantID uuid.UUID `json:"merchant_id" binding:"required"`
MerchantIDX int64 `json:"merchant_index" binding:"required"`
CustomerID string `json:"customer_id"`
CreatedBy uuid.UUID `json:"created_by" binding:"required"`
Code string `json:"code"`
IsPaid bool `json:"is_paid" binding:"required"`
Total float64 `json:"total" binding:"required"`
PaidNominal float64 `json:"paid_nominal" binding:"required"`
Change float64 `json:"change"`
Note string `json:"note"`
IsKeep bool `json:"is_keep"`
Products []db.SaleOrderProduct `json:"products" binding:"required"`
}
func (server Server) createSaleOrder(ctx *gin.Context) {
var req createSaleOrderRequest
code := util.RandomTransactionCode("S", req.MerchantIDX)
customerID := uuid.NullUUID{Valid: false, UUID: uuid.Nil}
if err := ctx.ShouldBindJSON(&req); err != nil {
ctx.JSON(http.StatusBadRequest, errorResponse(err, ""))
return
}
if len(req.CustomerID) > 0 {
customerID = uuid.NullUUID{Valid: true, UUID: uuid.MustParse(req.CustomerID)}
}
if len(req.Code) > 0 {
code = req.Code
}
arg := db.SaleOrderTxParams{
MerchantID: req.MerchantID,
CreatedBy: req.CreatedBy,
CustomerID: customerID,
Code: code,
IsPaid: req.IsPaid,
Total: req.Total,
PaidNominal: req.PaidNominal,
Note: sql.NullString{Valid: len(req.Note) > 0, String: req.Note},
IsKeep: req.IsKeep,
Change: req.Change,
Products: req.Products,
}
result, err := server.store.SaleOrderTx(ctx, arg)
if err != nil {
if pqErr, ok := err.(*pq.Error); ok {
switch pqErr.Code.Name() {
case "check_violation":
ctx.JSON(http.StatusConflict, errorResponse(err, "Stok tidak bisa kurang dari 0"))
return
}
}
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
return
}
ctx.JSON(http.StatusOK, result)
}

View File

@ -1,24 +1,64 @@
package api
import (
"fmt"
"path/filepath"
db "git.nochill.in/nochill/naice_pos/db/sqlc"
"git.nochill.in/nochill/naice_pos/token"
"git.nochill.in/nochill/naice_pos/util"
"github.com/gin-gonic/gin"
)
type Server struct {
store *db.Store
router *gin.Engine
config util.Config
store db.Store
tokenMaker token.Maker
router *gin.Engine
}
func NewServer(store *db.Store) *Server {
server := &Server{store: store}
func NewServer(config util.Config, store db.Store) (*Server, error) {
tokenMaker, err := token.NewPasetoMaker(config.TokenSymmetricKey)
if err != nil {
return nil, fmt.Errorf("cannot create token maker: %w", err)
}
server := &Server{
config: config,
store: store,
tokenMaker: tokenMaker,
}
server.getRoutes()
return server, nil
}
func (server *Server) getRoutes() {
router := gin.Default()
router.POST("/products", server.createProduct)
router.POST("/suppliers", server.createSupplier)
router.POST("/user/login", server.loginUser)
router.POST("/user/merchants", server.createUserMerchant)
router.POST("/user/renew_token", server.renewAccessToken)
router.Static("/public", filepath.Base("public"))
apiRoutes := router.Group("/api").Use(authMiddleware(server.tokenMaker))
apiRoutes.POST("/products", server.createProduct)
apiRoutes.PATCH("/products", server.updateProduct)
apiRoutes.GET("/products", server.listProducts)
apiRoutes.GET("/product/:id", server.getProduct)
apiRoutes.POST("/product/category", server.createProductCategory)
apiRoutes.GET("/product/categories", server.listProductCategories)
apiRoutes.PATCH("/product/category", server.updateProductCategory)
// apiRoutes.DELETE("/product/category/:id", server.)
apiRoutes.POST("/suppliers", server.createSupplier)
apiRoutes.POST("/purchase-products", server.createPurchase)
apiRoutes.POST("/sale-order", server.createSaleOrder)
server.router = router
return server
}
func (server *Server) Start(address string) error {

View File

@ -26,7 +26,7 @@ func (server *Server) createSupplier(ctx *gin.Context) {
var supDetail pqtype.NullRawMessage
if err := ctx.ShouldBindJSON(&req); err != nil {
ctx.JSON(http.StatusBadRequest, errorResponse(err))
ctx.JSON(http.StatusBadRequest, errorResponse(err, ""))
return
}
@ -44,7 +44,7 @@ func (server *Server) createSupplier(ctx *gin.Context) {
supplier, err := server.store.CreateSuppliers(ctx, arg)
if err != nil {
ctx.JSON(http.StatusInternalServerError, errorResponse(err))
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
return
}

101
api/token.go Normal file
View File

@ -0,0 +1,101 @@
package api
import (
"database/sql"
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
type renewAccessRequest struct {
RefreshToken string `json:"refresh_token" binding:"required"`
}
type renewAccessResponse struct {
AccesToken string `json:"access_token"`
AccessTokenExpiresAt time.Time `json:"access_token_expires_at"`
}
func (server *Server) renewAccessToken(ctx *gin.Context) {
var req renewAccessRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
ctx.JSON(http.StatusBadRequest, errorResponse(err, ""))
return
}
refreshPayload, err := server.tokenMaker.VerifyToken(req.RefreshToken)
if err != nil {
ctx.JSON(http.StatusUnauthorized, errorResponse(err, ""))
return
}
session, err := server.store.GetSession(ctx, refreshPayload.ID)
if err != nil {
if err == sql.ErrNoRows {
ctx.JSON(http.StatusNotFound, errorResponse(err, ""))
return
}
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
return
}
if session.IsBlocked {
err := fmt.Errorf("blocked session")
ctx.JSON(http.StatusUnauthorized, errorResponse(err, ""))
return
}
if session.Email != refreshPayload.Email {
err := fmt.Errorf("incorrect session user")
ctx.JSON(http.StatusUnauthorized, errorResponse(err, ""))
return
}
if session.RefreshToken != req.RefreshToken {
err := fmt.Errorf("mismatched session token")
ctx.JSON(http.StatusUnauthorized, errorResponse(err, ""))
return
}
if time.Now().After(refreshPayload.ExpiredAt) {
err := fmt.Errorf("Exprired session")
ctx.JSON(http.StatusUnauthorized, errorResponse(err, ""))
return
}
user, err := server.store.GetUserByEmail(ctx, refreshPayload.Email)
if err != nil {
if err == sql.ErrNoRows {
ctx.JSON(http.StatusNotFound, errorResponse(err, ""))
return
}
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
return
}
merchant, err := server.store.GetMerchantByUserId(ctx, user.ID)
if err != nil {
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
return
}
accessToken, accessPayload, err := server.tokenMaker.CreateToken(
refreshPayload.Email,
merchant.ID.String(),
server.config.TokenDuration,
)
if err != nil {
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
return
}
res := renewAccessResponse{
AccesToken: accessToken,
AccessTokenExpiresAt: accessPayload.ExpiredAt,
}
ctx.JSON(http.StatusOK, res)
}

186
api/user.go Normal file
View File

@ -0,0 +1,186 @@
package api
import (
"database/sql"
"net/http"
"time"
db "git.nochill.in/nochill/naice_pos/db/sqlc"
"git.nochill.in/nochill/naice_pos/util"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/lib/pq"
)
type createUserMerchantRequest struct {
Email string `json:"email" binding:"required,email"`
Fullname string `json:"fullname" binding:"required,alphanum"`
Password string `json:"password" binding:"required"`
OutletName string `json:"outlet_name" binding:"required"`
}
type userMerchantResponse struct {
ID uuid.UUID `json:"id"`
IndexID int64 `json:"index_id"`
Email string `json:"email"`
Fullname string `json:"fullname"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
OutletID uuid.UUID `json:"outlet_id"`
OutletIndexID int64 `json:"outlet_index_id"`
OutletName string `json:"outlet_name"`
OutletOwnerID uuid.UUID `json:"outlet_owner_id"`
}
func newUserMerchantResponse(user db.GetUserByEmailRow) userMerchantResponse {
return userMerchantResponse{
ID: user.ID,
IndexID: user.IndexID,
Email: user.Email,
Fullname: user.Fullname,
CreatedAt: sql.NullTime{Valid: true, Time: user.CreatedAt.Time},
UpdatedAt: sql.NullTime{Valid: true, Time: user.UpdatedAt.Time},
OutletID: user.ID_2,
OutletIndexID: user.IndexID_2,
OutletName: user.Name,
OutletOwnerID: user.OwnerID,
}
}
func (server *Server) createUserMerchant(ctx *gin.Context) {
var req createUserMerchantRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
ctx.JSON(http.StatusBadRequest, errorResponse(err, ""))
return
}
hashedPassword, err := util.HashPassword(req.Password)
if err != nil {
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
return
}
arg := db.UserMerchantTxParams{
Email: req.Email,
Fullname: req.Fullname,
Password: hashedPassword,
OutletName: req.OutletName,
}
user, err := server.store.CreateUserMerchantTx(ctx, arg)
if err != nil {
if pqErr, ok := err.(*pq.Error); ok {
switch pqErr.Code.Name() {
case "foreign_key_violation", "unique_violation":
ctx.JSON(http.StatusConflict, errorResponse(err, ""))
return
}
}
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
return
}
res := userMerchantResponse{
ID: user.OwnerProfile.ID,
IndexID: user.OwnerProfile.IndexID,
Email: user.OwnerProfile.Email,
Fullname: user.OwnerProfile.Fullname,
CreatedAt: sql.NullTime{Valid: true, Time: user.OwnerProfile.CreatedAt.Time},
UpdatedAt: sql.NullTime{Valid: true, Time: user.OwnerProfile.UpdatedAt.Time},
OutletID: user.OutletProfile.ID,
OutletIndexID: user.OutletProfile.IndexID,
OutletName: user.OutletProfile.Name,
OutletOwnerID: user.OutletProfile.OwnerID,
}
ctx.JSON(http.StatusOK, res)
}
type userLoginRequest struct {
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required"`
}
type userTokenResponse struct {
SessionID uuid.UUID `json:"session_id"`
AccesToken string `json:"access_token"`
AccessTokenExpiresAt time.Time `json:"access_token_expires_at"`
RefreshToken string `json:"refresh_token"`
RefreshTokenExpiresAt time.Time `json:"refresh_token_expires_at"`
}
type userLoginResponse struct {
UserTokenResponse userTokenResponse `json:"user_token"`
UserMerchantResponse userMerchantResponse `json:"user"`
}
func (server *Server) loginUser(ctx *gin.Context) {
var req userLoginRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
ctx.JSON(http.StatusBadRequest, errorResponse(err, ""))
return
}
user, err := server.store.GetUserByEmail(ctx, req.Email)
if err != nil {
if err == sql.ErrNoRows {
ctx.JSON(http.StatusNotFound, errorResponse(err, ""))
return
}
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
return
}
err = util.CheckPassword(req.Password, user.Password)
if err != nil {
ctx.JSON(http.StatusUnauthorized, errorResponse(err, ""))
return
}
accessToken, accessPayload, err := server.tokenMaker.CreateToken(
user.Email,
user.ID_2.String(),
server.config.TokenDuration,
)
if err != nil {
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
return
}
refreshToken, refreshTokenPayload, err := server.tokenMaker.CreateToken(
user.Email,
user.ID_2.String(),
server.config.RefreshTokenDuration,
)
session, err := server.store.CreateSession(ctx, db.CreateSessionParams{
ID: refreshTokenPayload.ID,
Email: user.Email,
RefreshToken: refreshToken,
UserAgent: ctx.Request.UserAgent(),
ClientIp: ctx.ClientIP(),
IsBlocked: false,
ExpiresAt: refreshTokenPayload.ExpiredAt,
})
if err != nil {
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
return
}
tokenResponse := userTokenResponse{
SessionID: session.ID,
AccesToken: accessToken,
AccessTokenExpiresAt: accessPayload.ExpiredAt,
RefreshToken: refreshToken,
RefreshTokenExpiresAt: refreshTokenPayload.ExpiredAt,
}
res := userLoginResponse{
UserTokenResponse: tokenResponse,
UserMerchantResponse: newUserMerchantResponse(user),
}
ctx.JSON(http.StatusOK, res)
}

197
api/user_test.go Normal file
View File

@ -0,0 +1,197 @@
package api
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"reflect"
"testing"
mockdb "git.nochill.in/nochill/naice_pos/db/mock"
db "git.nochill.in/nochill/naice_pos/db/sqlc"
"git.nochill.in/nochill/naice_pos/util"
"github.com/gin-gonic/gin"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"
)
type eqCreateUserMerchantParamsMatcher struct {
arg db.UserMerchantTxParams
password string
}
func (e eqCreateUserMerchantParamsMatcher) Matches(x interface{}) bool {
arg, ok := x.(db.UserMerchantTxParams)
if !ok {
return false
}
err := util.CheckPassword(e.password, arg.Password)
if err != nil {
return false
}
e.arg.Password = arg.Password
return reflect.DeepEqual(e.arg.Password, arg.Password)
}
func (e eqCreateUserMerchantParamsMatcher) String() string {
return fmt.Sprintf("matches arg %v and password %v", e.arg, e.password)
}
func EqCreateUserMerchant(arg db.UserMerchantTxParams, password string) gomock.Matcher {
return eqCreateUserMerchantParamsMatcher{arg, password}
}
func TestCreateUserMerchantAPI(t *testing.T) {
userMerchant, password := randomUser(t)
var userMerchantResult db.UserMerchantTxResult
// var userMerchantResponse createUserMerchantResponse
testCases := []struct {
name string
body gin.H
buildStubs func(store *mockdb.MockStore)
checkResponse func(recorder *httptest.ResponseRecorder)
}{
{
name: "OK",
body: gin.H{
"email": userMerchant.Email,
"fullname": userMerchant.Fullname,
"password": password,
"outlet_name": userMerchant.OutletName,
},
buildStubs: func(store *mockdb.MockStore) {
arg := db.UserMerchantTxParams{
Email: userMerchant.Email,
Fullname: userMerchant.Fullname,
OutletName: userMerchant.OutletName,
}
store.EXPECT().
CreateUserMerchantTx(gomock.Any(), EqCreateUserMerchant(arg, password)).
Times(1).
Return(userMerchantResult, nil)
},
checkResponse: func(recorder *httptest.ResponseRecorder) {
require.Equal(t, http.StatusOK, recorder.Code)
// requireBodyMatchUserMerchant(t, recorder.Body, userMerchantResponse)
},
},
}
for i := range testCases {
tc := testCases[i]
t.Run(tc.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
store := mockdb.NewMockStore(ctrl)
tc.buildStubs(store)
server := newTestServer(t, store)
recorder := httptest.NewRecorder()
data, err := json.Marshal(tc.body)
require.NoError(t, err)
url := "/user/merchants"
request, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data))
require.NoError(t, err)
server.router.ServeHTTP(recorder, request)
tc.checkResponse(recorder)
})
}
}
func TestUserLoginAPI(t *testing.T) {
user, password := randomUser(t)
var userProfile db.User
var userOutletProfile db.Merchant
testCases := []struct {
name string
body gin.H
buildStubs func(store *mockdb.MockStore)
checkResponse func(recorder *httptest.ResponseRecorder)
}{
{
name: "OK",
body: gin.H{
"email": user.Email,
"password": password,
},
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().
GetUserByEmail(gomock.Any(), gomock.Eq(user.Email)).
Times(1).
Return(userProfile, nil)
store.EXPECT().
GetMerchantByUserId(gomock.Any(), gomock.Any()).
Times(1).
Return(userOutletProfile, nil)
store.EXPECT().
CreateSession(gomock.Any(), gomock.Any()).
Times(1)
},
checkResponse: func(recorder *httptest.ResponseRecorder) {
require.Equal(t, http.StatusOK, recorder.Code)
},
},
}
for i := range testCases {
tc := testCases[i]
t.Run(tc.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
store := mockdb.NewMockStore(ctrl)
tc.buildStubs(store)
server := newTestServer(t, store)
recorder := httptest.NewRecorder()
data, err := json.Marshal(tc.body)
require.NoError(t, err)
url := "/user/login"
request, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data))
require.NoError(t, err)
server.router.ServeHTTP(recorder, request)
tc.checkResponse(recorder)
})
}
}
func randomUser(t *testing.T) (userMerchant db.UserMerchantTxParams, password string) {
password = util.RandomString(6)
hashedPassword, err := util.HashPassword(password)
require.NoError(t, err)
userMerchant = db.UserMerchantTxParams{
Email: util.RandomEmail(),
Fullname: util.RandomString(5),
Password: hashedPassword,
OutletName: util.RandomString(10),
}
return
}
func requireBodyMatchUserMerchant(t *testing.T, body *bytes.Buffer, userMerchant userMerchantResponse) {
data, err := ioutil.ReadAll(body)
require.NoError(t, err)
var gotUserMerchant db.UserMerchantTxParams
err = json.Unmarshal(data, &gotUserMerchant)
require.NoError(t, err)
}

View File

@ -3,11 +3,21 @@ CREATE TABLE users(
"index_id" bigserial not null,
"email" varchar unique not null,
"password" varchar not null,
"fullname" varchar,
"fullname" varchar not null,
"created_at" timestamp default(now()),
"updated_at" timestamp default(now())
);
CREATE TABLE product_types(
"id" smallserial primary key not null,
"name" varchar(35) not null
);
INSERT INTO product_types
VALUES
( 1, 'Barang Jadi'),
( 2, 'Bahan Baku');
CREATE TABLE merchants (
"id" uuid default gen_random_uuid() primary key not null,
"index_id" bigserial not null,
@ -16,6 +26,7 @@ CREATE TABLE merchants (
"created_at" timestamp default(now()),
"updated_at" timestamp default(now())
);
create table suppliers (
"id" uuid default gen_random_uuid() primary key not null,
"index_id" bigserial not null,
@ -26,6 +37,7 @@ create table suppliers (
"created_at" timestamp default(now()),
"updated_at" timestamp default(now())
);
CREATE TABLE customers (
"id" uuid default gen_random_uuid() primary key not null,
"index_id" bigserial not null,
@ -36,22 +48,26 @@ CREATE TABLE customers (
"created_at" timestamp default(now()),
"updated_at" timestamp default(now())
);
CREATE TABLE products (
"id" uuid default gen_random_uuid() primary key not null,
"merchant_id" uuid references "merchants"("id") not null,
"product_type_id" smallint references "product_types"("id") default(1) not null,
"index_id" bigserial not null,
"name" varchar not null,
"selling_price" double precision default(0::double precision) NOT NULL,
"purchase_price" double precision default(0:: double precision) NOT NULL,
"stock" double precision default(0::double precision) NOT NULL,
"stock" double precision default(0::double precision) NOT NULL CHECK("stock" >= 0),
"created_at" timestamp default(now()),
"updated_at" timestamp default(now())
);
CREATE TABLE purchase_order (
"id" uuid default gen_random_uuid() primary key not null,
"supplier_id" uuid references "suppliers"("id") not null,
"merchant_id" uuid references "merchants"("id") not null,
"index_id" bigserial not null,
"created_by" uuid references "users"("id") not null,
"code" varchar(100),
"is_paid" boolean not null,
"total" double precision not null,
@ -59,7 +75,7 @@ CREATE TABLE purchase_order (
"note" text,
"created_at" timestamp default(now()),
"updated_at" timestamp default(now())
"updated_at" timestamp default(now())
);
CREATE TABLE purchase_order_detail (
@ -80,6 +96,7 @@ CREATE TABLE sale_order (
"id" uuid default gen_random_uuid() primary key not null,
"index_id" bigserial not null,
"code" text,
"created_by" uuid references "users"("id") not null,
"merchant_id" uuid references "merchants"("id") not null,
"customer_id" uuid references "customers"("id"),
"is_paid" boolean,
@ -90,6 +107,7 @@ CREATE TABLE sale_order (
"created_at" timestamp default(now()),
"updated_at" timestamp default(now())
);
CREATE TABLE sale_order_detail (
"id" uuid default gen_random_uuid() primary key not null,
"index_id" bigserial not null,
@ -107,39 +125,25 @@ CREATE TABLE sale_order_detail (
CREATE INDEX ON "users"("index_id");
CREATE INDEX ON "merchants"("index_id");
CREATE INDEX ON "suppliers"("index_id");
CREATE INDEX ON "customers"("index_id");
CREATE INDEX ON "products" ("name");
CREATE INDEX ON "products" ("selling_price");
CREATE INDEX ON "products" ("index_id");
CREATE INDEX ON "products" ("purchase_price");
CREATE INDEX ON "products" ("stock");
CREATE INDEX ON "purchase_order" ("merchant_id");
CREATE INDEX ON "purchase_order" ("supplier_id");
CREATE INDEX ON "purchase_order" ("index_id");
CREATE INDEX ON "purchase_order" ("created_at");
CREATE INDEX ON "purchase_order_detail" ("index_id");
CREATE INDEX ON "sale_order" ("index_id");
CREATE INDEX ON "sale_order_detail" ("index_id");

View File

@ -0,0 +1 @@
DROP TABLE IF EXISTS user_sessions;

View File

@ -0,0 +1,13 @@
CREATE TABLE user_sessions(
"id" uuid default gen_random_uuid() primary key not null,
"index_id" bigserial not null,
"email" varchar not null references "users"("email") not null,
"refresh_token" varchar not null,
"user_agent" varchar not null,
"client_ip" varchar not null,
"is_blocked" boolean not null default false,
"expires_at" timestamp not null,
"created_at" timestamp default(now())
);
CREATE INDEX ON "user_sessions"("index_id");

View File

@ -0,0 +1,3 @@
ALTER TABLE "products" DROP COLUMN "product_category_id";
DROP TABLE IF EXISTS product_categories;

View File

@ -0,0 +1,8 @@
CREATE TABLE product_categories (
"id" uuid default gen_random_uuid() primary key not null,
"merchant_id" uuid references "merchants"("id") not null,
"index_id" bigserial not null,
"name" varchar not null
);
ALTER TABLE "products" ADD COLUMN "product_category_id" uuid NOT NULL DEFAULT gen_random_uuid();

View File

@ -0,0 +1 @@
ALTER TABLE "products" ALTER COLUMN "product_category_id" SET DEFAULT gen_random_uuid();

View File

@ -0,0 +1 @@
ALTER TABLE products ALTER COLUMN product_category_id DROP DEFAULT;

View File

@ -0,0 +1,2 @@
DROP TABLE IF EXISTS stock_logs;
DROP TYPE IF EXISTS stock_logs_type;

View File

@ -0,0 +1,24 @@
CREATE TYPE stock_logs_type AS ENUM (
'in',
'out'
);
CREATE TABLE stock_logs (
"id" uuid default gen_random_uuid() not null primary key,
"index_id" bigserial not null,
"merchant_id" uuid references "merchants"("id") not null,
"product_id" uuid references "products"("id") not null,
"created_by" uuid references "users"("id") not null,
"transaction_id" uuid,
"transaction_action_type" varchar not null , -- "penjualan / pembelian / pengaturan stok"
"transaction_description" varchar not null,
"type" stock_logs_type not null,
"selling_price" double precision default(0::double precision) NOT NULL,
"purchase_price" double precision default(0:: double precision) NOT NULL,
"quantity" double precision default(0::double precision) NOT NULL,
"created_at" timestamp default(now()),
"updated_at" timestamp default(now())
);
CREATE INDEX ON "stock_logs"("index_id");

View File

@ -0,0 +1 @@
ALTER TABLE sale_order DROP COLUMN IF EXISTS is_keep;

View File

@ -0,0 +1 @@
ALTER TABLE sale_order ADD COLUMN is_keep boolean not null default false;

View File

@ -0,0 +1,3 @@
ALTER TABLE sale_order DROP COLUMN change;
ALTER TABLE sale_order ALTER COLUMN is_paid DROP NOT null;
ALTER TABLE sale_order ALTER COLUMN code DROP NOT null;

View File

@ -0,0 +1,3 @@
ALTER TABLE sale_order ADD COLUMN change double precision default(0::double precision) NOT NULL;
ALTER TABLE sale_order ALTER COLUMN is_paid SET not null;
ALTER TABLE sale_order ALTER COLUMN code SET not null;

View File

@ -0,0 +1 @@
ALTER TABLE product DROP COLUMN IF EXISTS image;

View File

@ -0,0 +1 @@
ALTER TABLE products ADD COLUMN image text;

617
db/mock/store.go Normal file
View File

@ -0,0 +1,617 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: git.nochill.in/nochill/naice_pos/db/sqlc (interfaces: Store)
// Package mockdb is a generated GoMock package.
package mockdb
import (
context "context"
reflect "reflect"
db "git.nochill.in/nochill/naice_pos/db/sqlc"
gomock "github.com/golang/mock/gomock"
uuid "github.com/google/uuid"
)
// MockStore is a mock of Store interface.
type MockStore struct {
ctrl *gomock.Controller
recorder *MockStoreMockRecorder
}
// MockStoreMockRecorder is the mock recorder for MockStore.
type MockStoreMockRecorder struct {
mock *MockStore
}
// NewMockStore creates a new mock instance.
func NewMockStore(ctrl *gomock.Controller) *MockStore {
mock := &MockStore{ctrl: ctrl}
mock.recorder = &MockStoreMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockStore) EXPECT() *MockStoreMockRecorder {
return m.recorder
}
// CreateCustomers mocks base method.
func (m *MockStore) CreateCustomers(arg0 context.Context, arg1 db.CreateCustomersParams) (db.Customer, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateCustomers", arg0, arg1)
ret0, _ := ret[0].(db.Customer)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateCustomers indicates an expected call of CreateCustomers.
func (mr *MockStoreMockRecorder) CreateCustomers(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateCustomers", reflect.TypeOf((*MockStore)(nil).CreateCustomers), arg0, arg1)
}
// CreateMerchant mocks base method.
func (m *MockStore) CreateMerchant(arg0 context.Context, arg1 db.CreateMerchantParams) (db.Merchant, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateMerchant", arg0, arg1)
ret0, _ := ret[0].(db.Merchant)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateMerchant indicates an expected call of CreateMerchant.
func (mr *MockStoreMockRecorder) CreateMerchant(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateMerchant", reflect.TypeOf((*MockStore)(nil).CreateMerchant), arg0, arg1)
}
// CreateProduct mocks base method.
func (m *MockStore) CreateProduct(arg0 context.Context, arg1 db.CreateProductParams) (db.Product, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateProduct", arg0, arg1)
ret0, _ := ret[0].(db.Product)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateProduct indicates an expected call of CreateProduct.
func (mr *MockStoreMockRecorder) CreateProduct(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateProduct", reflect.TypeOf((*MockStore)(nil).CreateProduct), arg0, arg1)
}
// CreateProductCategory mocks base method.
func (m *MockStore) CreateProductCategory(arg0 context.Context, arg1 db.CreateProductCategoryParams) (db.ProductCategory, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateProductCategory", arg0, arg1)
ret0, _ := ret[0].(db.ProductCategory)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateProductCategory indicates an expected call of CreateProductCategory.
func (mr *MockStoreMockRecorder) CreateProductCategory(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateProductCategory", reflect.TypeOf((*MockStore)(nil).CreateProductCategory), arg0, arg1)
}
// CreatePurchaseOrder mocks base method.
func (m *MockStore) CreatePurchaseOrder(arg0 context.Context, arg1 db.CreatePurchaseOrderParams) (db.PurchaseOrder, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreatePurchaseOrder", arg0, arg1)
ret0, _ := ret[0].(db.PurchaseOrder)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreatePurchaseOrder indicates an expected call of CreatePurchaseOrder.
func (mr *MockStoreMockRecorder) CreatePurchaseOrder(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatePurchaseOrder", reflect.TypeOf((*MockStore)(nil).CreatePurchaseOrder), arg0, arg1)
}
// CreatePurchaseOrderDetail mocks base method.
func (m *MockStore) CreatePurchaseOrderDetail(arg0 context.Context, arg1 db.CreatePurchaseOrderDetailParams) (db.PurchaseOrderDetail, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreatePurchaseOrderDetail", arg0, arg1)
ret0, _ := ret[0].(db.PurchaseOrderDetail)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreatePurchaseOrderDetail indicates an expected call of CreatePurchaseOrderDetail.
func (mr *MockStoreMockRecorder) CreatePurchaseOrderDetail(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatePurchaseOrderDetail", reflect.TypeOf((*MockStore)(nil).CreatePurchaseOrderDetail), arg0, arg1)
}
// CreateSaleOrder mocks base method.
func (m *MockStore) CreateSaleOrder(arg0 context.Context, arg1 db.CreateSaleOrderParams) (db.SaleOrder, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateSaleOrder", arg0, arg1)
ret0, _ := ret[0].(db.SaleOrder)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateSaleOrder indicates an expected call of CreateSaleOrder.
func (mr *MockStoreMockRecorder) CreateSaleOrder(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSaleOrder", reflect.TypeOf((*MockStore)(nil).CreateSaleOrder), arg0, arg1)
}
// CreateSaleOrderDetail mocks base method.
func (m *MockStore) CreateSaleOrderDetail(arg0 context.Context, arg1 db.CreateSaleOrderDetailParams) (db.SaleOrderDetail, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateSaleOrderDetail", arg0, arg1)
ret0, _ := ret[0].(db.SaleOrderDetail)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateSaleOrderDetail indicates an expected call of CreateSaleOrderDetail.
func (mr *MockStoreMockRecorder) CreateSaleOrderDetail(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSaleOrderDetail", reflect.TypeOf((*MockStore)(nil).CreateSaleOrderDetail), arg0, arg1)
}
// CreateSession mocks base method.
func (m *MockStore) CreateSession(arg0 context.Context, arg1 db.CreateSessionParams) (db.UserSession, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateSession", arg0, arg1)
ret0, _ := ret[0].(db.UserSession)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateSession indicates an expected call of CreateSession.
func (mr *MockStoreMockRecorder) CreateSession(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSession", reflect.TypeOf((*MockStore)(nil).CreateSession), arg0, arg1)
}
// CreateStockLogs mocks base method.
func (m *MockStore) CreateStockLogs(arg0 context.Context, arg1 db.CreateStockLogsParams) (db.StockLog, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateStockLogs", arg0, arg1)
ret0, _ := ret[0].(db.StockLog)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateStockLogs indicates an expected call of CreateStockLogs.
func (mr *MockStoreMockRecorder) CreateStockLogs(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateStockLogs", reflect.TypeOf((*MockStore)(nil).CreateStockLogs), arg0, arg1)
}
// CreateSuppliers mocks base method.
func (m *MockStore) CreateSuppliers(arg0 context.Context, arg1 db.CreateSuppliersParams) (db.Supplier, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateSuppliers", arg0, arg1)
ret0, _ := ret[0].(db.Supplier)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateSuppliers indicates an expected call of CreateSuppliers.
func (mr *MockStoreMockRecorder) CreateSuppliers(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSuppliers", reflect.TypeOf((*MockStore)(nil).CreateSuppliers), arg0, arg1)
}
// CreateUser mocks base method.
func (m *MockStore) CreateUser(arg0 context.Context, arg1 db.CreateUserParams) (db.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateUser", arg0, arg1)
ret0, _ := ret[0].(db.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateUser indicates an expected call of CreateUser.
func (mr *MockStoreMockRecorder) CreateUser(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateUser", reflect.TypeOf((*MockStore)(nil).CreateUser), arg0, arg1)
}
// CreateUserMerchantTx mocks base method.
func (m *MockStore) CreateUserMerchantTx(arg0 context.Context, arg1 db.UserMerchantTxParams) (db.UserMerchantTxResult, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateUserMerchantTx", arg0, arg1)
ret0, _ := ret[0].(db.UserMerchantTxResult)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateUserMerchantTx indicates an expected call of CreateUserMerchantTx.
func (mr *MockStoreMockRecorder) CreateUserMerchantTx(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateUserMerchantTx", reflect.TypeOf((*MockStore)(nil).CreateUserMerchantTx), arg0, arg1)
}
// CustomersList mocks base method.
func (m *MockStore) CustomersList(arg0 context.Context, arg1 db.CustomersListParams) ([]db.Customer, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CustomersList", arg0, arg1)
ret0, _ := ret[0].([]db.Customer)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CustomersList indicates an expected call of CustomersList.
func (mr *MockStoreMockRecorder) CustomersList(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CustomersList", reflect.TypeOf((*MockStore)(nil).CustomersList), arg0, arg1)
}
// DeleteCustomer mocks base method.
func (m *MockStore) DeleteCustomer(arg0 context.Context, arg1 uuid.UUID) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteCustomer", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteCustomer indicates an expected call of DeleteCustomer.
func (mr *MockStoreMockRecorder) DeleteCustomer(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteCustomer", reflect.TypeOf((*MockStore)(nil).DeleteCustomer), arg0, arg1)
}
// DeleteProduct mocks base method.
func (m *MockStore) DeleteProduct(arg0 context.Context, arg1 uuid.UUID) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteProduct", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteProduct indicates an expected call of DeleteProduct.
func (mr *MockStoreMockRecorder) DeleteProduct(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteProduct", reflect.TypeOf((*MockStore)(nil).DeleteProduct), arg0, arg1)
}
// DeleteProductCategory mocks base method.
func (m *MockStore) DeleteProductCategory(arg0 context.Context, arg1 uuid.UUID) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteProductCategory", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteProductCategory indicates an expected call of DeleteProductCategory.
func (mr *MockStoreMockRecorder) DeleteProductCategory(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteProductCategory", reflect.TypeOf((*MockStore)(nil).DeleteProductCategory), arg0, arg1)
}
// DeleteSupplier mocks base method.
func (m *MockStore) DeleteSupplier(arg0 context.Context, arg1 uuid.UUID) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteSupplier", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteSupplier indicates an expected call of DeleteSupplier.
func (mr *MockStoreMockRecorder) DeleteSupplier(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSupplier", reflect.TypeOf((*MockStore)(nil).DeleteSupplier), arg0, arg1)
}
// GetMerchantById mocks base method.
func (m *MockStore) GetMerchantById(arg0 context.Context, arg1 uuid.UUID) (db.Merchant, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetMerchantById", arg0, arg1)
ret0, _ := ret[0].(db.Merchant)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetMerchantById indicates an expected call of GetMerchantById.
func (mr *MockStoreMockRecorder) GetMerchantById(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMerchantById", reflect.TypeOf((*MockStore)(nil).GetMerchantById), arg0, arg1)
}
// GetMerchantByUserId mocks base method.
func (m *MockStore) GetMerchantByUserId(arg0 context.Context, arg1 uuid.UUID) (db.Merchant, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetMerchantByUserId", arg0, arg1)
ret0, _ := ret[0].(db.Merchant)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetMerchantByUserId indicates an expected call of GetMerchantByUserId.
func (mr *MockStoreMockRecorder) GetMerchantByUserId(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMerchantByUserId", reflect.TypeOf((*MockStore)(nil).GetMerchantByUserId), arg0, arg1)
}
// GetPasswordByEmail mocks base method.
func (m *MockStore) GetPasswordByEmail(arg0 context.Context, arg1 string) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetPasswordByEmail", arg0, arg1)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetPasswordByEmail indicates an expected call of GetPasswordByEmail.
func (mr *MockStoreMockRecorder) GetPasswordByEmail(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPasswordByEmail", reflect.TypeOf((*MockStore)(nil).GetPasswordByEmail), arg0, arg1)
}
// GetProduct mocks base method.
func (m *MockStore) GetProduct(arg0 context.Context, arg1 uuid.UUID) (db.Product, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetProduct", arg0, arg1)
ret0, _ := ret[0].(db.Product)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetProduct indicates an expected call of GetProduct.
func (mr *MockStoreMockRecorder) GetProduct(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProduct", reflect.TypeOf((*MockStore)(nil).GetProduct), arg0, arg1)
}
// GetPurchaseOrderById mocks base method.
func (m *MockStore) GetPurchaseOrderById(arg0 context.Context, arg1 uuid.UUID) (db.PurchaseOrder, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetPurchaseOrderById", arg0, arg1)
ret0, _ := ret[0].(db.PurchaseOrder)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetPurchaseOrderById indicates an expected call of GetPurchaseOrderById.
func (mr *MockStoreMockRecorder) GetPurchaseOrderById(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPurchaseOrderById", reflect.TypeOf((*MockStore)(nil).GetPurchaseOrderById), arg0, arg1)
}
// GetPurchaseOrderDetailsByPuchaseOrderId mocks base method.
func (m *MockStore) GetPurchaseOrderDetailsByPuchaseOrderId(arg0 context.Context, arg1 uuid.UUID) (db.PurchaseOrderDetail, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetPurchaseOrderDetailsByPuchaseOrderId", arg0, arg1)
ret0, _ := ret[0].(db.PurchaseOrderDetail)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetPurchaseOrderDetailsByPuchaseOrderId indicates an expected call of GetPurchaseOrderDetailsByPuchaseOrderId.
func (mr *MockStoreMockRecorder) GetPurchaseOrderDetailsByPuchaseOrderId(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPurchaseOrderDetailsByPuchaseOrderId", reflect.TypeOf((*MockStore)(nil).GetPurchaseOrderDetailsByPuchaseOrderId), arg0, arg1)
}
// GetSession mocks base method.
func (m *MockStore) GetSession(arg0 context.Context, arg1 uuid.UUID) (db.UserSession, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetSession", arg0, arg1)
ret0, _ := ret[0].(db.UserSession)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetSession indicates an expected call of GetSession.
func (mr *MockStoreMockRecorder) GetSession(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSession", reflect.TypeOf((*MockStore)(nil).GetSession), arg0, arg1)
}
// GetStockForUpdateStock mocks base method.
func (m *MockStore) GetStockForUpdateStock(arg0 context.Context, arg1 uuid.UUID) (db.Product, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetStockForUpdateStock", arg0, arg1)
ret0, _ := ret[0].(db.Product)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetStockForUpdateStock indicates an expected call of GetStockForUpdateStock.
func (mr *MockStoreMockRecorder) GetStockForUpdateStock(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStockForUpdateStock", reflect.TypeOf((*MockStore)(nil).GetStockForUpdateStock), arg0, arg1)
}
// GetUserByEmail mocks base method.
func (m *MockStore) GetUserByEmail(arg0 context.Context, arg1 string) (db.GetUserByEmailRow, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetUserByEmail", arg0, arg1)
ret0, _ := ret[0].(db.GetUserByEmailRow)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetUserByEmail indicates an expected call of GetUserByEmail.
func (mr *MockStoreMockRecorder) GetUserByEmail(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserByEmail", reflect.TypeOf((*MockStore)(nil).GetUserByEmail), arg0, arg1)
}
// GetUserById mocks base method.
func (m *MockStore) GetUserById(arg0 context.Context, arg1 uuid.UUID) (db.GetUserByIdRow, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetUserById", arg0, arg1)
ret0, _ := ret[0].(db.GetUserByIdRow)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetUserById indicates an expected call of GetUserById.
func (mr *MockStoreMockRecorder) GetUserById(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserById", reflect.TypeOf((*MockStore)(nil).GetUserById), arg0, arg1)
}
// ListProductCategoriesByMerchantID mocks base method.
func (m *MockStore) ListProductCategoriesByMerchantID(arg0 context.Context, arg1 db.ListProductCategoriesByMerchantIDParams) ([]db.ProductCategory, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListProductCategoriesByMerchantID", arg0, arg1)
ret0, _ := ret[0].([]db.ProductCategory)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ListProductCategoriesByMerchantID indicates an expected call of ListProductCategoriesByMerchantID.
func (mr *MockStoreMockRecorder) ListProductCategoriesByMerchantID(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListProductCategoriesByMerchantID", reflect.TypeOf((*MockStore)(nil).ListProductCategoriesByMerchantID), arg0, arg1)
}
// ListProducts mocks base method.
func (m *MockStore) ListProducts(arg0 context.Context, arg1 db.ListProductsParams) ([]db.Product, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListProducts", arg0, arg1)
ret0, _ := ret[0].([]db.Product)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ListProducts indicates an expected call of ListProducts.
func (mr *MockStoreMockRecorder) ListProducts(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListProducts", reflect.TypeOf((*MockStore)(nil).ListProducts), arg0, arg1)
}
// ListPurchaseOrderByMerchantId mocks base method.
func (m *MockStore) ListPurchaseOrderByMerchantId(arg0 context.Context, arg1 uuid.UUID) ([]db.PurchaseOrder, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListPurchaseOrderByMerchantId", arg0, arg1)
ret0, _ := ret[0].([]db.PurchaseOrder)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ListPurchaseOrderByMerchantId indicates an expected call of ListPurchaseOrderByMerchantId.
func (mr *MockStoreMockRecorder) ListPurchaseOrderByMerchantId(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListPurchaseOrderByMerchantId", reflect.TypeOf((*MockStore)(nil).ListPurchaseOrderByMerchantId), arg0, arg1)
}
// PurchaseOrderTx mocks base method.
func (m *MockStore) PurchaseOrderTx(arg0 context.Context, arg1 db.PurchasoOrderTxParams) (db.PurchaseOrderTxResult, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "PurchaseOrderTx", arg0, arg1)
ret0, _ := ret[0].(db.PurchaseOrderTxResult)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// PurchaseOrderTx indicates an expected call of PurchaseOrderTx.
func (mr *MockStoreMockRecorder) PurchaseOrderTx(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PurchaseOrderTx", reflect.TypeOf((*MockStore)(nil).PurchaseOrderTx), arg0, arg1)
}
// SaleOrderTx mocks base method.
func (m *MockStore) SaleOrderTx(arg0 context.Context, arg1 db.SaleOrderTxParams) (db.SaleOrderTxResult, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SaleOrderTx", arg0, arg1)
ret0, _ := ret[0].(db.SaleOrderTxResult)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SaleOrderTx indicates an expected call of SaleOrderTx.
func (mr *MockStoreMockRecorder) SaleOrderTx(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaleOrderTx", reflect.TypeOf((*MockStore)(nil).SaleOrderTx), arg0, arg1)
}
// SuppliersList mocks base method.
func (m *MockStore) SuppliersList(arg0 context.Context, arg1 db.SuppliersListParams) ([]db.Supplier, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SuppliersList", arg0, arg1)
ret0, _ := ret[0].([]db.Supplier)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SuppliersList indicates an expected call of SuppliersList.
func (mr *MockStoreMockRecorder) SuppliersList(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SuppliersList", reflect.TypeOf((*MockStore)(nil).SuppliersList), arg0, arg1)
}
// UpdateCustomer mocks base method.
func (m *MockStore) UpdateCustomer(arg0 context.Context, arg1 uuid.UUID) (db.Customer, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateCustomer", arg0, arg1)
ret0, _ := ret[0].(db.Customer)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateCustomer indicates an expected call of UpdateCustomer.
func (mr *MockStoreMockRecorder) UpdateCustomer(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateCustomer", reflect.TypeOf((*MockStore)(nil).UpdateCustomer), arg0, arg1)
}
// UpdateProduct mocks base method.
func (m *MockStore) UpdateProduct(arg0 context.Context, arg1 db.UpdateProductParams) (db.Product, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateProduct", arg0, arg1)
ret0, _ := ret[0].(db.Product)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateProduct indicates an expected call of UpdateProduct.
func (mr *MockStoreMockRecorder) UpdateProduct(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateProduct", reflect.TypeOf((*MockStore)(nil).UpdateProduct), arg0, arg1)
}
// UpdateProductCategory mocks base method.
func (m *MockStore) UpdateProductCategory(arg0 context.Context, arg1 db.UpdateProductCategoryParams) (db.ProductCategory, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateProductCategory", arg0, arg1)
ret0, _ := ret[0].(db.ProductCategory)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateProductCategory indicates an expected call of UpdateProductCategory.
func (mr *MockStoreMockRecorder) UpdateProductCategory(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateProductCategory", reflect.TypeOf((*MockStore)(nil).UpdateProductCategory), arg0, arg1)
}
// UpdateProductStock mocks base method.
func (m *MockStore) UpdateProductStock(arg0 context.Context, arg1 db.UpdateProductStockParams) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateProductStock", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateProductStock indicates an expected call of UpdateProductStock.
func (mr *MockStoreMockRecorder) UpdateProductStock(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateProductStock", reflect.TypeOf((*MockStore)(nil).UpdateProductStock), arg0, arg1)
}
// UpdateSupplier mocks base method.
func (m *MockStore) UpdateSupplier(arg0 context.Context, arg1 uuid.UUID) (db.Supplier, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateSupplier", arg0, arg1)
ret0, _ := ret[0].(db.Supplier)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateSupplier indicates an expected call of UpdateSupplier.
func (mr *MockStoreMockRecorder) UpdateSupplier(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateSupplier", reflect.TypeOf((*MockStore)(nil).UpdateSupplier), arg0, arg1)
}

View File

@ -1,3 +1,8 @@
-- name: CreateMerchant :one
INSERT INTO merchants (name,owner_id)
VALUES ($1, $2)
RETURNING *;
-- name: GetMerchantById :one
SELECT * FROM merchants
WHERE id = $1;

View File

@ -3,10 +3,13 @@ INSERT INTO products (
merchant_id,
name,
selling_price,
product_type_id,
purchase_price,
product_category_id,
image,
stock
) VALUES (
$1, $2, $3, $4, $5
$1, $2, $3, $4, $5, $6, $7, $8
)
RETURNING *;
@ -27,13 +30,19 @@ WHERE id = $1;
-- name: ListProducts :many
SELECT * FROM products
WHERE merchant_id = $1
ORDER BY index_id
LIMIT $1
OFFSET $2;
LIMIT $2
OFFSET $3;
-- name: UpdateProduct :one
UPDATE products
SET name = $2, selling_price = $3, purchase_price = $4, stock = $5, updated_at = $6
SET name = $2,
selling_price = $3,
purchase_price = $4,
product_category_id = $5,
image = $6,
updated_at = $7
WHERE id = $1
RETURNING *;

View File

@ -0,0 +1,24 @@
-- name: CreateProductCategory :one
INSERT INTO product_categories (
merchant_id,
name
) VALUES (
$1, $2
)
RETURNING *;
-- name: ListProductCategoriesByMerchantID :many
SELECT * FROM product_categories
WHERE merchant_id = $1
ORDER BY index_id
LIMIT $2
OFFSET $3;
-- name: UpdateProductCategory :one
UPDATE product_categories
SET name = $1
WHERE id = $2
RETURNING *;
-- name: DeleteProductCategory :exec
DELETE FROM product_categories WHERE id = $1;

View File

@ -6,8 +6,19 @@ INSERT INTO purchase_order (
is_paid,
total,
paid_nominal,
note
note,
created_by
) VALUES (
$1, $2, $3, $4, $5, $6, $7
$1, $2, $3, $4, $5, $6, $7, $8
)
RETURNING *;
RETURNING *;
-- name: GetPurchaseOrderById :one
SELECT *
FROM purchase_order
WHERE id = $1;
-- name: ListPurchaseOrderByMerchantId :many
SELECT *
FROM purchase_order
WHERE merchant_id = $1;

View File

@ -9,4 +9,9 @@ INSERT INTO purchase_order_detail (
) VALUES (
$1, $2, $3, $4, $5, $6
)
RETURNING *;
RETURNING *;
-- name: GetPurchaseOrderDetailsByPuchaseOrderId :one
SELECT *
FROM purchase_order_detail
WHERE purchase_order_id = $1;

16
db/query/sale_order.sql Normal file
View File

@ -0,0 +1,16 @@
-- name: CreateSaleOrder :one
INSERT INTO sale_order (
merchant_id,
customer_id,
code,
created_by,
is_paid,
total,
paid_nominal,
note,
change,
is_keep
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10
)
RETURNING *;

View File

@ -0,0 +1,13 @@
-- name: CreateSaleOrderDetail :one
INSERT INTO sale_order_detail (
sale_order_id,
product_id,
product_name,
quantity,
sub_total,
product_price,
profit
) VALUES(
$1, $2, $3, $4, $5, $6, $7
)
RETURNING *;

17
db/query/sessions.sql Normal file
View File

@ -0,0 +1,17 @@
-- name: CreateSession :one
INSERT INTO user_sessions (
id,
email,
refresh_token,
user_agent,
client_ip,
is_blocked,
expires_at
) VALUES (
$1, $2, $3, $4, $5, $6, $7
) RETURNING *;
-- name: GetSession :one
SELECT * FROM user_sessions
WHERE id = $1
LIMIT 1;

16
db/query/stock_log.sql Normal file
View File

@ -0,0 +1,16 @@
-- name: CreateStockLogs :one
INSERT INTO stock_logs (
product_id,
merchant_id,
created_by,
transaction_id,
transaction_action_type,
transaction_description,
type,
selling_price,
purchase_price,
quantity
) VALUES (
$1, $2, $3, $4, $5 ,$6, $7, $8, $9, $10
)
RETURNING *;

24
db/query/users.sql Normal file
View File

@ -0,0 +1,24 @@
-- name: CreateUser :one
INSERT INTO users (
email,
password,
fullname
) VALUES ($1, $2, $3)
RETURNING *;
-- name: GetPasswordByEmail :one
SELECT password
FROM users
WHERE email = $1;
-- name: GetUserById :one
SELECT *
FROM users u
JOIN merchants m on u.id = m.owner_id
WHERE u.id = $1;
-- name: GetUserByEmail :one
SELECT *
FROM users u
JOIN merchants m on u.id = m.owner_id
WHERE email = $1;

34
db/sqlc/customer_test.go Normal file
View File

@ -0,0 +1,34 @@
package db
import (
"context"
"testing"
"git.nochill.in/nochill/naice_pos/util"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
)
func createRandomCustomer(t *testing.T) (Customer, CreateCustomersParams) {
arg := CreateCustomersParams{
MerchantID: uuid.MustParse("7e525a4b-4208-4f05-99a1-a75df475dd9b"),
Name: util.RandomString(10),
}
customer, err := testQueries.CreateCustomers(context.Background(), arg)
require.NoError(t, err)
return customer, arg
}
func TestCreateCustomer(t *testing.T) {
supplier, arg := createRandomCustomer(t)
require.Equal(t, arg.Name, supplier.Name)
require.Equal(t, arg.MerchantID, supplier.MerchantID)
require.NotZero(t, supplier.ID)
require.NotZero(t, supplier.CreatedAt)
require.NotZero(t, supplier.UpdatedAt)
}

View File

@ -65,7 +65,7 @@ func (q *Queries) CustomersList(ctx context.Context, arg CustomersListParams) ([
return nil, err
}
defer rows.Close()
var items []Customer
items := []Customer{}
for rows.Next() {
var i Customer
if err := rows.Scan(

View File

@ -6,20 +6,21 @@ import (
"os"
"testing"
"git.nochill.in/nochill/naice_pos/util"
_ "github.com/lib/pq"
)
const (
dbDriver = "postgres"
dbSource = "postgresql://postgres:awksed123@localhost:5432/nice_pos?sslmode=disable"
)
var testQueries *Queries
var testDB *sql.DB
func TestMain(m *testing.M) {
var err error
testDB, err = sql.Open(dbDriver, dbSource)
config, err := util.LoadConfig("../..")
if err != nil {
log.Fatal("cannot load config: ", err)
}
testDB, err = sql.Open(config.DBDriver, config.DBSource)
if err != nil {
log.Fatal("cannot connect to db:", err)
}

View File

@ -1,7 +1,7 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.17.2
// source: merchant.sql
// source: merchants.sql
package db
@ -11,6 +11,31 @@ import (
"github.com/google/uuid"
)
const createMerchant = `-- name: CreateMerchant :one
INSERT INTO merchants (name,owner_id)
VALUES ($1, $2)
RETURNING id, index_id, name, owner_id, created_at, updated_at
`
type CreateMerchantParams struct {
Name string `json:"name"`
OwnerID uuid.UUID `json:"owner_id"`
}
func (q *Queries) CreateMerchant(ctx context.Context, arg CreateMerchantParams) (Merchant, error) {
row := q.db.QueryRowContext(ctx, createMerchant, arg.Name, arg.OwnerID)
var i Merchant
err := row.Scan(
&i.ID,
&i.IndexID,
&i.Name,
&i.OwnerID,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const getMerchantById = `-- name: GetMerchantById :one
SELECT id, index_id, name, owner_id, created_at, updated_at FROM merchants
WHERE id = $1

View File

@ -0,0 +1 @@
package db

View File

@ -6,12 +6,57 @@ package db
import (
"database/sql"
"database/sql/driver"
"encoding/json"
"fmt"
"time"
"github.com/google/uuid"
"github.com/tabbed/pqtype"
)
type StockLogsType string
const (
StockLogsTypeIn StockLogsType = "in"
StockLogsTypeOut StockLogsType = "out"
)
func (e *StockLogsType) Scan(src interface{}) error {
switch s := src.(type) {
case []byte:
*e = StockLogsType(s)
case string:
*e = StockLogsType(s)
default:
return fmt.Errorf("unsupported scan type for StockLogsType: %T", src)
}
return nil
}
type NullStockLogsType struct {
StockLogsType StockLogsType
Valid bool // Valid is true if StockLogsType is not NULL
}
// Scan implements the Scanner interface.
func (ns *NullStockLogsType) Scan(value interface{}) error {
if value == nil {
ns.StockLogsType, ns.Valid = "", false
return nil
}
ns.Valid = true
return ns.StockLogsType.Scan(value)
}
// Value implements the driver Valuer interface.
func (ns NullStockLogsType) Value() (driver.Value, error) {
if !ns.Valid {
return nil, nil
}
return string(ns.StockLogsType), nil
}
type Customer struct {
ID uuid.UUID `json:"id"`
IndexID int64 `json:"index_id"`
@ -32,15 +77,30 @@ type Merchant struct {
}
type Product struct {
ID uuid.UUID `json:"id"`
MerchantID uuid.UUID `json:"merchant_id"`
IndexID int64 `json:"index_id"`
Name string `json:"name"`
SellingPrice float64 `json:"selling_price"`
PurchasePrice float64 `json:"purchase_price"`
Stock float64 `json:"stock"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
ID uuid.UUID `json:"id"`
MerchantID uuid.UUID `json:"merchant_id"`
ProductTypeID int16 `json:"product_type_id"`
IndexID int64 `json:"index_id"`
Name string `json:"name"`
SellingPrice float64 `json:"selling_price"`
PurchasePrice float64 `json:"purchase_price"`
Stock float64 `json:"stock"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
ProductCategoryID uuid.UUID `json:"product_category_id"`
Image sql.NullString `json:"image"`
}
type ProductCategory struct {
ID uuid.UUID `json:"id"`
MerchantID uuid.UUID `json:"merchant_id"`
IndexID int64 `json:"index_id"`
Name string `json:"name"`
}
type ProductType struct {
ID int16 `json:"id"`
Name string `json:"name"`
}
type PurchaseOrder struct {
@ -48,6 +108,7 @@ type PurchaseOrder struct {
SupplierID uuid.UUID `json:"supplier_id"`
MerchantID uuid.UUID `json:"merchant_id"`
IndexID int64 `json:"index_id"`
CreatedBy uuid.UUID `json:"created_by"`
Code sql.NullString `json:"code"`
IsPaid bool `json:"is_paid"`
Total float64 `json:"total"`
@ -74,15 +135,18 @@ type PurchaseOrderDetail struct {
type SaleOrder struct {
ID uuid.UUID `json:"id"`
IndexID int64 `json:"index_id"`
Code sql.NullString `json:"code"`
Code string `json:"code"`
CreatedBy uuid.UUID `json:"created_by"`
MerchantID uuid.UUID `json:"merchant_id"`
CustomerID uuid.NullUUID `json:"customer_id"`
IsPaid sql.NullBool `json:"is_paid"`
IsPaid bool `json:"is_paid"`
Total float64 `json:"total"`
PaidNominal float64 `json:"paid_nominal"`
Note sql.NullString `json:"note"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
IsKeep bool `json:"is_keep"`
Change float64 `json:"change"`
}
type SaleOrderDetail struct {
@ -99,6 +163,23 @@ type SaleOrderDetail struct {
UpdatedAt sql.NullTime `json:"updated_at"`
}
type StockLog struct {
ID uuid.UUID `json:"id"`
IndexID int64 `json:"index_id"`
MerchantID uuid.UUID `json:"merchant_id"`
ProductID uuid.UUID `json:"product_id"`
CreatedBy uuid.UUID `json:"created_by"`
TransactionID uuid.NullUUID `json:"transaction_id"`
TransactionActionType string `json:"transaction_action_type"`
TransactionDescription string `json:"transaction_description"`
Type StockLogsType `json:"type"`
SellingPrice float64 `json:"selling_price"`
PurchasePrice float64 `json:"purchase_price"`
Quantity float64 `json:"quantity"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
}
type Supplier struct {
ID uuid.UUID `json:"id"`
IndexID int64 `json:"index_id"`
@ -110,11 +191,23 @@ type Supplier struct {
}
type User struct {
ID uuid.UUID `json:"id"`
IndexID int64 `json:"index_id"`
Email string `json:"email"`
Password string `json:"password"`
Fullname sql.NullString `json:"fullname"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
ID uuid.UUID `json:"id"`
IndexID int64 `json:"index_id"`
Email string `json:"email"`
Password string `json:"password"`
Fullname string `json:"fullname"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
}
type UserSession struct {
ID uuid.UUID `json:"id"`
IndexID int64 `json:"index_id"`
Email string `json:"email"`
RefreshToken string `json:"refresh_token"`
UserAgent string `json:"user_agent"`
ClientIp string `json:"client_ip"`
IsBlocked bool `json:"is_blocked"`
ExpiresAt time.Time `json:"expires_at"`
CreatedAt sql.NullTime `json:"created_at"`
}

View File

@ -1,7 +1,7 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.17.2
// source: products.sql
// source: product.sql
package db
@ -17,20 +17,26 @@ INSERT INTO products (
merchant_id,
name,
selling_price,
product_type_id,
purchase_price,
product_category_id,
image,
stock
) VALUES (
$1, $2, $3, $4, $5
$1, $2, $3, $4, $5, $6, $7, $8
)
RETURNING id, merchant_id, index_id, name, selling_price, purchase_price, stock, created_at, updated_at
RETURNING id, merchant_id, product_type_id, index_id, name, selling_price, purchase_price, stock, created_at, updated_at, product_category_id, image
`
type CreateProductParams struct {
MerchantID uuid.UUID `json:"merchant_id"`
Name string `json:"name"`
SellingPrice float64 `json:"selling_price"`
PurchasePrice float64 `json:"purchase_price"`
Stock float64 `json:"stock"`
MerchantID uuid.UUID `json:"merchant_id"`
Name string `json:"name"`
SellingPrice float64 `json:"selling_price"`
ProductTypeID int16 `json:"product_type_id"`
PurchasePrice float64 `json:"purchase_price"`
ProductCategoryID uuid.UUID `json:"product_category_id"`
Image sql.NullString `json:"image"`
Stock float64 `json:"stock"`
}
func (q *Queries) CreateProduct(ctx context.Context, arg CreateProductParams) (Product, error) {
@ -38,13 +44,17 @@ func (q *Queries) CreateProduct(ctx context.Context, arg CreateProductParams) (P
arg.MerchantID,
arg.Name,
arg.SellingPrice,
arg.ProductTypeID,
arg.PurchasePrice,
arg.ProductCategoryID,
arg.Image,
arg.Stock,
)
var i Product
err := row.Scan(
&i.ID,
&i.MerchantID,
&i.ProductTypeID,
&i.IndexID,
&i.Name,
&i.SellingPrice,
@ -52,6 +62,8 @@ func (q *Queries) CreateProduct(ctx context.Context, arg CreateProductParams) (P
&i.Stock,
&i.CreatedAt,
&i.UpdatedAt,
&i.ProductCategoryID,
&i.Image,
)
return i, err
}
@ -66,7 +78,7 @@ func (q *Queries) DeleteProduct(ctx context.Context, id uuid.UUID) error {
}
const getProduct = `-- name: GetProduct :one
SELECT id, merchant_id, index_id, name, selling_price, purchase_price, stock, created_at, updated_at FROM products
SELECT id, merchant_id, product_type_id, index_id, name, selling_price, purchase_price, stock, created_at, updated_at, product_category_id, image FROM products
WHERE id = $1
`
@ -76,6 +88,7 @@ func (q *Queries) GetProduct(ctx context.Context, id uuid.UUID) (Product, error)
err := row.Scan(
&i.ID,
&i.MerchantID,
&i.ProductTypeID,
&i.IndexID,
&i.Name,
&i.SellingPrice,
@ -83,12 +96,14 @@ func (q *Queries) GetProduct(ctx context.Context, id uuid.UUID) (Product, error)
&i.Stock,
&i.CreatedAt,
&i.UpdatedAt,
&i.ProductCategoryID,
&i.Image,
)
return i, err
}
const getStockForUpdateStock = `-- name: GetStockForUpdateStock :one
SELECT id, merchant_id, index_id, name, selling_price, purchase_price, stock, created_at, updated_at FROM products
SELECT id, merchant_id, product_type_id, index_id, name, selling_price, purchase_price, stock, created_at, updated_at, product_category_id, image FROM products
WHERE id = $1
LIMIT 1
FOR NO KEY UPDATE
@ -100,6 +115,7 @@ func (q *Queries) GetStockForUpdateStock(ctx context.Context, id uuid.UUID) (Pro
err := row.Scan(
&i.ID,
&i.MerchantID,
&i.ProductTypeID,
&i.IndexID,
&i.Name,
&i.SellingPrice,
@ -107,34 +123,39 @@ func (q *Queries) GetStockForUpdateStock(ctx context.Context, id uuid.UUID) (Pro
&i.Stock,
&i.CreatedAt,
&i.UpdatedAt,
&i.ProductCategoryID,
&i.Image,
)
return i, err
}
const listProducts = `-- name: ListProducts :many
SELECT id, merchant_id, index_id, name, selling_price, purchase_price, stock, created_at, updated_at FROM products
SELECT id, merchant_id, product_type_id, index_id, name, selling_price, purchase_price, stock, created_at, updated_at, product_category_id, image FROM products
WHERE merchant_id = $1
ORDER BY index_id
LIMIT $1
OFFSET $2
LIMIT $2
OFFSET $3
`
type ListProductsParams struct {
Limit int32 `json:"limit"`
Offset int32 `json:"offset"`
MerchantID uuid.UUID `json:"merchant_id"`
Limit int32 `json:"limit"`
Offset int32 `json:"offset"`
}
func (q *Queries) ListProducts(ctx context.Context, arg ListProductsParams) ([]Product, error) {
rows, err := q.db.QueryContext(ctx, listProducts, arg.Limit, arg.Offset)
rows, err := q.db.QueryContext(ctx, listProducts, arg.MerchantID, arg.Limit, arg.Offset)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Product
items := []Product{}
for rows.Next() {
var i Product
if err := rows.Scan(
&i.ID,
&i.MerchantID,
&i.ProductTypeID,
&i.IndexID,
&i.Name,
&i.SellingPrice,
@ -142,6 +163,8 @@ func (q *Queries) ListProducts(ctx context.Context, arg ListProductsParams) ([]P
&i.Stock,
&i.CreatedAt,
&i.UpdatedAt,
&i.ProductCategoryID,
&i.Image,
); err != nil {
return nil, err
}
@ -158,18 +181,24 @@ func (q *Queries) ListProducts(ctx context.Context, arg ListProductsParams) ([]P
const updateProduct = `-- name: UpdateProduct :one
UPDATE products
SET name = $2, selling_price = $3, purchase_price = $4, stock = $5, updated_at = $6
SET name = $2,
selling_price = $3,
purchase_price = $4,
product_category_id = $5,
image = $6,
updated_at = $7
WHERE id = $1
RETURNING id, merchant_id, index_id, name, selling_price, purchase_price, stock, created_at, updated_at
RETURNING id, merchant_id, product_type_id, index_id, name, selling_price, purchase_price, stock, created_at, updated_at, product_category_id, image
`
type UpdateProductParams struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
SellingPrice float64 `json:"selling_price"`
PurchasePrice float64 `json:"purchase_price"`
Stock float64 `json:"stock"`
UpdatedAt sql.NullTime `json:"updated_at"`
ID uuid.UUID `json:"id"`
Name string `json:"name"`
SellingPrice float64 `json:"selling_price"`
PurchasePrice float64 `json:"purchase_price"`
ProductCategoryID uuid.UUID `json:"product_category_id"`
Image sql.NullString `json:"image"`
UpdatedAt sql.NullTime `json:"updated_at"`
}
func (q *Queries) UpdateProduct(ctx context.Context, arg UpdateProductParams) (Product, error) {
@ -178,13 +207,15 @@ func (q *Queries) UpdateProduct(ctx context.Context, arg UpdateProductParams) (P
arg.Name,
arg.SellingPrice,
arg.PurchasePrice,
arg.Stock,
arg.ProductCategoryID,
arg.Image,
arg.UpdatedAt,
)
var i Product
err := row.Scan(
&i.ID,
&i.MerchantID,
&i.ProductTypeID,
&i.IndexID,
&i.Name,
&i.SellingPrice,
@ -192,6 +223,8 @@ func (q *Queries) UpdateProduct(ctx context.Context, arg UpdateProductParams) (P
&i.Stock,
&i.CreatedAt,
&i.UpdatedAt,
&i.ProductCategoryID,
&i.Image,
)
return i, err
}

View File

@ -0,0 +1,114 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.17.2
// source: product_category.sql
package db
import (
"context"
"github.com/google/uuid"
)
const createProductCategory = `-- name: CreateProductCategory :one
INSERT INTO product_categories (
merchant_id,
name
) VALUES (
$1, $2
)
RETURNING id, merchant_id, index_id, name
`
type CreateProductCategoryParams struct {
MerchantID uuid.UUID `json:"merchant_id"`
Name string `json:"name"`
}
func (q *Queries) CreateProductCategory(ctx context.Context, arg CreateProductCategoryParams) (ProductCategory, error) {
row := q.db.QueryRowContext(ctx, createProductCategory, arg.MerchantID, arg.Name)
var i ProductCategory
err := row.Scan(
&i.ID,
&i.MerchantID,
&i.IndexID,
&i.Name,
)
return i, err
}
const deleteProductCategory = `-- name: DeleteProductCategory :exec
DELETE FROM product_categories WHERE id = $1
`
func (q *Queries) DeleteProductCategory(ctx context.Context, id uuid.UUID) error {
_, err := q.db.ExecContext(ctx, deleteProductCategory, id)
return err
}
const listProductCategoriesByMerchantID = `-- name: ListProductCategoriesByMerchantID :many
SELECT id, merchant_id, index_id, name FROM product_categories
WHERE merchant_id = $1
ORDER BY index_id
LIMIT $2
OFFSET $3
`
type ListProductCategoriesByMerchantIDParams struct {
MerchantID uuid.UUID `json:"merchant_id"`
Limit int32 `json:"limit"`
Offset int32 `json:"offset"`
}
func (q *Queries) ListProductCategoriesByMerchantID(ctx context.Context, arg ListProductCategoriesByMerchantIDParams) ([]ProductCategory, error) {
rows, err := q.db.QueryContext(ctx, listProductCategoriesByMerchantID, arg.MerchantID, arg.Limit, arg.Offset)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ProductCategory{}
for rows.Next() {
var i ProductCategory
if err := rows.Scan(
&i.ID,
&i.MerchantID,
&i.IndexID,
&i.Name,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const updateProductCategory = `-- name: UpdateProductCategory :one
UPDATE product_categories
SET name = $1
WHERE id = $2
RETURNING id, merchant_id, index_id, name
`
type UpdateProductCategoryParams struct {
Name string `json:"name"`
ID uuid.UUID `json:"id"`
}
func (q *Queries) UpdateProductCategory(ctx context.Context, arg UpdateProductCategoryParams) (ProductCategory, error) {
row := q.db.QueryRowContext(ctx, updateProductCategory, arg.Name, arg.ID)
var i ProductCategory
err := row.Scan(
&i.ID,
&i.MerchantID,
&i.IndexID,
&i.Name,
)
return i, err
}

View File

@ -0,0 +1,72 @@
package db
import (
"context"
"testing"
"git.nochill.in/nochill/naice_pos/util"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
)
var merchantID = uuid.MustParse("7e525a4b-4208-4f05-99a1-a75df475dd9b")
func createRandomProductCategory(t *testing.T) ProductCategory {
arg := CreateProductCategoryParams{
MerchantID: merchantID,
Name: util.RandomString(6),
}
productCategory, err := testQueries.CreateProductCategory(context.Background(), arg)
require.NoError(t, err)
require.Equal(t, productCategory.Name, arg.Name)
return productCategory
}
func TestCreateProductCategory(t *testing.T) {
createRandomProductCategory(t)
}
func TestListProductCategories(t *testing.T) {
tries := 5
for i := 0; i < tries; i++ {
createRandomProductCategory(t)
}
arg := ListProductCategoriesByMerchantIDParams{
MerchantID: merchantID,
Limit: 5,
Offset: 0,
}
listProductCategories, err := testQueries.ListProductCategoriesByMerchantID(context.Background(), arg)
require.NoError(t, err)
for _, productCategory := range listProductCategories {
require.NotEmpty(t, productCategory)
require.Equal(t, merchantID, productCategory.MerchantID)
}
}
func TestUpdateProductCategory(t *testing.T) {
productCategory := createRandomProductCategory(t)
arg := UpdateProductCategoryParams{
Name: util.RandomString(5),
ID: productCategory.ID,
}
updatedProductCategory, err := testQueries.UpdateProductCategory(context.Background(), arg)
require.NoError(t, err)
require.NotEqual(t, updatedProductCategory.Name, productCategory.Name)
require.Equal(t, arg.Name, updatedProductCategory.Name)
}
func TestDeleteProductCategory(t *testing.T) {
productCategory := createRandomProductCategory(t)
err := testQueries.DeleteProductCategory(context.Background(), productCategory.ID)
require.NoError(t, err)
}

View File

@ -13,16 +13,20 @@ import (
func createRandomProduct(t *testing.T) (Product, CreateProductParams) {
sellingPrice, _ := util.RandomFloat(999, 99999)
purchasePrice, _ := util.RandomFloat(999, 9999)
stock, _ := util.RandomFloat(10, 10000)
sellingPrice := util.RandomFloat(999, 99999)
purchasePrice := util.RandomFloat(999, 9999)
stock := util.RandomFloat(10, 10000)
productCategory := createRandomProductCategory(t)
arg := CreateProductParams{
MerchantID: uuid.MustParse("d9b0e126-991e-46ac-8c61-5efdd605f75d"),
Name: util.RandomString(10),
SellingPrice: sellingPrice,
PurchasePrice: purchasePrice,
Stock: stock,
MerchantID: uuid.MustParse("7e525a4b-4208-4f05-99a1-a75df475dd9b"),
ProductTypeID: 1,
Name: util.RandomString(10),
SellingPrice: sellingPrice,
PurchasePrice: purchasePrice,
ProductCategoryID: productCategory.ID,
Stock: stock,
Image: sql.NullString{Valid: false},
}
product, err := testQueries.CreateProduct(context.Background(), arg)
@ -76,7 +80,6 @@ func TestUpdateProduct(t *testing.T) {
Name: util.RandomString(6),
SellingPrice: float64(200),
PurchasePrice: float64(200),
Stock: float64(200),
}
updatedProduct, err := testQueries.UpdateProduct(context.Background(), arg)
@ -88,7 +91,6 @@ func TestUpdateProduct(t *testing.T) {
require.Equal(t, arg.Name, updatedProduct.Name)
require.Equal(t, arg.PurchasePrice, updatedProduct.PurchasePrice)
require.Equal(t, arg.SellingPrice, updatedProduct.SellingPrice)
require.Equal(t, arg.Stock, updatedProduct.Stock)
require.NotSame(t, createProduct.Name, updatedProduct.Name)
require.NotSame(t, createProduct.SellingPrice, updatedProduct.SellingPrice)
@ -108,21 +110,24 @@ func TestDeleteProduct(t *testing.T) {
}
func TestGetProducts(t *testing.T) {
var lastProduct Product
for i := 0; i < 6; i++ {
createRandomProduct(t)
lastProduct, _ = createRandomProduct(t)
}
arg := ListProductsParams{
Limit: 5,
Offset: 1,
MerchantID: lastProduct.MerchantID,
Limit: 5,
Offset: 0,
}
products, err := testQueries.ListProducts(context.Background(), arg)
require.NoError(t, err)
require.Len(t, products, 5)
require.NotEmpty(t, products)
for _, product := range products {
require.NotEmpty(t, product)
require.Equal(t, lastProduct.MerchantID, product.MerchantID)
}
}

View File

@ -20,11 +20,12 @@ INSERT INTO purchase_order (
is_paid,
total,
paid_nominal,
note
note,
created_by
) VALUES (
$1, $2, $3, $4, $5, $6, $7
$1, $2, $3, $4, $5, $6, $7, $8
)
RETURNING id, supplier_id, merchant_id, index_id, code, is_paid, total, paid_nominal, note, created_at, updated_at
RETURNING id, supplier_id, merchant_id, index_id, created_by, code, is_paid, total, paid_nominal, note, created_at, updated_at
`
type CreatePurchaseOrderParams struct {
@ -35,6 +36,7 @@ type CreatePurchaseOrderParams struct {
Total float64 `json:"total"`
PaidNominal float64 `json:"paid_nominal"`
Note sql.NullString `json:"note"`
CreatedBy uuid.UUID `json:"created_by"`
}
func (q *Queries) CreatePurchaseOrder(ctx context.Context, arg CreatePurchaseOrderParams) (PurchaseOrder, error) {
@ -46,6 +48,7 @@ func (q *Queries) CreatePurchaseOrder(ctx context.Context, arg CreatePurchaseOrd
arg.Total,
arg.PaidNominal,
arg.Note,
arg.CreatedBy,
)
var i PurchaseOrder
err := row.Scan(
@ -53,6 +56,7 @@ func (q *Queries) CreatePurchaseOrder(ctx context.Context, arg CreatePurchaseOrd
&i.SupplierID,
&i.MerchantID,
&i.IndexID,
&i.CreatedBy,
&i.Code,
&i.IsPaid,
&i.Total,
@ -63,3 +67,71 @@ func (q *Queries) CreatePurchaseOrder(ctx context.Context, arg CreatePurchaseOrd
)
return i, err
}
const getPurchaseOrderById = `-- name: GetPurchaseOrderById :one
SELECT id, supplier_id, merchant_id, index_id, created_by, code, is_paid, total, paid_nominal, note, created_at, updated_at
FROM purchase_order
WHERE id = $1
`
func (q *Queries) GetPurchaseOrderById(ctx context.Context, id uuid.UUID) (PurchaseOrder, error) {
row := q.db.QueryRowContext(ctx, getPurchaseOrderById, id)
var i PurchaseOrder
err := row.Scan(
&i.ID,
&i.SupplierID,
&i.MerchantID,
&i.IndexID,
&i.CreatedBy,
&i.Code,
&i.IsPaid,
&i.Total,
&i.PaidNominal,
&i.Note,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const listPurchaseOrderByMerchantId = `-- name: ListPurchaseOrderByMerchantId :many
SELECT id, supplier_id, merchant_id, index_id, created_by, code, is_paid, total, paid_nominal, note, created_at, updated_at
FROM purchase_order
WHERE merchant_id = $1
`
func (q *Queries) ListPurchaseOrderByMerchantId(ctx context.Context, merchantID uuid.UUID) ([]PurchaseOrder, error) {
rows, err := q.db.QueryContext(ctx, listPurchaseOrderByMerchantId, merchantID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []PurchaseOrder{}
for rows.Next() {
var i PurchaseOrder
if err := rows.Scan(
&i.ID,
&i.SupplierID,
&i.MerchantID,
&i.IndexID,
&i.CreatedBy,
&i.Code,
&i.IsPaid,
&i.Total,
&i.PaidNominal,
&i.Note,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}

View File

@ -59,3 +59,28 @@ func (q *Queries) CreatePurchaseOrderDetail(ctx context.Context, arg CreatePurch
)
return i, err
}
const getPurchaseOrderDetailsByPuchaseOrderId = `-- name: GetPurchaseOrderDetailsByPuchaseOrderId :one
SELECT id, index_id, code, merchant_id, purchase_order_id, product_id, quantity, sub_total, product_price, created_at, updated_at
FROM purchase_order_detail
WHERE purchase_order_id = $1
`
func (q *Queries) GetPurchaseOrderDetailsByPuchaseOrderId(ctx context.Context, purchaseOrderID uuid.UUID) (PurchaseOrderDetail, error) {
row := q.db.QueryRowContext(ctx, getPurchaseOrderDetailsByPuchaseOrderId, purchaseOrderID)
var i PurchaseOrderDetail
err := row.Scan(
&i.ID,
&i.IndexID,
&i.Code,
&i.MerchantID,
&i.PurchaseOrderID,
&i.ProductID,
&i.Quantity,
&i.SubTotal,
&i.ProductPrice,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}

52
db/sqlc/querier.go Normal file
View File

@ -0,0 +1,52 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.17.2
package db
import (
"context"
"github.com/google/uuid"
)
type Querier interface {
CreateCustomers(ctx context.Context, arg CreateCustomersParams) (Customer, error)
CreateMerchant(ctx context.Context, arg CreateMerchantParams) (Merchant, error)
CreateProduct(ctx context.Context, arg CreateProductParams) (Product, error)
CreateProductCategory(ctx context.Context, arg CreateProductCategoryParams) (ProductCategory, error)
CreatePurchaseOrder(ctx context.Context, arg CreatePurchaseOrderParams) (PurchaseOrder, error)
CreatePurchaseOrderDetail(ctx context.Context, arg CreatePurchaseOrderDetailParams) (PurchaseOrderDetail, error)
CreateSaleOrder(ctx context.Context, arg CreateSaleOrderParams) (SaleOrder, error)
CreateSaleOrderDetail(ctx context.Context, arg CreateSaleOrderDetailParams) (SaleOrderDetail, error)
CreateSession(ctx context.Context, arg CreateSessionParams) (UserSession, error)
CreateStockLogs(ctx context.Context, arg CreateStockLogsParams) (StockLog, error)
CreateSuppliers(ctx context.Context, arg CreateSuppliersParams) (Supplier, error)
CreateUser(ctx context.Context, arg CreateUserParams) (User, error)
CustomersList(ctx context.Context, arg CustomersListParams) ([]Customer, error)
DeleteCustomer(ctx context.Context, id uuid.UUID) error
DeleteProduct(ctx context.Context, id uuid.UUID) error
DeleteProductCategory(ctx context.Context, id uuid.UUID) error
DeleteSupplier(ctx context.Context, id uuid.UUID) error
GetMerchantById(ctx context.Context, id uuid.UUID) (Merchant, error)
GetMerchantByUserId(ctx context.Context, ownerID uuid.UUID) (Merchant, error)
GetPasswordByEmail(ctx context.Context, email string) (string, error)
GetProduct(ctx context.Context, id uuid.UUID) (Product, error)
GetPurchaseOrderById(ctx context.Context, id uuid.UUID) (PurchaseOrder, error)
GetPurchaseOrderDetailsByPuchaseOrderId(ctx context.Context, purchaseOrderID uuid.UUID) (PurchaseOrderDetail, error)
GetSession(ctx context.Context, id uuid.UUID) (UserSession, error)
GetStockForUpdateStock(ctx context.Context, id uuid.UUID) (Product, error)
GetUserByEmail(ctx context.Context, email string) (GetUserByEmailRow, error)
GetUserById(ctx context.Context, id uuid.UUID) (GetUserByIdRow, error)
ListProductCategoriesByMerchantID(ctx context.Context, arg ListProductCategoriesByMerchantIDParams) ([]ProductCategory, error)
ListProducts(ctx context.Context, arg ListProductsParams) ([]Product, error)
ListPurchaseOrderByMerchantId(ctx context.Context, merchantID uuid.UUID) ([]PurchaseOrder, error)
SuppliersList(ctx context.Context, arg SuppliersListParams) ([]Supplier, error)
UpdateCustomer(ctx context.Context, id uuid.UUID) (Customer, error)
UpdateProduct(ctx context.Context, arg UpdateProductParams) (Product, error)
UpdateProductCategory(ctx context.Context, arg UpdateProductCategoryParams) (ProductCategory, error)
UpdateProductStock(ctx context.Context, arg UpdateProductStockParams) error
UpdateSupplier(ctx context.Context, id uuid.UUID) (Supplier, error)
}
var _ Querier = (*Queries)(nil)

77
db/sqlc/sale_order.sql.go Normal file
View File

@ -0,0 +1,77 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.17.2
// source: sale_order.sql
package db
import (
"context"
"database/sql"
"github.com/google/uuid"
)
const createSaleOrder = `-- name: CreateSaleOrder :one
INSERT INTO sale_order (
merchant_id,
customer_id,
code,
created_by,
is_paid,
total,
paid_nominal,
note,
change,
is_keep
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10
)
RETURNING id, index_id, code, created_by, merchant_id, customer_id, is_paid, total, paid_nominal, note, created_at, updated_at, is_keep, change
`
type CreateSaleOrderParams struct {
MerchantID uuid.UUID `json:"merchant_id"`
CustomerID uuid.NullUUID `json:"customer_id"`
Code string `json:"code"`
CreatedBy uuid.UUID `json:"created_by"`
IsPaid bool `json:"is_paid"`
Total float64 `json:"total"`
PaidNominal float64 `json:"paid_nominal"`
Note sql.NullString `json:"note"`
Change float64 `json:"change"`
IsKeep bool `json:"is_keep"`
}
func (q *Queries) CreateSaleOrder(ctx context.Context, arg CreateSaleOrderParams) (SaleOrder, error) {
row := q.db.QueryRowContext(ctx, createSaleOrder,
arg.MerchantID,
arg.CustomerID,
arg.Code,
arg.CreatedBy,
arg.IsPaid,
arg.Total,
arg.PaidNominal,
arg.Note,
arg.Change,
arg.IsKeep,
)
var i SaleOrder
err := row.Scan(
&i.ID,
&i.IndexID,
&i.Code,
&i.CreatedBy,
&i.MerchantID,
&i.CustomerID,
&i.IsPaid,
&i.Total,
&i.PaidNominal,
&i.Note,
&i.CreatedAt,
&i.UpdatedAt,
&i.IsKeep,
&i.Change,
)
return i, err
}

View File

@ -0,0 +1,64 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.17.2
// source: sale_order_detail.sql
package db
import (
"context"
"github.com/google/uuid"
)
const createSaleOrderDetail = `-- name: CreateSaleOrderDetail :one
INSERT INTO sale_order_detail (
sale_order_id,
product_id,
product_name,
quantity,
sub_total,
product_price,
profit
) VALUES(
$1, $2, $3, $4, $5, $6, $7
)
RETURNING id, index_id, sale_order_id, product_id, product_name, quantity, sub_total, product_price, profit, created_at, updated_at
`
type CreateSaleOrderDetailParams struct {
SaleOrderID uuid.UUID `json:"sale_order_id"`
ProductID uuid.UUID `json:"product_id"`
ProductName string `json:"product_name"`
Quantity float64 `json:"quantity"`
SubTotal float64 `json:"sub_total"`
ProductPrice float64 `json:"product_price"`
Profit float64 `json:"profit"`
}
func (q *Queries) CreateSaleOrderDetail(ctx context.Context, arg CreateSaleOrderDetailParams) (SaleOrderDetail, error) {
row := q.db.QueryRowContext(ctx, createSaleOrderDetail,
arg.SaleOrderID,
arg.ProductID,
arg.ProductName,
arg.Quantity,
arg.SubTotal,
arg.ProductPrice,
arg.Profit,
)
var i SaleOrderDetail
err := row.Scan(
&i.ID,
&i.IndexID,
&i.SaleOrderID,
&i.ProductID,
&i.ProductName,
&i.Quantity,
&i.SubTotal,
&i.ProductPrice,
&i.Profit,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}

85
db/sqlc/sessions.sql.go Normal file
View File

@ -0,0 +1,85 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.17.2
// source: sessions.sql
package db
import (
"context"
"time"
"github.com/google/uuid"
)
const createSession = `-- name: CreateSession :one
INSERT INTO user_sessions (
id,
email,
refresh_token,
user_agent,
client_ip,
is_blocked,
expires_at
) VALUES (
$1, $2, $3, $4, $5, $6, $7
) RETURNING id, index_id, email, refresh_token, user_agent, client_ip, is_blocked, expires_at, created_at
`
type CreateSessionParams struct {
ID uuid.UUID `json:"id"`
Email string `json:"email"`
RefreshToken string `json:"refresh_token"`
UserAgent string `json:"user_agent"`
ClientIp string `json:"client_ip"`
IsBlocked bool `json:"is_blocked"`
ExpiresAt time.Time `json:"expires_at"`
}
func (q *Queries) CreateSession(ctx context.Context, arg CreateSessionParams) (UserSession, error) {
row := q.db.QueryRowContext(ctx, createSession,
arg.ID,
arg.Email,
arg.RefreshToken,
arg.UserAgent,
arg.ClientIp,
arg.IsBlocked,
arg.ExpiresAt,
)
var i UserSession
err := row.Scan(
&i.ID,
&i.IndexID,
&i.Email,
&i.RefreshToken,
&i.UserAgent,
&i.ClientIp,
&i.IsBlocked,
&i.ExpiresAt,
&i.CreatedAt,
)
return i, err
}
const getSession = `-- name: GetSession :one
SELECT id, index_id, email, refresh_token, user_agent, client_ip, is_blocked, expires_at, created_at FROM user_sessions
WHERE id = $1
LIMIT 1
`
func (q *Queries) GetSession(ctx context.Context, id uuid.UUID) (UserSession, error) {
row := q.db.QueryRowContext(ctx, getSession, id)
var i UserSession
err := row.Scan(
&i.ID,
&i.IndexID,
&i.Email,
&i.RefreshToken,
&i.UserAgent,
&i.ClientIp,
&i.IsBlocked,
&i.ExpiresAt,
&i.CreatedAt,
)
return i, err
}

76
db/sqlc/stock_log.sql.go Normal file
View File

@ -0,0 +1,76 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.17.2
// source: stock_log.sql
package db
import (
"context"
"github.com/google/uuid"
)
const createStockLogs = `-- name: CreateStockLogs :one
INSERT INTO stock_logs (
product_id,
merchant_id,
created_by,
transaction_id,
transaction_action_type,
transaction_description,
type,
selling_price,
purchase_price,
quantity
) VALUES (
$1, $2, $3, $4, $5 ,$6, $7, $8, $9, $10
)
RETURNING id, index_id, merchant_id, product_id, created_by, transaction_id, transaction_action_type, transaction_description, type, selling_price, purchase_price, quantity, created_at, updated_at
`
type CreateStockLogsParams struct {
ProductID uuid.UUID `json:"product_id"`
MerchantID uuid.UUID `json:"merchant_id"`
CreatedBy uuid.UUID `json:"created_by"`
TransactionID uuid.NullUUID `json:"transaction_id"`
TransactionActionType string `json:"transaction_action_type"`
TransactionDescription string `json:"transaction_description"`
Type StockLogsType `json:"type"`
SellingPrice float64 `json:"selling_price"`
PurchasePrice float64 `json:"purchase_price"`
Quantity float64 `json:"quantity"`
}
func (q *Queries) CreateStockLogs(ctx context.Context, arg CreateStockLogsParams) (StockLog, error) {
row := q.db.QueryRowContext(ctx, createStockLogs,
arg.ProductID,
arg.MerchantID,
arg.CreatedBy,
arg.TransactionID,
arg.TransactionActionType,
arg.TransactionDescription,
arg.Type,
arg.SellingPrice,
arg.PurchasePrice,
arg.Quantity,
)
var i StockLog
err := row.Scan(
&i.ID,
&i.IndexID,
&i.MerchantID,
&i.ProductID,
&i.CreatedBy,
&i.TransactionID,
&i.TransactionActionType,
&i.TransactionDescription,
&i.Type,
&i.SellingPrice,
&i.PurchasePrice,
&i.Quantity,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}

37
db/sqlc/stock_log_test.go Normal file
View File

@ -0,0 +1,37 @@
package db
import (
"context"
"fmt"
"testing"
"git.nochill.in/nochill/naice_pos/util"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
)
func createRandomStockLog(t *testing.T) {
product, _ := createRandomProduct(t)
user := createRandomUser(t)
arg := CreateStockLogsParams{
ProductID: product.ID,
MerchantID: product.MerchantID,
CreatedBy: user.ID,
TransactionID: uuid.NullUUID{Valid: true, UUID: uuid.New()},
TransactionActionType: "Stock_log",
TransactionDescription: fmt.Sprintf("Penyesuaian stok %s", util.RandomTransactionCode("P", user.IndexID)),
SellingPrice: util.RandomFloat(10000, 99999),
PurchasePrice: util.RandomFloat(5000, 50000),
Quantity: util.RandomFloat(1, 100),
Type: util.STOCK_LOG_IN,
}
stockLog, err := testQueries.CreateStockLogs(context.Background(), arg)
require.NoError(t, err)
require.NotEmpty(t, stockLog)
}
func TestCreateStockLog(t *testing.T) {
createRandomStockLog(t)
}

View File

@ -4,23 +4,28 @@ import (
"context"
"database/sql"
"fmt"
"github.com/google/uuid"
)
type Store struct {
type Store interface {
Querier
PurchaseOrderTx(ctx context.Context, arg PurchasoOrderTxParams) (PurchaseOrderTxResult, error)
CreateUserMerchantTx(ctx context.Context, arg UserMerchantTxParams) (UserMerchantTxResult, error)
SaleOrderTx(ctx context.Context, arg SaleOrderTxParams) (SaleOrderTxResult, error)
}
type SQLStore struct {
*Queries
db *sql.DB
}
func NewStore(db *sql.DB) *Store {
return &Store{
func NewStore(db *sql.DB) Store {
return &SQLStore{
db: db,
Queries: New(db),
}
}
func (store *Store) execTx(ctx context.Context, fn func(*Queries) error) error {
func (store *SQLStore) execTx(ctx context.Context, fn func(*Queries) error) error {
tx, err := store.db.BeginTx(ctx, nil)
if err != nil {
return err
@ -39,85 +44,45 @@ func (store *Store) execTx(ctx context.Context, fn func(*Queries) error) error {
}
type PurchaseOrderProduct struct {
ProductID uuid.UUID `json:"product_id"`
Quantity float64 `json:"quantity"`
Sub_total float64 `json:"sub_total"`
Price float64 `json:"price"`
type UserMerchantTxParams struct {
Email string `json:"email"`
Fullname string `json:"fullname"`
Password string `json:"password"`
OutletName string `json:"outlet_name"`
}
type PurchasoOrderTxParams struct {
MerchantID uuid.UUID `json:"merchant_id"`
SupplierID uuid.UUID `json:"supplier_id"`
Code sql.NullString `json:"code"`
IsPaid bool `json:"is_paid"`
Total float64 `json:"total"`
PaidNominal float64 `json:"paid_nominal"`
Note sql.NullString `json:"note"`
Products []PurchaseOrderProduct `json:"products"`
type UserMerchantTxResult struct {
OwnerProfile User `json:"owner_profile"`
OutletProfile Merchant `json:"outlet_profile"`
}
type PurchaseOrderTxResult struct {
PurchaseOrder PurchaseOrder `json:"purchase_order"`
PurchaseOrderDetail []PurchaseOrderDetail `json:"detail"`
}
func (store *Store) PurchaseOrderTx(ctx context.Context, arg PurchasoOrderTxParams) (PurchaseOrderTxResult, error) {
var result PurchaseOrderTxResult
func (store *SQLStore) CreateUserMerchantTx(ctx context.Context, arg UserMerchantTxParams) (UserMerchantTxResult, error) {
var result UserMerchantTxResult
err := store.execTx(ctx, func(q *Queries) error {
var err error
// create purchase order
result.PurchaseOrder, err = q.CreatePurchaseOrder(ctx, CreatePurchaseOrderParams{
MerchantID: arg.MerchantID,
SupplierID: arg.SupplierID,
Code: arg.Code,
IsPaid: arg.IsPaid,
Total: arg.Total,
PaidNominal: arg.PaidNominal,
Note: arg.Note,
result.OwnerProfile, err = q.CreateUser(ctx, CreateUserParams{
Email: arg.Email,
Password: arg.Password,
Fullname: arg.Fullname,
})
if err != nil {
return err
}
// create purchase order detail for each products in purchase order
for i := 0; i < len(arg.Products); i++ {
purchaseOrderDetail, err := q.CreatePurchaseOrderDetail(ctx, CreatePurchaseOrderDetailParams{
PurchaseOrderID: result.PurchaseOrder.ID,
MerchantID: arg.MerchantID,
ProductID: arg.Products[i].ProductID,
Quantity: arg.Products[i].Quantity,
SubTotal: arg.Products[i].Sub_total,
ProductPrice: arg.Products[i].Price,
})
if err != nil {
return err
}
result.PurchaseOrderDetail = append(result.PurchaseOrderDetail, purchaseOrderDetail)
product, err := q.GetStockForUpdateStock(ctx, arg.Products[i].ProductID)
if err != nil {
return err
}
err = q.UpdateProductStock(ctx, UpdateProductStockParams{
ID: product.ID,
Stock: product.Stock + arg.Products[i].Quantity,
})
if err != nil {
return err
}
result.OutletProfile, err = q.CreateMerchant(ctx, CreateMerchantParams{
Name: arg.OutletName,
OwnerID: result.OwnerProfile.ID,
})
if err != nil {
return err
}
return nil
})
return result, err
}

View File

@ -6,6 +6,7 @@ import (
"testing"
"git.nochill.in/nochill/naice_pos/util"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
)
@ -13,6 +14,7 @@ func TestPurchaseOrder(t *testing.T) {
var products []PurchaseOrderProduct
store := NewStore(testDB)
user := createRandomUser(t) // TODO: Change to use UserMerchantTx
supplier, _ := createRandomSupplier(t)
product1, _ := createRandomProduct(t)
product2, _ := createRandomProduct(t)
@ -20,12 +22,12 @@ func TestPurchaseOrder(t *testing.T) {
errs := make(chan error)
results := make(chan PurchaseOrderTxResult)
purchaseProducts1Quantity, _ := util.RandomFloat(1, 99)
purchaseProducts1Price, _ := util.RandomFloat(999, 9999)
purchaseProducts1Quantity := util.RandomFloat(1, 99)
purchaseProducts1Price := util.RandomFloat(999, 9999)
purchaseProducts1SubTotal := purchaseProducts1Price * purchaseProducts1Quantity
purchaseProducts2Quantity, _ := util.RandomFloat(1, 99)
purchaseProducts2Price, _ := util.RandomFloat(999, 9999)
purchaseProducts2Quantity := util.RandomFloat(1, 99)
purchaseProducts2Price := util.RandomFloat(999, 9999)
purchaseProducts2SubTotal := purchaseProducts2Price * purchaseProducts2Quantity
purchaseProducts_1 := PurchaseOrderProduct{
@ -44,12 +46,15 @@ func TestPurchaseOrder(t *testing.T) {
products = append(products, purchaseProducts_1, purchaseProducts_2)
for i := 0; i < 5; i++ {
tries := 3
for i := 0; i < tries; i++ {
go func() {
result, err := store.PurchaseOrderTx(context.Background(), PurchasoOrderTxParams{
MerchantID: supplier.MerchantID,
MerchantID: supplier.MerchantID, // GET FROM UserMerchantTx
CreatedBy: user.ID, // TODO: GET FROM UserMerchantTx
SupplierID: supplier.ID,
Code: sql.NullString{Valid: true, String: ""},
Code: sql.NullString{Valid: true, String: util.RandomTransactionCode("P", util.RandomInt(1, 10))},
IsPaid: true,
Total: product1.PurchasePrice + product2.PurchasePrice,
PaidNominal: product1.PurchasePrice + product2.PurchasePrice,
@ -61,7 +66,7 @@ func TestPurchaseOrder(t *testing.T) {
}()
}
for i := 0; i < 5; i++ {
for i := 0; i < tries; i++ {
err := <-errs
require.NoError(t, err)
@ -75,3 +80,114 @@ func TestPurchaseOrder(t *testing.T) {
require.NotZero(t, purchaseOrder.PaidNominal, product1.PurchasePrice+product2.PurchasePrice)
}
}
func TestCreateSaleOrder(t *testing.T) {
var products []SaleOrderProduct
store := NewStore(testDB)
user := createRandomUser(t)
customer, _ := createRandomCustomer(t)
product1, _ := createRandomProduct(t)
product2, _ := createRandomProduct(t)
errs := make(chan error)
results := make(chan SaleOrderTxResult)
saleProducts1Quantity := util.RandomFloat(1, 99)
saleProducts1Price := util.RandomFloat(999, 9999)
saleProducts1SubTotal := saleProducts1Price * saleProducts1Quantity
saleProducts1Profit := saleProducts1SubTotal - saleProducts1Quantity*product1.SellingPrice
saleProducts2Quantity := util.RandomFloat(1, 99)
saleProducts2Price := util.RandomFloat(999, 9999)
saleProducts2SubTotal := saleProducts2Price * saleProducts2Quantity
saleProducts2Profit := saleProducts2SubTotal - saleProducts2Quantity*product2.SellingPrice
saleProducts_1 := SaleOrderProduct{
ProductID: product1.ID,
Quantity: saleProducts1Quantity,
Sub_total: saleProducts1SubTotal,
Price: saleProducts1Price,
ProductName: product1.Name,
Profit: saleProducts1Profit,
}
saleProducts_2 := SaleOrderProduct{
ProductID: product2.ID,
Quantity: saleProducts2Quantity,
Sub_total: saleProducts2SubTotal,
Price: saleProducts2Price,
ProductName: product2.Name,
Profit: saleProducts2Profit,
}
products = append(products, saleProducts_1, saleProducts_2)
tries := 3
for i := 0; i < tries; i++ {
go func() {
result, err := store.SaleOrderTx(context.Background(), SaleOrderTxParams{
MerchantID: customer.MerchantID,
CreatedBy: user.ID,
CustomerID: uuid.NullUUID{Valid: true, UUID: customer.ID},
Code: util.RandomTransactionCode("S", util.RandomInt(1, 10)),
IsPaid: true,
Total: product1.PurchasePrice + product2.PurchasePrice,
PaidNominal: product1.PurchasePrice + product2.PurchasePrice,
Note: sql.NullString{Valid: true, String: ""},
Products: products,
})
errs <- err
results <- result
}()
}
for i := 0; i < tries; i++ {
err := <-errs
require.NoError(t, err)
result := <-results
require.NotEmpty(t, result)
saleOrder := result.SaleOrder
require.NotEmpty(t, saleOrder)
require.Equal(t, saleOrder.MerchantID, customer.MerchantID) // TODO: Change to use UserMerchantTx result
}
}
func TestCreateUserMerchant(t *testing.T) {
store := NewStore(testDB)
errs := make(chan error)
results := make(chan UserMerchantTxResult)
tries := 3
for i := 0; i < tries; i++ {
randString := util.RandomString(10)
password, err := util.HashPassword(randString)
require.NoError(t, err)
go func() {
result, err := store.CreateUserMerchantTx(context.Background(), UserMerchantTxParams{
Email: util.RandomEmail(),
Fullname: util.RandomString(6),
Password: password,
OutletName: util.RandomString(6),
})
errs <- err
results <- result
}()
}
for i := 0; i < tries; i++ {
err := <-errs
require.NoError(t, err)
result := <-results
require.NotEmpty(t, result)
}
}

View File

@ -73,7 +73,7 @@ func (q *Queries) SuppliersList(ctx context.Context, arg SuppliersListParams) ([
return nil, err
}
defer rows.Close()
var items []Supplier
items := []Supplier{}
for rows.Next() {
var i Supplier
if err := rows.Scan(

View File

@ -11,7 +11,7 @@ import (
func createRandomSupplier(t *testing.T) (Supplier, CreateSuppliersParams) {
arg := CreateSuppliersParams{
MerchantID: uuid.MustParse("d9b0e126-991e-46ac-8c61-5efdd605f75d"),
MerchantID: uuid.MustParse("7e525a4b-4208-4f05-99a1-a75df475dd9b"),
Name: util.RandomString(10),
}

View File

@ -0,0 +1,111 @@
package db
import (
"context"
"database/sql"
"fmt"
"git.nochill.in/nochill/naice_pos/util"
"github.com/google/uuid"
)
type PurchaseOrderProduct struct {
ProductID uuid.UUID `json:"product_id"`
Quantity float64 `json:"quantity"`
Sub_total float64 `json:"sub_total"`
Price float64 `json:"price"`
}
type PurchasoOrderTxParams struct {
MerchantID uuid.UUID `json:"merchant_id"`
CreatedBy uuid.UUID `json:"created_by"` // user_id
SupplierID uuid.UUID `json:"supplier_id"`
Code sql.NullString `json:"code"`
IsPaid bool `json:"is_paid"`
Total float64 `json:"total"`
PaidNominal float64 `json:"paid_nominal"`
Note sql.NullString `json:"note"`
Products []PurchaseOrderProduct `json:"products"`
}
type PurchaseOrderTxResult struct {
PurchaseOrder PurchaseOrder `json:"purchase_order"`
PurchaseOrderDetail []PurchaseOrderDetail `json:"detail"`
}
func (store *SQLStore) PurchaseOrderTx(ctx context.Context, arg PurchasoOrderTxParams) (PurchaseOrderTxResult, error) {
var result PurchaseOrderTxResult
err := store.execTx(ctx, func(q *Queries) error {
var err error
// create purchase order
result.PurchaseOrder, err = q.CreatePurchaseOrder(ctx, CreatePurchaseOrderParams{
MerchantID: arg.MerchantID,
SupplierID: arg.SupplierID,
Code: arg.Code,
CreatedBy: arg.CreatedBy,
IsPaid: arg.IsPaid,
Total: arg.Total,
PaidNominal: arg.PaidNominal,
Note: arg.Note,
})
if err != nil {
return err
}
// create purchase order detail for each products in purchase order
for i := 0; i < len(arg.Products); i++ {
purchaseOrderDetail, err := q.CreatePurchaseOrderDetail(ctx, CreatePurchaseOrderDetailParams{
PurchaseOrderID: result.PurchaseOrder.ID,
MerchantID: arg.MerchantID,
ProductID: arg.Products[i].ProductID,
Quantity: arg.Products[i].Quantity,
SubTotal: arg.Products[i].Sub_total,
ProductPrice: arg.Products[i].Price,
})
if err != nil {
return err
}
result.PurchaseOrderDetail = append(result.PurchaseOrderDetail, purchaseOrderDetail)
product, err := q.GetStockForUpdateStock(ctx, arg.Products[i].ProductID)
if err != nil {
return err
}
err = q.UpdateProductStock(ctx, UpdateProductStockParams{
ID: product.ID,
Stock: product.Stock + arg.Products[i].Quantity,
})
if err != nil {
return err
}
_, err = q.CreateStockLogs(ctx, CreateStockLogsParams{
ProductID: arg.Products[i].ProductID,
MerchantID: arg.MerchantID,
CreatedBy: arg.CreatedBy,
TransactionID: uuid.NullUUID{UUID: result.PurchaseOrder.ID, Valid: true},
TransactionActionType: "purchase_order",
TransactionDescription: fmt.Sprintf("Penambahan stok produk %s dari pembelian dengan code %s", product.Name, arg.Code.String),
Type: util.STOCK_LOG_IN,
SellingPrice: product.SellingPrice,
PurchasePrice: arg.Products[i].Price,
Quantity: arg.Products[i].Quantity,
})
if err != nil {
return err
}
}
return nil
})
return result, err
}

116
db/sqlc/tx_sale_order.go Normal file
View File

@ -0,0 +1,116 @@
package db
import (
"context"
"database/sql"
"fmt"
"git.nochill.in/nochill/naice_pos/util"
"github.com/google/uuid"
)
type SaleOrderProduct struct {
ProductID uuid.UUID `json:"product_id"`
ProductName string `json:"product_name"`
Quantity float64 `json:"quantity"`
Sub_total float64 `json:"sub_total"`
Price float64 `json:"price"`
Profit float64 `json:"profit"`
}
type SaleOrderTxParams struct {
MerchantID uuid.UUID `json:"merchant_id"`
CreatedBy uuid.UUID `json:"created_by"` // user_id
CustomerID uuid.NullUUID `json:"customer_id"`
Code string `json:"code"`
IsPaid bool `json:"is_paid"`
Total float64 `json:"total"`
PaidNominal float64 `json:"paid_nominal"`
Note sql.NullString `json:"note"`
IsKeep bool `json:"is_keep"`
Change float64 `json:"change"`
Products []SaleOrderProduct `json:"products"`
}
type SaleOrderTxResult struct {
SaleOrder SaleOrder `json:"sale_order"`
SaleOrderDetail []SaleOrderDetail `json:"detail"`
}
func (store *SQLStore) SaleOrderTx(ctx context.Context, arg SaleOrderTxParams) (SaleOrderTxResult, error) {
var result SaleOrderTxResult
err := store.execTx(ctx, func(q *Queries) error {
var err error
result.SaleOrder, err = q.CreateSaleOrder(ctx, CreateSaleOrderParams{
MerchantID: arg.MerchantID,
CustomerID: uuid.NullUUID{Valid: arg.CustomerID.Valid, UUID: arg.CustomerID.UUID},
Code: arg.Code,
CreatedBy: arg.CreatedBy,
IsPaid: arg.IsPaid,
Total: arg.Total,
PaidNominal: arg.PaidNominal,
Note: sql.NullString{Valid: len(arg.Note.String) > 0, String: arg.Note.String},
IsKeep: arg.IsKeep,
})
if err != nil {
return err
}
for i := 0; i < len(arg.Products); i++ {
saleOrderDetail, err := q.CreateSaleOrderDetail(ctx, CreateSaleOrderDetailParams{
SaleOrderID: result.SaleOrder.ID,
ProductID: arg.Products[i].ProductID,
ProductName: arg.Products[i].ProductName,
Quantity: arg.Products[i].Quantity,
SubTotal: arg.Products[i].Sub_total,
ProductPrice: arg.Products[i].Price,
Profit: arg.Products[i].Profit,
})
if err != nil {
return err
}
result.SaleOrderDetail = append(result.SaleOrderDetail, saleOrderDetail)
product, err := q.GetStockForUpdateStock(ctx, arg.Products[i].ProductID)
if err != nil {
return err
}
err = q.UpdateProductStock(ctx, UpdateProductStockParams{
ID: product.ID,
Stock: product.Stock - arg.Products[i].Quantity,
})
if err != nil {
return err
}
_, err = q.CreateStockLogs(ctx, CreateStockLogsParams{
ProductID: arg.Products[i].ProductID,
MerchantID: arg.MerchantID,
CreatedBy: arg.CreatedBy,
TransactionID: uuid.NullUUID{UUID: result.SaleOrder.ID, Valid: true},
TransactionActionType: "sale_order",
TransactionDescription: fmt.Sprintf("Pengurangan stok produk %s dari penjualan dengan code %s", product.Name, arg.Code),
Type: util.STOCK_LOG_OUT,
SellingPrice: arg.Products[i].Price,
PurchasePrice: product.PurchasePrice,
Quantity: arg.Products[i].Quantity,
})
if err != nil {
return err
}
}
return nil
})
return result, err
}

144
db/sqlc/users.sql.go Normal file
View File

@ -0,0 +1,144 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.17.2
// source: users.sql
package db
import (
"context"
"database/sql"
"github.com/google/uuid"
)
const createUser = `-- name: CreateUser :one
INSERT INTO users (
email,
password,
fullname
) VALUES ($1, $2, $3)
RETURNING id, index_id, email, password, fullname, created_at, updated_at
`
type CreateUserParams struct {
Email string `json:"email"`
Password string `json:"password"`
Fullname string `json:"fullname"`
}
func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, error) {
row := q.db.QueryRowContext(ctx, createUser, arg.Email, arg.Password, arg.Fullname)
var i User
err := row.Scan(
&i.ID,
&i.IndexID,
&i.Email,
&i.Password,
&i.Fullname,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const getPasswordByEmail = `-- name: GetPasswordByEmail :one
SELECT password
FROM users
WHERE email = $1
`
func (q *Queries) GetPasswordByEmail(ctx context.Context, email string) (string, error) {
row := q.db.QueryRowContext(ctx, getPasswordByEmail, email)
var password string
err := row.Scan(&password)
return password, err
}
const getUserByEmail = `-- name: GetUserByEmail :one
SELECT u.id, u.index_id, email, password, fullname, u.created_at, u.updated_at, m.id, m.index_id, name, owner_id, m.created_at, m.updated_at
FROM users u
JOIN merchants m on u.id = m.owner_id
WHERE email = $1
`
type GetUserByEmailRow struct {
ID uuid.UUID `json:"id"`
IndexID int64 `json:"index_id"`
Email string `json:"email"`
Password string `json:"password"`
Fullname string `json:"fullname"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
ID_2 uuid.UUID `json:"id_2"`
IndexID_2 int64 `json:"index_id_2"`
Name string `json:"name"`
OwnerID uuid.UUID `json:"owner_id"`
CreatedAt_2 sql.NullTime `json:"created_at_2"`
UpdatedAt_2 sql.NullTime `json:"updated_at_2"`
}
func (q *Queries) GetUserByEmail(ctx context.Context, email string) (GetUserByEmailRow, error) {
row := q.db.QueryRowContext(ctx, getUserByEmail, email)
var i GetUserByEmailRow
err := row.Scan(
&i.ID,
&i.IndexID,
&i.Email,
&i.Password,
&i.Fullname,
&i.CreatedAt,
&i.UpdatedAt,
&i.ID_2,
&i.IndexID_2,
&i.Name,
&i.OwnerID,
&i.CreatedAt_2,
&i.UpdatedAt_2,
)
return i, err
}
const getUserById = `-- name: GetUserById :one
SELECT u.id, u.index_id, email, password, fullname, u.created_at, u.updated_at, m.id, m.index_id, name, owner_id, m.created_at, m.updated_at
FROM users u
JOIN merchants m on u.id = m.owner_id
WHERE u.id = $1
`
type GetUserByIdRow struct {
ID uuid.UUID `json:"id"`
IndexID int64 `json:"index_id"`
Email string `json:"email"`
Password string `json:"password"`
Fullname string `json:"fullname"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
ID_2 uuid.UUID `json:"id_2"`
IndexID_2 int64 `json:"index_id_2"`
Name string `json:"name"`
OwnerID uuid.UUID `json:"owner_id"`
CreatedAt_2 sql.NullTime `json:"created_at_2"`
UpdatedAt_2 sql.NullTime `json:"updated_at_2"`
}
func (q *Queries) GetUserById(ctx context.Context, id uuid.UUID) (GetUserByIdRow, error) {
row := q.db.QueryRowContext(ctx, getUserById, id)
var i GetUserByIdRow
err := row.Scan(
&i.ID,
&i.IndexID,
&i.Email,
&i.Password,
&i.Fullname,
&i.CreatedAt,
&i.UpdatedAt,
&i.ID_2,
&i.IndexID_2,
&i.Name,
&i.OwnerID,
&i.CreatedAt_2,
&i.UpdatedAt_2,
)
return i, err
}

30
db/sqlc/users_test.go Normal file
View File

@ -0,0 +1,30 @@
package db
import (
"context"
"testing"
"git.nochill.in/nochill/naice_pos/util"
"github.com/stretchr/testify/require"
)
func createRandomUser(t *testing.T) User {
arg := CreateUserParams{
Email: util.RandomEmail(),
Password: util.RandomString(5),
Fullname: util.RandomString(6),
}
user, err := testQueries.CreateUser(context.Background(), arg)
require.NoError(t, err)
require.Equal(t, arg.Email, user.Email)
require.Equal(t, arg.Password, user.Password)
require.Equal(t, arg.Fullname, user.Fullname)
return user
}
func TestGetUserByID(t *testing.T) {
createRandomUser(t)
}

13
dev.env Normal file
View File

@ -0,0 +1,13 @@
DB_TYPE=postgres
DB_USERNAME=postgres
DB_PASSWORD=awksed123
DB_NAME=nice_pos
DB_HOST=localhost
DB_PORT=5432
DB_SOURCE = postgresql://postgres:awksed123@localhost:5432/nice_pos?sslmode=disable
SERVER_ADDRESS = 0.0.0.0:8888
TOKEN_SYMMETRIC_KEY=75629266996751511372336382467976
TOKEN_DURATION = 16h
REFRESH_TOKEN_DURATION = 24h

19
go.mod
View File

@ -3,38 +3,55 @@ module git.nochill.in/nochill/naice_pos
go 1.20
require (
github.com/aead/chacha20poly1305 v0.0.0-20201124145622-1a5aba2a8b29
github.com/gin-gonic/gin v1.9.0
github.com/golang/mock v1.6.0
github.com/google/uuid v1.3.0
github.com/lib/pq v1.10.7
github.com/o1egl/paseto v1.0.0
github.com/shopspring/decimal v1.3.1
github.com/spf13/viper v1.15.0
github.com/stretchr/testify v1.8.2
github.com/tabbed/pqtype v0.1.1
golang.org/x/crypto v0.7.0
)
require (
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect
github.com/bytedance/sonic v1.8.3 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.11.2 // indirect
github.com/goccy/go-json v0.10.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.2.2 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.7 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/afero v1.9.3 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.10 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.7.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

473
go.sum
View File

@ -1,16 +1,83 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/aead/chacha20poly1305 v0.0.0-20170617001512-233f39982aeb/go.mod h1:UzH9IX1MMqOcwhoNOIjmTQeAxrFgzs50j4golQtXXxU=
github.com/aead/chacha20poly1305 v0.0.0-20201124145622-1a5aba2a8b29 h1:1DcvRPZOdbQRg5nAHt2jrc5QbV0AGuhDdfQI6gXjiFE=
github.com/aead/chacha20poly1305 v0.0.0-20201124145622-1a5aba2a8b29/go.mod h1:UzH9IX1MMqOcwhoNOIjmTQeAxrFgzs50j4golQtXXxU=
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.8.3 h1:pf6fGl5eqWYKkx1RcD4qpuX+BIUaduv/wTm5ekWJ80M=
github.com/bytedance/sonic v1.8.3/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
@ -20,75 +87,477 @@ github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyh
github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/leodido/go-urn v1.2.2 h1:7z68G0FCGvDk646jz1AelTYNYWrTNm0bEcFAo147wt4=
github.com/leodido/go-urn v1.2.2/go.mod h1:kUaIbLZWttglzwNuG0pgsh5vuV6u2YcGBYz1hIPjtOQ=
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/o1egl/paseto v1.0.0 h1:bwpvPu2au176w4IBlhbyUv/S5VPptERIA99Oap5qUd0=
github.com/o1egl/paseto v1.0.0/go.mod h1:5HxsZPmw/3RI2pAwGo1HhOOwSdvBpcuVzO7uDkm+CLU=
github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us=
github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rwtodd/Go.Sed v0.0.0-20210816025313-55464686f9ef/go.mod h1:8AEUvGVi2uQ5b24BIhcr0GCcpd/RNAFWaN2CJFrWIIQ=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU=
github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/tabbed/pqtype v0.1.1 h1:PhEcb9JZ8jr7SUjJDFjRPxny0M8fkXZrxn/a9yQfoZg=
github.com/tabbed/pqtype v0.1.1/go.mod h1:HLt2kLJPcUhODQkYn3mJkMHXVsuv3Z2n5NZEeKXL0Uk=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.10 h1:eimT6Lsr+2lzmSZxPhLFoOWFmQqwk0fllJJ5hEbTXtQ=
github.com/ugorji/go/codec v1.2.10/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

22
main.go
View File

@ -6,25 +6,27 @@ import (
"git.nochill.in/nochill/naice_pos/api"
db "git.nochill.in/nochill/naice_pos/db/sqlc"
"git.nochill.in/nochill/naice_pos/util"
_ "github.com/lib/pq"
)
const (
dbDriver = "postgres"
dbSource = "postgresql://postgres:awksed123@localhost:5432/nice_pos?sslmode=disable"
serverAddress = "0.0.0.0:8888"
)
func main() {
dbConn, err := sql.Open(dbDriver, dbSource)
config, err := util.LoadConfig(".")
if err != nil {
log.Fatal("cannot connect to db:", err)
log.Fatal("cannot load config: ", err)
}
dbConn, err := sql.Open(config.DBDriver, config.DBSource)
if err != nil {
log.Fatal("cannot connect to db: ", err)
}
store := db.NewStore(dbConn)
server := api.NewServer(store)
server, err := api.NewServer(config, store)
if err != nil {
log.Fatal("cannot make server: ", err)
}
err = server.Start(serverAddress)
err = server.Start(config.ServerAddress)
if err != nil {
log.Fatal("cannot start server: ", err)
}

View File

@ -7,5 +7,6 @@ packages:
engine: "postgresql"
emit_json_tags: true
emit_prepared_queries: false
emit_interface: false
emit_interface: true
emit_exact_table_names: false
emit_empty_slices: true

10
token/maker.go Normal file
View File

@ -0,0 +1,10 @@
package token
import (
"time"
)
type Maker interface {
CreateToken(email string, merchantID string, duration time.Duration) (string, *Payload, error)
VerifyToken(token string) (*Payload, error)
}

53
token/paseto_maker.go Normal file
View File

@ -0,0 +1,53 @@
package token
import (
"fmt"
"time"
"github.com/aead/chacha20poly1305"
"github.com/o1egl/paseto"
)
type PasetoMaker struct {
paseto *paseto.V2
symmetricKey []byte
}
func NewPasetoMaker(symmetricKey string) (Maker, error) {
if len(symmetricKey) != chacha20poly1305.KeySize {
return nil, fmt.Errorf("invalid key size: must be exactly %d characters", chacha20poly1305.KeySize)
}
maker := &PasetoMaker{
paseto: paseto.NewV2(),
symmetricKey: []byte(symmetricKey),
}
return maker, nil
}
func (maker *PasetoMaker) CreateToken(email string, merchant_id string, duration time.Duration) (string, *Payload, error) {
payload, err := NewPayload(email, merchant_id, duration)
if err != nil {
return "", payload, err
}
token, err := maker.paseto.Encrypt(maker.symmetricKey, payload, nil)
return token, payload, err
}
func (maker *PasetoMaker) VerifyToken(token string) (*Payload, error) {
payload := &Payload{}
err := maker.paseto.Decrypt(token, maker.symmetricKey, payload, nil)
if err != nil {
return nil, ErrInvalidToken
}
err = payload.Valid()
if err != nil {
return nil, err
}
return payload, nil
}

View File

@ -0,0 +1,52 @@
package token
import (
"testing"
"time"
"git.nochill.in/nochill/naice_pos/util"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
)
var merchantID = uuid.New().String()
func TestPasetoMaker(t *testing.T) {
maker, err := NewPasetoMaker(util.RandomString(32))
require.NoError(t, err)
email := util.RandomEmail()
duration := time.Minute
issuedAt := time.Now()
expiredAt := issuedAt.Add(duration)
token, payload, err := maker.CreateToken(email, merchantID, duration)
require.NoError(t, err)
require.NotEmpty(t, token)
require.NotEmpty(t, payload)
payload, err = maker.VerifyToken(token)
require.NoError(t, err)
require.NotEmpty(t, payload)
require.NotZero(t, payload.ID)
require.Equal(t, email, payload.Email)
require.WithinDuration(t, issuedAt, payload.IssuedAt, time.Second)
require.WithinDuration(t, expiredAt, payload.ExpiredAt, time.Second)
}
func TestExpiredPasetoToken(t *testing.T) {
maker, err := NewPasetoMaker(util.RandomString(32))
require.NoError(t, err)
token, payload, err := maker.CreateToken(util.RandomEmail(), merchantID, -time.Minute)
require.NoError(t, err)
require.NotEmpty(t, token)
require.NotEmpty(t, payload)
payload, err = maker.VerifyToken(token)
require.Error(t, err)
require.EqualError(t, err, ErrExpiredToken.Error())
require.Nil(t, payload)
}

44
token/payload.go Normal file
View File

@ -0,0 +1,44 @@
package token
import (
"errors"
"time"
"github.com/google/uuid"
)
var (
ErrExpiredToken = errors.New("token has expired")
ErrInvalidToken = errors.New("token is invalid")
)
type Payload struct {
ID uuid.UUID `json:"id"`
Email string `json:"email"`
MerchantID uuid.UUID `json:"merchant_id"`
IssuedAt time.Time `json:"issued_at"`
ExpiredAt time.Time `json:"expired_at"`
}
func NewPayload(email string, merchant_id string, duration time.Duration) (*Payload, error) {
tokenID, err := uuid.NewRandom()
if err != nil {
return nil, err
}
payload := &Payload{
ID: tokenID,
Email: email,
MerchantID: uuid.MustParse(merchant_id),
IssuedAt: time.Now(),
ExpiredAt: time.Now().Add(duration),
}
return payload, nil
}
func (payload *Payload) Valid() error {
if time.Now().After(payload.ExpiredAt) {
return ErrExpiredToken
}
return nil
}

32
util/config.go Normal file
View File

@ -0,0 +1,32 @@
package util
import (
"time"
"github.com/spf13/viper"
)
type Config struct {
DBDriver string `mapstructure:"DB_TYPE"`
DBSource string `mapstructure:"DB_SOURCE"`
ServerAddress string `mapstructure:"SERVER_ADDRESS"`
TokenSymmetricKey string `mapstructure:"TOKEN_SYMMETRIC_KEY"`
TokenDuration time.Duration `mapstructure:"TOKEN_DURATION"`
RefreshTokenDuration time.Duration `mapstructure:"REFRESH_TOKEN_DURATION"`
}
func LoadConfig(path string) (config Config, err error) {
viper.AddConfigPath(path)
viper.SetConfigName("dev")
viper.SetConfigType("env")
viper.AutomaticEnv()
err = viper.ReadInConfig()
if err != nil {
return
}
err = viper.Unmarshal(&config)
return
}

6
util/enums.go Normal file
View File

@ -0,0 +1,6 @@
package util
const (
STOCK_LOG_IN = "in"
STOCK_LOG_OUT = "out"
)

19
util/password.go Normal file
View File

@ -0,0 +1,19 @@
package util
import (
"fmt"
"golang.org/x/crypto/bcrypt"
)
func HashPassword(password string) (string, error) {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", fmt.Errorf("Failed to hash password: %w", err)
}
return string(hashedPassword), nil
}
func CheckPassword(password string, hashedPassword string) error {
return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
}

23
util/password_test.go Normal file
View File

@ -0,0 +1,23 @@
package util
import (
"testing"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/bcrypt"
)
func TestPasswordUtil(t *testing.T) {
password := RandomString(10)
hashedPassword, err := HashPassword(password)
require.NoError(t, err)
require.NotEmpty(t, hashedPassword)
err = CheckPassword(password, hashedPassword)
require.NoError(t, err)
wrongPassword := RandomString(5)
err = CheckPassword(wrongPassword, hashedPassword)
require.EqualError(t, err, bcrypt.ErrMismatchedHashAndPassword.Error())
}

View File

@ -1,6 +1,7 @@
package util
import (
"fmt"
"math/rand"
"strings"
"time"
@ -20,9 +21,10 @@ func RandomInt(min, max int64) int64 {
}
// RandomFloat with rounding for testing
func RandomFloat(min, max float64) (float64, bool) {
func RandomFloat(min, max float64) float64 {
val := (min + rand.Float64()*(max-min)) * 100
return decimal.NewFromFloat(val).Truncate(2).Float64()
val, _ = decimal.NewFromFloat(val).Truncate(2).Float64()
return val
}
// RandomString generates a random string of length n
@ -37,3 +39,12 @@ func RandomString(n int) string {
return sb.String()
}
func RandomEmail() string {
return fmt.Sprintf("%s@mail.com", RandomString(5))
}
func RandomTransactionCode(prefix string, merchant_index int64) string {
time_now := time.Now().Unix()
return fmt.Sprintf("%s%d%d", prefix, merchant_index, time_now)
}

8
util/validate.go Normal file
View File

@ -0,0 +1,8 @@
package util
import "github.com/google/uuid"
func IsValidUUID(u string) bool {
_, err := uuid.Parse(u)
return err == nil
}