From 8bd03f1489e0c0be634d4b62fed43b8cf73ef3e0 Mon Sep 17 00:00:00 2001 From: nikhilnarayanan623 Date: Thu, 10 Aug 2023 21:20:51 +0530 Subject: [PATCH] aws s3 service added --- cmd/api/docs/docs.go | 74 ++++++++++++-------------- cmd/api/docs/swagger.json | 74 ++++++++++++-------------- cmd/api/docs/swagger.yaml | 54 +++++++++---------- go.mod | 2 + go.sum | 11 ++++ pkg/api/handler/product.go | 29 ++++++++--- pkg/api/handler/request/product.go | 12 +++-- pkg/config/config.go | 6 +++ pkg/di/wire.go | 2 + pkg/di/wire_gen.go | 7 ++- pkg/repository/interfaces/product.go | 2 +- pkg/repository/product.go | 2 +- pkg/service/cloud/aws.go | 78 ++++++++++++++++++++++++++++ pkg/service/cloud/cloud.go | 11 ++++ pkg/usecase/product.go | 42 ++++++++++++--- 15 files changed, 274 insertions(+), 132 deletions(-) create mode 100644 pkg/service/cloud/aws.go create mode 100644 pkg/service/cloud/cloud.go diff --git a/cmd/api/docs/docs.go b/cmd/api/docs/docs.go index 2531737..89d4b7b 100644 --- a/cmd/api/docs/docs.go +++ b/cmd/api/docs/docs.go @@ -1805,9 +1805,6 @@ const docTemplate = `{ }, "post": { "description": "API for admin to add a new product", - "consumes": [ - "application/json" - ], "produces": [ "application/json" ], @@ -1818,13 +1815,39 @@ const docTemplate = `{ "operationId": "SaveProduct", "parameters": [ { - "description": "Product input", - "name": "input", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/request.Product" - } + "type": "string", + "description": "Product Name", + "name": "name", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "Product Description", + "name": "description", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Category Id", + "name": "category_id", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Product Price", + "name": "price", + "in": "formData", + "required": true + }, + { + "type": "file", + "description": "Product Description", + "name": "image", + "in": "formData", + "required": true } ], "responses": { @@ -3805,37 +3828,6 @@ const docTemplate = `{ } } }, - "request.Product": { - "type": "object", - "required": [ - "category_id", - "description", - "image", - "price", - "product_name" - ], - "properties": { - "category_id": { - "type": "integer" - }, - "description": { - "type": "string", - "maxLength": 100, - "minLength": 10 - }, - "image": { - "type": "string" - }, - "price": { - "type": "integer" - }, - "product_name": { - "type": "string", - "maxLength": 50, - "minLength": 3 - } - } - }, "request.ProductItem": { "type": "object", "required": [ diff --git a/cmd/api/docs/swagger.json b/cmd/api/docs/swagger.json index cf98542..d6a9eb2 100644 --- a/cmd/api/docs/swagger.json +++ b/cmd/api/docs/swagger.json @@ -1793,9 +1793,6 @@ }, "post": { "description": "API for admin to add a new product", - "consumes": [ - "application/json" - ], "produces": [ "application/json" ], @@ -1806,13 +1803,39 @@ "operationId": "SaveProduct", "parameters": [ { - "description": "Product input", - "name": "input", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/request.Product" - } + "type": "string", + "description": "Product Name", + "name": "name", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "Product Description", + "name": "description", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Category Id", + "name": "category_id", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Product Price", + "name": "price", + "in": "formData", + "required": true + }, + { + "type": "file", + "description": "Product Description", + "name": "image", + "in": "formData", + "required": true } ], "responses": { @@ -3793,37 +3816,6 @@ } } }, - "request.Product": { - "type": "object", - "required": [ - "category_id", - "description", - "image", - "price", - "product_name" - ], - "properties": { - "category_id": { - "type": "integer" - }, - "description": { - "type": "string", - "maxLength": 100, - "minLength": 10 - }, - "image": { - "type": "string" - }, - "price": { - "type": "integer" - }, - "product_name": { - "type": "string", - "maxLength": 50, - "minLength": 3 - } - } - }, "request.ProductItem": { "type": "object", "required": [ diff --git a/cmd/api/docs/swagger.yaml b/cmd/api/docs/swagger.yaml index 9f201af..5e5029a 100644 --- a/cmd/api/docs/swagger.yaml +++ b/cmd/api/docs/swagger.yaml @@ -267,29 +267,6 @@ definitions: - offer_id - product_id type: object - request.Product: - properties: - category_id: - type: integer - description: - maxLength: 100 - minLength: 10 - type: string - image: - type: string - price: - type: integer - product_name: - maxLength: 50 - minLength: 3 - type: string - required: - - category_id - - description - - image - - price - - product_name - type: object request.ProductItem: properties: images: @@ -1629,17 +1606,34 @@ paths: tags: - Admin Products post: - consumes: - - application/json description: API for admin to add a new product operationId: SaveProduct parameters: - - description: Product input - in: body - name: input + - description: Product Name + in: formData + name: name required: true - schema: - $ref: '#/definitions/request.Product' + type: string + - description: Product Description + in: formData + name: description + required: true + type: string + - description: Category Id + in: formData + name: category_id + required: true + type: integer + - description: Product Price + in: formData + name: price + required: true + type: integer + - description: Product Description + in: formData + name: image + required: true + type: file produces: - application/json responses: diff --git a/go.mod b/go.mod index f5cad82..8103b7f 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.19 require ( github.com/DATA-DOG/go-sqlmock v1.5.0 + github.com/aws/aws-sdk-go v1.44.319 github.com/gin-gonic/gin v1.9.1 github.com/go-playground/validator/v10 v10.14.1 github.com/golang-jwt/jwt v3.2.2+incompatible @@ -54,6 +55,7 @@ require ( github.com/jackc/pgx/v5 v5.4.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect diff --git a/go.sum b/go.sum index d37680d..db4259d 100644 --- a/go.sum +++ b/go.sum @@ -47,6 +47,8 @@ github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20O github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/aws/aws-sdk-go v1.44.319 h1:cwynvM8DBwWGzlINTZ6XLkGy5O99wZIS0197j3B61Fs= +github.com/aws/aws-sdk-go v1.44.319/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= @@ -215,6 +217,10 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -419,6 +425,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= @@ -485,12 +492,14 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 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= @@ -500,6 +509,7 @@ 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.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= @@ -668,6 +678,7 @@ 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.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/api/handler/product.go b/pkg/api/handler/product.go index 02cd1d8..219549d 100644 --- a/pkg/api/handler/product.go +++ b/pkg/api/handler/product.go @@ -258,23 +258,40 @@ func (c *ProductHandler) GetAllVariations(ctx *gin.Context) { // @Description API for admin to add a new product // @ID SaveProduct // @Tags Admin Products -// @Accept json // @Produce json -// @Param input body request.Product{} true "Product input" +// @Param name formData string true "Product Name" +// @Param description formData string true "Product Description" +// @Param category_id formData int true "Category Id" +// @Param price formData int true "Product Price" +// @Param image formData file true "Product Description" // @Success 200 {object} response.Response{} "successfully product added" // @Router /admin/products [post] // @Failure 400 {object} response.Response{} "invalid input" // @Failure 409 {object} response.Response{} "Product name already exist" func (p *ProductHandler) SaveProduct(ctx *gin.Context) { - var body request.Product + name, err1 := request.GetFormValuesAsString(ctx, "name") + description, err2 := request.GetFormValuesAsString(ctx, "description") + categoryID, err3 := request.GetFormValuesAsUint(ctx, "category_id") + price, err4 := request.GetFormValuesAsUint(ctx, "price") - if err := ctx.ShouldBindJSON(&body); err != nil { - response.ErrorResponse(ctx, http.StatusBadRequest, BindJsonFailMessage, err, nil) + fileHeader, err5 := ctx.FormFile("image") + + err := errors.Join(err1, err2, err3, err4, err5) + + if err != nil { + response.ErrorResponse(ctx, http.StatusBadRequest, BindFormValueMessage, err, nil) return } - err := p.productUseCase.SaveProduct(ctx, body) + product := request.Product{ + Name: name, + Description: description, + CategoryID: categoryID, + Price: price, + ImageFileHeader: fileHeader, + } + err = p.productUseCase.SaveProduct(ctx, product) if err != nil { statusCode := http.StatusInternalServerError diff --git a/pkg/api/handler/request/product.go b/pkg/api/handler/request/product.go index d5c934b..d91f6fe 100644 --- a/pkg/api/handler/request/product.go +++ b/pkg/api/handler/request/product.go @@ -1,12 +1,14 @@ package request +import "mime/multipart" + // for a new product type Product struct { - Name string `json:"product_name" binding:"required,min=3,max=50"` - Description string `json:"description" binding:"required,min=10,max=100"` - CategoryID uint `json:"category_id" binding:"required"` - Price uint `json:"price" binding:"required,numeric"` - Image string `json:"image" binding:"required"` + Name string `json:"product_name" binding:"required,min=3,max=50"` + Description string `json:"description" binding:"required,min=10,max=100"` + CategoryID uint `json:"category_id" binding:"required"` + Price uint `json:"price" binding:"required,numeric"` + ImageFileHeader *multipart.FileHeader } type UpdateProduct struct { ID uint `json:"product_id" binding:"required"` diff --git a/pkg/config/config.go b/pkg/config/config.go index 57445ab..15cf7a6 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -33,6 +33,11 @@ type Config struct { GoathClientID string `mapstructure:"GOAUTH_CLIENT_ID"` GoauthClientSecret string `mapstructure:"GOAUTH_CLIENT_SECRET"` GoauthCallbackUrl string `mapstructure:"GOAUTH_CALL_BACK_URL"` + + AwsAccessKeyID string `mapstructure:"AWS_ACCESS_KEY_ID"` + AwsSecretKey string `mapstructure:"AWS_SECRET_ACCESS_KEY"` + AwsRegion string `mapstructure:"AWS_REGION"` + AwsBucketName string `mapstructure:"AWS_BUCKET_NAME"` } // name of envs and used to read from system envs @@ -44,6 +49,7 @@ var envsNames = []string{ "RAZOR_PAY_KEY", "RAZOR_PAY_SECRET", // razor pay "STRIPE_SECRET", "STRIPE_PUBLISH_KEY", "STRIPE_WEBHOOK", // stripe "GOAUTH_CLIENT_ID", "GOAUTH_CLIENT_SECRET", "GOAUTH_CALL_BACK_URL", //goath + "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_REGION", "AWS_BUCKET_NAME", // aws s3 } var config Config diff --git a/pkg/di/wire.go b/pkg/di/wire.go index 476a1b2..e74e205 100644 --- a/pkg/di/wire.go +++ b/pkg/di/wire.go @@ -11,6 +11,7 @@ import ( "github.com/nikhilnarayanan623/ecommerce-gin-clean-arch/pkg/config" "github.com/nikhilnarayanan623/ecommerce-gin-clean-arch/pkg/db" "github.com/nikhilnarayanan623/ecommerce-gin-clean-arch/pkg/repository" + "github.com/nikhilnarayanan623/ecommerce-gin-clean-arch/pkg/service/cloud" "github.com/nikhilnarayanan623/ecommerce-gin-clean-arch/pkg/service/otp" "github.com/nikhilnarayanan623/ecommerce-gin-clean-arch/pkg/service/token" "github.com/nikhilnarayanan623/ecommerce-gin-clean-arch/pkg/usecase" @@ -22,6 +23,7 @@ func InitializeApi(cfg config.Config) (*http.ServerHTTP, error) { //external token.NewTokenService, otp.NewOtpAuth, + cloud.NewAWSCloudService, // repository diff --git a/pkg/di/wire_gen.go b/pkg/di/wire_gen.go index db9625b..475d175 100644 --- a/pkg/di/wire_gen.go +++ b/pkg/di/wire_gen.go @@ -13,6 +13,7 @@ import ( "github.com/nikhilnarayanan623/ecommerce-gin-clean-arch/pkg/config" "github.com/nikhilnarayanan623/ecommerce-gin-clean-arch/pkg/db" "github.com/nikhilnarayanan623/ecommerce-gin-clean-arch/pkg/repository" + "github.com/nikhilnarayanan623/ecommerce-gin-clean-arch/pkg/service/cloud" "github.com/nikhilnarayanan623/ecommerce-gin-clean-arch/pkg/service/otp" "github.com/nikhilnarayanan623/ecommerce-gin-clean-arch/pkg/service/token" "github.com/nikhilnarayanan623/ecommerce-gin-clean-arch/pkg/usecase" @@ -46,7 +47,11 @@ func InitializeApi(cfg config.Config) (*http.ServerHTTP, error) { couponRepository := repository.NewCouponRepository(gormDB) paymentUseCase := usecase.NewPaymentUseCase(paymentRepository, orderRepository, userRepository, cartRepository, couponRepository) paymentHandler := handler.NewPaymentHandler(paymentUseCase) - productUseCase := usecase.NewProductUseCase(productRepository) + cloudService, err := cloud.NewAWSCloudService(cfg) + if err != nil { + return nil, err + } + productUseCase := usecase.NewProductUseCase(productRepository, cloudService) productHandler := handler.NewProductHandler(productUseCase) orderUseCase := usecase.NewOrderUseCase(orderRepository, cartRepository, userRepository, paymentRepository) orderHandler := handler.NewOrderHandler(orderUseCase) diff --git a/pkg/repository/interfaces/product.go b/pkg/repository/interfaces/product.go index 4029735..ebdd5bc 100644 --- a/pkg/repository/interfaces/product.go +++ b/pkg/repository/interfaces/product.go @@ -38,7 +38,7 @@ type ProductRepository interface { IsProductNameExist(ctx context.Context, productName string) (exist bool, err error) FindAllProducts(ctx context.Context, pagination request.Pagination) ([]response.Product, error) - SaveProduct(ctx context.Context, product request.Product) error + SaveProduct(ctx context.Context, product domain.Product) error UpdateProduct(ctx context.Context, product domain.Product) error // product items diff --git a/pkg/repository/product.go b/pkg/repository/product.go index 7f86937..d775d92 100644 --- a/pkg/repository/product.go +++ b/pkg/repository/product.go @@ -184,7 +184,7 @@ func (c *productDatabase) IsProductNameExist(ctx context.Context, productName st } // to add a new product in database -func (c *productDatabase) SaveProduct(ctx context.Context, product request.Product) error { +func (c *productDatabase) SaveProduct(ctx context.Context, product domain.Product) error { query := `INSERT INTO products (name, description, category_id, price, image, created_at) VALUES($1, $2, $3, $4, $5, $6)` diff --git a/pkg/service/cloud/aws.go b/pkg/service/cloud/aws.go new file mode 100644 index 0000000..c00cbb5 --- /dev/null +++ b/pkg/service/cloud/aws.go @@ -0,0 +1,78 @@ +package cloud + +import ( + "context" + "fmt" + "mime/multipart" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/google/uuid" + "github.com/nikhilnarayanan623/ecommerce-gin-clean-arch/pkg/config" + "github.com/nikhilnarayanan623/ecommerce-gin-clean-arch/pkg/utils" +) + +type awsService struct { + service *s3.S3 + bucketName string +} + +const ( + filePreSignExpireDuration = time.Hour * 12 +) + +func NewAWSCloudService(cfg config.Config) (CloudService, error) { + + session, err := session.NewSession(&aws.Config{ + Region: aws.String(cfg.AwsRegion), + Credentials: credentials.NewStaticCredentials(cfg.AwsAccessKeyID, cfg.AwsSecretKey, ""), + }) + if err != nil { + return nil, fmt.Errorf("failed to create session for aws service : %w", err) + } + + service := s3.New(session) + + return &awsService{ + service: service, + bucketName: cfg.AwsBucketName, + }, nil +} + +func (c *awsService) SaveFile(ctx context.Context, fileHeader *multipart.FileHeader) (string, error) { + + file, err := fileHeader.Open() + if err != nil { + return "", utils.PrependMessageToError(err, "failed to open file") + } + + uploadID := uuid.New().String() + + _, err = c.service.PutObject(&s3.PutObjectInput{ + Body: file, + Bucket: aws.String(c.bucketName), + Key: aws.String(uploadID), + }) + if err != nil { + return "", utils.PrependMessageToError(err, "failed to upload file") + } + + return uploadID, nil +} +func (c *awsService) GetFileUrl(ctx context.Context, uploadID string) (string, error) { + + req, _ := c.service.GetObjectRequest(&s3.GetObjectInput{ + Bucket: aws.String(c.bucketName), + Key: aws.String(uploadID), + }) + + url, err := req.Presign(filePreSignExpireDuration) + if err != nil { + return "", utils.PrependMessageToError(err, "failed to pre sign url fo uploaded file") + } + + return url, nil +} diff --git a/pkg/service/cloud/cloud.go b/pkg/service/cloud/cloud.go new file mode 100644 index 0000000..6a49c84 --- /dev/null +++ b/pkg/service/cloud/cloud.go @@ -0,0 +1,11 @@ +package cloud + +import ( + "context" + "mime/multipart" +) + +type CloudService interface { + SaveFile(ctx context.Context, fileHeader *multipart.FileHeader) (uploadId string, err error) + GetFileUrl(ctx context.Context, uploadID string) (url string, err error) +} diff --git a/pkg/usecase/product.go b/pkg/usecase/product.go index 43dee10..3eb6b39 100644 --- a/pkg/usecase/product.go +++ b/pkg/usecase/product.go @@ -7,17 +7,22 @@ import ( "github.com/nikhilnarayanan623/ecommerce-gin-clean-arch/pkg/api/handler/response" "github.com/nikhilnarayanan623/ecommerce-gin-clean-arch/pkg/domain" "github.com/nikhilnarayanan623/ecommerce-gin-clean-arch/pkg/repository/interfaces" + "github.com/nikhilnarayanan623/ecommerce-gin-clean-arch/pkg/service/cloud" service "github.com/nikhilnarayanan623/ecommerce-gin-clean-arch/pkg/usecase/interfaces" "github.com/nikhilnarayanan623/ecommerce-gin-clean-arch/pkg/utils" ) type productUseCase struct { - productRepo interfaces.ProductRepository + productRepo interfaces.ProductRepository + cloudService cloud.CloudService } // to get a new instance of productUseCase -func NewProductUseCase(productRepo interfaces.ProductRepository) service.ProductUseCase { - return &productUseCase{productRepo: productRepo} +func NewProductUseCase(productRepo interfaces.ProductRepository, cloudService cloud.CloudService) service.ProductUseCase { + return &productUseCase{ + productRepo: productRepo, + cloudService: cloudService, + } } func (c *productUseCase) FindAllCategories(ctx context.Context, pagination request.Pagination) ([]response.Category, error) { @@ -149,8 +154,22 @@ func (c *productUseCase) FindAllVariationsAndItsValues(ctx context.Context, cate } // to get all product -func (c *productUseCase) FindAllProducts(ctx context.Context, pagination request.Pagination) (products []response.Product, err error) { - return c.productRepo.FindAllProducts(ctx, pagination) +func (c *productUseCase) FindAllProducts(ctx context.Context, pagination request.Pagination) ([]response.Product, error) { + products, err := c.productRepo.FindAllProducts(ctx, pagination) + if err != nil { + return nil, utils.PrependMessageToError(err, "failed to get product details from database") + } + + for i := range products { + + url, err := c.cloudService.GetFileUrl(ctx, products[i].Image) + if err != nil { + continue + } + products[i].Image = url + } + + return products, nil } // to add new product @@ -164,7 +183,18 @@ func (c *productUseCase) SaveProduct(ctx context.Context, product request.Produc return utils.PrependMessageToError(ErrProductAlreadyExist, "product name "+product.Name) } - err = c.productRepo.SaveProduct(ctx, product) + uploadID, err := c.cloudService.SaveFile(ctx, product.ImageFileHeader) + if err != nil { + return utils.PrependMessageToError(err, "failed to save image on cloud storage") + } + + err = c.productRepo.SaveProduct(ctx, domain.Product{ + Name: product.Name, + Description: product.Description, + CategoryID: product.CategoryID, + Price: product.Price, + Image: uploadID, + }) if err != nil { return utils.PrependMessageToError(err, "failed to save product") }