diff --git a/.gitignore b/.gitignore index 4f509e5..12ef58b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -*.env \ No newline at end of file +*.env +public \ No newline at end of file diff --git a/api/product.go b/api/product.go index 154ce72..4408e9a 100644 --- a/api/product.go +++ b/api/product.go @@ -3,33 +3,59 @@ 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 { - Name string `json:"name" binding:"required"` - SellingPrice float64 `json:"selling_price" binding:"required"` - ProductTypeID int16 `json:"product_type_id" binding:"required"` - PurchasePrice float64 `json:"purchase_price" binding:"required"` - ProductCategoryID string `json:"product_category_id" binding:"required"` - Stock float64 `json:"stock" binding:"number"` + 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 { + 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: authPayload.MerchantID, Name: req.Name, @@ -38,6 +64,7 @@ func (server *Server) createProduct(ctx *gin.Context) { 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) @@ -109,19 +136,38 @@ func (server *Server) listProducts(ctx *gin.Context) { } type updateProductRequest struct { - ProductID uuid.UUID `json:"product_id" binding:"required"` - Name string `json:"name" binding:"required"` - SellingPrice float64 `json:"selling_price" binding:"required"` - PurchasePrice float64 `json:"purchase_price" binding:"required"` + 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 - if err := ctx.ShouldBindJSON(&req); err != nil { + 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, diff --git a/api/server.go b/api/server.go index e84b817..dc2afb4 100644 --- a/api/server.go +++ b/api/server.go @@ -2,6 +2,7 @@ package api import ( "fmt" + "path/filepath" db "git.nochill.in/nochill/naice_pos/db/sqlc" "git.nochill.in/nochill/naice_pos/token" @@ -37,6 +38,7 @@ func (server *Server) getRoutes() { 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)) diff --git a/db/migrations/000008_alter_product_table_add_product_image.down.sql b/db/migrations/000008_alter_product_table_add_product_image.down.sql new file mode 100644 index 0000000..12b2230 --- /dev/null +++ b/db/migrations/000008_alter_product_table_add_product_image.down.sql @@ -0,0 +1 @@ +ALTER TABLE product DROP COLUMN IF EXISTS image; \ No newline at end of file diff --git a/db/migrations/000008_alter_product_table_add_product_image.up.sql b/db/migrations/000008_alter_product_table_add_product_image.up.sql new file mode 100644 index 0000000..26038f5 --- /dev/null +++ b/db/migrations/000008_alter_product_table_add_product_image.up.sql @@ -0,0 +1 @@ +ALTER TABLE products ADD COLUMN image text; \ No newline at end of file diff --git a/db/query/product.sql b/db/query/product.sql index fcb7200..9ee6e2b 100644 --- a/db/query/product.sql +++ b/db/query/product.sql @@ -6,9 +6,10 @@ INSERT INTO products ( product_type_id, purchase_price, product_category_id, + image, stock ) VALUES ( - $1, $2, $3, $4, $5, $6, $7 + $1, $2, $3, $4, $5, $6, $7, $8 ) RETURNING *; @@ -36,7 +37,12 @@ OFFSET $3; -- name: UpdateProduct :one UPDATE products -SET name = $2, selling_price = $3, purchase_price = $4, product_category_id = $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 *; diff --git a/db/sqlc/models.go b/db/sqlc/models.go index fa80854..576c731 100644 --- a/db/sqlc/models.go +++ b/db/sqlc/models.go @@ -77,17 +77,18 @@ type Merchant struct { } type Product struct { - 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"` + 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 { diff --git a/db/sqlc/product.sql.go b/db/sqlc/product.sql.go index 103398d..d50adff 100644 --- a/db/sqlc/product.sql.go +++ b/db/sqlc/product.sql.go @@ -20,21 +20,23 @@ INSERT INTO products ( product_type_id, purchase_price, product_category_id, + image, stock ) VALUES ( - $1, $2, $3, $4, $5, $6, $7 + $1, $2, $3, $4, $5, $6, $7, $8 ) -RETURNING id, merchant_id, product_type_id, index_id, name, selling_price, purchase_price, stock, created_at, updated_at, product_category_id +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"` - ProductTypeID int16 `json:"product_type_id"` - PurchasePrice float64 `json:"purchase_price"` - ProductCategoryID uuid.UUID `json:"product_category_id"` - 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) { @@ -45,6 +47,7 @@ func (q *Queries) CreateProduct(ctx context.Context, arg CreateProductParams) (P arg.ProductTypeID, arg.PurchasePrice, arg.ProductCategoryID, + arg.Image, arg.Stock, ) var i Product @@ -60,6 +63,7 @@ func (q *Queries) CreateProduct(ctx context.Context, arg CreateProductParams) (P &i.CreatedAt, &i.UpdatedAt, &i.ProductCategoryID, + &i.Image, ) return i, err } @@ -74,7 +78,7 @@ func (q *Queries) DeleteProduct(ctx context.Context, id uuid.UUID) error { } const getProduct = `-- name: GetProduct :one -SELECT id, merchant_id, product_type_id, index_id, name, selling_price, purchase_price, stock, created_at, updated_at, product_category_id 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 ` @@ -93,12 +97,13 @@ func (q *Queries) GetProduct(ctx context.Context, id uuid.UUID) (Product, error) &i.CreatedAt, &i.UpdatedAt, &i.ProductCategoryID, + &i.Image, ) return i, err } const getStockForUpdateStock = `-- name: GetStockForUpdateStock :one -SELECT id, merchant_id, product_type_id, index_id, name, selling_price, purchase_price, stock, created_at, updated_at, product_category_id 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 @@ -119,12 +124,13 @@ func (q *Queries) GetStockForUpdateStock(ctx context.Context, id uuid.UUID) (Pro &i.CreatedAt, &i.UpdatedAt, &i.ProductCategoryID, + &i.Image, ) return i, err } const listProducts = `-- name: ListProducts :many -SELECT id, merchant_id, product_type_id, index_id, name, selling_price, purchase_price, stock, created_at, updated_at, product_category_id 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 $2 @@ -158,6 +164,7 @@ func (q *Queries) ListProducts(ctx context.Context, arg ListProductsParams) ([]P &i.CreatedAt, &i.UpdatedAt, &i.ProductCategoryID, + &i.Image, ); err != nil { return nil, err } @@ -174,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, product_category_id = $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, product_type_id, index_id, name, selling_price, purchase_price, stock, created_at, updated_at, product_category_id +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"` - ProductCategoryID uuid.UUID `json:"product_category_id"` - 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) { @@ -195,6 +208,7 @@ func (q *Queries) UpdateProduct(ctx context.Context, arg UpdateProductParams) (P arg.SellingPrice, arg.PurchasePrice, arg.ProductCategoryID, + arg.Image, arg.UpdatedAt, ) var i Product @@ -210,6 +224,7 @@ func (q *Queries) UpdateProduct(ctx context.Context, arg UpdateProductParams) (P &i.CreatedAt, &i.UpdatedAt, &i.ProductCategoryID, + &i.Image, ) return i, err } diff --git a/db/sqlc/product_test.go b/db/sqlc/product_test.go index a3bb500..984f938 100644 --- a/db/sqlc/product_test.go +++ b/db/sqlc/product_test.go @@ -26,6 +26,7 @@ func createRandomProduct(t *testing.T) (Product, CreateProductParams) { PurchasePrice: purchasePrice, ProductCategoryID: productCategory.ID, Stock: stock, + Image: sql.NullString{Valid: false}, } product, err := testQueries.CreateProduct(context.Background(), arg)