├── .dockerignore ├── .gitignore ├── Dockerfile ├── Makefile ├── README.md ├── apis ├── buyer.go ├── middleware.go ├── order.go ├── product.go └── seller.go ├── cmds ├── deploy-down.sh ├── deploy.sh ├── docker-down-dev.sh ├── docker-down-prod.sh ├── docker-down-test.sh ├── docker-log-dev.sh ├── docker-start-dev.sh ├── docker-start-test.sh ├── docker-stop-dev.sh ├── docker-stop-prod.sh ├── docker-up-dev.sh ├── docker-up-prod.sh ├── docker-up-test.sh └── env ├── configs ├── auth.go ├── config.go ├── constants.go ├── mongodb.go └── server.go ├── docker-compose.dev.yml ├── docker-compose.prod.yml ├── docker-compose.test.yml ├── docker-compose.yml ├── docs ├── API.md ├── NOTES.md └── testing │ ├── TEST.md │ └── pic │ ├── postman │ ├── 1.getall.PNG │ ├── 2.getid.PNG │ ├── 3.buyercreate.PNG │ ├── orderaccept1.PNG │ ├── orderaccept2.PNG │ ├── orderaccept3.PNG │ ├── orderall.PNG │ ├── ordercreate.PNG │ ├── orderid.PNG │ ├── productall.PNG │ ├── productcreate.PNG │ ├── productid.PNG │ ├── productseller.PNG │ ├── sellerall.PNG │ ├── sellercreate.PNG │ └── sellerid.PNG │ ├── repo1.PNG │ ├── repo2.PNG │ └── usecase.PNG ├── env-dev ├── env-prod ├── env-test ├── go.mod ├── go.sum ├── main.go ├── models ├── buyer.go ├── counter.go ├── order.go ├── product.go └── seller.go ├── repositories └── mongodb │ ├── buyer.go │ ├── buyer_test.go │ ├── counter.go │ ├── counter_test.go │ ├── order.go │ ├── order_test.go │ ├── product.go │ ├── product_test.go │ ├── seller.go │ └── seller_test.go ├── usecases ├── buyer.go ├── buyer_test.go ├── order.go ├── order_test.go ├── product.go ├── product_test.go ├── seller.go └── seller_test.go └── utils ├── auth ├── auth.go └── password.go ├── errors ├── bad_request.go ├── base.go ├── default.go ├── mysql.go ├── not_found.go └── unprocessable.go ├── http └── http.go ├── mongodb └── mongodb.go └── test ├── drop.go └── error.go /.dockerignore: -------------------------------------------------------------------------------- 1 | main 2 | gin-bin -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.env* 2 | /node_modules 3 | dump/ 4 | *_add_dummy_data* 5 | scripts/ 6 | sql/ 7 | gin-bin 8 | main 9 | reksti-ordering-backend -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Start from golang base image 2 | FROM golang:1.13-alpine as builder 3 | 4 | # ENV GO111MODULE=on 5 | # ARG 6 | ARG PORT 7 | ARG MONGO_HOST 8 | ARG MONGO_DATABASE 9 | ARG MONGO_OPTIONS 10 | ARG TIMEOUT_ON_SECONDS 11 | ARG OPERATION_ON_EACH_CONTEXT 12 | ARG SECRET 13 | ARG REFRESH_SECRET 14 | # ENV 15 | ENV PORT=${PORT} 16 | ENV MONGO_HOST=${MONGO_HOST}} 17 | ENV MONGO_DATABASE=${MONGO_DATABASE}} 18 | ENV MONGO_OPTIONS=${MONGO_OPTIONS}} 19 | ENV TIMEOUT_ON_SECONDS=${TIMEOUT_ON_SECONDS} 20 | ENV OPERATION_ON_EACH_CONTEXT=${TIMEOUT_ON_SECONDS} 21 | ENV SECRET=${SECRET} 22 | ENV REFRESH_SECRET=${REFRESH_SECRET} 23 | # Install git. 24 | # Git is required for fetching the dependencies. 25 | RUN apk update && apk add --no-cache git 26 | 27 | # Set the current working directory inside the container 28 | WORKDIR /app 29 | 30 | # Copy go mod and sum files 31 | COPY go.mod go.sum ./ 32 | 33 | # Download all dependencies. Dependencies will be cached if the go.mod and the go.sum files are not changed 34 | RUN go mod download 35 | 36 | # Copy the source from the current directory to the working Directory inside the container 37 | COPY . . 38 | 39 | # Build the Go app 40 | RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main . 41 | 42 | # Start a new stage from scratch 43 | FROM alpine:latest 44 | RUN apk --no-cache add ca-certificates 45 | 46 | WORKDIR /root/ 47 | 48 | # Copy the Pre-built binary file from the previous stage. Observe we also copied the .env file 49 | COPY --from=builder /app/main . 50 | COPY --from=builder /app/.env . 51 | 52 | # Expose port 8800 to the outside world 53 | EXPOSE 8800 54 | 55 | #Command to run the executable 56 | CMD ["./main"] -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: mock test 2 | include .env 3 | 4 | # run: test-api test-service 5 | run: 6 | ./cmds/env .env go run main.go 7 | 8 | build: 9 | ./cmds/env .env go build main.go 10 | 11 | dev: 12 | ./cmds/env .env 13 | gin --appPort ${PORT} --all -i main.go 14 | 15 | test: 16 | go get -u github.com/kyoh86/richgo 17 | ./cmds/env env-test richgo test -count=1 ./... -v -cover 18 | go mod tidy 19 | 20 | test-repo: 21 | go get -u github.com/kyoh86/richgo 22 | ./cmds/env env-test richgo test -count=1 ./repositories/mongodb -v -cover 23 | go mod tidy 24 | 25 | test-usecase: 26 | go get -u github.com/kyoh86/richgo 27 | ./cmds/env env-test richgo test -count=1 ./usecases -v -cover 28 | go mod tidy 29 | 30 | tidy: 31 | go mod tidy 32 | 33 | download: 34 | go mod download 35 | 36 | mock: 37 | @mockery --dir models --all -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # e-commerce backend 2 | Backend for e-commerce app 3 | 4 | # DOCS 5 | 6 | * [API Docs](docs/API.md) 7 | * [Testing](docs/testing/TEST.md) 8 | * [Notes](docs/NOTES.md) 9 | # Development Setup 10 | 11 | ## Tools 12 | 13 | - Go version 1.3 14 | - Go Modules for dependency management. 15 | - MongoDB for Database 16 | 17 | ## Run Program 18 | 19 | This program needs environment variables. See example below. 20 | 21 | ```shell 22 | PORT=8811 23 | 24 | MONGO_HOSTS=127.0.0.1:27217 25 | MONGO_DATABASE=ecommerce 26 | MONGO_OPTIONS= 27 | 28 | TIMEOUT_ON_SECONDS=120 29 | OPERATION_ON_EACH_CONTEXT=500 30 | ``` 31 | 32 | You can store environment variables to a file, such as ".env". But, if you want to differentiate environment variables used by docker, you can give another name, such as ".env-no-docker". Run the program with commands below using gin for auto reloading. 33 | 34 | ```shell 35 | make dev 36 | ``` 37 | 38 | # Test Program 39 | To ensure code quality, one must test the code by its units. One can use docker to initiate the test dependency such as MySQL. To do test, one also need env configuration. See example below that is suited for the docker-compose.test configuration. Name the below file to `env-test` 40 | 41 | ```env 42 | MONGO_HOSTS=localhost:27117 43 | MONGO_DATABASE=test 44 | 45 | TIMEOUT_ON_SECONDS=120 46 | OPERATION_ON_EACH_CONTEXT=500 47 | ``` 48 | 49 | Run the test using 50 | ```shell 51 | make test 52 | ``` 53 | 54 | # Deployment 55 | One can deploy this application using Docker. Make sure docker is installed on your system. There is already an example of env file for production called env-prod. 56 | Create .env file from env-prod: 57 | ```shell 58 | cp env-prod .env 59 | ``` 60 | Then deploy the database depency docker container using: 61 | 62 | ``` shell 63 | ./cmds/docker-up-prod.sh 64 | ``` 65 | 66 | Finally deploy the Go application using: 67 | ``` shell 68 | ./cmds/deploy.sh 69 | ``` -------------------------------------------------------------------------------- /apis/buyer.go: -------------------------------------------------------------------------------- 1 | package apis 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "strconv" 7 | 8 | "github.com/gorilla/mux" 9 | "github.com/masterraf21/ecommerce-backend/models" 10 | httpUtil "github.com/masterraf21/ecommerce-backend/utils/http" 11 | ) 12 | 13 | type buyerAPI struct { 14 | BuyerUsecase models.BuyerUsecase 15 | } 16 | 17 | // NewBuyerAPI will create api for buyer 18 | func NewBuyerAPI(r *mux.Router, buc models.BuyerUsecase) { 19 | buyerAPI := &buyerAPI{ 20 | BuyerUsecase: buc, 21 | } 22 | 23 | r.HandleFunc("/buyer", buyerAPI.Create).Methods("POST") 24 | r.HandleFunc("/buyer", buyerAPI.GetAll).Methods("GET") 25 | r.HandleFunc("/buyer/{id_buyer}", buyerAPI.GetByID).Methods("GET") 26 | } 27 | 28 | func (b *buyerAPI) Create(w http.ResponseWriter, r *http.Request) { 29 | var body models.BuyerBody 30 | if err := json.NewDecoder(r.Body).Decode(&body); err != nil { 31 | httpUtil.HandleError(w, r, err, "bad request body", http.StatusBadRequest) 32 | } 33 | defer r.Body.Close() 34 | 35 | id, err := b.BuyerUsecase.CreateBuyer(body) 36 | if err != nil { 37 | httpUtil.HandleError(w, r, err, "failed to creata buyer", http.StatusInternalServerError) 38 | return 39 | } 40 | 41 | var response struct { 42 | ID uint32 `json:"id_buyer"` 43 | } 44 | response.ID = id 45 | 46 | httpUtil.HandleJSONResponse(w, r, response) 47 | } 48 | 49 | func (b *buyerAPI) GetAll(w http.ResponseWriter, r *http.Request) { 50 | result, err := b.BuyerUsecase.GetAll() 51 | if err != nil { 52 | httpUtil.HandleError(w, r, err, "failed to get buyer data", http.StatusInternalServerError) 53 | return 54 | } 55 | 56 | var data struct { 57 | Data []models.Buyer `json:"data"` 58 | } 59 | data.Data = result 60 | httpUtil.HandleJSONResponse(w, r, data) 61 | } 62 | 63 | func (b *buyerAPI) GetByID(w http.ResponseWriter, r *http.Request) { 64 | params := mux.Vars(r) 65 | 66 | buyerID, err := strconv.ParseInt(params["id_buyer"], 10, 64) 67 | if err != nil { 68 | httpUtil.HandleError( 69 | w, 70 | r, 71 | err, 72 | params["id_buyer"]+" is not integer", 73 | http.StatusBadRequest, 74 | ) 75 | return 76 | } 77 | 78 | result, err := b.BuyerUsecase.GetByID(uint32(buyerID)) 79 | if err != nil { 80 | httpUtil.HandleError(w, r, err, "failed to get buyer data by id", http.StatusInternalServerError) 81 | return 82 | } 83 | 84 | var data struct { 85 | Data *models.Buyer `json:"data"` 86 | } 87 | data.Data = result 88 | 89 | httpUtil.HandleJSONResponse(w, r, data) 90 | } 91 | -------------------------------------------------------------------------------- /apis/middleware.go: -------------------------------------------------------------------------------- 1 | package apis 2 | -------------------------------------------------------------------------------- /apis/order.go: -------------------------------------------------------------------------------- 1 | package apis 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "strconv" 7 | 8 | "github.com/gorilla/mux" 9 | "github.com/masterraf21/ecommerce-backend/models" 10 | httpUtil "github.com/masterraf21/ecommerce-backend/utils/http" 11 | ) 12 | 13 | type orderAPI struct { 14 | OrderUsecase models.OrderUsecase 15 | } 16 | 17 | // NewOrderAPI will create api for order 18 | func NewOrderAPI(r *mux.Router, oru models.OrderUsecase) { 19 | orderAPI := &orderAPI{ 20 | OrderUsecase: oru, 21 | } 22 | 23 | r.HandleFunc("/order", orderAPI.Create).Methods("POST") 24 | r.HandleFunc("/order", orderAPI.GetAll).Methods("GET") 25 | r.HandleFunc("/order/{id_order}", orderAPI.GetByID).Methods("GET") 26 | r.HandleFunc("/order/seller/{id_seller}", orderAPI.GetBySellerID).Methods("GET") 27 | r.HandleFunc("/order/buyer/{id_buyer}", orderAPI.GetByBuyerID).Methods("GET") 28 | r.HandleFunc("/order/seller/{id_seller}/accepted", orderAPI.GetAcceptedOrderBySellerID).Methods("GET") 29 | r.HandleFunc("/order/buyer/{id_buyer}/accepted", orderAPI.GetAcceptedOrderByBuyerID).Methods("GET") 30 | r.HandleFunc("/order/seller/{id_seller}/pending", orderAPI.GetPendingOrderBySellerID).Methods("GET") 31 | r.HandleFunc("/order/buyer/{id_buyer}/pending", orderAPI.GetPendingOrderByBuyerID).Methods("GET") 32 | r.HandleFunc("/order/{id_order}/accept", orderAPI.AcceptOrder).Methods("POST") 33 | } 34 | 35 | func (o *orderAPI) Create(w http.ResponseWriter, r *http.Request) { 36 | var body models.OrderBody 37 | if err := json.NewDecoder(r.Body).Decode(&body); err != nil { 38 | httpUtil.HandleError(w, r, err, "bad request body", http.StatusBadRequest) 39 | } 40 | defer r.Body.Close() 41 | 42 | id, err := o.OrderUsecase.CreateOrder(body) 43 | if err != nil { 44 | httpUtil.HandleError(w, r, err, "failed to creata order", http.StatusInternalServerError) 45 | return 46 | } 47 | 48 | var response struct { 49 | ID uint32 `json:"id_order"` 50 | } 51 | response.ID = id 52 | 53 | httpUtil.HandleJSONResponse(w, r, response) 54 | } 55 | 56 | func (o *orderAPI) GetAll(w http.ResponseWriter, r *http.Request) { 57 | result, err := o.OrderUsecase.GetAll() 58 | if err != nil { 59 | httpUtil.HandleError(w, r, err, "failed to get order data", http.StatusInternalServerError) 60 | return 61 | } 62 | 63 | var data struct { 64 | Data []models.Order `json:"data"` 65 | } 66 | data.Data = result 67 | httpUtil.HandleJSONResponse(w, r, data) 68 | } 69 | 70 | func (o *orderAPI) GetByID(w http.ResponseWriter, r *http.Request) { 71 | params := mux.Vars(r) 72 | 73 | orderID, err := strconv.ParseInt(params["id_order"], 10, 64) 74 | if err != nil { 75 | httpUtil.HandleError( 76 | w, 77 | r, 78 | err, 79 | params["id_order"]+" is not integer", 80 | http.StatusBadRequest, 81 | ) 82 | return 83 | } 84 | 85 | result, err := o.OrderUsecase.GetByID(uint32(orderID)) 86 | if err != nil { 87 | httpUtil.HandleError(w, r, err, "failed to get order data by id", http.StatusInternalServerError) 88 | return 89 | } 90 | 91 | var data struct { 92 | Data *models.Order `json:"data"` 93 | } 94 | data.Data = result 95 | 96 | httpUtil.HandleJSONResponse(w, r, data) 97 | } 98 | 99 | func (o *orderAPI) GetBySellerID(w http.ResponseWriter, r *http.Request) { 100 | params := mux.Vars(r) 101 | 102 | sellerID, err := strconv.ParseInt(params["id_seller"], 10, 64) 103 | if err != nil { 104 | httpUtil.HandleError( 105 | w, 106 | r, 107 | err, 108 | params["id_seller"]+" is not integer", 109 | http.StatusBadRequest, 110 | ) 111 | return 112 | } 113 | 114 | result, err := o.OrderUsecase.GetBySellerID(uint32(sellerID)) 115 | if err != nil { 116 | httpUtil.HandleError(w, r, err, "failed to get order data by id seller", http.StatusInternalServerError) 117 | return 118 | } 119 | 120 | var data struct { 121 | Data []models.Order `json:"data"` 122 | } 123 | data.Data = result 124 | 125 | httpUtil.HandleJSONResponse(w, r, data) 126 | } 127 | 128 | func (o *orderAPI) GetByBuyerID(w http.ResponseWriter, r *http.Request) { 129 | params := mux.Vars(r) 130 | 131 | buyerID, err := strconv.ParseInt(params["id_buyer"], 10, 64) 132 | if err != nil { 133 | httpUtil.HandleError( 134 | w, 135 | r, 136 | err, 137 | params["id_buyer"]+" is not integer", 138 | http.StatusBadRequest, 139 | ) 140 | return 141 | } 142 | 143 | result, err := o.OrderUsecase.GetByBuyerID(uint32(buyerID)) 144 | if err != nil { 145 | httpUtil.HandleError(w, r, err, "failed to get order data by id buyer", http.StatusInternalServerError) 146 | return 147 | } 148 | 149 | var data struct { 150 | Data []models.Order `json:"data"` 151 | } 152 | data.Data = result 153 | 154 | httpUtil.HandleJSONResponse(w, r, data) 155 | } 156 | 157 | func (o *orderAPI) GetAcceptedOrderBySellerID(w http.ResponseWriter, r *http.Request) { 158 | params := mux.Vars(r) 159 | 160 | sellerID, err := strconv.ParseInt(params["id_seller"], 10, 64) 161 | if err != nil { 162 | httpUtil.HandleError( 163 | w, 164 | r, 165 | err, 166 | params["id_seller"]+" is not integer", 167 | http.StatusBadRequest, 168 | ) 169 | return 170 | } 171 | 172 | result, err := o.OrderUsecase.GetBySellerIDAndStatus(uint32(sellerID), "Accepted") 173 | if err != nil { 174 | httpUtil.HandleError(w, r, err, "failed to get order data by id seller", http.StatusInternalServerError) 175 | return 176 | } 177 | 178 | var data struct { 179 | Data []models.Order `json:"data"` 180 | } 181 | data.Data = result 182 | 183 | httpUtil.HandleJSONResponse(w, r, data) 184 | } 185 | 186 | func (o *orderAPI) GetPendingOrderBySellerID(w http.ResponseWriter, r *http.Request) { 187 | params := mux.Vars(r) 188 | 189 | sellerID, err := strconv.ParseInt(params["id_seller"], 10, 64) 190 | if err != nil { 191 | httpUtil.HandleError( 192 | w, 193 | r, 194 | err, 195 | params["id_seller"]+" is not integer", 196 | http.StatusBadRequest, 197 | ) 198 | return 199 | } 200 | 201 | result, err := o.OrderUsecase.GetBySellerIDAndStatus(uint32(sellerID), "Pending") 202 | if err != nil { 203 | httpUtil.HandleError(w, r, err, "failed to get order data by id seller", http.StatusInternalServerError) 204 | return 205 | } 206 | 207 | var data struct { 208 | Data []models.Order `json:"data"` 209 | } 210 | data.Data = result 211 | 212 | httpUtil.HandleJSONResponse(w, r, data) 213 | } 214 | 215 | func (o *orderAPI) GetAcceptedOrderByBuyerID(w http.ResponseWriter, r *http.Request) { 216 | params := mux.Vars(r) 217 | 218 | buyerID, err := strconv.ParseInt(params["id_buyer"], 10, 64) 219 | if err != nil { 220 | httpUtil.HandleError( 221 | w, 222 | r, 223 | err, 224 | params["id_buyer"]+" is not integer", 225 | http.StatusBadRequest, 226 | ) 227 | return 228 | } 229 | 230 | result, err := o.OrderUsecase.GetByBuyerIDAndStatus(uint32(buyerID), "Accepted") 231 | if err != nil { 232 | httpUtil.HandleError(w, r, err, "failed to get order data by id buyer", http.StatusInternalServerError) 233 | return 234 | } 235 | 236 | var data struct { 237 | Data []models.Order `json:"data"` 238 | } 239 | data.Data = result 240 | 241 | httpUtil.HandleJSONResponse(w, r, data) 242 | } 243 | 244 | func (o *orderAPI) GetPendingOrderByBuyerID(w http.ResponseWriter, r *http.Request) { 245 | params := mux.Vars(r) 246 | 247 | buyerID, err := strconv.ParseInt(params["id_buyer"], 10, 64) 248 | if err != nil { 249 | httpUtil.HandleError( 250 | w, 251 | r, 252 | err, 253 | params["id_buyer"]+" is not integer", 254 | http.StatusBadRequest, 255 | ) 256 | return 257 | } 258 | 259 | result, err := o.OrderUsecase.GetByBuyerIDAndStatus(uint32(buyerID), "Pending") 260 | if err != nil { 261 | httpUtil.HandleError(w, r, err, "failed to get order data by id buyer", http.StatusInternalServerError) 262 | return 263 | } 264 | 265 | var data struct { 266 | Data []models.Order `json:"data"` 267 | } 268 | data.Data = result 269 | 270 | httpUtil.HandleJSONResponse(w, r, data) 271 | } 272 | 273 | func (o *orderAPI) AcceptOrder(w http.ResponseWriter, r *http.Request) { 274 | params := mux.Vars(r) 275 | 276 | orderID, err := strconv.ParseInt(params["id_order"], 10, 64) 277 | if err != nil { 278 | httpUtil.HandleError( 279 | w, 280 | r, 281 | err, 282 | params["id_order"]+" is not integer", 283 | http.StatusBadRequest, 284 | ) 285 | return 286 | } 287 | 288 | err = o.OrderUsecase.AcceptOrder(uint32(orderID)) 289 | if err != nil { 290 | httpUtil.HandleError(w, r, err, "error accepting order", http.StatusInternalServerError) 291 | return 292 | } 293 | 294 | httpUtil.HandleNoJSONResponse(w) 295 | } 296 | -------------------------------------------------------------------------------- /apis/product.go: -------------------------------------------------------------------------------- 1 | package apis 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "strconv" 7 | 8 | "github.com/gorilla/mux" 9 | "github.com/masterraf21/ecommerce-backend/models" 10 | httpUtil "github.com/masterraf21/ecommerce-backend/utils/http" 11 | ) 12 | 13 | type productAPI struct { 14 | ProductUsecase models.ProductUsecase 15 | } 16 | 17 | // NewProductAPI will create api for product 18 | func NewProductAPI(r *mux.Router, pru models.ProductUsecase) { 19 | productAPI := &productAPI{ 20 | ProductUsecase: pru, 21 | } 22 | 23 | r.HandleFunc("/product", productAPI.Create).Methods("POST") 24 | r.HandleFunc("/product", productAPI.GetAll).Methods("GET") 25 | r.HandleFunc("/product/{id_product}", productAPI.GetByID).Methods("GET") 26 | r.HandleFunc("/product/seller/{id_seller}", productAPI.GetBySellerID).Methods("GET") 27 | } 28 | 29 | func (p *productAPI) Create(w http.ResponseWriter, r *http.Request) { 30 | var body models.ProductBody 31 | if err := json.NewDecoder(r.Body).Decode(&body); err != nil { 32 | httpUtil.HandleError(w, r, err, "bad request body", http.StatusBadRequest) 33 | } 34 | defer r.Body.Close() 35 | 36 | id, err := p.ProductUsecase.CreateProduct(body) 37 | if err != nil { 38 | httpUtil.HandleError(w, r, err, "failed to creata product", http.StatusInternalServerError) 39 | return 40 | } 41 | 42 | var response struct { 43 | ID uint32 `json:"id_product"` 44 | } 45 | response.ID = id 46 | 47 | httpUtil.HandleJSONResponse(w, r, response) 48 | } 49 | 50 | func (p *productAPI) GetAll(w http.ResponseWriter, r *http.Request) { 51 | result, err := p.ProductUsecase.GetAll() 52 | if err != nil { 53 | httpUtil.HandleError(w, r, err, "failed to get product data", http.StatusInternalServerError) 54 | return 55 | } 56 | 57 | var data struct { 58 | Data []models.Product `json:"data"` 59 | } 60 | data.Data = result 61 | httpUtil.HandleJSONResponse(w, r, data) 62 | } 63 | 64 | func (p *productAPI) GetByID(w http.ResponseWriter, r *http.Request) { 65 | params := mux.Vars(r) 66 | 67 | productID, err := strconv.ParseInt(params["id_product"], 10, 64) 68 | if err != nil { 69 | httpUtil.HandleError( 70 | w, 71 | r, 72 | err, 73 | params["id_product"]+" is not integer", 74 | http.StatusBadRequest, 75 | ) 76 | return 77 | } 78 | 79 | result, err := p.ProductUsecase.GetByID(uint32(productID)) 80 | if err != nil { 81 | httpUtil.HandleError(w, r, err, "failed to get product data by id", http.StatusInternalServerError) 82 | return 83 | } 84 | 85 | var data struct { 86 | Data *models.Product `json:"data"` 87 | } 88 | data.Data = result 89 | 90 | httpUtil.HandleJSONResponse(w, r, data) 91 | } 92 | 93 | func (p *productAPI) GetBySellerID(w http.ResponseWriter, r *http.Request) { 94 | params := mux.Vars(r) 95 | 96 | sellerID, err := strconv.ParseInt(params["id_seller"], 10, 64) 97 | if err != nil { 98 | httpUtil.HandleError( 99 | w, 100 | r, 101 | err, 102 | params["id_seller"]+" is not integer", 103 | http.StatusBadRequest, 104 | ) 105 | return 106 | } 107 | 108 | result, err := p.ProductUsecase.GetBySellerID(uint32(sellerID)) 109 | if err != nil { 110 | httpUtil.HandleError(w, r, err, "failed to get product data by seller id", http.StatusInternalServerError) 111 | return 112 | } 113 | 114 | var data struct { 115 | Data []models.Product `json:"data"` 116 | } 117 | data.Data = result 118 | 119 | httpUtil.HandleJSONResponse(w, r, data) 120 | } 121 | -------------------------------------------------------------------------------- /apis/seller.go: -------------------------------------------------------------------------------- 1 | package apis 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "strconv" 7 | 8 | "github.com/gorilla/mux" 9 | "github.com/masterraf21/ecommerce-backend/models" 10 | httpUtil "github.com/masterraf21/ecommerce-backend/utils/http" 11 | ) 12 | 13 | type sellerAPI struct { 14 | SellerUsecase models.SellerUsecase 15 | } 16 | 17 | // NewSellerAPI will create api for seller 18 | func NewSellerAPI(r *mux.Router, suc models.SellerUsecase) { 19 | sellerAPI := &sellerAPI{ 20 | SellerUsecase: suc, 21 | } 22 | 23 | r.HandleFunc("/seller", sellerAPI.Create).Methods("POST") 24 | r.HandleFunc("/seller", sellerAPI.GetAll).Methods("GET") 25 | r.HandleFunc("/seller/{id_seller}", sellerAPI.GetByID).Methods("GET") 26 | } 27 | 28 | func (s *sellerAPI) Create(w http.ResponseWriter, r *http.Request) { 29 | var body models.SellerBody 30 | if err := json.NewDecoder(r.Body).Decode(&body); err != nil { 31 | httpUtil.HandleError(w, r, err, "bad request body", http.StatusBadRequest) 32 | return 33 | } 34 | defer r.Body.Close() 35 | 36 | id, err := s.SellerUsecase.CreateSeller(body) 37 | if err != nil { 38 | httpUtil.HandleError(w, r, err, "failed to create seller", http.StatusInternalServerError) 39 | return 40 | } 41 | 42 | var response struct { 43 | ID uint32 `json:"id_seller"` 44 | } 45 | response.ID = id 46 | 47 | httpUtil.HandleJSONResponse(w, r, response) 48 | } 49 | 50 | func (s *sellerAPI) GetAll(w http.ResponseWriter, r *http.Request) { 51 | result, err := s.SellerUsecase.GetAll() 52 | if err != nil { 53 | httpUtil.HandleError(w, r, err, "failed to get seller data", http.StatusInternalServerError) 54 | return 55 | } 56 | 57 | var data struct { 58 | Data []models.Seller `json:"data"` 59 | } 60 | data.Data = result 61 | 62 | httpUtil.HandleJSONResponse(w, r, data) 63 | } 64 | 65 | func (s *sellerAPI) GetByID(w http.ResponseWriter, r *http.Request) { 66 | params := mux.Vars(r) 67 | 68 | sellerID, err := strconv.ParseInt(params["id_seller"], 10, 64) 69 | if err != nil { 70 | httpUtil.HandleError( 71 | w, 72 | r, 73 | err, 74 | params["id_seller"]+" is not integer", 75 | http.StatusBadRequest, 76 | ) 77 | return 78 | } 79 | 80 | result, err := s.SellerUsecase.GetByID(uint32(sellerID)) 81 | if err != nil { 82 | httpUtil.HandleError(w, r, err, "failed to get seller data by id", http.StatusInternalServerError) 83 | return 84 | } 85 | 86 | var data struct { 87 | Data *models.Seller `json:"data"` 88 | } 89 | data.Data = result 90 | 91 | httpUtil.HandleJSONResponse(w, r, data) 92 | } 93 | -------------------------------------------------------------------------------- /cmds/deploy-down.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose down -------------------------------------------------------------------------------- /cmds/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose up -d -------------------------------------------------------------------------------- /cmds/docker-down-dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose -f docker-compose.dev.yml down -------------------------------------------------------------------------------- /cmds/docker-down-prod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose -f docker-compose.prod.yml down -------------------------------------------------------------------------------- /cmds/docker-down-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose -f docker-compose.test.yml down -------------------------------------------------------------------------------- /cmds/docker-log-dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose -f docker-compose.dev.yml logs -f -------------------------------------------------------------------------------- /cmds/docker-start-dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose -f docker-compose.dev.yml start -------------------------------------------------------------------------------- /cmds/docker-start-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose -f docker-compose.test.yml start -------------------------------------------------------------------------------- /cmds/docker-stop-dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose -f docker-compose.dev.yml stop -------------------------------------------------------------------------------- /cmds/docker-stop-prod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose -f docker-compose.prod.yml stop -------------------------------------------------------------------------------- /cmds/docker-up-dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose -f docker-compose.dev.yml up -d -------------------------------------------------------------------------------- /cmds/docker-up-prod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose -f docker-compose.prod.yml up -d -------------------------------------------------------------------------------- /cmds/docker-up-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose -f docker-compose.test.yml up -d -------------------------------------------------------------------------------- /cmds/env: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | args_array=($*) 4 | cmd="${args_array[@]:1}" 5 | 6 | while IFS='&&' read -ra arr; do 7 | for i in "${arr[@]}"; do 8 | strlen=`echo -n $i | wc -m` 9 | 10 | if [ $strlen != "0" ] 11 | then 12 | env $(cat $1 | xargs) $i 13 | fi 14 | 15 | done 16 | done <<< "$cmd" 17 | -------------------------------------------------------------------------------- /configs/auth.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | type auth struct { 8 | Secret string 9 | RefreshSecret string 10 | } 11 | 12 | func setupAuth() *auth { 13 | v := &auth{ 14 | Secret: os.Getenv("SECRET"), 15 | RefreshSecret: os.Getenv("REFRESH_SECRET"), 16 | } 17 | 18 | return v 19 | } 20 | -------------------------------------------------------------------------------- /configs/config.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | // Server config 4 | var Server *server 5 | 6 | // MongoDB Config 7 | var MongoDB *mongodb 8 | 9 | // Constant config 10 | var Constant *constant 11 | 12 | // Auth config 13 | var Auth *auth 14 | 15 | func init() { 16 | Server = setupServer() 17 | MongoDB = setupMongoDB() 18 | Constant = setupConstant() 19 | Auth = setupAuth() 20 | } 21 | -------------------------------------------------------------------------------- /configs/constants.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "reflect" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | func toInt(v interface{}) (int, error) { 12 | switch v.(type) { 13 | case int: 14 | return v.(int), nil 15 | case int32: 16 | return int(v.(int32)), nil 17 | case uint32: 18 | return int(v.(uint32)), nil 19 | case float32: 20 | return int(v.(float32)), nil 21 | case float64: 22 | return int(v.(float64)), nil 23 | case string: 24 | t, err := strconv.Atoi(v.(string)) 25 | if err != nil { 26 | return 0, fmt.Errorf("toInt: string %s cannot be converted to int", v.(string)) 27 | } 28 | return t, nil 29 | default: 30 | return 0, fmt.Errorf("toInt: %s cannot be converted to int", reflect.ValueOf(v).Kind().String()) 31 | } 32 | } 33 | 34 | type constant struct { 35 | TimeoutOnSeconds time.Duration 36 | OperationOnEachContext int 37 | } 38 | 39 | func setupConstant() *constant { 40 | timeoutOnSecondsInt, _ := toInt(os.Getenv("TIMEOUT_ON_SECONDS")) 41 | timeoutOnSeconds := time.Duration(timeoutOnSecondsInt) 42 | operationOnEachContext, _ := toInt(os.Getenv("OPERATION_ON_EACH_CONTEXT")) 43 | 44 | v := &constant{ 45 | TimeoutOnSeconds: timeoutOnSeconds, 46 | OperationOnEachContext: operationOnEachContext, 47 | } 48 | 49 | return v 50 | } 51 | -------------------------------------------------------------------------------- /configs/mongodb.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | type mongodb struct { 9 | Hosts string 10 | Database string 11 | Options string 12 | } 13 | 14 | func setupMongoDB() *mongodb { 15 | v := &mongodb{ 16 | Hosts: os.Getenv("MONGO_HOSTS"), 17 | Database: os.Getenv("MONGO_DATABASE"), 18 | Options: os.Getenv("MONGO_OPTIONS"), 19 | } 20 | 21 | if v.Database == "" { 22 | v.Database = "db" 23 | fmt.Printf("using default %s as MongoDB's database name\n", v.Database) 24 | } 25 | 26 | return v 27 | } 28 | -------------------------------------------------------------------------------- /configs/server.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | type server struct { 8 | Port string 9 | } 10 | 11 | func setupServer() *server { 12 | v := server{ 13 | Port: os.Getenv("PORT"), 14 | } 15 | 16 | if v.Port == "" { 17 | v.Port = "8000" 18 | } 19 | 20 | return &v 21 | } 22 | -------------------------------------------------------------------------------- /docker-compose.dev.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | services: 4 | mongo-dev: 5 | image: "mongo:latest" 6 | container_name: mongo_dev_ecommerce 7 | networks: 8 | - dev 9 | environment: 10 | - MONGO_INITDB_DATABASE= 11 | ports: 12 | - 27217:27017 13 | 14 | networks: 15 | dev: 16 | driver: bridge 17 | -------------------------------------------------------------------------------- /docker-compose.prod.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | services: 4 | mongo-dev: 5 | image: "mongo:latest" 6 | container_name: mongo_prod_ecommerce 7 | networks: 8 | - prod 9 | environment: 10 | - MONGO_INITDB_DATABASE=ecommerce 11 | volumes: 12 | - db:/data/db 13 | expose: 14 | - 27017 15 | ports: 16 | - 27317:27017 17 | 18 | volumes: 19 | db: 20 | 21 | networks: 22 | prod: 23 | driver: bridge 24 | -------------------------------------------------------------------------------- /docker-compose.test.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | services: 4 | mongo-test: 5 | image: "mongo:latest" 6 | environment: 7 | - MONGO_INITDB_DATABASE=test 8 | ports: 9 | - 27117:27017 10 | expose: 11 | - "27117" 12 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | services: 4 | app: 5 | env_file: .env 6 | container_name: backend 7 | build: 8 | context: . 9 | dockerfile: Dockerfile 10 | args: 11 | - PORT=${PORT} 12 | - MONGO_HOSTS=${MONGO_HOSTS} 13 | - MONGO_DATABASE=${MONGO_DATABASE} 14 | - MONGO_OPTIONS=${MONGO_OPTIONS} 15 | - TIMEOUT_ON_SECONDS=${TIMEOUT_ON_SECONDS} 16 | - OPERATION_ON_EACH_CONTEXT=${TIMEOUT_ON_SECONDS} 17 | - SECRET=${SECRET} 18 | - REFRESH_SECRET=${REFRESH_SECRET} 19 | ports: 20 | - 8800:8800 21 | restart: on-failure 22 | volumes: 23 | - api:/usr/src/app/ 24 | networks: 25 | - prod 26 | 27 | volumes: 28 | api: 29 | 30 | networks: 31 | prod: 32 | driver: bridge 33 | -------------------------------------------------------------------------------- /docs/API.md: -------------------------------------------------------------------------------- 1 | 2 | # API Documentation for e-commerce App 3 | Built using docgen https://github.com/thedevsaddam/docgen 4 | 5 | 6 | ## Indices 7 | 8 | * [Buyer](#buyer) 9 | 10 | * [CREATE Buyer](#1-create-buyer) 11 | * [GET All Buyer](#2-get-all-buyer) 12 | * [GET Buyer By ID](#3-get-buyer-by-id) 13 | 14 | * [Order](#order) 15 | 16 | * [Accept Order](#1-accept-order) 17 | * [CREATE Order](#2-create-order) 18 | * [GET Acccepted Order By Seller ID](#3-get-acccepted-order-by-seller-id) 19 | * [GET Accepted Order By Buyer ID](#4-get-accepted-order-by-buyer-id) 20 | * [GET All Order](#5-get-all-order) 21 | * [GET Order By Buyer ID](#6-get-order-by-buyer-id) 22 | * [GET Order By ID Copy](#7-get-order-by-id-copy) 23 | * [GET Order By Seller ID](#8-get-order-by-seller-id) 24 | * [GET Pending Order By Buyer ID](#9-get-pending-order-by-buyer-id) 25 | * [GET Pending Order By Seller ID](#10-get-pending-order-by-seller-id) 26 | 27 | * [Product](#product) 28 | 29 | * [CREATE Product](#1-create-product) 30 | * [GET All Product](#2-get-all-product) 31 | * [GET Product By ID](#3-get-product-by-id) 32 | * [GET Product By Seller ID](#4-get-product-by-seller-id) 33 | 34 | * [Seller](#seller) 35 | 36 | * [CREATE Seller](#1-create-seller) 37 | * [GET All Seller](#2-get-all-seller) 38 | * [GET Seller By ID](#3-get-seller-by-id) 39 | 40 | 41 | -------- 42 | 43 | 44 | ## Buyer 45 | 46 | 47 | 48 | ### 1. CREATE Buyer 49 | 50 | 51 | 52 | ***Endpoint:*** 53 | 54 | ```bash 55 | Method: POST 56 | Type: RAW 57 | URL: {{e-commerce}}/buyer 58 | ``` 59 | 60 | 61 | 62 | ***Body:*** 63 | 64 | ```js 65 | { 66 | "email": "apa@gmail.com", 67 | "name": "Aang", 68 | "password": "huyu", 69 | "delivery_address": "huhuhuhu" 70 | } 71 | ``` 72 | 73 | 74 | 75 | ### 2. GET All Buyer 76 | 77 | 78 | 79 | ***Endpoint:*** 80 | 81 | ```bash 82 | Method: GET 83 | Type: 84 | URL: {{e-commerce}}/buyer 85 | ``` 86 | 87 | 88 | 89 | ### 3. GET Buyer By ID 90 | 91 | 92 | 93 | ***Endpoint:*** 94 | 95 | ```bash 96 | Method: GET 97 | Type: 98 | URL: {{e-commerce}}/buyer/:id_buyer 99 | ``` 100 | 101 | 102 | 103 | ***URL variables:*** 104 | 105 | | Key | Value | Description | 106 | | --- | ------|-------------| 107 | | id_buyer | 1 | | 108 | 109 | 110 | 111 | ## Order 112 | 113 | 114 | 115 | ### 1. Accept Order 116 | 117 | 118 | 119 | ***Endpoint:*** 120 | 121 | ```bash 122 | Method: POST 123 | Type: 124 | URL: {{e-commerce}}/order/:id_order/accept 125 | ``` 126 | 127 | 128 | 129 | ***URL variables:*** 130 | 131 | | Key | Value | Description | 132 | | --- | ------|-------------| 133 | | id_order | 1 | | 134 | 135 | 136 | 137 | ### 2. CREATE Order 138 | 139 | 140 | 141 | ***Endpoint:*** 142 | 143 | ```bash 144 | Method: POST 145 | Type: RAW 146 | URL: {{e-commerce}}/order 147 | ``` 148 | 149 | 150 | 151 | ***Body:*** 152 | 153 | ```js 154 | { 155 | "id_buyer": 1, 156 | "id_seller": 1, 157 | "products": [ 158 | { 159 | "id_product": 1, 160 | "quantity": 10 161 | } 162 | ] 163 | } 164 | ``` 165 | 166 | 167 | 168 | ### 3. GET Acccepted Order By Seller ID 169 | 170 | 171 | 172 | ***Endpoint:*** 173 | 174 | ```bash 175 | Method: GET 176 | Type: 177 | URL: {{e-commerce}}/order/seller/:id_seller/accepted 178 | ``` 179 | 180 | 181 | 182 | ***URL variables:*** 183 | 184 | | Key | Value | Description | 185 | | --- | ------|-------------| 186 | | id_seller | 1 | | 187 | 188 | 189 | 190 | ### 4. GET Accepted Order By Buyer ID 191 | 192 | 193 | 194 | ***Endpoint:*** 195 | 196 | ```bash 197 | Method: GET 198 | Type: 199 | URL: {{e-commerce}}/order/buyer/:id_buyer/accepted 200 | ``` 201 | 202 | 203 | 204 | ***URL variables:*** 205 | 206 | | Key | Value | Description | 207 | | --- | ------|-------------| 208 | | id_buyer | | | 209 | 210 | 211 | 212 | ### 5. GET All Order 213 | 214 | 215 | 216 | ***Endpoint:*** 217 | 218 | ```bash 219 | Method: GET 220 | Type: 221 | URL: {{e-commerce}}/order 222 | ``` 223 | 224 | 225 | 226 | ### 6. GET Order By Buyer ID 227 | 228 | 229 | 230 | ***Endpoint:*** 231 | 232 | ```bash 233 | Method: GET 234 | Type: 235 | URL: {{e-commerce}}/order/buyer/:id_buyer 236 | ``` 237 | 238 | 239 | 240 | ***URL variables:*** 241 | 242 | | Key | Value | Description | 243 | | --- | ------|-------------| 244 | | id_buyer | 1 | | 245 | 246 | 247 | 248 | ### 7. GET Order By ID 249 | 250 | 251 | 252 | ***Endpoint:*** 253 | 254 | ```bash 255 | Method: GET 256 | Type: 257 | URL: {{e-commerce}}/order/:id_order 258 | ``` 259 | 260 | 261 | 262 | ***URL variables:*** 263 | 264 | | Key | Value | Description | 265 | | --- | ------|-------------| 266 | | id_order | 2 | | 267 | 268 | 269 | 270 | ### 8. GET Order By Seller ID 271 | 272 | 273 | 274 | ***Endpoint:*** 275 | 276 | ```bash 277 | Method: GET 278 | Type: 279 | URL: {{e-commerce}}/order/seller/:id_seller 280 | ``` 281 | 282 | 283 | 284 | ***URL variables:*** 285 | 286 | | Key | Value | Description | 287 | | --- | ------|-------------| 288 | | id_seller | | | 289 | 290 | 291 | 292 | ### 9. GET Pending Order By Buyer ID 293 | 294 | 295 | 296 | ***Endpoint:*** 297 | 298 | ```bash 299 | Method: GET 300 | Type: 301 | URL: {{e-commerce}}/order/buyer/:id_buyer/pending 302 | ``` 303 | 304 | 305 | 306 | ***URL variables:*** 307 | 308 | | Key | Value | Description | 309 | | --- | ------|-------------| 310 | | id_buyer | | | 311 | 312 | 313 | 314 | ### 10. GET Pending Order By Seller ID 315 | 316 | 317 | 318 | ***Endpoint:*** 319 | 320 | ```bash 321 | Method: GET 322 | Type: 323 | URL: {{e-commerce}}/order/seller/:id_seller/pending 324 | ``` 325 | 326 | 327 | 328 | ***URL variables:*** 329 | 330 | | Key | Value | Description | 331 | | --- | ------|-------------| 332 | | id_seller | 1 | | 333 | 334 | 335 | 336 | ## Product 337 | 338 | 339 | 340 | ### 1. CREATE Product 341 | 342 | 343 | 344 | ***Endpoint:*** 345 | 346 | ```bash 347 | Method: POST 348 | Type: RAW 349 | URL: {{e-commerce}}/product 350 | ``` 351 | 352 | 353 | 354 | ***Body:*** 355 | 356 | ```js 357 | { 358 | "product_name": "Hey", 359 | "description": "Wuwhwuw", 360 | "price": 123100, 361 | "id_seller": 1 362 | } 363 | ``` 364 | 365 | 366 | 367 | ### 2. GET All Product 368 | 369 | 370 | 371 | ***Endpoint:*** 372 | 373 | ```bash 374 | Method: GET 375 | Type: 376 | URL: {{e-commerce}}/product 377 | ``` 378 | 379 | 380 | 381 | ### 3. GET Product By ID 382 | 383 | 384 | 385 | ***Endpoint:*** 386 | 387 | ```bash 388 | Method: GET 389 | Type: 390 | URL: {{e-commerce}}/product/:id_product 391 | ``` 392 | 393 | 394 | 395 | ***URL variables:*** 396 | 397 | | Key | Value | Description | 398 | | --- | ------|-------------| 399 | | id_product | 1123 | | 400 | 401 | 402 | 403 | ### 4. GET Product By Seller ID 404 | 405 | 406 | 407 | ***Endpoint:*** 408 | 409 | ```bash 410 | Method: GET 411 | Type: 412 | URL: {{e-commerce}}/product/seller/:id_seller 413 | ``` 414 | 415 | 416 | 417 | ***URL variables:*** 418 | 419 | | Key | Value | Description | 420 | | --- | ------|-------------| 421 | | id_seller | 12 | | 422 | 423 | 424 | 425 | ## Seller 426 | 427 | 428 | 429 | ### 1. CREATE Seller 430 | 431 | 432 | 433 | ***Endpoint:*** 434 | 435 | ```bash 436 | Method: POST 437 | Type: RAW 438 | URL: {{e-commerce}}/seller 439 | ``` 440 | 441 | 442 | 443 | ***Body:*** 444 | 445 | ```js 446 | { 447 | "email": "apa@gmail.com", 448 | "name": "Aang", 449 | "password": "huyu", 450 | "delivery_address": "asdadssda" 451 | } 452 | ``` 453 | 454 | 455 | 456 | ### 2. GET All Seller 457 | 458 | 459 | 460 | ***Endpoint:*** 461 | 462 | ```bash 463 | Method: GET 464 | Type: 465 | URL: {{e-commerce}}/seller 466 | ``` 467 | 468 | 469 | 470 | ### 3. GET Seller By ID 471 | 472 | 473 | 474 | ***Endpoint:*** 475 | 476 | ```bash 477 | Method: GET 478 | Type: 479 | URL: {{e-commerce}}/seller/:id_seller 480 | ``` 481 | 482 | 483 | 484 | ***URL variables:*** 485 | 486 | | Key | Value | Description | 487 | | --- | ------|-------------| 488 | | id_seller | | | 489 | 490 | 491 | 492 | --- 493 | [Back to top](#e-commerce) 494 | > Made with ♥ by [thedevsaddam](https://github.com/thedevsaddam) | Generated at: 2021-04-22 10:57:40 by [docgen](https://github.com/thedevsaddam/docgen) 495 | -------------------------------------------------------------------------------- /docs/NOTES.md: -------------------------------------------------------------------------------- 1 | ## Notes for e Commerce App 2 | 3 | - All Requirement are implemented except JWT Authentication. I already created the utility functions for JWT. Will be implemented soon. 4 | - I changed the schema for product, assuming price and total price are interchangeable. Insted to handle multiple producst in a single order i created a composite of product detailing the product called order_detail. Hence i use mongoDB for flexibility in creating data and not bounded by SQL schema restrictions. 5 | -------------------------------------------------------------------------------- /docs/testing/TEST.md: -------------------------------------------------------------------------------- 1 | # Testing Result for e-commerce App 2 | 3 | ## Unit Testing 4 | 5 | Tested using go test 6 | 7 | | No| Name | Result | 8 | | --| ------|-------------| 9 | | 1 | Repository Units | ![Repo1](pic/repo1.PNG) | 10 | | 2 | Repository Units extended | ![Repo2](pic/repo2.PNG) | 11 | | 3 | Usecase Units | ![usecase](pic/usecase.PNG) | 12 | 13 | ## Integrated testing 14 | 15 | Tested using Postman 16 | 17 | | No | API | Name | Result | 18 | | --| ------|-------|-------------| 19 | | 1 | Buyer | Get All Buyer | ![getall](pic/postman/1.getall.PNG) | 20 | | 2 | Buyer | Get Buyer by ID | ![getid](pic/postman/2.getid.PNG) | 21 | | 3 | Buyer | Create Buyer | ![create](pic/postman/3.buyercreate.PNG) | 22 | | 4 | Seller | Get All Seller | ![create](pic/postman/sellerall.PNG) | 23 | | 5 | Seller | Get Seller by ID | ![create](pic/postman/sellerid.PNG) | 24 | | 6 | Seller | Create seller | ![create](pic/postman/sellercreate.PNG) | 25 | | 7 | Product | Get All Product | ![create](pic/postman/productall.PNG) | 26 | | 8 | Product | Get Product by ID | ![create](pic/postman/productid.PNG) | 27 | | 9 | Product | Get Product by seller ID | ![create](pic/postman/productseller.PNG) | 28 | | 10 | Product | Create Product | ![create](pic/postman/productcreate.PNG) | 29 | | 11 | Order | Get All Order | ![create](pic/postman/orderall.PNG) | 30 | | 12 | Order | Get Order by ID | ![create](pic/postman/orderid.PNG) | 31 | | 13 | Order | Order Order | ![create](pic/postman/ordercreate.PNG) | 32 | | 14 | Order | Accept Order | ![create](pic/postman/orderaccept1.PNG) | 33 | | | | Accept Order | ![create](pic/postman/orderaccept2.PNG) | 34 | | | | Accept Order | ![create](pic/postman/orderaccept3.PNG) | -------------------------------------------------------------------------------- /docs/testing/pic/postman/1.getall.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterraf21/ecommerce-backend/8cb44260b6ca30fd44b407bf7a01b49556a78e65/docs/testing/pic/postman/1.getall.PNG -------------------------------------------------------------------------------- /docs/testing/pic/postman/2.getid.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterraf21/ecommerce-backend/8cb44260b6ca30fd44b407bf7a01b49556a78e65/docs/testing/pic/postman/2.getid.PNG -------------------------------------------------------------------------------- /docs/testing/pic/postman/3.buyercreate.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterraf21/ecommerce-backend/8cb44260b6ca30fd44b407bf7a01b49556a78e65/docs/testing/pic/postman/3.buyercreate.PNG -------------------------------------------------------------------------------- /docs/testing/pic/postman/orderaccept1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterraf21/ecommerce-backend/8cb44260b6ca30fd44b407bf7a01b49556a78e65/docs/testing/pic/postman/orderaccept1.PNG -------------------------------------------------------------------------------- /docs/testing/pic/postman/orderaccept2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterraf21/ecommerce-backend/8cb44260b6ca30fd44b407bf7a01b49556a78e65/docs/testing/pic/postman/orderaccept2.PNG -------------------------------------------------------------------------------- /docs/testing/pic/postman/orderaccept3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterraf21/ecommerce-backend/8cb44260b6ca30fd44b407bf7a01b49556a78e65/docs/testing/pic/postman/orderaccept3.PNG -------------------------------------------------------------------------------- /docs/testing/pic/postman/orderall.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterraf21/ecommerce-backend/8cb44260b6ca30fd44b407bf7a01b49556a78e65/docs/testing/pic/postman/orderall.PNG -------------------------------------------------------------------------------- /docs/testing/pic/postman/ordercreate.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterraf21/ecommerce-backend/8cb44260b6ca30fd44b407bf7a01b49556a78e65/docs/testing/pic/postman/ordercreate.PNG -------------------------------------------------------------------------------- /docs/testing/pic/postman/orderid.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterraf21/ecommerce-backend/8cb44260b6ca30fd44b407bf7a01b49556a78e65/docs/testing/pic/postman/orderid.PNG -------------------------------------------------------------------------------- /docs/testing/pic/postman/productall.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterraf21/ecommerce-backend/8cb44260b6ca30fd44b407bf7a01b49556a78e65/docs/testing/pic/postman/productall.PNG -------------------------------------------------------------------------------- /docs/testing/pic/postman/productcreate.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterraf21/ecommerce-backend/8cb44260b6ca30fd44b407bf7a01b49556a78e65/docs/testing/pic/postman/productcreate.PNG -------------------------------------------------------------------------------- /docs/testing/pic/postman/productid.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterraf21/ecommerce-backend/8cb44260b6ca30fd44b407bf7a01b49556a78e65/docs/testing/pic/postman/productid.PNG -------------------------------------------------------------------------------- /docs/testing/pic/postman/productseller.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterraf21/ecommerce-backend/8cb44260b6ca30fd44b407bf7a01b49556a78e65/docs/testing/pic/postman/productseller.PNG -------------------------------------------------------------------------------- /docs/testing/pic/postman/sellerall.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterraf21/ecommerce-backend/8cb44260b6ca30fd44b407bf7a01b49556a78e65/docs/testing/pic/postman/sellerall.PNG -------------------------------------------------------------------------------- /docs/testing/pic/postman/sellercreate.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterraf21/ecommerce-backend/8cb44260b6ca30fd44b407bf7a01b49556a78e65/docs/testing/pic/postman/sellercreate.PNG -------------------------------------------------------------------------------- /docs/testing/pic/postman/sellerid.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterraf21/ecommerce-backend/8cb44260b6ca30fd44b407bf7a01b49556a78e65/docs/testing/pic/postman/sellerid.PNG -------------------------------------------------------------------------------- /docs/testing/pic/repo1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterraf21/ecommerce-backend/8cb44260b6ca30fd44b407bf7a01b49556a78e65/docs/testing/pic/repo1.PNG -------------------------------------------------------------------------------- /docs/testing/pic/repo2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterraf21/ecommerce-backend/8cb44260b6ca30fd44b407bf7a01b49556a78e65/docs/testing/pic/repo2.PNG -------------------------------------------------------------------------------- /docs/testing/pic/usecase.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterraf21/ecommerce-backend/8cb44260b6ca30fd44b407bf7a01b49556a78e65/docs/testing/pic/usecase.PNG -------------------------------------------------------------------------------- /env-dev: -------------------------------------------------------------------------------- 1 | PORT=8811 2 | 3 | MONGO_HOSTS=127.0.0.1:27217 4 | MONGO_DATABASE=ecommerce 5 | MONGO_OPTIONS= 6 | 7 | SECRET=0TUfjkBQqz 8 | REFRESH_SECRET=r3P0oP12ml 9 | 10 | TIMEOUT_ON_SECONDS=120 11 | OPERATION_ON_EACH_CONTEXT=500 -------------------------------------------------------------------------------- /env-prod: -------------------------------------------------------------------------------- 1 | PORT=8800 2 | 3 | MONGO_HOSTS=mongo_prod_ecommerce:27017 4 | MONGO_DATABASE=ecommerce 5 | MONGO_OPTIONS= 6 | 7 | SECRET=0TUfjkBQqz 8 | REFRESH_SECRET=r3P0oP12ml 9 | 10 | TIMEOUT_ON_SECONDS=120 11 | OPERATION_ON_EACH_CONTEXT=500 -------------------------------------------------------------------------------- /env-test: -------------------------------------------------------------------------------- 1 | MONGO_HOSTS=localhost:27117 2 | MONGO_DATABASE=test 3 | 4 | SECRET=0TUfjkBQqz 5 | REFRESH_SECRET=r3P0oP12ml 6 | 7 | TIMEOUT_ON_SECONDS=120 8 | OPERATION_ON_EACH_CONTEXT=500 -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/masterraf21/ecommerce-backend 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 7 | github.com/gorilla/mux v1.8.0 8 | github.com/myesui/uuid v1.0.0 // indirect 9 | github.com/rs/cors v1.7.0 10 | github.com/sirupsen/logrus v1.8.1 11 | github.com/stretchr/testify v1.6.1 12 | github.com/twinj/uuid v1.0.0 13 | go.mongodb.org/mongo-driver v1.5.1 14 | golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 15 | golang.org/x/sys v0.0.0-20210421221651-33663a62ff08 // indirect 16 | gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 17 | gopkg.in/stretchr/testify.v1 v1.2.2 // indirect 18 | gopkg.in/yaml.v2 v2.4.0 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/aws/aws-sdk-go v1.34.28 h1:sscPpn/Ns3i0F4HPEWAVcwdIRaZZCuL7llJ2/60yPIk= 3 | github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= 8 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 9 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 10 | github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= 11 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 12 | github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= 13 | github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= 14 | github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= 15 | github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= 16 | github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= 17 | github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= 18 | github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= 19 | github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= 20 | github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= 21 | github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= 22 | github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= 23 | github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= 24 | github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= 25 | github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= 26 | github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= 27 | github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= 28 | github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= 29 | github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= 30 | github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= 31 | github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= 32 | github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= 33 | github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= 34 | github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= 35 | github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= 36 | github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= 37 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 38 | github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= 39 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 40 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= 41 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 42 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 43 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 44 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 45 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= 46 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 47 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 48 | github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= 49 | github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= 50 | github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M= 51 | github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 52 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 53 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 54 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 55 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 56 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 57 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 58 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 59 | github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= 60 | github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= 61 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= 62 | github.com/myesui/uuid v1.0.0 h1:xCBmH4l5KuvLYc5L7AS7SZg9/jKdIFubM7OVoLqaQUI= 63 | github.com/myesui/uuid v1.0.0/go.mod h1:2CDfNgU0LR8mIdO8vdWd8i9gWWxLlcoIGGpSNgafq84= 64 | github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= 65 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 66 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 67 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 68 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 69 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 70 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 71 | github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 72 | github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 73 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 74 | github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= 75 | github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= 76 | github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 77 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 78 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 79 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= 80 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 81 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 82 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 83 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 84 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 85 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 86 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 87 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 88 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 89 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 90 | github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= 91 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 92 | github.com/twinj/uuid v1.0.0 h1:fzz7COZnDrXGTAOHGuUGYd6sG+JMq+AoE7+Jlu0przk= 93 | github.com/twinj/uuid v1.0.0/go.mod h1:mMgcE1RHFUFqe5AfiwlINXisXfDGro23fWdPUfOMjRY= 94 | github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= 95 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= 96 | github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w= 97 | github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= 98 | github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc= 99 | github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= 100 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= 101 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= 102 | go.mongodb.org/mongo-driver v1.5.1 h1:9nOVLGDfOaZ9R0tBumx/BcuqkbFpyTCU2r/Po7A2azI= 103 | go.mongodb.org/mongo-driver v1.5.1/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS4xxMmUqw= 104 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 105 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 106 | golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= 107 | golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM= 108 | golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 109 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 110 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 111 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= 112 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 113 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 114 | golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 115 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 116 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= 117 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 118 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 119 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 120 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 121 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 122 | golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 123 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 124 | golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 125 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= 126 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 127 | golang.org/x/sys v0.0.0-20210421221651-33663a62ff08 h1:qyN5bV+96OX8pL78eXDuz6YlDPzCYgdW74H5yE9BoSU= 128 | golang.org/x/sys v0.0.0-20210421221651-33663a62ff08/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 129 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 130 | golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= 131 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 132 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 133 | golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 134 | golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 135 | golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 136 | golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 137 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 138 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 139 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 140 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 141 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 142 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 143 | gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw= 144 | gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= 145 | gopkg.in/stretchr/testify.v1 v1.2.2 h1:yhQC6Uy5CqibAIlk1wlusa/MJ3iAN49/BsR/dCCKz3M= 146 | gopkg.in/stretchr/testify.v1 v1.2.2/go.mod h1:QI5V/q6UbPmuhtm10CaFZxED9NreB8PnFYN9JcR6TxU= 147 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 148 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 149 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 150 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 151 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 152 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 153 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "net/http" 7 | "os" 8 | "os/signal" 9 | "time" 10 | 11 | "github.com/gorilla/mux" 12 | "github.com/masterraf21/ecommerce-backend/apis" 13 | "github.com/masterraf21/ecommerce-backend/configs" 14 | "github.com/masterraf21/ecommerce-backend/usecases" 15 | 16 | repoMongo "github.com/masterraf21/ecommerce-backend/repositories/mongodb" 17 | 18 | "github.com/masterraf21/ecommerce-backend/utils/mongodb" 19 | 20 | "go.mongodb.org/mongo-driver/mongo" 21 | 22 | "github.com/rs/cors" 23 | ) 24 | 25 | // Server represents server 26 | type Server struct { 27 | Instance *mongo.Database 28 | Port string 29 | ServerReady chan bool 30 | } 31 | 32 | func main() { 33 | instance := mongodb.ConfigureMongo() 34 | serverReady := make(chan bool) 35 | server := Server{ 36 | Instance: instance, 37 | Port: configs.Server.Port, 38 | ServerReady: serverReady, 39 | } 40 | server.Start() 41 | } 42 | 43 | // Start will start server 44 | func (s *Server) Start() { 45 | port := configs.Server.Port 46 | if port == "" { 47 | port = "8000" 48 | } 49 | 50 | r := new(mux.Router) 51 | 52 | counterRepo := repoMongo.NewCounterRepo(s.Instance) 53 | buyerRepo := repoMongo.NewBuyerRepo(s.Instance, counterRepo) 54 | sellerRepo := repoMongo.NewSellerRepo(s.Instance, counterRepo) 55 | productRepo := repoMongo.NewProductRepo(s.Instance, counterRepo) 56 | orderRepo := repoMongo.NewOrderRepo(s.Instance, counterRepo) 57 | 58 | buyerUsecase := usecases.NewBuyerUsecase(buyerRepo) 59 | sellerUsecase := usecases.NewSellerUsecase(sellerRepo) 60 | productUsecase := usecases.NewProductUsecase(productRepo, sellerRepo) 61 | orderUsecase := usecases.NewOrderUsecase(orderRepo, buyerRepo, sellerRepo, productRepo) 62 | 63 | apis.NewBuyerAPI(r, buyerUsecase) 64 | apis.NewSellerAPI(r, sellerUsecase) 65 | apis.NewProductAPI(r, productUsecase) 66 | apis.NewOrderAPI(r, orderUsecase) 67 | 68 | handler := cors.Default().Handler(r) 69 | 70 | srv := &http.Server{ 71 | Handler: handler, 72 | Addr: ":" + port, 73 | WriteTimeout: 15 * time.Second, 74 | ReadTimeout: 15 * time.Second, 75 | } 76 | 77 | go func() { 78 | log.Printf("Starting server on port %s!", port) 79 | if err := srv.ListenAndServe(); err != nil { 80 | log.Println("Shutting Down Server...") 81 | log.Fatal(err.Error()) 82 | } 83 | }() 84 | 85 | if s.ServerReady != nil { 86 | s.ServerReady <- true 87 | } 88 | 89 | quit := make(chan os.Signal) 90 | signal.Notify(quit, os.Interrupt) 91 | <-quit 92 | 93 | ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) 94 | defer cancel() 95 | if err := srv.Shutdown(ctx); err != nil { 96 | log.Fatalf("failed to gracefully shutdown the server: %s", err) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /models/buyer.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "go.mongodb.org/mongo-driver/bson/primitive" 4 | 5 | // Buyer represents buyer 6 | type Buyer struct { 7 | ID uint32 `bson:"id_buyer" json:"id_buyer"` 8 | Email string `bson:"email" json:"email"` 9 | Name string `bson:"name" json:"name"` 10 | Password string `bson:"password" json:"password"` 11 | DeliveryAddress string `bson:"delivery_address" json:"delivery_address"` 12 | } 13 | 14 | // BuyerBody body for buyer 15 | type BuyerBody struct { 16 | Email string `json:"email"` 17 | Name string `json:"name"` 18 | Password string `json:"password"` 19 | DeliveryAddress string `json:"delivery_address"` 20 | } 21 | 22 | // BuyerRepository represents repo functions for Buyer 23 | type BuyerRepository interface { 24 | Store(buyer *Buyer) (primitive.ObjectID, error) 25 | GetAll() ([]Buyer, error) 26 | GetByID(id uint32) (*Buyer, error) 27 | GetByOID(oid primitive.ObjectID) (*Buyer, error) 28 | UpdateArbitrary(id uint32, key string, value interface{}) error 29 | } 30 | 31 | // BuyerUsecase will create usecase for buyer 32 | type BuyerUsecase interface { 33 | CreateBuyer(buyer BuyerBody) (uint32, error) 34 | GetAll() ([]Buyer, error) 35 | GetByID(id uint32) (*Buyer, error) 36 | } 37 | -------------------------------------------------------------------------------- /models/counter.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // Counter represents counter for 4 | type Counter struct { 5 | BuyerID uint32 `bson:"id_buyer" json:"id_buyer"` 6 | ProductID uint32 `bson:"id_product" json:"id_product"` 7 | SellerID uint32 `bson:"id_seller" json:"id_seller"` 8 | OrderID uint32 `bson:"id_order" json:"id_order"` 9 | } 10 | 11 | // CounterRepository repo for counter 12 | type CounterRepository interface { 13 | Get(collectionName string, identifier string) (uint32, error) 14 | } 15 | -------------------------------------------------------------------------------- /models/order.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "go.mongodb.org/mongo-driver/bson/primitive" 4 | 5 | // Order represents order 6 | type Order struct { 7 | ID uint32 `bson:"id_order" json:"id_order"` 8 | BuyerID uint32 `bson:"id_buyer" json:"id_buyer"` 9 | Buyer *Buyer `bson:"buyer" json:"buyer"` 10 | SellerID uint32 `bson:"id_seller" json:"id_seller"` 11 | Seller *Seller `bson:"seller" json:"seller"` 12 | SourceAddress string `bson:"source_address" json:"source_address"` 13 | DeliveryAddress string `bson:"delivery_address" json:"delivery_address"` 14 | Products []OrderDetail `bson:"products" json:"products"` 15 | TotalPrice float32 `bson:"total_price" json:"total_price"` 16 | Status string `bson:"status" json:"status"` 17 | } 18 | 19 | // OrderDetail will detail the order 20 | type OrderDetail struct { 21 | ProductID uint32 `json:"id_product" bson:"id_product"` 22 | Product *Product `json:"product" bson:"product"` 23 | Quantity uint32 `json:"quantity" bson:"quantity"` 24 | TotalPrice float32 `json:"total_price" bson:"total_price"` 25 | } 26 | 27 | // ProductDetail for body 28 | type ProductDetail struct { 29 | ProductID uint32 `json:"id_product"` 30 | Quantity uint32 `json:"quantity"` 31 | } 32 | 33 | // OrderBody body from json 34 | type OrderBody struct { 35 | BuyerID uint32 `json:"id_buyer"` 36 | SellerID uint32 `json:"id_seller"` 37 | Products []ProductDetail `json:"products"` 38 | } 39 | 40 | // OrderRepository reprresents repo functions for order 41 | type OrderRepository interface { 42 | Store(order *Order) (primitive.ObjectID, error) 43 | GetAll() ([]Order, error) 44 | GetByID(id uint32) (*Order, error) 45 | GetByOID(oid primitive.ObjectID) (*Order, error) 46 | UpdateArbitrary(id uint32, key string, value interface{}) error 47 | GetBySellerID(sellerID uint32) ([]Order, error) 48 | GetByBuyerID(buyerID uint32) ([]Order, error) 49 | GetByBuyerIDAndStatus(buyerID uint32, status string) ([]Order, error) 50 | GetBySellerIDAndStatus(sellerID uint32, status string) ([]Order, error) 51 | GetByStatus(status string) ([]Order, error) 52 | } 53 | 54 | // OrderUsecase usecase for order 55 | type OrderUsecase interface { 56 | CreateOrder(order OrderBody) (uint32, error) 57 | AcceptOrder(id uint32) error 58 | GetAll() ([]Order, error) 59 | GetByID(id uint32) (*Order, error) 60 | GetBySellerID(sellerID uint32) ([]Order, error) 61 | GetByBuyerID(buyerID uint32) ([]Order, error) 62 | GetByBuyerIDAndStatus(buyerID uint32, status string) ([]Order, error) 63 | GetBySellerIDAndStatus(sellerID uint32, status string) ([]Order, error) 64 | GetByStatus(status string) ([]Order, error) 65 | } 66 | -------------------------------------------------------------------------------- /models/product.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "go.mongodb.org/mongo-driver/bson/primitive" 4 | 5 | // Product represents product 6 | type Product struct { 7 | ID uint32 `bson:"id_product" json:"id_product"` 8 | ProductName string `bson:"product_name" json:"product_name"` 9 | Description string `bson:"description" json:"description"` 10 | Price float32 `bson:"price" json:"price"` 11 | SellerID uint32 `bson:"id_seller" json:"id_seller"` 12 | Seller *Seller `bson:"seller" json:"seller"` 13 | } 14 | 15 | // ProductBody for receiving body grom json 16 | type ProductBody struct { 17 | ProductName string `json:"product_name"` 18 | Description string `json:"description"` 19 | Price float32 `json:"price"` 20 | SellerID uint32 `json:"id_seller"` 21 | } 22 | 23 | // ProductRepository represents repo functions for product 24 | type ProductRepository interface { 25 | Store(product *Product) (primitive.ObjectID, error) 26 | GetAll() ([]Product, error) 27 | GetBySellerID(sellerID uint32) ([]Product, error) 28 | GetByID(id uint32) (*Product, error) 29 | GetByOID(oid primitive.ObjectID) (*Product, error) 30 | UpdateArbitrary(id uint32, key string, value interface{}) error 31 | } 32 | 33 | // ProductUsecase usecase for product 34 | type ProductUsecase interface { 35 | CreateProduct(product ProductBody) (uint32, error) 36 | GetAll() ([]Product, error) 37 | GetBySellerID(sellerID uint32) ([]Product, error) 38 | GetByID(id uint32) (*Product, error) 39 | } 40 | -------------------------------------------------------------------------------- /models/seller.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "go.mongodb.org/mongo-driver/bson/primitive" 4 | 5 | // Seller represents seller 6 | type Seller struct { 7 | ID uint32 `bson:"id_seller" json:"id_seller"` 8 | Email string `bson:"email" json:"email"` 9 | Name string `bson:"name" json:"name"` 10 | Password string `bson:"password" json:"password"` 11 | PickupAddress string `bson:"pickup_address" json:"pickup_address"` 12 | } 13 | 14 | // SellerRepository represents repo functions for seller 15 | type SellerRepository interface { 16 | Store(seller *Seller) (primitive.ObjectID, error) 17 | GetAll() ([]Seller, error) 18 | GetByID(id uint32) (*Seller, error) 19 | GetByOID(oid primitive.ObjectID) (*Seller, error) 20 | UpdateArbitrary(id uint32, key string, value interface{}) error 21 | } 22 | 23 | // SellerBody body for buyer 24 | type SellerBody struct { 25 | Email string `json:"email"` 26 | Name string `json:"name"` 27 | Password string `json:"password"` 28 | PickupAddress string `json:"pickup_address"` 29 | } 30 | 31 | // SellerUsecase for seller usecase 32 | type SellerUsecase interface { 33 | CreateSeller(seller SellerBody) (uint32, error) 34 | GetAll() ([]Seller, error) 35 | GetByID(id uint32) (*Seller, error) 36 | } 37 | -------------------------------------------------------------------------------- /repositories/mongodb/buyer.go: -------------------------------------------------------------------------------- 1 | package mongodb 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | "time" 7 | 8 | "github.com/masterraf21/ecommerce-backend/configs" 9 | "github.com/masterraf21/ecommerce-backend/models" 10 | "go.mongodb.org/mongo-driver/bson/primitive" 11 | "go.mongodb.org/mongo-driver/mongo" 12 | "gopkg.in/mgo.v2/bson" 13 | ) 14 | 15 | type buyerRepo struct { 16 | Instance *mongo.Database 17 | CounterRepo models.CounterRepository 18 | } 19 | 20 | // NewBuyerRepo will create an object representing BuyerRepository 21 | func NewBuyerRepo(instance *mongo.Database, ctr models.CounterRepository) models.BuyerRepository { 22 | return &buyerRepo{Instance: instance, CounterRepo: ctr} 23 | } 24 | 25 | func (r *buyerRepo) Store(buyer *models.Buyer) (uid primitive.ObjectID, err error) { 26 | collectionName := "buyer" 27 | identifier := "id_buyer" 28 | 29 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 30 | defer cancel() 31 | 32 | id, err := r.CounterRepo.Get(collectionName, identifier) 33 | if err != nil { 34 | return 35 | } 36 | 37 | collection := r.Instance.Collection(collectionName) 38 | buyer.ID = id 39 | 40 | result, err := collection.InsertOne(ctx, buyer) 41 | if err != nil { 42 | return 43 | } 44 | 45 | _id := result.InsertedID 46 | uid = _id.(primitive.ObjectID) 47 | 48 | return 49 | } 50 | 51 | func (r *buyerRepo) GetAll() (res []models.Buyer, err error) { 52 | collection := r.Instance.Collection("buyer") 53 | 54 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 55 | defer cancel() 56 | 57 | cursor, err := collection.Find(ctx, bson.M{}) 58 | if err != nil { 59 | if strings.Contains(err.Error(), "mongo: no documents") { 60 | res = make([]models.Buyer, 0) 61 | err = nil 62 | return 63 | } 64 | 65 | return 66 | } 67 | 68 | if err = cursor.All(ctx, &res); err != nil { 69 | return 70 | } 71 | 72 | return 73 | } 74 | 75 | func (r *buyerRepo) GetByOID(oid primitive.ObjectID) (res *models.Buyer, err error) { 76 | collection := r.Instance.Collection("buyer") 77 | 78 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 79 | defer cancel() 80 | 81 | err = collection.FindOne(ctx, bson.M{"_id": oid}).Decode(&res) 82 | if err != nil { 83 | if strings.Contains(err.Error(), "mongo: no documents") { 84 | err = nil 85 | return 86 | } 87 | 88 | return 89 | } 90 | 91 | return 92 | } 93 | 94 | func (r *buyerRepo) GetByID(id uint32) (res *models.Buyer, err error) { 95 | collection := r.Instance.Collection("buyer") 96 | 97 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 98 | defer cancel() 99 | 100 | err = collection.FindOne(ctx, bson.M{"id_buyer": id}).Decode(&res) 101 | if err != nil { 102 | if strings.Contains(err.Error(), "mongo: no documents") { 103 | err = nil 104 | return 105 | } 106 | 107 | return 108 | } 109 | 110 | return 111 | } 112 | 113 | func (r *buyerRepo) UpdateArbitrary(id uint32, key string, value interface{}) error { 114 | collection := r.Instance.Collection("buyer") 115 | 116 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 117 | defer cancel() 118 | 119 | _, err := collection.UpdateOne( 120 | ctx, 121 | bson.M{"id_buyer": id}, 122 | bson.M{"$set": bson.M{key: value}}, 123 | ) 124 | if err != nil { 125 | return err 126 | } 127 | 128 | return nil 129 | } 130 | -------------------------------------------------------------------------------- /repositories/mongodb/buyer_test.go: -------------------------------------------------------------------------------- 1 | package mongodb 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/masterraf21/ecommerce-backend/configs" 9 | "github.com/masterraf21/ecommerce-backend/models" 10 | "github.com/masterraf21/ecommerce-backend/utils/mongodb" 11 | testUtil "github.com/masterraf21/ecommerce-backend/utils/test" 12 | 13 | "github.com/stretchr/testify/suite" 14 | "go.mongodb.org/mongo-driver/mongo" 15 | ) 16 | 17 | type buyerRepoTestSuite struct { 18 | suite.Suite 19 | Instance *mongo.Database 20 | BuyerRepo models.BuyerRepository 21 | } 22 | 23 | func TestBuyerRepository(t *testing.T) { 24 | suite.Run(t, new(buyerRepoTestSuite)) 25 | } 26 | 27 | func (s *buyerRepoTestSuite) SetupSuite() { 28 | instance := mongodb.ConfigureMongo() 29 | s.Instance = instance 30 | counterRepo := NewCounterRepo(instance) 31 | s.BuyerRepo = NewBuyerRepo(instance, counterRepo) 32 | } 33 | 34 | func (s *buyerRepoTestSuite) TearDownTest() { 35 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 36 | defer cancel() 37 | 38 | err := testUtil.DropBuyer(ctx, s.Instance) 39 | testUtil.HandleError(err) 40 | err = testUtil.DropCounter(ctx, s.Instance) 41 | testUtil.HandleError(err) 42 | } 43 | 44 | func (s *buyerRepoTestSuite) TearDownSuite() { 45 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 46 | defer cancel() 47 | 48 | err := testUtil.DropBuyer(ctx, s.Instance) 49 | testUtil.HandleError(err) 50 | err = testUtil.DropCounter(ctx, s.Instance) 51 | testUtil.HandleError(err) 52 | } 53 | 54 | func (s *buyerRepoTestSuite) TestStore1() { 55 | s.Run("Store a single Buyer data", func() { 56 | buyer := models.Buyer{ 57 | Email: "test", 58 | Name: "test", 59 | Password: "test", 60 | DeliveryAddress: "test", 61 | } 62 | 63 | oid, err := s.BuyerRepo.Store(&buyer) 64 | testUtil.HandleError(err) 65 | 66 | result, err := s.BuyerRepo.GetByOID(oid) 67 | s.Require().NoError(err) 68 | s.Assert().EqualValues(1, result.ID) 69 | s.Assert().Equal(buyer.Email, result.Email) 70 | s.Assert().Equal(buyer.Name, result.Name) 71 | s.Assert().Equal(buyer.Password, result.Password) 72 | s.Assert().Equal(buyer.DeliveryAddress, result.DeliveryAddress) 73 | }) 74 | } 75 | 76 | func (s *buyerRepoTestSuite) TestStore2() { 77 | s.Run("Store a single Buyer data after previous stored data updated", func() { 78 | buyer := models.Buyer{ 79 | Email: "test", 80 | Name: "test", 81 | Password: "test", 82 | DeliveryAddress: "test", 83 | } 84 | _, err := s.BuyerRepo.Store(&buyer) 85 | testUtil.HandleError(err) 86 | 87 | _, err = s.BuyerRepo.Store(&buyer) 88 | testUtil.HandleError(err) 89 | 90 | err = s.BuyerRepo.UpdateArbitrary(uint32(1), "name", "update") 91 | testUtil.HandleError(err) 92 | 93 | oid, err := s.BuyerRepo.Store(&buyer) 94 | testUtil.HandleError(err) 95 | 96 | result, err := s.BuyerRepo.GetByOID(oid) 97 | testUtil.HandleError(err) 98 | s.Require().NoError(err) 99 | s.Assert().EqualValues(3, result.ID) 100 | s.Assert().Equal(buyer.Email, result.Email) 101 | s.Assert().Equal(buyer.Name, result.Name) 102 | s.Assert().Equal(buyer.Password, result.Password) 103 | s.Assert().Equal(buyer.DeliveryAddress, result.DeliveryAddress) 104 | }) 105 | } 106 | 107 | func (s *buyerRepoTestSuite) TestGet1() { 108 | s.Run("Get all Buyer data", func() { 109 | buyer := models.Buyer{ 110 | Email: "test", 111 | Name: "test", 112 | Password: "test", 113 | DeliveryAddress: "test", 114 | } 115 | _, err := s.BuyerRepo.Store(&buyer) 116 | testUtil.HandleError(err) 117 | 118 | _, err = s.BuyerRepo.Store(&buyer) 119 | testUtil.HandleError(err) 120 | 121 | _, err = s.BuyerRepo.Store(&buyer) 122 | testUtil.HandleError(err) 123 | 124 | result, err := s.BuyerRepo.GetAll() 125 | testUtil.HandleError(err) 126 | 127 | s.Assert().Equal(3, len(result)) 128 | }) 129 | } 130 | 131 | func (s *buyerRepoTestSuite) TestGet2() { 132 | s.Run("Get Empty Data", func() { 133 | result, err := s.BuyerRepo.GetByID(uint32(1)) 134 | testUtil.HandleError(err) 135 | s.Assert().Nil(result) 136 | }) 137 | } 138 | 139 | func (s *buyerRepoTestSuite) TestUpdate() { 140 | s.Run("Update a buyer Data arbitrarily", func() { 141 | buyer := models.Buyer{ 142 | Email: "test", 143 | Name: "test", 144 | Password: "test", 145 | DeliveryAddress: "test", 146 | } 147 | oid, err := s.BuyerRepo.Store(&buyer) 148 | testUtil.HandleError(err) 149 | 150 | err = s.BuyerRepo.UpdateArbitrary(uint32(1), "name", "update") 151 | testUtil.HandleError(err) 152 | 153 | result, err := s.BuyerRepo.GetByOID(oid) 154 | testUtil.HandleError(err) 155 | s.Require().NoError(err) 156 | s.Require().Equal("update", result.Name) 157 | }) 158 | } 159 | -------------------------------------------------------------------------------- /repositories/mongodb/counter.go: -------------------------------------------------------------------------------- 1 | package mongodb 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "reflect" 7 | "time" 8 | 9 | "github.com/masterraf21/ecommerce-backend/configs" 10 | "github.com/masterraf21/ecommerce-backend/models" 11 | 12 | "go.mongodb.org/mongo-driver/mongo" 13 | "go.mongodb.org/mongo-driver/mongo/options" 14 | "gopkg.in/mgo.v2/bson" 15 | ) 16 | 17 | type counterRepo struct { 18 | Instance *mongo.Database 19 | } 20 | 21 | // NewCounterRepo will initiate new repo for counter 22 | func NewCounterRepo(instance *mongo.Database) models.CounterRepository { 23 | return &counterRepo{instance} 24 | } 25 | 26 | func contains(s []string, e string) bool { 27 | for _, a := range s { 28 | if a == e { 29 | return true 30 | } 31 | } 32 | return false 33 | } 34 | 35 | func dropCounter(ctx context.Context, db *mongo.Database) (err error) { 36 | collection := db.Collection("counter") 37 | err = collection.Drop(ctx) 38 | return 39 | } 40 | 41 | func initCollection(ctx context.Context, db *mongo.Database) error { 42 | counter := models.Counter{ 43 | BuyerID: 0, 44 | ProductID: 0, 45 | SellerID: 0, 46 | OrderID: 0, 47 | } 48 | 49 | collection := db.Collection("counter") 50 | _, err := collection.InsertOne(ctx, counter) 51 | if err != nil { 52 | return err 53 | } 54 | 55 | return nil 56 | } 57 | 58 | func getLatestCounter(ctx context.Context, db *mongo.Database) (res *models.Counter, err error) { 59 | collection := db.Collection("counter") 60 | myOptions := options.FindOne() 61 | myOptions.SetSort(bson.M{"$natural": -1}) 62 | 63 | err = collection.FindOne(ctx, bson.M{}, myOptions).Decode(&res) 64 | if err != nil { 65 | return 66 | } 67 | 68 | return 69 | } 70 | 71 | func incrementCounter(ctx context.Context, db *mongo.Database, identifier string, latestCounter models.Counter) error { 72 | collection := db.Collection("counter") 73 | 74 | counterMap := structToMap(latestCounter) 75 | latestID, ok := counterMap[identifier] 76 | 77 | if !ok { 78 | return errors.New("Identifier not found") 79 | } 80 | id := latestID.(uint32) 81 | 82 | counterMap[identifier] = id + 1 83 | 84 | dataBson := bson.M{} 85 | for k, v := range counterMap { 86 | dataBson[k] = v 87 | } 88 | 89 | _, err := collection.InsertOne(ctx, dataBson) 90 | if err != nil { 91 | return err 92 | } 93 | 94 | return nil 95 | } 96 | 97 | func structToMap(item interface{}) map[string]interface{} { 98 | res := map[string]interface{}{} 99 | if item == nil { 100 | return res 101 | } 102 | v := reflect.TypeOf(item) 103 | reflectValue := reflect.ValueOf(item) 104 | reflectValue = reflect.Indirect(reflectValue) 105 | 106 | if v.Kind() == reflect.Ptr { 107 | v = v.Elem() 108 | } 109 | for i := 0; i < v.NumField(); i++ { 110 | tag := v.Field(i).Tag.Get("bson") 111 | field := reflectValue.Field(i).Interface() 112 | if tag != "" && tag != "-" { 113 | if v.Field(i).Type.Kind() == reflect.Struct { 114 | res[tag] = structToMap(field) 115 | } else { 116 | res[tag] = field 117 | } 118 | } 119 | } 120 | return res 121 | } 122 | 123 | func (r *counterRepo) Get(collectionName string, identifier string) (id uint32, err error) { 124 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 125 | defer cancel() 126 | 127 | names, err := r.Instance.ListCollectionNames(ctx, bson.M{}) 128 | if err != nil { 129 | return 130 | } 131 | 132 | if !contains(names, "counter") { 133 | initCollection(ctx, r.Instance) 134 | } 135 | 136 | latest, err := getLatestCounter(ctx, r.Instance) 137 | if err != nil { 138 | return 139 | } 140 | latestMap := structToMap(latest) 141 | 142 | latestIDRaw, ok := latestMap[identifier] 143 | if !ok { 144 | err = errors.New("Identifier not found") 145 | return 146 | } 147 | 148 | latestID := latestIDRaw.(uint32) 149 | 150 | id = latestID + 1 151 | 152 | err = incrementCounter(ctx, r.Instance, identifier, *latest) 153 | if err != nil { 154 | return 155 | } 156 | 157 | return 158 | } 159 | -------------------------------------------------------------------------------- /repositories/mongodb/counter_test.go: -------------------------------------------------------------------------------- 1 | package mongodb 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/masterraf21/ecommerce-backend/configs" 9 | "github.com/masterraf21/ecommerce-backend/models" 10 | "github.com/masterraf21/ecommerce-backend/utils/mongodb" 11 | testUtil "github.com/masterraf21/ecommerce-backend/utils/test" 12 | "github.com/stretchr/testify/suite" 13 | "go.mongodb.org/mongo-driver/mongo" 14 | "go.mongodb.org/mongo-driver/mongo/options" 15 | "gopkg.in/mgo.v2/bson" 16 | ) 17 | 18 | type counterRepoTestSuite struct { 19 | suite.Suite 20 | Instance *mongo.Database 21 | CounterRepo models.CounterRepository 22 | } 23 | 24 | func TestCounterRepository(t *testing.T) { 25 | suite.Run(t, new(counterRepoTestSuite)) 26 | } 27 | 28 | func (s *counterRepoTestSuite) SetupSuite() { 29 | instance := mongodb.ConfigureMongo() 30 | s.Instance = instance 31 | s.CounterRepo = NewCounterRepo(instance) 32 | } 33 | 34 | func (s *counterRepoTestSuite) TearDownTest() { 35 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 36 | defer cancel() 37 | 38 | err := testUtil.DropBuyer(ctx, s.Instance) 39 | testUtil.HandleError(err) 40 | err = testUtil.DropCounter(ctx, s.Instance) 41 | testUtil.HandleError(err) 42 | } 43 | 44 | func (s *counterRepoTestSuite) TearDownSuite() { 45 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 46 | defer cancel() 47 | 48 | err := testUtil.DropBuyer(ctx, s.Instance) 49 | testUtil.HandleError(err) 50 | err = testUtil.DropCounter(ctx, s.Instance) 51 | testUtil.HandleError(err) 52 | } 53 | 54 | func (s *counterRepoTestSuite) TestGetEmpty() { 55 | s.Run("Get id with empty document", func() { 56 | collectionName := "buyer" 57 | identifier := "id_buyer" 58 | 59 | id, err := s.CounterRepo.Get(collectionName, identifier) 60 | testUtil.HandleError(err) 61 | 62 | s.Equal(uint32(1), id) 63 | }) 64 | } 65 | 66 | func (s *counterRepoTestSuite) TestGetExisting() { 67 | s.Run("Get id with existing document", func() { 68 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 69 | defer cancel() 70 | 71 | collectionName := "buyer" 72 | identifier := "id_buyer" 73 | 74 | collection := s.Instance.Collection(collectionName) 75 | buyer := models.Buyer{ 76 | ID: 1, 77 | Email: "test", 78 | Name: "test", 79 | Password: "test", 80 | DeliveryAddress: "test", 81 | } 82 | _, err := collection.UpdateOne( 83 | ctx, 84 | bson.M{identifier: buyer.ID}, 85 | bson.M{"$set": buyer}, 86 | options.Update().SetUpsert(true), 87 | ) 88 | testUtil.HandleError(err) 89 | 90 | id, err := s.CounterRepo.Get(collectionName, identifier) 91 | testUtil.HandleError(err) 92 | 93 | s.Assert().EqualValues(1, id) 94 | }) 95 | } 96 | -------------------------------------------------------------------------------- /repositories/mongodb/order.go: -------------------------------------------------------------------------------- 1 | package mongodb 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | "time" 7 | 8 | "github.com/masterraf21/ecommerce-backend/configs" 9 | "github.com/masterraf21/ecommerce-backend/models" 10 | "go.mongodb.org/mongo-driver/bson/primitive" 11 | "go.mongodb.org/mongo-driver/mongo" 12 | "gopkg.in/mgo.v2/bson" 13 | ) 14 | 15 | type orderRepo struct { 16 | Instance *mongo.Database 17 | CounterRepo models.CounterRepository 18 | } 19 | 20 | // NewOrderRepo will initiate order repository object 21 | func NewOrderRepo(instance *mongo.Database, ctr models.CounterRepository) models.OrderRepository { 22 | return &orderRepo{Instance: instance, CounterRepo: ctr} 23 | } 24 | 25 | func (r *orderRepo) Store(order *models.Order) (oid primitive.ObjectID, err error) { 26 | collectionName := "order" 27 | identifier := "id_order" 28 | 29 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 30 | defer cancel() 31 | 32 | id, err := r.CounterRepo.Get(collectionName, identifier) 33 | if err != nil { 34 | return 35 | } 36 | 37 | collection := r.Instance.Collection(collectionName) 38 | order.ID = id 39 | 40 | result, err := collection.InsertOne(ctx, order) 41 | if err != nil { 42 | return 43 | } 44 | 45 | _id := result.InsertedID 46 | oid = _id.(primitive.ObjectID) 47 | 48 | return 49 | } 50 | 51 | func (r *orderRepo) GetAll() (res []models.Order, err error) { 52 | collection := r.Instance.Collection("order") 53 | 54 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 55 | defer cancel() 56 | 57 | cursor, err := collection.Find(ctx, bson.M{}) 58 | if err != nil { 59 | if strings.Contains(err.Error(), "mongo: no documents") { 60 | res = make([]models.Order, 0) 61 | err = nil 62 | return 63 | } 64 | 65 | return 66 | } 67 | 68 | if err = cursor.All(ctx, &res); err != nil { 69 | return 70 | } 71 | 72 | return 73 | } 74 | 75 | func (r *orderRepo) GetByID(id uint32) (res *models.Order, err error) { 76 | collection := r.Instance.Collection("order") 77 | 78 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 79 | defer cancel() 80 | 81 | err = collection.FindOne(ctx, bson.M{"id_order": id}).Decode(&res) 82 | if err != nil { 83 | if strings.Contains(err.Error(), "mongo: no documents") { 84 | err = nil 85 | return 86 | } 87 | 88 | return 89 | } 90 | 91 | return 92 | } 93 | 94 | func (r *orderRepo) GetByOID(oid primitive.ObjectID) (res *models.Order, err error) { 95 | collection := r.Instance.Collection("order") 96 | 97 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 98 | defer cancel() 99 | 100 | err = collection.FindOne(ctx, bson.M{"_id": oid}).Decode(&res) 101 | if err != nil { 102 | if strings.Contains(err.Error(), "mongo: no documents") { 103 | err = nil 104 | return 105 | } 106 | 107 | return 108 | } 109 | 110 | return 111 | } 112 | 113 | func (r *orderRepo) UpdateArbitrary(id uint32, key string, value interface{}) error { 114 | collection := r.Instance.Collection("order") 115 | 116 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 117 | defer cancel() 118 | 119 | _, err := collection.UpdateOne( 120 | ctx, 121 | bson.M{"id_order": id}, 122 | bson.M{"$set": bson.M{key: value}}, 123 | ) 124 | if err != nil { 125 | return err 126 | } 127 | 128 | return nil 129 | } 130 | 131 | func (r *orderRepo) GetBySellerID(sellerID uint32) (res []models.Order, err error) { 132 | collection := r.Instance.Collection("order") 133 | 134 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 135 | defer cancel() 136 | 137 | cursor, err := collection.Find(ctx, bson.M{"id_seller": sellerID}) 138 | if err != nil { 139 | if strings.Contains(err.Error(), "mongo: no documents") { 140 | res = make([]models.Order, 0) 141 | err = nil 142 | return 143 | } 144 | 145 | return 146 | } 147 | 148 | if err = cursor.All(ctx, &res); err != nil { 149 | return 150 | } 151 | 152 | return 153 | } 154 | 155 | func (r *orderRepo) GetByBuyerID(buyerID uint32) (res []models.Order, err error) { 156 | collection := r.Instance.Collection("order") 157 | 158 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 159 | defer cancel() 160 | 161 | cursor, err := collection.Find(ctx, bson.M{"id_buyer": buyerID}) 162 | if err != nil { 163 | if strings.Contains(err.Error(), "mongo: no documents") { 164 | res = make([]models.Order, 0) 165 | err = nil 166 | return 167 | } 168 | 169 | return 170 | } 171 | 172 | if err = cursor.All(ctx, &res); err != nil { 173 | return 174 | } 175 | 176 | return 177 | } 178 | 179 | func (r *orderRepo) GetByBuyerIDAndStatus(buyerID uint32, status string) (res []models.Order, err error) { 180 | collection := r.Instance.Collection("order") 181 | 182 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 183 | defer cancel() 184 | 185 | cursor, err := collection.Find(ctx, bson.M{"id_buyer": buyerID, "status": status}) 186 | if err != nil { 187 | if strings.Contains(err.Error(), "mongo: no documents") { 188 | res = make([]models.Order, 0) 189 | err = nil 190 | return 191 | } 192 | 193 | return 194 | } 195 | 196 | if err = cursor.All(ctx, &res); err != nil { 197 | return 198 | } 199 | 200 | return 201 | } 202 | 203 | func (r *orderRepo) GetBySellerIDAndStatus(sellerID uint32, status string) (res []models.Order, err error) { 204 | collection := r.Instance.Collection("order") 205 | 206 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 207 | defer cancel() 208 | 209 | cursor, err := collection.Find(ctx, bson.M{"id_seller": sellerID, "status": status}) 210 | if err != nil { 211 | if strings.Contains(err.Error(), "mongo: no documents") { 212 | res = make([]models.Order, 0) 213 | err = nil 214 | return 215 | } 216 | 217 | return 218 | } 219 | 220 | if err = cursor.All(ctx, &res); err != nil { 221 | return 222 | } 223 | 224 | return 225 | } 226 | 227 | func (r *orderRepo) GetByStatus(status string) (res []models.Order, err error) { 228 | collection := r.Instance.Collection("order") 229 | 230 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 231 | defer cancel() 232 | 233 | cursor, err := collection.Find(ctx, bson.M{"status": status}) 234 | if err != nil { 235 | if strings.Contains(err.Error(), "mongo: no documents") { 236 | res = make([]models.Order, 0) 237 | err = nil 238 | return 239 | } 240 | 241 | return 242 | } 243 | 244 | if err = cursor.All(ctx, &res); err != nil { 245 | return 246 | } 247 | 248 | return 249 | } 250 | -------------------------------------------------------------------------------- /repositories/mongodb/order_test.go: -------------------------------------------------------------------------------- 1 | package mongodb 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | "testing" 7 | "time" 8 | 9 | "github.com/masterraf21/ecommerce-backend/configs" 10 | "github.com/masterraf21/ecommerce-backend/models" 11 | "github.com/masterraf21/ecommerce-backend/utils/mongodb" 12 | testUtil "github.com/masterraf21/ecommerce-backend/utils/test" 13 | 14 | "github.com/stretchr/testify/suite" 15 | "go.mongodb.org/mongo-driver/mongo" 16 | ) 17 | 18 | type orderRepoTestSuite struct { 19 | suite.Suite 20 | Instance *mongo.Database 21 | OrderRepo models.OrderRepository 22 | } 23 | 24 | func TestOrderRepository(t *testing.T) { 25 | suite.Run(t, new(orderRepoTestSuite)) 26 | } 27 | 28 | func (s *orderRepoTestSuite) SetupSuite() { 29 | instance := mongodb.ConfigureMongo() 30 | s.Instance = instance 31 | counterRepo := NewCounterRepo(instance) 32 | s.OrderRepo = NewOrderRepo(instance, counterRepo) 33 | } 34 | 35 | func (s *orderRepoTestSuite) TearDownTest() { 36 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 37 | defer cancel() 38 | 39 | err := testUtil.DropOrder(ctx, s.Instance) 40 | testUtil.HandleError(err) 41 | err = testUtil.DropCounter(ctx, s.Instance) 42 | testUtil.HandleError(err) 43 | } 44 | 45 | func (s *orderRepoTestSuite) TearDownSuite() { 46 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 47 | defer cancel() 48 | 49 | err := testUtil.DropOrder(ctx, s.Instance) 50 | testUtil.HandleError(err) 51 | err = testUtil.DropCounter(ctx, s.Instance) 52 | testUtil.HandleError(err) 53 | } 54 | 55 | func (s *orderRepoTestSuite) TestStore() { 56 | s.Run("Store a single Order data", func() { 57 | buyer := models.Buyer{ 58 | ID: 1, 59 | Email: "test", 60 | Name: "test", 61 | Password: "test", 62 | DeliveryAddress: "test", 63 | } 64 | 65 | seller := models.Seller{ 66 | ID: 1, 67 | Email: "test", 68 | Name: "test", 69 | Password: "test", 70 | PickupAddress: "test", 71 | } 72 | 73 | product := models.Product{ 74 | ID: 1, 75 | ProductName: "test", 76 | Description: "test", 77 | Price: 10.11, 78 | SellerID: seller.ID, 79 | Seller: &seller, 80 | } 81 | 82 | detail := models.OrderDetail{ 83 | ProductID: product.ID, 84 | Product: &product, 85 | Quantity: 1, 86 | TotalPrice: 10, 87 | } 88 | 89 | order := models.Order{ 90 | BuyerID: buyer.ID, 91 | SellerID: seller.ID, 92 | Seller: &seller, 93 | SourceAddress: "test", 94 | DeliveryAddress: "test", 95 | Products: []models.OrderDetail{ 96 | detail, detail, detail, 97 | }, 98 | TotalPrice: 100.123, 99 | Status: "test", 100 | } 101 | 102 | oid, err := s.OrderRepo.Store(&order) 103 | testUtil.HandleError(err) 104 | 105 | result, err := s.OrderRepo.GetByOID(oid) 106 | testUtil.HandleError(err) 107 | 108 | s.Assert().EqualValues(1, result.ID) 109 | s.Assert().Nil(result.Buyer) 110 | s.Assert().True(reflect.DeepEqual(seller, *result.Seller)) 111 | }) 112 | } 113 | 114 | func (s *orderRepoTestSuite) TestGet1() { 115 | s.Run("Get Order by ID", func() { 116 | buyer := models.Buyer{ 117 | ID: 1, 118 | Email: "test", 119 | Name: "test", 120 | Password: "test", 121 | DeliveryAddress: "test", 122 | } 123 | 124 | seller := models.Seller{ 125 | ID: 1, 126 | Email: "test", 127 | Name: "test", 128 | Password: "test", 129 | PickupAddress: "test", 130 | } 131 | 132 | product := models.Product{ 133 | ID: 1, 134 | ProductName: "test", 135 | Description: "test", 136 | Price: 10.11, 137 | SellerID: seller.ID, 138 | Seller: &seller, 139 | } 140 | 141 | detail := models.OrderDetail{ 142 | ProductID: product.ID, 143 | Product: &product, 144 | Quantity: 1, 145 | TotalPrice: 10, 146 | } 147 | 148 | order := models.Order{ 149 | BuyerID: buyer.ID, 150 | Buyer: &buyer, 151 | SellerID: seller.ID, 152 | Seller: &seller, 153 | SourceAddress: "test", 154 | DeliveryAddress: "test", 155 | Products: []models.OrderDetail{ 156 | detail, detail, detail, 157 | }, 158 | TotalPrice: 100.123, 159 | Status: "test", 160 | } 161 | 162 | _, err := s.OrderRepo.Store(&order) 163 | testUtil.HandleError(err) 164 | 165 | result, err := s.OrderRepo.GetByID(uint32(1)) 166 | testUtil.HandleError(err) 167 | 168 | s.Assert().EqualValues(1, result.ID) 169 | s.Assert().True(reflect.DeepEqual(buyer, *result.Buyer)) 170 | s.Assert().True(reflect.DeepEqual(seller, *result.Seller)) 171 | }) 172 | 173 | s.Run("Get All Order data", func() { 174 | buyer := models.Buyer{ 175 | ID: 1, 176 | Email: "test", 177 | Name: "test", 178 | Password: "test", 179 | DeliveryAddress: "test", 180 | } 181 | 182 | seller := models.Seller{ 183 | ID: 1, 184 | Email: "test", 185 | Name: "test", 186 | Password: "test", 187 | PickupAddress: "test", 188 | } 189 | 190 | product := models.Product{ 191 | ID: 1, 192 | ProductName: "test", 193 | Description: "test", 194 | Price: 10.11, 195 | SellerID: seller.ID, 196 | Seller: &seller, 197 | } 198 | 199 | detail := models.OrderDetail{ 200 | ProductID: product.ID, 201 | Product: &product, 202 | Quantity: 1, 203 | TotalPrice: 10, 204 | } 205 | 206 | order := models.Order{ 207 | BuyerID: buyer.ID, 208 | Buyer: &buyer, 209 | SellerID: seller.ID, 210 | Seller: &seller, 211 | SourceAddress: "test", 212 | DeliveryAddress: "test", 213 | Products: []models.OrderDetail{ 214 | detail, detail, detail, 215 | }, 216 | TotalPrice: 100.123, 217 | Status: "test", 218 | } 219 | 220 | _, err := s.OrderRepo.Store(&order) 221 | testUtil.HandleError(err) 222 | 223 | _, err = s.OrderRepo.Store(&order) 224 | testUtil.HandleError(err) 225 | 226 | _, err = s.OrderRepo.Store(&order) 227 | testUtil.HandleError(err) 228 | 229 | result, err := s.OrderRepo.GetAll() 230 | testUtil.HandleError(err) 231 | 232 | s.Assert().Equal(4, len(result)) 233 | }) 234 | } 235 | 236 | func (s *orderRepoTestSuite) TestGet2() { 237 | s.Run("Get Order By Buyer ID and Status", func() { 238 | buyer := models.Buyer{ 239 | ID: 1, 240 | Email: "test", 241 | Name: "test", 242 | Password: "test", 243 | DeliveryAddress: "test", 244 | } 245 | 246 | buyer2 := models.Buyer{ 247 | ID: 2, 248 | Email: "test", 249 | Name: "test", 250 | Password: "test", 251 | DeliveryAddress: "test", 252 | } 253 | 254 | seller := models.Seller{ 255 | ID: 1, 256 | Email: "test", 257 | Name: "test", 258 | Password: "test", 259 | PickupAddress: "test", 260 | } 261 | 262 | product := models.Product{ 263 | ID: 1, 264 | ProductName: "test", 265 | Description: "test", 266 | Price: 10.11, 267 | SellerID: seller.ID, 268 | Seller: &seller, 269 | } 270 | 271 | detail := models.OrderDetail{ 272 | ProductID: product.ID, 273 | Product: &product, 274 | Quantity: 1, 275 | TotalPrice: 10, 276 | } 277 | 278 | order := models.Order{ 279 | BuyerID: buyer.ID, 280 | Buyer: &buyer, 281 | SellerID: seller.ID, 282 | Seller: &seller, 283 | SourceAddress: "test", 284 | DeliveryAddress: "test", 285 | Products: []models.OrderDetail{ 286 | detail, detail, detail, 287 | }, 288 | TotalPrice: 100.123, 289 | Status: "test", 290 | } 291 | 292 | order2 := models.Order{ 293 | BuyerID: buyer.ID, 294 | Buyer: &buyer, 295 | SellerID: seller.ID, 296 | Seller: &seller, 297 | SourceAddress: "test", 298 | DeliveryAddress: "test", 299 | Products: []models.OrderDetail{ 300 | detail, detail, detail, 301 | }, 302 | TotalPrice: 100.123, 303 | Status: "test", 304 | } 305 | 306 | order3 := models.Order{ 307 | BuyerID: buyer2.ID, 308 | Buyer: &buyer, 309 | SellerID: seller.ID, 310 | Seller: &seller, 311 | SourceAddress: "test", 312 | DeliveryAddress: "test", 313 | Products: []models.OrderDetail{ 314 | detail, detail, detail, 315 | }, 316 | TotalPrice: 100.123, 317 | Status: "Completed", 318 | } 319 | 320 | _, err := s.OrderRepo.Store(&order) 321 | testUtil.HandleError(err) 322 | 323 | _, err = s.OrderRepo.Store(&order2) 324 | testUtil.HandleError(err) 325 | 326 | _, err = s.OrderRepo.Store(&order3) 327 | testUtil.HandleError(err) 328 | 329 | result, err := s.OrderRepo.GetByBuyerIDAndStatus(buyer2.ID, "Completed") 330 | testUtil.HandleError(err) 331 | 332 | s.Assert().Equal(1, len(result)) 333 | }) 334 | } 335 | 336 | func (s *orderRepoTestSuite) TestGet3() { 337 | s.Run("Get empty data", func() { 338 | result, err := s.OrderRepo.GetByBuyerIDAndStatus(uint32(1), "test") 339 | testUtil.HandleError(err) 340 | s.Require().Equal(0, len(result)) 341 | }) 342 | } 343 | 344 | func (s *orderRepoTestSuite) TestUpdate() { 345 | s.Run("Update arbitrary Order data field", func() { 346 | buyer := models.Buyer{ 347 | ID: 1, 348 | Email: "test", 349 | Name: "test", 350 | Password: "test", 351 | DeliveryAddress: "test", 352 | } 353 | 354 | seller := models.Seller{ 355 | ID: 1, 356 | Email: "test", 357 | Name: "test", 358 | Password: "test", 359 | PickupAddress: "test", 360 | } 361 | 362 | product := models.Product{ 363 | ID: 1, 364 | ProductName: "test", 365 | Description: "test", 366 | Price: 10.11, 367 | SellerID: seller.ID, 368 | Seller: &seller, 369 | } 370 | 371 | detail := models.OrderDetail{ 372 | ProductID: product.ID, 373 | Product: &product, 374 | Quantity: 1, 375 | TotalPrice: 10, 376 | } 377 | 378 | order := models.Order{ 379 | BuyerID: buyer.ID, 380 | Buyer: &buyer, 381 | SellerID: seller.ID, 382 | Seller: &seller, 383 | SourceAddress: "test", 384 | DeliveryAddress: "test", 385 | Products: []models.OrderDetail{ 386 | detail, detail, detail, 387 | }, 388 | TotalPrice: 100.123, 389 | Status: "test", 390 | } 391 | 392 | oid, err := s.OrderRepo.Store(&order) 393 | testUtil.HandleError(err) 394 | 395 | err = s.OrderRepo.UpdateArbitrary(uint32(1), "source_address", "update") 396 | testUtil.HandleError(err) 397 | 398 | result, err := s.OrderRepo.GetByOID(oid) 399 | testUtil.HandleError(err) 400 | 401 | s.Assert().Equal("update", result.SourceAddress) 402 | }) 403 | } 404 | -------------------------------------------------------------------------------- /repositories/mongodb/product.go: -------------------------------------------------------------------------------- 1 | package mongodb 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | "time" 7 | 8 | "github.com/masterraf21/ecommerce-backend/configs" 9 | "github.com/masterraf21/ecommerce-backend/models" 10 | "go.mongodb.org/mongo-driver/bson/primitive" 11 | "go.mongodb.org/mongo-driver/mongo" 12 | "gopkg.in/mgo.v2/bson" 13 | ) 14 | 15 | type productRepo struct { 16 | Instance *mongo.Database 17 | CounterRepo models.CounterRepository 18 | } 19 | 20 | // NewProductRepo will initiate product repo 21 | func NewProductRepo(instance *mongo.Database, ctr models.CounterRepository) models.ProductRepository { 22 | return &productRepo{Instance: instance, CounterRepo: ctr} 23 | } 24 | 25 | func (r *productRepo) Store(product *models.Product) (oid primitive.ObjectID, err error) { 26 | collectionName := "product" 27 | identifier := "id_product" 28 | 29 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 30 | defer cancel() 31 | 32 | id, err := r.CounterRepo.Get(collectionName, identifier) 33 | if err != nil { 34 | return 35 | } 36 | 37 | collection := r.Instance.Collection(collectionName) 38 | product.ID = id 39 | 40 | result, err := collection.InsertOne(ctx, product) 41 | if err != nil { 42 | return 43 | } 44 | 45 | _id := result.InsertedID 46 | oid = _id.(primitive.ObjectID) 47 | 48 | return 49 | } 50 | 51 | func (r *productRepo) GetAll() (res []models.Product, error error) { 52 | collection := r.Instance.Collection("product") 53 | 54 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 55 | defer cancel() 56 | 57 | cursor, err := collection.Find(ctx, bson.M{}) 58 | if err != nil { 59 | if strings.Contains(err.Error(), "mongo: no documents") { 60 | res = make([]models.Product, 0) 61 | err = nil 62 | return 63 | } 64 | 65 | return 66 | } 67 | 68 | if err = cursor.All(ctx, &res); err != nil { 69 | return 70 | } 71 | 72 | return 73 | } 74 | 75 | func (r *productRepo) GetBySellerID(sellerID uint32) (res []models.Product, err error) { 76 | collection := r.Instance.Collection("product") 77 | 78 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 79 | defer cancel() 80 | 81 | cursor, err := collection.Find(ctx, bson.M{"id_seller": sellerID}) 82 | if err != nil { 83 | if strings.Contains(err.Error(), "mongo: no documents") { 84 | res = make([]models.Product, 0) 85 | err = nil 86 | return 87 | } 88 | 89 | return 90 | } 91 | 92 | if err = cursor.All(ctx, &res); err != nil { 93 | return 94 | } 95 | 96 | return 97 | } 98 | 99 | func (r *productRepo) GetByID(id uint32) (res *models.Product, err error) { 100 | collection := r.Instance.Collection("product") 101 | 102 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 103 | defer cancel() 104 | 105 | err = collection.FindOne(ctx, bson.M{"id_product": id}).Decode(&res) 106 | if err != nil { 107 | if strings.Contains(err.Error(), "mongo: no documents") { 108 | err = nil 109 | return 110 | } 111 | 112 | return 113 | } 114 | 115 | return 116 | } 117 | 118 | func (r *productRepo) GetByOID(oid primitive.ObjectID) (res *models.Product, err error) { 119 | collection := r.Instance.Collection("product") 120 | 121 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 122 | defer cancel() 123 | 124 | err = collection.FindOne(ctx, bson.M{"_id": oid}).Decode(&res) 125 | if err != nil { 126 | if strings.Contains(err.Error(), "mongo: no documents") { 127 | err = nil 128 | return 129 | } 130 | 131 | return 132 | } 133 | 134 | return 135 | } 136 | 137 | func (r *productRepo) UpdateArbitrary(id uint32, key string, value interface{}) error { 138 | collection := r.Instance.Collection("product") 139 | 140 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 141 | defer cancel() 142 | 143 | _, err := collection.UpdateOne( 144 | ctx, 145 | bson.M{"id_product": id}, 146 | bson.M{"$set": bson.M{key: value}}, 147 | ) 148 | if err != nil { 149 | return err 150 | } 151 | 152 | return nil 153 | } 154 | -------------------------------------------------------------------------------- /repositories/mongodb/product_test.go: -------------------------------------------------------------------------------- 1 | package mongodb 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | "testing" 7 | "time" 8 | 9 | "github.com/masterraf21/ecommerce-backend/configs" 10 | "github.com/masterraf21/ecommerce-backend/models" 11 | "github.com/masterraf21/ecommerce-backend/utils/mongodb" 12 | testUtil "github.com/masterraf21/ecommerce-backend/utils/test" 13 | 14 | "github.com/stretchr/testify/suite" 15 | "go.mongodb.org/mongo-driver/mongo" 16 | ) 17 | 18 | type productRepoTestSuite struct { 19 | suite.Suite 20 | Instance *mongo.Database 21 | ProductRepo models.ProductRepository 22 | } 23 | 24 | func TestProductRepository(t *testing.T) { 25 | suite.Run(t, new(productRepoTestSuite)) 26 | } 27 | 28 | func (s *productRepoTestSuite) SetupSuite() { 29 | instance := mongodb.ConfigureMongo() 30 | s.Instance = instance 31 | counterRepo := NewCounterRepo(instance) 32 | s.ProductRepo = NewProductRepo(instance, counterRepo) 33 | } 34 | 35 | func (s *productRepoTestSuite) TearDownTest() { 36 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 37 | defer cancel() 38 | 39 | err := testUtil.DropProduct(ctx, s.Instance) 40 | testUtil.HandleError(err) 41 | err = testUtil.DropCounter(ctx, s.Instance) 42 | testUtil.HandleError(err) 43 | } 44 | 45 | func (s *productRepoTestSuite) TearDownSuite() { 46 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 47 | defer cancel() 48 | 49 | err := testUtil.DropProduct(ctx, s.Instance) 50 | testUtil.HandleError(err) 51 | err = testUtil.DropCounter(ctx, s.Instance) 52 | testUtil.HandleError(err) 53 | } 54 | 55 | func (s *productRepoTestSuite) TestStore() { 56 | s.Run("Store a single Product data", func() { 57 | seller := models.Seller{ 58 | ID: 1, 59 | Email: "test", 60 | Name: "test", 61 | Password: "test", 62 | PickupAddress: "test", 63 | } 64 | 65 | product := models.Product{ 66 | ProductName: "test", 67 | Description: "test", 68 | Price: 10.11, 69 | SellerID: seller.ID, 70 | Seller: &seller, 71 | } 72 | 73 | oid, err := s.ProductRepo.Store(&product) 74 | testUtil.HandleError(err) 75 | 76 | result, err := s.ProductRepo.GetByOID(oid) 77 | s.Require().NoError(err) 78 | s.Assert().EqualValues(1, result.ID) 79 | s.Assert().Equal(product.ProductName, result.ProductName) 80 | s.Assert().True(reflect.DeepEqual(seller, *result.Seller)) 81 | }) 82 | 83 | s.Run("Store a single Product data after existing data stored", func() { 84 | seller := models.Seller{ 85 | ID: 1, 86 | Email: "test", 87 | Name: "test", 88 | Password: "test", 89 | PickupAddress: "test", 90 | } 91 | 92 | product := models.Product{ 93 | ProductName: "test", 94 | Description: "test", 95 | Price: 10.11, 96 | SellerID: seller.ID, 97 | Seller: &seller, 98 | } 99 | 100 | oid, err := s.ProductRepo.Store(&product) 101 | testUtil.HandleError(err) 102 | 103 | result, err := s.ProductRepo.GetByOID(oid) 104 | s.Require().NoError(err) 105 | s.Assert().EqualValues(2, result.ID) 106 | s.Assert().Equal(product.ProductName, result.ProductName) 107 | s.Assert().True(reflect.DeepEqual(seller, *result.Seller)) 108 | }) 109 | } 110 | 111 | func (s *productRepoTestSuite) TestGet() { 112 | s.Run("Get a Product data by ID", func() { 113 | seller := models.Seller{ 114 | ID: 1, 115 | Email: "test", 116 | Name: "test", 117 | Password: "test", 118 | PickupAddress: "test", 119 | } 120 | 121 | product := models.Product{ 122 | ProductName: "test", 123 | Description: "test", 124 | Price: 10.11, 125 | SellerID: seller.ID, 126 | Seller: &seller, 127 | } 128 | 129 | _, err := s.ProductRepo.Store(&product) 130 | testUtil.HandleError(err) 131 | 132 | result, err := s.ProductRepo.GetByID(uint32(1)) 133 | s.Require().NoError(err) 134 | s.Assert().EqualValues(1, result.ID) 135 | s.Assert().Equal(product.ProductName, result.ProductName) 136 | s.Assert().True(reflect.DeepEqual(seller, *result.Seller)) 137 | }) 138 | 139 | s.Run("Get all Product data", func() { 140 | seller := models.Seller{ 141 | ID: 1, 142 | Email: "test", 143 | Name: "test", 144 | Password: "test", 145 | PickupAddress: "test", 146 | } 147 | 148 | product := models.Product{ 149 | ProductName: "test", 150 | Description: "test", 151 | Price: 10.11, 152 | SellerID: seller.ID, 153 | Seller: &seller, 154 | } 155 | 156 | _, err := s.ProductRepo.Store(&product) 157 | testUtil.HandleError(err) 158 | 159 | _, err = s.ProductRepo.Store(&product) 160 | testUtil.HandleError(err) 161 | 162 | _, err = s.ProductRepo.Store(&product) 163 | testUtil.HandleError(err) 164 | 165 | result, err := s.ProductRepo.GetAll() 166 | testUtil.HandleError(err) 167 | 168 | s.Assert().Equal(4, len(result)) 169 | }) 170 | } 171 | 172 | func (s *productRepoTestSuite) TestGet2() { 173 | s.Run("Get By Seller ID", func() { 174 | seller1 := models.Seller{ 175 | ID: 1, 176 | Email: "test", 177 | Name: "test", 178 | Password: "test", 179 | PickupAddress: "test", 180 | } 181 | 182 | seller2 := models.Seller{ 183 | ID: 2, 184 | Email: "test", 185 | Name: "test", 186 | Password: "test", 187 | PickupAddress: "test", 188 | } 189 | 190 | product1 := models.Product{ 191 | ProductName: "test", 192 | Description: "test", 193 | Price: 10.11, 194 | SellerID: seller1.ID, 195 | Seller: &seller1, 196 | } 197 | 198 | product2 := models.Product{ 199 | ProductName: "test", 200 | Description: "test", 201 | Price: 10.11, 202 | SellerID: seller2.ID, 203 | Seller: &seller2, 204 | } 205 | 206 | _, err := s.ProductRepo.Store(&product1) 207 | testUtil.HandleError(err) 208 | 209 | _, err = s.ProductRepo.Store(&product1) 210 | testUtil.HandleError(err) 211 | 212 | _, err = s.ProductRepo.Store(&product1) 213 | testUtil.HandleError(err) 214 | 215 | _, err = s.ProductRepo.Store(&product2) 216 | testUtil.HandleError(err) 217 | 218 | _, err = s.ProductRepo.Store(&product2) 219 | testUtil.HandleError(err) 220 | 221 | result, err := s.ProductRepo.GetBySellerID(uint32(2)) 222 | testUtil.HandleError(err) 223 | 224 | s.Assert().Equal(2, len(result)) 225 | }) 226 | } 227 | 228 | func (s *productRepoTestSuite) TestUpdate() { 229 | s.Run("Update a product data arbitrarily", func() { 230 | seller := models.Seller{ 231 | ID: 1, 232 | Email: "test", 233 | Name: "test", 234 | Password: "test", 235 | PickupAddress: "test", 236 | } 237 | 238 | product := models.Product{ 239 | ProductName: "test", 240 | Description: "test", 241 | Price: 10.11, 242 | SellerID: seller.ID, 243 | Seller: &seller, 244 | } 245 | 246 | oid, err := s.ProductRepo.Store(&product) 247 | testUtil.HandleError(err) 248 | 249 | err = s.ProductRepo.UpdateArbitrary(uint32(1), "price", float32(99.9)) 250 | testUtil.HandleError(err) 251 | 252 | result, err := s.ProductRepo.GetByOID(oid) 253 | testUtil.HandleError(err) 254 | 255 | s.Require().Equal(float32(99.9), result.Price) 256 | }) 257 | } 258 | -------------------------------------------------------------------------------- /repositories/mongodb/seller.go: -------------------------------------------------------------------------------- 1 | package mongodb 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | "time" 7 | 8 | "github.com/masterraf21/ecommerce-backend/configs" 9 | "github.com/masterraf21/ecommerce-backend/models" 10 | "go.mongodb.org/mongo-driver/bson/primitive" 11 | "go.mongodb.org/mongo-driver/mongo" 12 | "gopkg.in/mgo.v2/bson" 13 | ) 14 | 15 | type sellerRepo struct { 16 | Instance *mongo.Database 17 | CounterRepo models.CounterRepository 18 | } 19 | 20 | // NewSellerRepo will initiate object representing SellerRepository 21 | func NewSellerRepo(instance *mongo.Database, ctr models.CounterRepository) models.SellerRepository { 22 | return &sellerRepo{Instance: instance, CounterRepo: ctr} 23 | } 24 | 25 | func (r *sellerRepo) Store(seller *models.Seller) (oid primitive.ObjectID, err error) { 26 | collectionName := "seller" 27 | identifier := "id_seller" 28 | 29 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 30 | defer cancel() 31 | 32 | id, err := r.CounterRepo.Get(collectionName, identifier) 33 | if err != nil { 34 | return 35 | } 36 | 37 | collection := r.Instance.Collection(collectionName) 38 | seller.ID = id 39 | 40 | result, err := collection.InsertOne(ctx, seller) 41 | if err != nil { 42 | return 43 | } 44 | 45 | _id := result.InsertedID 46 | oid = _id.(primitive.ObjectID) 47 | 48 | return 49 | } 50 | 51 | func (r *sellerRepo) GetAll() (res []models.Seller, err error) { 52 | collection := r.Instance.Collection("seller") 53 | 54 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 55 | defer cancel() 56 | 57 | cursor, err := collection.Find(ctx, bson.M{}) 58 | if err != nil { 59 | return 60 | } 61 | 62 | if err = cursor.All(ctx, &res); err != nil { 63 | return 64 | } 65 | 66 | return 67 | } 68 | 69 | func (r *sellerRepo) GetByID(id uint32) (res *models.Seller, err error) { 70 | collection := r.Instance.Collection("seller") 71 | 72 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 73 | defer cancel() 74 | 75 | err = collection.FindOne(ctx, bson.M{"id_seller": id}).Decode(&res) 76 | if err != nil { 77 | if strings.Contains(err.Error(), "mongo: no documents") { 78 | err = nil 79 | return 80 | } 81 | 82 | return 83 | } 84 | 85 | return 86 | } 87 | 88 | func (r *sellerRepo) GetByOID(oid primitive.ObjectID) (res *models.Seller, err error) { 89 | collection := r.Instance.Collection("seller") 90 | 91 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 92 | defer cancel() 93 | 94 | err = collection.FindOne(ctx, bson.M{"_id": oid}).Decode(&res) 95 | if err != nil { 96 | if strings.Contains(err.Error(), "mongo: no documents") { 97 | err = nil 98 | return 99 | } 100 | 101 | return 102 | } 103 | 104 | return 105 | } 106 | 107 | func (r *sellerRepo) UpdateArbitrary(id uint32, key string, value interface{}) error { 108 | collection := r.Instance.Collection("seller") 109 | 110 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 111 | defer cancel() 112 | 113 | _, err := collection.UpdateOne( 114 | ctx, 115 | bson.M{"id_seller": id}, 116 | bson.M{"$set": bson.M{key: value}}, 117 | ) 118 | if err != nil { 119 | return err 120 | } 121 | 122 | return nil 123 | } 124 | -------------------------------------------------------------------------------- /repositories/mongodb/seller_test.go: -------------------------------------------------------------------------------- 1 | package mongodb 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/masterraf21/ecommerce-backend/configs" 9 | "github.com/masterraf21/ecommerce-backend/models" 10 | "github.com/masterraf21/ecommerce-backend/utils/mongodb" 11 | testUtil "github.com/masterraf21/ecommerce-backend/utils/test" 12 | 13 | "github.com/stretchr/testify/suite" 14 | "go.mongodb.org/mongo-driver/mongo" 15 | ) 16 | 17 | type sellerRepoTestSuite struct { 18 | suite.Suite 19 | Instance *mongo.Database 20 | SellerRepo models.SellerRepository 21 | } 22 | 23 | func TestSellerRepository(t *testing.T) { 24 | suite.Run(t, new(sellerRepoTestSuite)) 25 | } 26 | 27 | func (s *sellerRepoTestSuite) SetupSuite() { 28 | instance := mongodb.ConfigureMongo() 29 | s.Instance = instance 30 | counterRepo := NewCounterRepo(instance) 31 | s.SellerRepo = NewSellerRepo(instance, counterRepo) 32 | } 33 | 34 | func (s *sellerRepoTestSuite) TearDownTest() { 35 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 36 | defer cancel() 37 | 38 | err := testUtil.DropSeller(ctx, s.Instance) 39 | testUtil.HandleError(err) 40 | err = testUtil.DropCounter(ctx, s.Instance) 41 | testUtil.HandleError(err) 42 | } 43 | 44 | func (s *sellerRepoTestSuite) TearDownSuite() { 45 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 46 | defer cancel() 47 | 48 | err := testUtil.DropSeller(ctx, s.Instance) 49 | testUtil.HandleError(err) 50 | err = testUtil.DropCounter(ctx, s.Instance) 51 | testUtil.HandleError(err) 52 | } 53 | 54 | func (s *sellerRepoTestSuite) TestStore() { 55 | s.Run("Store a single Seller Data", func() { 56 | seller := models.Seller{ 57 | Email: "test", 58 | Name: "test", 59 | Password: "test", 60 | PickupAddress: "test", 61 | } 62 | 63 | oid, err := s.SellerRepo.Store(&seller) 64 | testUtil.HandleError(err) 65 | 66 | result, err := s.SellerRepo.GetByOID(oid) 67 | s.Require().NoError(err) 68 | s.Assert().EqualValues(1, result.ID) 69 | s.Assert().Equal(seller.Email, result.Email) 70 | s.Assert().Equal(seller.Name, result.Name) 71 | s.Assert().Equal(seller.Password, result.Password) 72 | s.Assert().Equal(seller.PickupAddress, result.PickupAddress) 73 | }) 74 | 75 | s.Run("Store a single Seller Data after existing data stored", func() { 76 | seller := models.Seller{ 77 | Email: "test", 78 | Name: "test", 79 | Password: "test", 80 | PickupAddress: "test", 81 | } 82 | 83 | oid, err := s.SellerRepo.Store(&seller) 84 | testUtil.HandleError(err) 85 | 86 | result, err := s.SellerRepo.GetByOID(oid) 87 | s.Require().NoError(err) 88 | s.Assert().EqualValues(2, result.ID) 89 | s.Assert().Equal(seller.Email, result.Email) 90 | s.Assert().Equal(seller.Name, result.Name) 91 | s.Assert().Equal(seller.Password, result.Password) 92 | s.Assert().Equal(seller.PickupAddress, result.PickupAddress) 93 | }) 94 | } 95 | 96 | func (s *sellerRepoTestSuite) TestGet1() { 97 | s.Run("Get a Seller Data by ID", func() { 98 | seller := models.Seller{ 99 | Email: "test", 100 | Name: "test", 101 | Password: "test", 102 | PickupAddress: "test", 103 | } 104 | 105 | _, err := s.SellerRepo.Store(&seller) 106 | testUtil.HandleError(err) 107 | 108 | result, err := s.SellerRepo.GetByID(uint32(1)) 109 | s.Require().NoError(err) 110 | s.Assert().EqualValues(1, result.ID) 111 | s.Assert().Equal(seller.Email, result.Email) 112 | s.Assert().Equal(seller.Name, result.Name) 113 | s.Assert().Equal(seller.Password, result.Password) 114 | s.Assert().Equal(seller.PickupAddress, result.PickupAddress) 115 | }) 116 | 117 | s.Run("Get all Seller Data", func() { 118 | seller := models.Seller{ 119 | Email: "test", 120 | Name: "test", 121 | Password: "test", 122 | PickupAddress: "test", 123 | } 124 | 125 | _, err := s.SellerRepo.Store(&seller) 126 | testUtil.HandleError(err) 127 | 128 | _, err = s.SellerRepo.Store(&seller) 129 | testUtil.HandleError(err) 130 | 131 | _, err = s.SellerRepo.Store(&seller) 132 | testUtil.HandleError(err) 133 | 134 | result, err := s.SellerRepo.GetAll() 135 | testUtil.HandleError(err) 136 | 137 | s.Assert().Equal(4, len(result)) 138 | }) 139 | } 140 | 141 | func (s *sellerRepoTestSuite) TestUpdate() { 142 | s.Run("Update a seller data arbitrarily", func() { 143 | seller := models.Seller{ 144 | Email: "test", 145 | Name: "test", 146 | Password: "test", 147 | PickupAddress: "test", 148 | } 149 | 150 | oid, err := s.SellerRepo.Store(&seller) 151 | testUtil.HandleError(err) 152 | 153 | err = s.SellerRepo.UpdateArbitrary(uint32(1), "name", "update") 154 | testUtil.HandleError(err) 155 | 156 | result, err := s.SellerRepo.GetByOID(oid) 157 | testUtil.HandleError(err) 158 | s.Require().NoError(err) 159 | s.Require().Equal("update", result.Name) 160 | }) 161 | } 162 | -------------------------------------------------------------------------------- /usecases/buyer.go: -------------------------------------------------------------------------------- 1 | package usecases 2 | 3 | import ( 4 | "github.com/masterraf21/ecommerce-backend/utils/auth" 5 | 6 | "github.com/masterraf21/ecommerce-backend/models" 7 | ) 8 | 9 | type buyerUsecase struct { 10 | Repo models.BuyerRepository 11 | } 12 | 13 | // NewBuyerUsecase will initiate usecase 14 | func NewBuyerUsecase(br models.BuyerRepository) models.BuyerUsecase { 15 | return &buyerUsecase{Repo: br} 16 | } 17 | 18 | func (u *buyerUsecase) CreateBuyer(body models.BuyerBody) (id uint32, err error) { 19 | hash, err := auth.GeneratePassword(body.Password) 20 | if err != nil { 21 | return 22 | } 23 | buyer := models.Buyer{ 24 | Email: body.Email, 25 | Name: body.Name, 26 | Password: hash, 27 | DeliveryAddress: body.DeliveryAddress, 28 | } 29 | 30 | oid, err := u.Repo.Store(&buyer) 31 | if err != nil { 32 | return 33 | } 34 | result, err := u.Repo.GetByOID(oid) 35 | if err != nil { 36 | return 37 | } 38 | 39 | id = result.ID 40 | 41 | return 42 | } 43 | 44 | func (u *buyerUsecase) GetAll() (res []models.Buyer, err error) { 45 | res, err = u.Repo.GetAll() 46 | return 47 | } 48 | 49 | func (u *buyerUsecase) GetByID(id uint32) (res *models.Buyer, err error) { 50 | res, err = u.Repo.GetByID(id) 51 | return 52 | } 53 | -------------------------------------------------------------------------------- /usecases/buyer_test.go: -------------------------------------------------------------------------------- 1 | package usecases 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/masterraf21/ecommerce-backend/configs" 9 | "github.com/masterraf21/ecommerce-backend/models" 10 | repoMongo "github.com/masterraf21/ecommerce-backend/repositories/mongodb" 11 | "github.com/masterraf21/ecommerce-backend/utils/mongodb" 12 | testUtil "github.com/masterraf21/ecommerce-backend/utils/test" 13 | 14 | "go.mongodb.org/mongo-driver/mongo" 15 | 16 | "github.com/stretchr/testify/suite" 17 | ) 18 | 19 | type buyerUsecaseTestSuite struct { 20 | suite.Suite 21 | Instance *mongo.Database 22 | BuyerUsecase models.BuyerUsecase 23 | } 24 | 25 | func TestBuyerUsecase(t *testing.T) { 26 | suite.Run(t, new(buyerUsecaseTestSuite)) 27 | } 28 | 29 | func (s *buyerUsecaseTestSuite) SetupSuite() { 30 | instance := mongodb.ConfigureMongo() 31 | counterRepo := repoMongo.NewCounterRepo(instance) 32 | buyerRepo := repoMongo.NewBuyerRepo(instance, counterRepo) 33 | s.BuyerUsecase = NewBuyerUsecase(buyerRepo) 34 | s.Instance = instance 35 | } 36 | 37 | func (s *buyerUsecaseTestSuite) TearDownSuite() { 38 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 39 | defer cancel() 40 | 41 | err := testUtil.DropBuyer(ctx, s.Instance) 42 | testUtil.HandleError(err) 43 | err = testUtil.DropCounter(ctx, s.Instance) 44 | testUtil.HandleError(err) 45 | } 46 | 47 | func (s *buyerUsecaseTestSuite) TearDownTest() { 48 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 49 | defer cancel() 50 | 51 | err := testUtil.DropBuyer(ctx, s.Instance) 52 | testUtil.HandleError(err) 53 | err = testUtil.DropCounter(ctx, s.Instance) 54 | testUtil.HandleError(err) 55 | } 56 | 57 | func (s *buyerUsecaseTestSuite) TestCreate() { 58 | s.Run("Create Buyer from body", func() { 59 | body := models.BuyerBody{ 60 | Email: "test", 61 | Name: "test", 62 | Password: "test", 63 | DeliveryAddress: "test", 64 | } 65 | 66 | id, err := s.BuyerUsecase.CreateBuyer(body) 67 | testUtil.HandleError(err) 68 | 69 | result, err := s.BuyerUsecase.GetByID(id) 70 | testUtil.HandleError(err) 71 | 72 | s.Assert().EqualValues(1, id) 73 | s.Assert().Equal(body.Email, result.Email) 74 | s.Assert().Equal(body.Name, result.Name) 75 | s.Assert().Equal(body.DeliveryAddress, result.DeliveryAddress) 76 | s.Assert().NotEmpty(result.Password) 77 | }) 78 | } 79 | -------------------------------------------------------------------------------- /usecases/order.go: -------------------------------------------------------------------------------- 1 | package usecases 2 | 3 | import ( 4 | "github.com/masterraf21/ecommerce-backend/models" 5 | ) 6 | 7 | type orderUsecase struct { 8 | Repo models.OrderRepository 9 | BuyerRepo models.BuyerRepository 10 | SellerRepo models.SellerRepository 11 | ProductRepo models.ProductRepository 12 | } 13 | 14 | // NewOrderUsecase will nititate usecase for order 15 | func NewOrderUsecase( 16 | orr models.OrderRepository, 17 | brr models.BuyerRepository, 18 | slr models.SellerRepository, 19 | prr models.ProductRepository, 20 | ) models.OrderUsecase { 21 | return &orderUsecase{ 22 | Repo: orr, 23 | BuyerRepo: brr, 24 | SellerRepo: slr, 25 | ProductRepo: prr, 26 | } 27 | } 28 | 29 | func (u *orderUsecase) CreateOrder(body models.OrderBody) (id uint32, err error) { 30 | sellerPtr, err := u.SellerRepo.GetByID(body.SellerID) 31 | if err != nil { 32 | return 33 | } 34 | 35 | buyerPtr, err := u.BuyerRepo.GetByID(body.BuyerID) 36 | if err != nil { 37 | return 38 | } 39 | 40 | orderDetails := make([]models.OrderDetail, 0) 41 | 42 | var productPtr *models.Product 43 | for _, product := range body.Products { 44 | productPtr, err = u.ProductRepo.GetByID(product.ProductID) 45 | if err != nil { 46 | return 47 | } 48 | 49 | var orderDetail models.OrderDetail 50 | 51 | if productPtr != nil { 52 | orderDetail = models.OrderDetail{ 53 | ProductID: product.ProductID, 54 | Quantity: product.Quantity, 55 | Product: productPtr, 56 | TotalPrice: float32(product.Quantity) * productPtr.Price, 57 | } 58 | } else { 59 | orderDetail = models.OrderDetail{ 60 | ProductID: product.ProductID, 61 | Quantity: product.Quantity, 62 | } 63 | } 64 | 65 | orderDetails = append(orderDetails, orderDetail) 66 | } 67 | 68 | totalPrice := float32(0) 69 | for _, orderDetail := range orderDetails { 70 | totalPrice += orderDetail.TotalPrice 71 | } 72 | 73 | order := models.Order{ 74 | BuyerID: body.BuyerID, 75 | Buyer: buyerPtr, 76 | SellerID: body.SellerID, 77 | Seller: sellerPtr, 78 | Products: orderDetails, 79 | TotalPrice: totalPrice, 80 | Status: "Pending", 81 | } 82 | if sellerPtr != nil { 83 | order.SourceAddress = sellerPtr.PickupAddress 84 | } 85 | if buyerPtr != nil { 86 | order.DeliveryAddress = buyerPtr.DeliveryAddress 87 | } 88 | 89 | oid, err := u.Repo.Store(&order) 90 | if err != nil { 91 | return 92 | } 93 | 94 | result, err := u.Repo.GetByOID(oid) 95 | if err != nil { 96 | return 97 | } 98 | id = result.ID 99 | 100 | return 101 | } 102 | 103 | func (u *orderUsecase) AcceptOrder(id uint32) (err error) { 104 | err = u.Repo.UpdateArbitrary(id, "status", "Accepted") 105 | return 106 | } 107 | 108 | func (u *orderUsecase) GetAll() (res []models.Order, err error) { 109 | res, err = u.Repo.GetAll() 110 | return 111 | } 112 | 113 | func (u *orderUsecase) GetByID(id uint32) (res *models.Order, err error) { 114 | res, err = u.Repo.GetByID(id) 115 | return 116 | } 117 | 118 | func (u *orderUsecase) GetBySellerID(sellerID uint32) (res []models.Order, err error) { 119 | res, err = u.Repo.GetBySellerID(sellerID) 120 | return 121 | } 122 | 123 | func (u *orderUsecase) GetByBuyerID(buyerID uint32) (res []models.Order, err error) { 124 | res, err = u.Repo.GetByBuyerID(buyerID) 125 | return 126 | } 127 | 128 | func (u *orderUsecase) GetByBuyerIDAndStatus(buyerID uint32, status string) (res []models.Order, err error) { 129 | res, err = u.Repo.GetByBuyerIDAndStatus(buyerID, status) 130 | return 131 | } 132 | 133 | func (u *orderUsecase) GetBySellerIDAndStatus(sellerID uint32, status string) (res []models.Order, err error) { 134 | res, err = u.Repo.GetByBuyerIDAndStatus(sellerID, status) 135 | return 136 | } 137 | 138 | func (u *orderUsecase) GetByStatus(status string) (res []models.Order, err error) { 139 | res, err = u.Repo.GetByStatus(status) 140 | return 141 | } 142 | -------------------------------------------------------------------------------- /usecases/order_test.go: -------------------------------------------------------------------------------- 1 | package usecases 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/masterraf21/ecommerce-backend/configs" 9 | "github.com/masterraf21/ecommerce-backend/models" 10 | repoMongo "github.com/masterraf21/ecommerce-backend/repositories/mongodb" 11 | "github.com/masterraf21/ecommerce-backend/utils/mongodb" 12 | testUtil "github.com/masterraf21/ecommerce-backend/utils/test" 13 | 14 | "go.mongodb.org/mongo-driver/mongo" 15 | 16 | "github.com/stretchr/testify/suite" 17 | ) 18 | 19 | type orderUsecaseTestSuite struct { 20 | suite.Suite 21 | Instance *mongo.Database 22 | OrderUsecase models.OrderUsecase 23 | SellerRepo models.SellerRepository 24 | ProductRepo models.ProductRepository 25 | BuyerRepo models.BuyerRepository 26 | } 27 | 28 | func TestOrderUsecase(t *testing.T) { 29 | suite.Run(t, new(orderUsecaseTestSuite)) 30 | } 31 | 32 | func (s *orderUsecaseTestSuite) SetupSuite() { 33 | instance := mongodb.ConfigureMongo() 34 | counterRepo := repoMongo.NewCounterRepo(instance) 35 | productRepo := repoMongo.NewProductRepo(instance, counterRepo) 36 | sellerRepo := repoMongo.NewSellerRepo(instance, counterRepo) 37 | orderRepo := repoMongo.NewOrderRepo(instance, counterRepo) 38 | buyerRepo := repoMongo.NewBuyerRepo(instance, counterRepo) 39 | 40 | s.OrderUsecase = NewOrderUsecase( 41 | orderRepo, 42 | buyerRepo, 43 | sellerRepo, 44 | productRepo, 45 | ) 46 | s.SellerRepo = sellerRepo 47 | s.ProductRepo = productRepo 48 | s.BuyerRepo = buyerRepo 49 | s.Instance = instance 50 | } 51 | 52 | func (s *orderUsecaseTestSuite) TearDownSuite() { 53 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 54 | defer cancel() 55 | 56 | err := testUtil.DropProduct(ctx, s.Instance) 57 | testUtil.HandleError(err) 58 | err = testUtil.DropSeller(ctx, s.Instance) 59 | testUtil.HandleError(err) 60 | err = testUtil.DropBuyer(ctx, s.Instance) 61 | testUtil.HandleError(err) 62 | err = testUtil.DropOrder(ctx, s.Instance) 63 | testUtil.HandleError(err) 64 | err = testUtil.DropCounter(ctx, s.Instance) 65 | testUtil.HandleError(err) 66 | } 67 | 68 | func (s *orderUsecaseTestSuite) TearDownTest() { 69 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 70 | defer cancel() 71 | 72 | err := testUtil.DropProduct(ctx, s.Instance) 73 | testUtil.HandleError(err) 74 | err = testUtil.DropSeller(ctx, s.Instance) 75 | testUtil.HandleError(err) 76 | err = testUtil.DropBuyer(ctx, s.Instance) 77 | testUtil.HandleError(err) 78 | err = testUtil.DropOrder(ctx, s.Instance) 79 | testUtil.HandleError(err) 80 | err = testUtil.DropCounter(ctx, s.Instance) 81 | testUtil.HandleError(err) 82 | } 83 | 84 | func (s *orderUsecaseTestSuite) TestCreate() { 85 | s.Run("Create Order from body", func() { 86 | seller := models.Seller{ 87 | Email: "test", 88 | Name: "test", 89 | Password: "test", 90 | PickupAddress: "test pickup", 91 | } 92 | 93 | buyer := models.Buyer{ 94 | Email: "test", 95 | Name: "test", 96 | Password: "test", 97 | DeliveryAddress: "test delivery", 98 | } 99 | 100 | product := models.Product{ 101 | Seller: &seller, 102 | ProductName: "test", 103 | Description: "test", 104 | Price: 125000, 105 | SellerID: uint32(1), 106 | } 107 | 108 | product2 := models.Product{ 109 | Seller: &seller, 110 | ProductName: "test", 111 | Description: "test", 112 | Price: 15000, 113 | SellerID: uint32(1), 114 | } 115 | 116 | _, err := s.BuyerRepo.Store(&buyer) 117 | testUtil.HandleError(err) 118 | _, err = s.SellerRepo.Store(&seller) 119 | testUtil.HandleError(err) 120 | _, err = s.ProductRepo.Store(&product) 121 | testUtil.HandleError(err) 122 | _, err = s.ProductRepo.Store(&product2) 123 | testUtil.HandleError(err) 124 | 125 | body := models.OrderBody{ 126 | BuyerID: uint32(1), 127 | SellerID: uint32(1), 128 | Products: []models.ProductDetail{ 129 | { 130 | ProductID: uint32(1), 131 | Quantity: 10, 132 | }, 133 | { 134 | ProductID: uint32(2), 135 | Quantity: 4, 136 | }, 137 | }, 138 | } 139 | 140 | totalPrice := product.Price*10 + product2.Price*4 141 | 142 | id, err := s.OrderUsecase.CreateOrder(body) 143 | testUtil.HandleError(err) 144 | 145 | result, err := s.OrderUsecase.GetByID(id) 146 | testUtil.HandleError(err) 147 | 148 | s.Assert().EqualValues(1, id) 149 | s.Assert().Equal(&seller, result.Seller) 150 | s.Assert().Equal(&buyer, result.Buyer) 151 | s.Assert().EqualValues(totalPrice, result.TotalPrice) 152 | }) 153 | } 154 | 155 | func (s *orderUsecaseTestSuite) TestCreate2() { 156 | s.Run("Create order without other data available", func() { 157 | body := models.OrderBody{ 158 | BuyerID: uint32(1), 159 | SellerID: uint32(1), 160 | Products: []models.ProductDetail{ 161 | { 162 | ProductID: uint32(1), 163 | Quantity: 10, 164 | }, 165 | { 166 | ProductID: uint32(2), 167 | Quantity: 4, 168 | }, 169 | }, 170 | } 171 | 172 | totalPrice := 0 173 | 174 | id, err := s.OrderUsecase.CreateOrder(body) 175 | testUtil.HandleError(err) 176 | 177 | result, err := s.OrderUsecase.GetByID(id) 178 | testUtil.HandleError(err) 179 | 180 | s.Assert().EqualValues(1, id) 181 | s.Assert().Nil(result.Buyer) 182 | s.Assert().Nil(result.Seller) 183 | s.Assert().EqualValues(totalPrice, result.TotalPrice) 184 | s.Assert().Equal("", result.DeliveryAddress) 185 | s.Assert().Equal("", result.SourceAddress) 186 | }) 187 | } 188 | -------------------------------------------------------------------------------- /usecases/product.go: -------------------------------------------------------------------------------- 1 | package usecases 2 | 3 | import "github.com/masterraf21/ecommerce-backend/models" 4 | 5 | type productUsecase struct { 6 | Repo models.ProductRepository 7 | SellerRepo models.SellerRepository 8 | } 9 | 10 | // NewProductUsecase will initiate usecase 11 | func NewProductUsecase(prr models.ProductRepository, ssr models.SellerRepository) models.ProductUsecase { 12 | return &productUsecase{Repo: prr, SellerRepo: ssr} 13 | } 14 | 15 | func (u *productUsecase) CreateProduct(body models.ProductBody) (id uint32, err error) { 16 | sellerPtr, err := u.SellerRepo.GetByID(body.SellerID) 17 | if err != nil { 18 | return 19 | } 20 | 21 | product := models.Product{ 22 | ProductName: body.ProductName, 23 | Description: body.Description, 24 | Price: body.Price, 25 | SellerID: body.SellerID, 26 | Seller: sellerPtr, 27 | } 28 | 29 | oid, err := u.Repo.Store(&product) 30 | if err != nil { 31 | return 32 | } 33 | result, err := u.Repo.GetByOID(oid) 34 | if err != nil { 35 | return 36 | } 37 | 38 | id = result.ID 39 | 40 | return 41 | } 42 | 43 | func (u *productUsecase) GetAll() (res []models.Product, err error) { 44 | res, err = u.Repo.GetAll() 45 | return 46 | } 47 | 48 | func (u *productUsecase) GetBySellerID(sellerID uint32) (res []models.Product, err error) { 49 | res, err = u.Repo.GetBySellerID(sellerID) 50 | return 51 | } 52 | 53 | func (u *productUsecase) GetByID(id uint32) (res *models.Product, err error) { 54 | res, err = u.Repo.GetByID(id) 55 | return 56 | } 57 | -------------------------------------------------------------------------------- /usecases/product_test.go: -------------------------------------------------------------------------------- 1 | package usecases 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/masterraf21/ecommerce-backend/configs" 9 | "github.com/masterraf21/ecommerce-backend/models" 10 | repoMongo "github.com/masterraf21/ecommerce-backend/repositories/mongodb" 11 | "github.com/masterraf21/ecommerce-backend/utils/mongodb" 12 | testUtil "github.com/masterraf21/ecommerce-backend/utils/test" 13 | 14 | "go.mongodb.org/mongo-driver/mongo" 15 | 16 | "github.com/stretchr/testify/suite" 17 | ) 18 | 19 | type productUsecaseTestSuite struct { 20 | suite.Suite 21 | Instance *mongo.Database 22 | ProductUsecase models.ProductUsecase 23 | SellerRepo models.SellerRepository 24 | } 25 | 26 | func TestProductUsecase(t *testing.T) { 27 | suite.Run(t, new(productUsecaseTestSuite)) 28 | } 29 | 30 | func (s *productUsecaseTestSuite) SetupSuite() { 31 | instance := mongodb.ConfigureMongo() 32 | counterRepo := repoMongo.NewCounterRepo(instance) 33 | productRepo := repoMongo.NewProductRepo(instance, counterRepo) 34 | sellerRepo := repoMongo.NewSellerRepo(instance, counterRepo) 35 | s.ProductUsecase = NewProductUsecase(productRepo, sellerRepo) 36 | s.SellerRepo = sellerRepo 37 | s.Instance = instance 38 | } 39 | 40 | func (s *productUsecaseTestSuite) TearDownSuite() { 41 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 42 | defer cancel() 43 | 44 | err := testUtil.DropProduct(ctx, s.Instance) 45 | testUtil.HandleError(err) 46 | err = testUtil.DropSeller(ctx, s.Instance) 47 | testUtil.HandleError(err) 48 | err = testUtil.DropCounter(ctx, s.Instance) 49 | testUtil.HandleError(err) 50 | } 51 | 52 | func (s *productUsecaseTestSuite) TearDownTest() { 53 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 54 | defer cancel() 55 | 56 | err := testUtil.DropBuyer(ctx, s.Instance) 57 | testUtil.HandleError(err) 58 | err = testUtil.DropSeller(ctx, s.Instance) 59 | testUtil.HandleError(err) 60 | err = testUtil.DropCounter(ctx, s.Instance) 61 | testUtil.HandleError(err) 62 | } 63 | 64 | func (s *productUsecaseTestSuite) TestStore() { 65 | s.Run("Test Store Product from Body", func() { 66 | seller := models.Seller{ 67 | Email: "test", 68 | Name: "test", 69 | Password: "test", 70 | PickupAddress: "test", 71 | } 72 | 73 | body := models.ProductBody{ 74 | ProductName: "test", 75 | Description: "test", 76 | Price: 1.2, 77 | SellerID: uint32(1), 78 | } 79 | 80 | _, err := s.SellerRepo.Store(&seller) 81 | testUtil.HandleError(err) 82 | 83 | id, err := s.ProductUsecase.CreateProduct(body) 84 | testUtil.HandleError(err) 85 | 86 | result, err := s.ProductUsecase.GetByID(id) 87 | testUtil.HandleError(err) 88 | 89 | s.Assert().EqualValues(1, id) 90 | s.Assert().Equal(&seller, result.Seller) 91 | s.Assert().Equal(body.ProductName, result.ProductName) 92 | s.Assert().Equal(body.Description, result.Description) 93 | s.Assert().Equal(body.Price, result.Price) 94 | }) 95 | } 96 | -------------------------------------------------------------------------------- /usecases/seller.go: -------------------------------------------------------------------------------- 1 | package usecases 2 | 3 | import ( 4 | "github.com/masterraf21/ecommerce-backend/models" 5 | "github.com/masterraf21/ecommerce-backend/utils/auth" 6 | ) 7 | 8 | type sellerUsecase struct { 9 | Repo models.SellerRepository 10 | } 11 | 12 | // NewSellerUsecase will initiate usecase 13 | func NewSellerUsecase(srr models.SellerRepository) models.SellerUsecase { 14 | return &sellerUsecase{Repo: srr} 15 | } 16 | 17 | func (u *sellerUsecase) CreateSeller(body models.SellerBody) (id uint32, err error) { 18 | hash, err := auth.GeneratePassword(body.Password) 19 | if err != nil { 20 | return 21 | } 22 | seller := models.Seller{ 23 | Email: body.Email, 24 | Name: body.Name, 25 | Password: hash, 26 | PickupAddress: body.PickupAddress, 27 | } 28 | 29 | oid, err := u.Repo.Store(&seller) 30 | if err != nil { 31 | return 32 | } 33 | result, err := u.Repo.GetByOID(oid) 34 | if err != nil { 35 | return 36 | } 37 | 38 | id = result.ID 39 | 40 | return 41 | } 42 | 43 | func (u *sellerUsecase) GetAll() (res []models.Seller, err error) { 44 | res, err = u.Repo.GetAll() 45 | return 46 | } 47 | 48 | func (u *sellerUsecase) GetByID(id uint32) (res *models.Seller, err error) { 49 | res, err = u.Repo.GetByID(id) 50 | return 51 | } 52 | -------------------------------------------------------------------------------- /usecases/seller_test.go: -------------------------------------------------------------------------------- 1 | package usecases 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/masterraf21/ecommerce-backend/configs" 9 | "github.com/masterraf21/ecommerce-backend/models" 10 | repoMongo "github.com/masterraf21/ecommerce-backend/repositories/mongodb" 11 | "github.com/masterraf21/ecommerce-backend/utils/mongodb" 12 | testUtil "github.com/masterraf21/ecommerce-backend/utils/test" 13 | 14 | "go.mongodb.org/mongo-driver/mongo" 15 | 16 | "github.com/stretchr/testify/suite" 17 | ) 18 | 19 | type sellerUsecaseTestSuite struct { 20 | suite.Suite 21 | Instance *mongo.Database 22 | SellerUsecase models.SellerUsecase 23 | } 24 | 25 | func TestSellerUsecase(t *testing.T) { 26 | suite.Run(t, new(sellerUsecaseTestSuite)) 27 | } 28 | 29 | func (s *sellerUsecaseTestSuite) SetupSuite() { 30 | instance := mongodb.ConfigureMongo() 31 | counterRepo := repoMongo.NewCounterRepo(instance) 32 | SellerRepo := repoMongo.NewSellerRepo(instance, counterRepo) 33 | s.SellerUsecase = NewSellerUsecase(SellerRepo) 34 | s.Instance = instance 35 | } 36 | 37 | func (s *sellerUsecaseTestSuite) TearDownSuite() { 38 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 39 | defer cancel() 40 | 41 | err := testUtil.DropSeller(ctx, s.Instance) 42 | testUtil.HandleError(err) 43 | err = testUtil.DropCounter(ctx, s.Instance) 44 | testUtil.HandleError(err) 45 | } 46 | 47 | func (s *sellerUsecaseTestSuite) TearDownTest() { 48 | ctx, cancel := context.WithTimeout(context.Background(), configs.Constant.TimeoutOnSeconds*time.Second) 49 | defer cancel() 50 | 51 | err := testUtil.DropSeller(ctx, s.Instance) 52 | testUtil.HandleError(err) 53 | err = testUtil.DropCounter(ctx, s.Instance) 54 | testUtil.HandleError(err) 55 | } 56 | 57 | func (s *sellerUsecaseTestSuite) TestCreate() { 58 | s.Run("Create Seller from body", func() { 59 | body := models.SellerBody{ 60 | Email: "test", 61 | Name: "test", 62 | Password: "test", 63 | PickupAddress: "test", 64 | } 65 | 66 | id, err := s.SellerUsecase.CreateSeller(body) 67 | testUtil.HandleError(err) 68 | 69 | result, err := s.SellerUsecase.GetByID(id) 70 | testUtil.HandleError(err) 71 | 72 | s.Assert().EqualValues(1, id) 73 | s.Assert().Equal(body.Email, result.Email) 74 | s.Assert().Equal(body.Name, result.Name) 75 | s.Assert().Equal(body.PickupAddress, result.PickupAddress) 76 | s.Assert().NotEmpty(result.Password) 77 | }) 78 | } 79 | -------------------------------------------------------------------------------- /utils/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strconv" 7 | "time" 8 | 9 | jwt "github.com/dgrijalva/jwt-go" 10 | "github.com/masterraf21/ecommerce-backend/configs" 11 | "github.com/twinj/uuid" 12 | ) 13 | 14 | // AccessDetails preserve jwt metadata 15 | type AccessDetails struct { 16 | Role string `json:"role"` 17 | ID uint32 `json:"id"` 18 | } 19 | 20 | // TokenDetails will hold detail for token 21 | type TokenDetails struct { 22 | AccessToken string 23 | RefreshToken string 24 | AccessUUID string 25 | RefreshUUID string 26 | AtExpires int64 27 | RtExpires int64 28 | } 29 | 30 | type jWTClaims struct { 31 | jwt.StandardClaims 32 | Role string `json:"role"` 33 | ID uint32 `json:"id"` 34 | } 35 | 36 | // CreateToken util for creating jw token 37 | func CreateToken(role string, id uint32) (*TokenDetails, error) { 38 | secret := configs.Auth.Secret 39 | refreshSecret := configs.Auth.RefreshSecret 40 | td := &TokenDetails{} 41 | td.AtExpires = time.Now().Add(time.Minute * 30).Unix() 42 | td.AccessToken = uuid.NewV4().String() 43 | 44 | td.RtExpires = time.Now().Add(time.Hour * 24 * 7).Unix() 45 | td.RefreshUUID = uuid.NewV4().String() 46 | 47 | var err error 48 | // Creating Access Token 49 | atClaims := jwt.MapClaims{} 50 | atClaims["authorized"] = true 51 | atClaims["access_uuid"] = td.AccessUUID 52 | atClaims["id"] = id 53 | atClaims["role"] = role 54 | atClaims["exp"] = td.AtExpires 55 | at := jwt.NewWithClaims(jwt.SigningMethodHS256, atClaims) 56 | td.AccessToken, err = at.SignedString([]byte(secret)) 57 | 58 | if err != nil { 59 | return nil, err 60 | } 61 | // Creating Refresh Token 62 | rtClaims := jwt.MapClaims{} 63 | rtClaims["refresh_uuid"] = td.RefreshUUID 64 | rtClaims["id"] = id 65 | rtClaims["role"] = role 66 | rtClaims["exp"] = td.RtExpires 67 | rt := jwt.NewWithClaims(jwt.SigningMethodHS256, rtClaims) 68 | td.RefreshToken, err = rt.SignedString([]byte(refreshSecret)) 69 | 70 | if err != nil { 71 | return nil, err 72 | } 73 | return td, nil 74 | } 75 | 76 | // VerifyToken Will parse token 77 | func VerifyToken(r *http.Request) (*jwt.Token, error) { 78 | tokenString := ExtractToken(r) 79 | token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { 80 | // Make sure that the token method conform to "SigningMethodHMAC" 81 | if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { 82 | return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) 83 | } 84 | return []byte(configs.Auth.Secret), nil 85 | }) 86 | if err != nil { 87 | return nil, err 88 | } 89 | return token, nil 90 | } 91 | 92 | // ExtractToken will extract jwt from header 93 | func ExtractToken(r *http.Request) string { 94 | jwt := r.Header.Get("x-access-token") 95 | return jwt 96 | } 97 | 98 | // IsTokenValid will check if token is valid 99 | func IsTokenValid(r *http.Request) error { 100 | token, err := VerifyToken(r) 101 | if err != nil { 102 | return err 103 | } 104 | if _, ok := token.Claims.(jwt.Claims); !ok && !token.Valid { 105 | return err 106 | } 107 | return nil 108 | } 109 | 110 | // ExtractMetadata will gain metada from token 111 | func ExtractMetadata(r *http.Request) (*AccessDetails, error) { 112 | token, err := VerifyToken(r) 113 | claims, ok := token.Claims.(jwt.MapClaims) 114 | if ok && token.Valid { 115 | role, ok := claims["role"].(string) 116 | if !ok { 117 | return nil, err 118 | } 119 | 120 | id, err := strconv.ParseUint(fmt.Sprintf("%.f", claims["user_id"]), 10, 64) 121 | ids := uint32(id) 122 | if err != nil { 123 | return nil, err 124 | } 125 | 126 | return &AccessDetails{ 127 | Role: role, 128 | ID: ids, 129 | }, nil 130 | } 131 | 132 | return nil, err 133 | } 134 | -------------------------------------------------------------------------------- /utils/auth/password.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import "golang.org/x/crypto/bcrypt" 4 | 5 | // GeneratePassword will hash plaintext 6 | func GeneratePassword(plain string) (password string, err error) { 7 | pwd := []byte(plain) 8 | hash, err := bcrypt.GenerateFromPassword(pwd, bcrypt.MinCost) 9 | if err != nil { 10 | return 11 | } 12 | 13 | password = string(hash) 14 | 15 | return 16 | } 17 | 18 | // ComparePassword will compare password 19 | func ComparePassword(hashed string, plain string) (bool, error) { 20 | byteHash := []byte(hashed) 21 | bytePlain := []byte(plain) 22 | 23 | err := bcrypt.CompareHashAndPassword(byteHash, bytePlain) 24 | if err != nil { 25 | return false, err 26 | } 27 | 28 | return true, nil 29 | } 30 | -------------------------------------------------------------------------------- /utils/errors/bad_request.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "net/http" 5 | "runtime/debug" 6 | ) 7 | 8 | // BadRequestError type 9 | func BadRequestError(endpoint, method, message string) *Base { 10 | return NewError( 11 | "BadRequestError", 12 | endpoint, 13 | method, 14 | message, 15 | http.StatusBadRequest, 16 | debug.Stack(), 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /utils/errors/base.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "regexp" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | type content struct { 13 | Name string `json:"name"` 14 | Endpoint string `json:"endpoint"` 15 | Method string `json:"method"` 16 | ErrorCode int16 `json:"httpStatus"` 17 | Source string `json:"source,omitempty"` 18 | Message string `json:"message"` 19 | Timestamp int64 `json:"timestamp"` 20 | CleanedStack string `json:"stack"` 21 | } 22 | 23 | // Base Error 24 | type Base struct { 25 | c content 26 | stack string 27 | } 28 | 29 | var serviceName string 30 | 31 | func init() { 32 | serviceName = strings.ReplaceAll(os.Getenv("SERVICE_NAME"), "_", "-") 33 | } 34 | 35 | // Error getter message 36 | func (err *Base) Error() string { 37 | if err.c.CleanedStack == "" { 38 | err.c.CleanedStack = cleanStack(err.stack) 39 | } 40 | 41 | j, _ := json.Marshal(&err.c) 42 | 43 | return fmt.Sprintf("%s\n", string(j)) 44 | } 45 | 46 | // SetSource setter 47 | func (err *Base) SetSource(source string) { 48 | err.c.Source = source 49 | } 50 | 51 | // Name getter 52 | func (err *Base) Name() string { 53 | return err.c.Name 54 | } 55 | 56 | // ErrorCode getter 57 | func (err *Base) ErrorCode() int16 { 58 | return err.c.ErrorCode 59 | } 60 | 61 | // Message getter 62 | func (err *Base) Message() string { 63 | return err.c.Message 64 | } 65 | 66 | // NewError function 67 | func NewError( 68 | name, 69 | endpoint, 70 | method, 71 | message string, 72 | errorCode int16, 73 | stack []byte) *Base { 74 | return &Base{ 75 | c: content{ 76 | Name: name, 77 | Endpoint: endpoint, 78 | Method: method, 79 | ErrorCode: errorCode, 80 | Message: message, 81 | Timestamp: time.Now().UnixNano() / 1000, 82 | CleanedStack: "", 83 | }, 84 | stack: string(stack), 85 | } 86 | } 87 | 88 | func cleanStack(stack string) string { 89 | splitted := strings.Split(stack, "\n") 90 | 91 | splitted = splitted[5:] 92 | 93 | for i, str := range splitted { 94 | arrStr := strings.Split(str, "/"+serviceName) 95 | cleanedStr := arrStr[len(arrStr)-1] 96 | cleanedStr = strings.ReplaceAll(cleanedStr, os.Getenv("GOPATH"), "GOPATH") 97 | cleanedStr = strings.ReplaceAll(cleanedStr, os.Getenv("GOROOT"), "GOROOT") 98 | cleanedStr = strings.ReplaceAll(cleanedStr, "\t", "") 99 | 100 | regexFunctionArgs := regexp.MustCompile(`\(0x.*\)`) 101 | cleanedStr = regexFunctionArgs.ReplaceAllString(cleanedStr, "()") 102 | 103 | regexMemoryAddr := regexp.MustCompile(`\ \+0x.*`) 104 | cleanedStr = regexMemoryAddr.ReplaceAllString(cleanedStr, "") 105 | 106 | splitted[i] = cleanedStr 107 | } 108 | 109 | return strings.Join(splitted, "; ") 110 | } 111 | -------------------------------------------------------------------------------- /utils/errors/default.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "net/http" 5 | "runtime/debug" 6 | ) 7 | 8 | // DefaultError type 9 | func DefaultError(endpoint, method, message string) *Base { 10 | return NewError( 11 | "DefaultError", 12 | endpoint, 13 | method, 14 | message, 15 | http.StatusInternalServerError, 16 | debug.Stack(), 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /utils/errors/mysql.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "net/http" 5 | "runtime/debug" 6 | ) 7 | 8 | // MySQLError type 9 | func MySQLError(endpoint, method, message string) *Base { 10 | return NewError( 11 | "MySQLError", 12 | endpoint, 13 | method, 14 | message, 15 | http.StatusInternalServerError, 16 | debug.Stack(), 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /utils/errors/not_found.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "net/http" 5 | "runtime/debug" 6 | ) 7 | 8 | // NotFoundError type 9 | func NotFoundError(endpoint, method, message string) *Base { 10 | return NewError( 11 | "NotFoundError", 12 | endpoint, 13 | method, 14 | message, 15 | http.StatusNotFound, 16 | debug.Stack(), 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /utils/errors/unprocessable.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "net/http" 5 | "runtime/debug" 6 | ) 7 | 8 | // UnprocessableError type 9 | func UnprocessableError(endpoint, method, message string) *Base { 10 | return NewError( 11 | "UnprocessableError", 12 | endpoint, 13 | method, 14 | message, 15 | http.StatusUnprocessableEntity, 16 | debug.Stack(), 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /utils/http/http.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/masterraf21/ecommerce-backend/utils/errors" 8 | logger "github.com/sirupsen/logrus" 9 | ) 10 | 11 | // HandleError HTTP Response 12 | func HandleError(w http.ResponseWriter, r *http.Request, err error, message string, statusCode int) { 13 | body := struct { 14 | Message string `json:"message"` 15 | Detail string `json:"detail"` 16 | }{ 17 | Message: message, 18 | Detail: err.Error(), 19 | } 20 | 21 | switch statusCode { 22 | case http.StatusBadRequest: 23 | logger.Error(errors.BadRequestError(r.URL.EscapedPath(), r.Method, message+", "+err.Error())) 24 | case http.StatusUnprocessableEntity: 25 | logger.Error(errors.UnprocessableError(r.URL.EscapedPath(), r.Method, message+", "+err.Error())) 26 | case http.StatusNotFound: 27 | logger.Error(errors.NotFoundError(r.URL.EscapedPath(), r.Method, message+", "+err.Error())) 28 | case http.StatusInternalServerError: 29 | logger.Error(errors.DefaultError(r.URL.EscapedPath(), r.Method, message+", "+err.Error())) 30 | default: 31 | logger.Error(errors.DefaultError(r.URL.EscapedPath(), r.Method, message+", "+err.Error())) 32 | } 33 | 34 | w.Header().Set("Content-Type", "application/json") 35 | w.WriteHeader(statusCode) 36 | json.NewEncoder(w).Encode(body) 37 | } 38 | 39 | // HandleJSONResponse HTTP 40 | func HandleJSONResponse(w http.ResponseWriter, r *http.Request, v interface{}) { 41 | message, err := json.Marshal(v) 42 | if err != nil { 43 | HandleError(w, r, err, "failed to marshal response body", http.StatusInternalServerError) 44 | return 45 | } 46 | 47 | w.Header().Set("Content-Type", "application/json") 48 | w.WriteHeader(http.StatusOK) 49 | w.Write(message) 50 | } 51 | 52 | // HandleNoJSONResponse HTTP 53 | func HandleNoJSONResponse(w http.ResponseWriter) { 54 | w.Header().Set("Content-Type", "application/json") 55 | w.WriteHeader(http.StatusNoContent) 56 | } 57 | -------------------------------------------------------------------------------- /utils/mongodb/mongodb.go: -------------------------------------------------------------------------------- 1 | package mongodb 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "time" 8 | 9 | "github.com/masterraf21/ecommerce-backend/configs" 10 | "go.mongodb.org/mongo-driver/mongo" 11 | "go.mongodb.org/mongo-driver/mongo/options" 12 | "go.mongodb.org/mongo-driver/mongo/readpref" 13 | ) 14 | 15 | // Option type 16 | type Option struct { 17 | Hosts string 18 | Database string 19 | Options string 20 | } 21 | 22 | // ConfigureMongo for connecting to mongo 23 | func ConfigureMongo() *mongo.Database { 24 | option := Option{ 25 | Hosts: configs.MongoDB.Hosts, 26 | Database: configs.MongoDB.Database, 27 | Options: configs.MongoDB.Options, 28 | } 29 | 30 | instance, err := Init(option) 31 | if err != nil { 32 | log.Fatalf("%s: %s", "Failed to connect mongodb", err) 33 | } 34 | 35 | log.Println("MongoDB connection is successfully established!") 36 | 37 | return instance 38 | } 39 | 40 | // NewWriteModelCollection function 41 | func NewWriteModelCollection() []mongo.WriteModel { 42 | return []mongo.WriteModel{} 43 | } 44 | 45 | // NewUpdateModel function 46 | func NewUpdateModel() *mongo.UpdateOneModel { 47 | return mongo.NewUpdateOneModel() 48 | } 49 | 50 | // BulkWrite function 51 | func BulkWrite(collection *mongo.Collection, wmc []mongo.WriteModel, timeoutInSeconds time.Duration) error { 52 | ctx, cancel := context.WithTimeout(context.Background(), timeoutInSeconds*time.Second) 53 | defer cancel() 54 | 55 | _, err := collection.BulkWrite(ctx, wmc) 56 | if err != nil { 57 | return err 58 | } 59 | 60 | return nil 61 | } 62 | 63 | // Init function 64 | func Init(option Option) (*mongo.Database, error) { 65 | if option.Hosts == "" { 66 | option.Hosts = "127.0.0.1:27017" 67 | } 68 | 69 | if option.Database == "" { 70 | return nil, fmt.Errorf("connecting to unknown database in MongoDB") 71 | } 72 | 73 | uri := "mongodb://" + option.Hosts + "/" + option.Database + "?" + option.Options 74 | 75 | client, err := mongo.NewClient(options.Client().ApplyURI(uri)) 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | if err := connectMongo(client); err != nil { 81 | return nil, err 82 | } 83 | 84 | if err := pingMongo(client, readpref.Primary()); err != nil { 85 | return nil, err 86 | } 87 | 88 | return client.Database(option.Database), nil 89 | } 90 | 91 | func connectMongo(c *mongo.Client) error { 92 | ctx, cancel := context.WithTimeout(context.Background(), 300*time.Second) 93 | defer cancel() 94 | 95 | return c.Connect(ctx) 96 | } 97 | 98 | func pingMongo(c *mongo.Client, rp *readpref.ReadPref) error { 99 | ctx, cancel := context.WithTimeout(context.Background(), 300*time.Second) 100 | defer cancel() 101 | 102 | return c.Ping(ctx, rp) 103 | } 104 | -------------------------------------------------------------------------------- /utils/test/drop.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "context" 5 | 6 | "go.mongodb.org/mongo-driver/mongo" 7 | ) 8 | 9 | // DropCounter will drop counter table 10 | func DropCounter(ctx context.Context, db *mongo.Database) (err error) { 11 | collection := db.Collection("counter") 12 | err = collection.Drop(ctx) 13 | return 14 | } 15 | 16 | // DropBuyer will drop buyer table 17 | func DropBuyer(ctx context.Context, db *mongo.Database) (err error) { 18 | collection := db.Collection("buyer") 19 | err = collection.Drop(ctx) 20 | return 21 | } 22 | 23 | // DropSeller will drop seller table 24 | func DropSeller(ctx context.Context, db *mongo.Database) (err error) { 25 | collection := db.Collection("seller") 26 | err = collection.Drop(ctx) 27 | return 28 | } 29 | 30 | // DropProduct will drop product table 31 | func DropProduct(ctx context.Context, db *mongo.Database) (err error) { 32 | collection := db.Collection("product") 33 | err = collection.Drop(ctx) 34 | return 35 | } 36 | 37 | // DropOrder will drop order table 38 | func DropOrder(ctx context.Context, db *mongo.Database) (err error) { 39 | collection := db.Collection("order") 40 | err = collection.Drop(ctx) 41 | return 42 | } 43 | -------------------------------------------------------------------------------- /utils/test/error.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | // HandleError for error handling in test suite 4 | func HandleError(err error) { 5 | if err != nil { 6 | panic(err) 7 | } 8 | } 9 | --------------------------------------------------------------------------------