From 9c74b911c3b9f10ef72edd25f64fb066412a6ee9 Mon Sep 17 00:00:00 2001 From: Piotr Biernat Date: Sun, 25 Dec 2022 21:05:44 +0100 Subject: [PATCH] Refactoring --- Dockerfile.target | 6 +- bin/entrypoint.sh | 1 + src/cmd/migrate/main.go | 18 +-- src/cmd/server/main.go | 17 ++- src/cmd/worker/main.go | 117 +++++++++++--------- src/go.mod | 8 +- src/go.sum | 26 +++-- src/internal/app/definition/basket_http.go | 9 -- src/internal/app/definition/basket_model.go | 13 --- src/internal/app/definition/error.go | 9 -- src/internal/app/definition/health_http.go | 5 - src/internal/app/server/basket_handler.go | 84 +++++++++++--- src/internal/app/server/config.go | 3 + src/internal/app/server/health_handler.go | 2 +- src/internal/app/server/router.go | 25 +++++ src/internal/app/server/server.go | 66 ++++++++--- src/internal/app/service/basket.go | 108 ++++++++++++++---- src/internal/app/ui/basket.go | 88 +++++++++++++++ 18 files changed, 437 insertions(+), 168 deletions(-) delete mode 100644 src/internal/app/definition/basket_http.go delete mode 100644 src/internal/app/definition/basket_model.go delete mode 100644 src/internal/app/definition/error.go delete mode 100644 src/internal/app/definition/health_http.go create mode 100644 src/internal/app/ui/basket.go diff --git a/Dockerfile.target b/Dockerfile.target index 944c9ce..decc891 100644 --- a/Dockerfile.target +++ b/Dockerfile.target @@ -6,10 +6,10 @@ FROM ${BUILDER_IMAGE} AS builder # FROM gcr.io/distroless/base-debian10 FROM alpine:3.17 -ARG BUILD_TIME -ARG BIN_OUTPUT ARG SVC_NAME ARG SVC_VER +ARG BIN_OUTPUT +ARG BUILD_TIME LABEL dev.egommerce.image.author="Piotr Biernat" LABEL dev.egommerce.image.vendor="Egommerce" @@ -26,5 +26,5 @@ RUN chmod 755 /bin/entrypoint.sh /bin/migrate.sh EXPOSE 80 -CMD ["/app"] +CMD ["sh", "-c", "/app"] ENTRYPOINT ["entrypoint.sh"] diff --git a/bin/entrypoint.sh b/bin/entrypoint.sh index 01a6dc9..5615ef8 100755 --- a/bin/entrypoint.sh +++ b/bin/entrypoint.sh @@ -18,6 +18,7 @@ waitForService "postgres-db:5432" waitForService "api-eventbus:5672" waitForService "api-logger:24224" waitForService "api-registry:8500" +waitForService "pricing-svc:80" # run migrations migrate.sh diff --git a/src/cmd/migrate/main.go b/src/cmd/migrate/main.go index 8b0ad37..fecaf60 100644 --- a/src/cmd/migrate/main.go +++ b/src/cmd/migrate/main.go @@ -58,17 +58,18 @@ func main() { mig.SetTableName(mTblName) err := mig.DiscoverSQLMigrations("./migrations") if err != nil { - fmt.Println(err) + logger.Log("migration dicovery error: %#v", err) } oldVersion, newVersion, err := mig.Run(db, flag.Args()...) if err != nil { - exitf(err.Error()) + logger.Log("migration runner error: %#v", err) + os.Exit(1) } if newVersion != oldVersion { - fmt.Printf("migrated from version %d to %d\n", oldVersion, newVersion) + logger.Log("migrated from version %d to %d\n", oldVersion, newVersion) } else { - fmt.Printf("version is %d\n", oldVersion) + logger.Log("version is %d\n", oldVersion) } } @@ -77,12 +78,3 @@ func usage() { flag.PrintDefaults() os.Exit(2) } - -func errorf(s string, args ...interface{}) { - fmt.Fprintf(os.Stderr, s+"\n", args...) -} - -func exitf(s string, args ...interface{}) { - errorf(s, args...) - os.Exit(1) -} diff --git a/src/cmd/server/main.go b/src/cmd/server/main.go index 163eb3e..83d60ff 100644 --- a/src/cmd/server/main.go +++ b/src/cmd/server/main.go @@ -10,15 +10,19 @@ import ( "git.pbiernat.dev/egommerce/basket-service/internal/app/server" "git.pbiernat.dev/egommerce/go-api-pkg/fluentd" amqp "git.pbiernat.dev/egommerce/go-api-pkg/rabbitmq" + "github.com/go-redis/redis/v8" ) const ( defAppName = "basket-svc" defAppDomain = "basket-svc" + defPathPrefix = "/basket" defNetAddr = ":80" defLoggerAddr = "api-logger:24224" defRegistryAddr = "api-registry:8500" defDbURL = "postgres://postgres:12345678@postgres-db:5432/egommerce" + defCacheAddr = "api-cache:6379" + defCachePassword = "12345678" defMongoDbURL = "mongodb://mongodb:12345678@mongo-db:27017" defEventBusURL = "amqp://guest:guest@api-eventbus:5672" ebEventsExchange = "api-events" @@ -35,11 +39,14 @@ func main() { c.AppID, _ = os.Hostname() c.AppName = config.GetEnv("APP_NAME", defAppName) c.AppDomain = config.GetEnv("APP_DOMAIN", defAppDomain) + c.PathPrefix = config.GetEnv("APP_PATH_PREFIX", defPathPrefix) c.NetAddr = config.GetEnv("SERVER_ADDR", defNetAddr) c.Port, _ = strconv.Atoi(c.NetAddr[1:]) c.LoggerAddr = config.GetEnv("LOGGER_ADDR", defLoggerAddr) c.RegistryAddr = config.GetEnv("REGISTRY_ADDR", defRegistryAddr) c.DbURL = config.GetEnv("DATABASE_URL", defDbURL) + c.CacheAddr = config.GetEnv("CACHE_ADDR", defCacheAddr) + c.CachePassword = config.GetEnv("CACHE_PASSWORD", defCachePassword) c.EventBusURL = config.GetEnv("EVENTBUS_URL", defEventBusURL) c.EventBusExchange = ebEventsExchange c.KVNamespace = config.GetEnv("APP_KV_NAMESPACE", defKVNmspc) @@ -56,6 +63,14 @@ func main() { } defer dbConn.Close() + // redis conn + redis := redis.NewClient(&redis.Options{ + Addr: c.CacheAddr, + Password: c.CachePassword, + DB: 0, + }) + defer redis.Close() + // eventbus conn ebConn, ebCh, err := amqp.Open(c.EventBusURL) if err != nil { @@ -72,7 +87,7 @@ func main() { } // start server - srv := server.NewServer(c, logger, dbConn, ebCh) + srv := server.NewServer(c, logger, dbConn, redis, ebCh) forever := make(chan struct{}) srv.StartWithGracefulShutdown(forever) diff --git a/src/cmd/worker/main.go b/src/cmd/worker/main.go index d3b9711..6bc2717 100644 --- a/src/cmd/worker/main.go +++ b/src/cmd/worker/main.go @@ -14,13 +14,15 @@ import ( "git.pbiernat.dev/egommerce/basket-service/internal/app/config" "git.pbiernat.dev/egommerce/basket-service/internal/app/database" - def "git.pbiernat.dev/egommerce/basket-service/internal/app/definition" "git.pbiernat.dev/egommerce/basket-service/internal/app/event" "git.pbiernat.dev/egommerce/basket-service/internal/app/server" "git.pbiernat.dev/egommerce/basket-service/internal/app/service" + "git.pbiernat.dev/egommerce/basket-service/internal/app/ui" discovery "git.pbiernat.dev/egommerce/go-api-pkg/consul" "git.pbiernat.dev/egommerce/go-api-pkg/fluentd" - amqp "git.pbiernat.dev/egommerce/go-api-pkg/rabbitmq" + "git.pbiernat.dev/egommerce/go-api-pkg/rabbitmq" + "github.com/go-redis/redis/v8" + "github.com/streadway/amqp" ) const ( @@ -28,6 +30,8 @@ const ( defLoggerAddr = "api-logger:24224" defRegistryAddr = "api-registry:8500" defDbURL = "postgres://postgres:12345678@postgres-db:5432/egommerce" + defCacheAddr = "api-cache:6379" + defCachePassword = "12345678" defMongoDbURL = "mongodb://mongodb:12345678@mongo-db:27017" defEventBusURL = "amqp://guest:guest@api-eventbus:5672" ebEventsExchange = "api-events" @@ -46,6 +50,8 @@ func main() { c.LoggerAddr = config.GetEnv("LOGGER_ADDR", defLoggerAddr) c.RegistryAddr = config.GetEnv("REGISTRY_ADDR", defRegistryAddr) c.DbURL = config.GetEnv("DATABASE_URL", defDbURL) + c.CacheAddr = config.GetEnv("CACHE_ADDR", defCacheAddr) + c.CachePassword = config.GetEnv("CACHE_PASSWORD", defCachePassword) c.EventBusURL = config.GetEnv("EVENTBUS_URL", defEventBusURL) c.EventBusExchange = ebEventsExchange c.EventBusQueue = ebEventsQueue @@ -55,7 +61,7 @@ func main() { logger := fluentd.NewLogger(c.GetAppFullName(), logHost, logPort) defer logger.Close() - consul, err := discovery.NewService(c.RegistryAddr, c.AppID, c.AppName, c.AppID, "", 0) + consul, err := discovery.NewService(c.RegistryAddr, c.AppID, c.AppName, c.AppID, "", "", 0) if err != nil { logger.Log("Error connecting to %s: %v", c.RegistryAddr, err) } @@ -76,16 +82,24 @@ func main() { } defer dbConn.Close() + // redis conn + redis := redis.NewClient(&redis.Options{ + Addr: c.CacheAddr, + Password: c.CachePassword, + DB: 0, + }) + defer redis.Close() + // eventbus conn - ebConn, ebCh, err := amqp.Open(c.EventBusURL) + ebConn, ebCh, err := rabbitmq.Open(c.EventBusURL) if err != nil { logger.Log("Failed to connect to EventBus server: %v\n", err) os.Exit(1) } defer ebCh.Close() - defer amqp.Close(ebConn) + defer rabbitmq.Close(ebConn) - err = amqp.NewExchange(ebCh, c.EventBusExchange) + err = rabbitmq.NewExchange(ebCh, c.EventBusExchange) if err != nil { logger.Log("Failed to declare EventBus exchange: %v\n", err) os.Exit(1) @@ -105,14 +119,9 @@ func main() { os.Exit(1) } - amqp.BindQueueToExchange(ebCh, c.EventBusQueue, c.EventBusExchange, "catalog.basket.productAddedToBasket") - amqp.BindQueueToExchange(ebCh, c.EventBusQueue, c.EventBusExchange, "catalog.basket.productRemovedFromBasket") - amqp.BindQueueToExchange(ebCh, c.EventBusQueue, c.EventBusExchange, "catalog.basket.updateQuantity") - // err = amqp.BindQueueToExchange(ebCh, c.EventBusQueue, c.EventBusExchange, "catalog-svc.product.addedToBasket") - // if err != nil { - // logger.Log("Failed to prepare EventBus queue: %v\n", err) - // os.Exit(1) - // } + rabbitmq.BindQueueToExchange(ebCh, c.EventBusQueue, c.EventBusExchange, "catalog.basket.productAddedToBasket") + rabbitmq.BindQueueToExchange(ebCh, c.EventBusQueue, c.EventBusExchange, "catalog.basket.productRemovedFromBasket") + rabbitmq.BindQueueToExchange(ebCh, c.EventBusQueue, c.EventBusExchange, "catalog.basket.updateQuantity") // event consume msgs, err := ebCh.Consume( @@ -141,51 +150,55 @@ func main() { }() go func() { - bSrvc := service.NewBasketService(dbConn, ebCh, logger) + basketSrv := service.NewBasketService(dbConn, redis, ebCh, logger) for d := range msgs { - msg, err := amqp.Deserialize(d.Body) - if err != nil { - logger.Log("json error: %v\n", err) - d.Reject(false) // FIXME: how to handle erros in queue...???? - continue - } - - eName := fmt.Sprintf("%s", msg["event"]) - data := (msg["data"]).(map[string]interface{}) - logger.Log("Message<%s>: %s\n", eName, data) - - basketID := data["basket_id"].(string) // FIXME Check input params! - productID := data["product_id"].(string) // FIXME Check input params! - - switch true { - case strings.Contains(eName, event.EVENT_PRODUCT_ADDED_TO_BASKET): - var basket *def.BasketModel - basket, err := bSrvc.FetchFromDB(basketID) + go func(d amqp.Delivery) { + msg, err := rabbitmq.Deserialize(d.Body) if err != nil { - logger.Log("Basket#:%s not found. Creating...", basketID) - basket, err = bSrvc.Create(basketID) + logger.Log("deserialize error: %v\n", err) + d.Reject(false) // FIXME: or Nack? how to handle erros in queue... + return + } + + eName := fmt.Sprintf("%s", msg["event"]) + data := (msg["data"]).(map[string]interface{}) + logger.Log("Message<%s>: %v\n", eName, data) + + reqID := data["request_id"].(string) // FIXME Check input params! + basketID := data["basket_id"].(string) // FIXME Check input params! + + switch true { + case strings.Contains(eName, event.EVENT_PRODUCT_ADDED_TO_BASKET): + productID := int(data["product_id"].(float64)) + qty := int(data["quantity"].(float64)) + + basket, err := ui.AddProductToBasket(basketSrv, productID, qty, basketID, reqID) if err != nil { - logger.Log("Creating basket error: %v", err) - d.Reject(false) - continue + fmt.Println("worker error: ", err) + logger.Log("%s error: %s", event.EVENT_PRODUCT_ADDED_TO_BASKET, err.Error()) + d.Reject(false) // FIXME: or Nack? how to handle erros in queue... + break } + + logger.Log("Product #%s added to basket #%s. ReqID: #%s", productID, basket.ID, reqID) + case strings.Contains(eName, event.EVENT_PRODUCT_REMOVED_FROM_BASKET): + productID := int(data["product_id"].(float64)) + qty := int(data["quantity"].(float64)) + + basket, err := ui.RemoveProductFromBasket(basketSrv, productID, qty, basketID, reqID) + if err != nil { + logger.Log("%s error: %s", event.EVENT_PRODUCT_ADDED_TO_BASKET, err.Error()) + d.Reject(false) // FIXME: or Nack? how to handle erros in queue... + break + } + + logger.Log("Product #%s removed from basket #%s. ReqID: #%s", productID, basket.ID, reqID) } - err = bSrvc.AddProduct(productID, basketID, 1) // FIXME: change to Update quantity - which add or delete product to/from basket depends on new quantity - if err != nil { - logger.Log("Error adding product to basket: %v", err) - d.Reject(false) - } - logger.Log("Fetched basket: %v", basket) - - case strings.Contains(eName, event.EVENT_PRODUCT_REMOVED_FROM_BASKET): - // remove product from basket - logger.Log("Event: %s", event.EVENT_PRODUCT_REMOVED_FROM_BASKET) - } - - logger.Log("ACK: %s", eName) - d.Ack(false) + logger.Log("ACK: %s", eName) + d.Ack(false) + }(d) } }() diff --git a/src/go.mod b/src/go.mod index 6d7392d..4bb84ca 100644 --- a/src/go.mod +++ b/src/go.mod @@ -3,12 +3,13 @@ module git.pbiernat.dev/egommerce/basket-service go 1.18 require ( - git.pbiernat.dev/egommerce/go-api-pkg v0.0.113 + git.pbiernat.dev/egommerce/api-entities v0.0.26 + git.pbiernat.dev/egommerce/go-api-pkg v0.0.136 github.com/georgysavva/scany/v2 v2.0.0 github.com/go-pg/migrations/v8 v8.1.0 github.com/go-pg/pg/v10 v10.10.7 + github.com/go-redis/redis/v8 v8.11.5 github.com/gofiber/fiber/v2 v2.40.1 - github.com/jackc/pgtype v1.12.0 github.com/jackc/pgx/v5 v5.1.1 github.com/joho/godotenv v1.4.0 github.com/streadway/amqp v1.0.0 @@ -17,6 +18,8 @@ require ( require ( github.com/andybalholm/brotli v1.0.4 // indirect github.com/armon/go-metrics v0.4.1 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/fatih/color v1.13.0 // indirect github.com/fluent/fluent-logger-golang v1.9.0 // indirect github.com/go-pg/zerochecker v0.2.0 // indirect @@ -30,6 +33,7 @@ require ( github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect + github.com/jackc/pgtype v1.13.0 // indirect github.com/jackc/puddle/v2 v2.1.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/klauspost/compress v1.15.9 // indirect diff --git a/src/go.sum b/src/go.sum index 9a435e0..505f01a 100644 --- a/src/go.sum +++ b/src/go.sum @@ -1,6 +1,10 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -git.pbiernat.dev/egommerce/go-api-pkg v0.0.113 h1:kf7HesezhXIAMNYgLCm8x6YVqyLqJRqaPKIFEXf4xSs= -git.pbiernat.dev/egommerce/go-api-pkg v0.0.113/go.mod h1:nAwcw2MZtn/54YKq8VQK6RJAsiuoLUtPuazXg8JcqK8= +git.pbiernat.dev/egommerce/api-entities v0.0.26 h1:Avz02GINwuYWOjw1fmZIJ3QgGEIz3a5vRQZNaxxUQIk= +git.pbiernat.dev/egommerce/api-entities v0.0.26/go.mod h1:+BXvUcr6Cr6QNpJsW8BUfe1vVILdWDADNE0e3u0lNvU= +git.pbiernat.dev/egommerce/go-api-pkg v0.0.135 h1:qOa6MB6d2/lr0t9c3WWP84rf/T57PNYgizTmuNCDws8= +git.pbiernat.dev/egommerce/go-api-pkg v0.0.135/go.mod h1:w2N79aoumjrrcrGPJLkCwxAHtrLd7G4Uj8VOxvPooa0= +git.pbiernat.dev/egommerce/go-api-pkg v0.0.136 h1:SzJRAkqJKdng/3d0V7o/R0yGh7QaZynPBn/P++on9RA= +git.pbiernat.dev/egommerce/go-api-pkg v0.0.136/go.mod h1:w2N79aoumjrrcrGPJLkCwxAHtrLd7G4Uj8VOxvPooa0= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= @@ -23,6 +27,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -35,6 +41,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -61,6 +69,8 @@ github.com/go-pg/pg/v10 v10.10.7 h1:Q7Bs45kP9MIg03v/ejwdqsPd1T0cecgeDoTJVg/UJuQ= github.com/go-pg/pg/v10 v10.10.7/go.mod h1:GLmFXufrElQHf5uzM3BQlcfwV3nsgnHue5uzjQ6Nqxg= github.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU= github.com/go-pg/zerochecker v0.2.0/go.mod h1:NJZ4wKL0NmTtz0GKCoJ8kym6Xn/EQzXRl2OnAe7MmDo= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gofiber/fiber/v2 v2.40.1 h1:pc7n9VVpGIqNsvg9IPLQhyFEMJL8gCs1kneH5D1pIl4= github.com/gofiber/fiber/v2 v2.40.1/go.mod h1:Gko04sLksnHbzLSRBFWPFdzM9Ws9pRxvvIaohJK1dsk= @@ -172,8 +182,8 @@ github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01C github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w= -github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgtype v1.13.0 h1:XkIc7A+1BmZD19bB2NxrtjJweHxQ9agqvM+9URc68Cg= +github.com/jackc/pgtype v1.13.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= @@ -252,16 +262,16 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M= github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -523,8 +533,8 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/internal/app/definition/basket_http.go b/src/internal/app/definition/basket_http.go deleted file mode 100644 index 001c4dc..0000000 --- a/src/internal/app/definition/basket_http.go +++ /dev/null @@ -1,9 +0,0 @@ -package definition - -type BasketCheckoutRequest struct { - BasketID string `json:"basket_id"` -} - -type BasketCheckoutResponse struct { - ID string `json:"order_id"` -} diff --git a/src/internal/app/definition/basket_model.go b/src/internal/app/definition/basket_model.go deleted file mode 100644 index 232d622..0000000 --- a/src/internal/app/definition/basket_model.go +++ /dev/null @@ -1,13 +0,0 @@ -package definition - -import ( - "time" - - "github.com/jackc/pgtype" -) - -type BasketModel struct { - ID string `db:"id"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt pgtype.Timestamp `db:"updated_at"` -} diff --git a/src/internal/app/definition/error.go b/src/internal/app/definition/error.go deleted file mode 100644 index 28f7dbf..0000000 --- a/src/internal/app/definition/error.go +++ /dev/null @@ -1,9 +0,0 @@ -package definition - -type ErrorResponse struct { - Error string `json:"error"` -} - -func Error(err string) *ErrorResponse { - return &ErrorResponse{err} -} diff --git a/src/internal/app/definition/health_http.go b/src/internal/app/definition/health_http.go deleted file mode 100644 index 6920fca..0000000 --- a/src/internal/app/definition/health_http.go +++ /dev/null @@ -1,5 +0,0 @@ -package definition - -type HealthResponse struct { - Status string `json:"status,omitempty"` -} diff --git a/src/internal/app/server/basket_handler.go b/src/internal/app/server/basket_handler.go index 24c71e7..f4f3e6a 100644 --- a/src/internal/app/server/basket_handler.go +++ b/src/internal/app/server/basket_handler.go @@ -1,24 +1,82 @@ package server import ( - def "git.pbiernat.dev/egommerce/basket-service/internal/app/definition" + "context" + "time" + + def "git.pbiernat.dev/egommerce/api-entities/http" "git.pbiernat.dev/egommerce/basket-service/internal/app/service" + "git.pbiernat.dev/egommerce/basket-service/internal/app/ui" "github.com/gofiber/fiber/v2" ) -func (s *Server) CheckoutHandler(c *fiber.Ctx) error { - reqID, _ := s.GetRequestID(c) - data := new(def.BasketCheckoutRequest) - if err := c.BodyParser(data); err != nil { - return err +func (s *Server) GetBasketHandler(c *fiber.Ctx) error { + req := new(def.GetBasketRequest) + if err := c.BodyParser(req); err != nil { + return s.Error400(c, err.Error()) } - basketID := data.BasketID - // vlaidate, pre check... etc - basket := service.NewBasketService(s.db, s.ebCh, s.log) - basket.Checkout(reqID, basketID) + basketID := req.BasketID + basketSrv := service.NewBasketService(s.db, s.cache, s.ebCh, s.log) + ctx := context.Background() + basket, err := basketSrv.FetchFromDB(ctx, basketID) + if err != nil { + return s.Error400(c, "Failed to retrieve basket") + } - return c.JSON(&def.BasketCheckoutResponse{ - ID: data.BasketID, - }) + res := &def.GetBasketResponse{ + ID: basket.ID, + State: basket.State, + CreatedAt: time.Duration(basket.CreatedAt.Time.Unix()), + } + if basket.UpdatedAt.Time.Unix() > 0 { + res.UpdatedAt = time.Duration(basket.UpdatedAt.Time.Unix()) + } + + return c.JSON(res) +} + +func (s *Server) GetBasketItemsHandler(c *fiber.Ctx) error { + basketID := c.Params("basketId", "") + basketSrv := service.NewBasketService(s.db, s.cache, s.ebCh, s.log) + ctx := context.Background() + items, err := basketSrv.FetchItems(ctx, basketID) + if err != nil { + return s.Error400(c, "Failed to retrieve basket items") + } + + var res []*def.GetBasketItemsResponse // FIXME + for _, item := range items { + resItem := &def.GetBasketItemsResponse{ + ID: item.ID, + BasketID: item.BasketID, + ProductID: item.ProductID, + Quantity: item.Quantity, + Price: item.Price, + CreatedAt: time.Duration(item.CreatedAt.Time.Unix()), + } + if item.UpdatedAt.Time.Unix() > 0 { + resItem.UpdatedAt = time.Duration(item.UpdatedAt.Time.Unix()) + } + res = append(res, resItem) + } + + return c.JSON(res) +} + +func (s *Server) CheckoutHandler(c *fiber.Ctx) error { + reqID, _ := s.GetRequestID(c) + req := new(def.BasketCheckoutRequest) + if err := c.BodyParser(req); err != nil { + return s.Error400(c, err.Error()) + } + + basketID := req.BasketID + basketSrv := service.NewBasketService(s.db, s.cache, s.ebCh, s.log) + res, err := ui.CheckoutBasket(basketSrv, basketID, reqID) + if err != nil { + return s.Error400(c, "Failed to create order") + } + + return c.JSON(res) } diff --git a/src/internal/app/server/config.go b/src/internal/app/server/config.go index 9fce58d..5213283 100644 --- a/src/internal/app/server/config.go +++ b/src/internal/app/server/config.go @@ -6,6 +6,7 @@ type Config struct { AppID string AppName string AppDomain string + PathPrefix string NetAddr string Port int RegistryAddr string @@ -13,6 +14,8 @@ type Config struct { LoggerAddr string `json:"logger_addr"` DbURL string `json:"db_url"` + CacheAddr string `json:"cache_addr"` + CachePassword string `json:"cache_password"` MongoDbUrl string `json:"mongodb_url"` EventBusURL string `json:"eventbus_url"` EventBusExchange string `json:"eventbus_exchange"` diff --git a/src/internal/app/server/health_handler.go b/src/internal/app/server/health_handler.go index d62ee74..73d756e 100644 --- a/src/internal/app/server/health_handler.go +++ b/src/internal/app/server/health_handler.go @@ -3,7 +3,7 @@ package server import ( "github.com/gofiber/fiber/v2" - def "git.pbiernat.dev/egommerce/basket-service/internal/app/definition" + def "git.pbiernat.dev/egommerce/api-entities/http" ) func (s *Server) HealthHandler(c *fiber.Ctx) error { diff --git a/src/internal/app/server/router.go b/src/internal/app/server/router.go index f738bfc..38cb6aa 100644 --- a/src/internal/app/server/router.go +++ b/src/internal/app/server/router.go @@ -1,25 +1,50 @@ package server import ( + "net/http" "strings" "git.pbiernat.dev/egommerce/go-api-pkg/fluentd" "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/cors" +) + +var ( + defaultCORS = cors.New(cors.Config{ + AllowOrigins: "*", + AllowCredentials: true, + AllowMethods: "GET, POST, PATCH, PUT, DELETE, OPTIONS", + AllowHeaders: "Accept, Authorization, Content-Type, Vary, X-Request-Id", + }) ) func SetupRoutes(s *Server) { + s.App.Options("*", defaultCORS) + s.App.Get("/health", s.HealthHandler) s.App.Get("/config", s.ConfigHandler) api := s.App.Group("/api") v1 := api.Group("/v1") + v1.Get("/basket", s.GetBasketHandler) + v1.Get("/basket/:basketId/items", s.GetBasketItemsHandler) v1.Post("/checkout", s.CheckoutHandler) } func SetupMiddlewares(s *Server) { + s.App.Use(defaultCORS) s.App.Use(LoggingMiddleware(s.log)) } +func CORSPreflightMiddleware(c *fiber.Ctx) error { + if string(c.Request().Header.Method()) == http.MethodOptions { + c.Response().SetStatusCode(http.StatusOK) + c.Next() + } + + return c.Next() +} + // Middlewares func LoggingMiddleware(log *fluentd.Logger) func(c *fiber.Ctx) error { return func(c *fiber.Ctx) error { diff --git a/src/internal/app/server/server.go b/src/internal/app/server/server.go index f443551..ef406e9 100644 --- a/src/internal/app/server/server.go +++ b/src/internal/app/server/server.go @@ -2,18 +2,19 @@ package server import ( "bytes" + "context" "encoding/json" - "errors" - "fmt" "os" "os/signal" "syscall" "time" + "github.com/go-redis/redis/v8" "github.com/gofiber/fiber/v2" "github.com/jackc/pgx/v5/pgxpool" "github.com/streadway/amqp" + def "git.pbiernat.dev/egommerce/api-entities/http" discovery "git.pbiernat.dev/egommerce/go-api-pkg/consul" "git.pbiernat.dev/egommerce/go-api-pkg/fluentd" ) @@ -23,6 +24,7 @@ type Server struct { conf *Config log *fluentd.Logger db *pgxpool.Pool + cache *redis.Client ebCh *amqp.Channel discovery *discovery.Service name string @@ -34,9 +36,8 @@ type Headers struct { RequestID string `reqHeader:"x-request-id"` } -func NewServer(conf *Config, logger *fluentd.Logger, db *pgxpool.Pool, ebCh *amqp.Channel) *Server { - logger.Log("API_ID: %s", conf.AppID) - consul, err := discovery.NewService(conf.RegistryAddr, conf.AppID, conf.AppName, conf.AppID, conf.AppDomain, conf.Port) +func NewServer(conf *Config, logger *fluentd.Logger, db *pgxpool.Pool, cache *redis.Client, ebCh *amqp.Channel) *Server { + consul, err := discovery.NewService(conf.RegistryAddr, conf.AppID, conf.AppName, conf.AppID, conf.AppDomain, conf.PathPrefix, conf.Port) if err != nil { logger.Log("Error connecting to %s: %v", conf.RegistryAddr, err) } @@ -59,6 +60,7 @@ func NewServer(conf *Config, logger *fluentd.Logger, db *pgxpool.Pool, ebCh *amq conf, logger, db, + cache, ebCh, consul, conf.AppName, @@ -66,14 +68,22 @@ func NewServer(conf *Config, logger *fluentd.Logger, db *pgxpool.Pool, ebCh *amq conf.KVNamespace, } - go func(s *Server) { // Consul KV config updater - interval := time.Second * 30 + go func(s *Server) { // Consul KV updater + interval := time.Second * 15 ticker := time.NewTicker(interval) for range ticker.C { s.updateKVConfig() } }(s) + go func(s *Server) { // Server metadata cache updater + interval := time.Second * 5 + ticker := time.NewTicker(interval) + for range ticker.C { + s.cacheMetadata() + } + }(s) + SetupMiddlewares(s) SetupRoutes(s) @@ -114,31 +124,51 @@ func (s *Server) GetRequestID(c *fiber.Ctx) (string, error) { return hdr.RequestID, nil } -func (s *Server) updateKVConfig() error { // FIXME: duplicated in cmd/worker/main.go +func (s *Server) Error400(c *fiber.Ctx, msg string) error { + return c.Status(fiber.StatusBadRequest).JSON(&def.ErrorResponse{Error: msg}) +} + +func (s *Server) Error404(c *fiber.Ctx, msg string) error { + return c.Status(fiber.StatusNotFound).JSON(&def.ErrorResponse{Error: msg}) +} + +func (s *Server) updateKVConfig() { // FIXME: duplicated in cmd/worker/main.go config, _, err := s.discovery.KV().Get(s.kvNmspc, nil) - if err != nil { - fmt.Println(err) - - return err - } - - if config == nil { - return errors.New("empty KV config data") + if err != nil || config == nil { + return } kvCnf := bytes.NewBuffer(config.Value) decoder := json.NewDecoder(kvCnf) if err := decoder.Decode(&s.conf); err != nil { - return err + return + } +} + +func (s *Server) cacheMetadata() { + ctx := context.Background() + key, address := "internal__"+s.conf.AppName+"__ips", s.conf.AppID // FIXME: key name + + pos := s.cache.LPos(ctx, key, address, redis.LPosArgs{}).Val() + if pos >= 0 { + s.cache.LRem(ctx, key, 0, address) } - return nil + s.cache.LPush(ctx, key, address).Err() +} + +func (s *Server) clearMetadataCache() { + ctx := context.Background() + key, address := "internal__"+s.conf.AppName+"__ips", s.conf.AppID // FIXME: key name + + s.cache.LRem(ctx, key, 0, address) } func (s *Server) gracefulShutdown() error { s.log.Log("Server is going down...") s.log.Log("Unregistering service: %s", s.discovery.GetID()) s.discovery.Unregister() + s.clearMetadataCache() s.ebCh.Close() s.db.Close() diff --git a/src/internal/app/service/basket.go b/src/internal/app/service/basket.go index 67d17b1..57c8e37 100644 --- a/src/internal/app/service/basket.go +++ b/src/internal/app/service/basket.go @@ -3,41 +3,48 @@ package service import ( "context" - def "git.pbiernat.dev/egommerce/basket-service/internal/app/definition" + "git.pbiernat.dev/egommerce/api-entities/model" "git.pbiernat.dev/egommerce/basket-service/internal/app/event" + "git.pbiernat.dev/egommerce/go-api-pkg/api" "git.pbiernat.dev/egommerce/go-api-pkg/fluentd" - amqp "git.pbiernat.dev/egommerce/go-api-pkg/rabbitmq" + "git.pbiernat.dev/egommerce/go-api-pkg/rabbitmq" "github.com/georgysavva/scany/v2/pgxscan" + "github.com/go-redis/redis/v8" "github.com/jackc/pgx/v5/pgxpool" - base "github.com/streadway/amqp" + "github.com/streadway/amqp" +) + +const ( + SERVICE_USER_AGENT = "basket-httpclient" ) type BasketService struct { dbConn *pgxpool.Pool - ebCh *base.Channel + redis *redis.Client + ebCh *amqp.Channel log *fluentd.Logger } -func NewBasketService(dbConn *pgxpool.Pool, chn *base.Channel, log *fluentd.Logger) *BasketService { - return &BasketService{dbConn, chn, log} +func NewBasketService(dbConn *pgxpool.Pool, redis *redis.Client, chn *amqp.Channel, log *fluentd.Logger) *BasketService { + return &BasketService{dbConn, redis, chn, log} } -func (s *BasketService) Create(basketID string) (*def.BasketModel, error) { - ctx := context.Background() +func (s *BasketService) Log(format string, val ...any) { + s.log.Log(format, val...) +} +func (s *BasketService) CreateBasket(ctx context.Context, basketID string) (*model.BasketModel, error) { sql := `INSERT INTO basket.basket(id) VALUES($1)` if _, err := s.dbConn.Exec(ctx, sql, basketID); err != nil { return nil, err } - return &def.BasketModel{ID: basketID}, nil // FIXME - WTF is that xD?? + return &model.BasketModel{ID: basketID}, nil // FIXME } -func (s *BasketService) FetchFromDB(basketID string) (*def.BasketModel, error) { - ctx := context.Background() - - basket := new(def.BasketModel) - err := pgxscan.Get(ctx, s.dbConn, basket, `SELECT id, created_at, updated_at FROM basket.basket WHERE id=$1`, basketID) +func (s *BasketService) FetchFromDB(ctx context.Context, basketID string) (*model.BasketModel, error) { + basket := new(model.BasketModel) + err := pgxscan.Get(ctx, s.dbConn, basket, `SELECT id,state,created_at,updated_at FROM basket.basket WHERE id=$1 LIMIT 1`, basketID) if err != nil { return nil, err } @@ -45,12 +52,71 @@ func (s *BasketService) FetchFromDB(basketID string) (*def.BasketModel, error) { return basket, nil } -func (s *BasketService) AddProduct(productId, basketID string, qty int) error { - ctx := context.Background() - s.log.Log("Adding product#:%s into Basket#:%s", productId, basketID) +func (s *BasketService) FetchItems(ctx context.Context, basketID string) ([]*model.BasketItemModel, error) { + items := []*model.BasketItemModel{} + sql := `SELECT id,basket_id,product_id,quantity,price,created_at FROM basket.basket_item WHERE basket_id=$1` + rows, err := s.dbConn.Query(ctx, sql, basketID) + if err != nil { + // s.Log("Fetch basket items error: %v", err) + return nil, err + } - sql := `INSERT INTO basket.basket_item(basket_id, product_id) VALUES($1,$2)` - if _, err := s.dbConn.Exec(ctx, sql, basketID, productId); err != nil { + if err := pgxscan.ScanAll(&items, rows); err != nil { + // s.Log("Fetch basket items error: %v", err) + return nil, err + } + + return items, nil +} + +func (s *BasketService) FetchItem(ctx context.Context, basketID string, productID int) (*model.BasketItemModel, error) { + item := new(model.BasketItemModel) + sql := `SELECT id,basket_id,product_id,quantity,price,created_at FROM basket.basket_item WHERE basket_id=$1 AND product_id=$2 LIMIT 1` + err := pgxscan.Get(ctx, s.dbConn, item, sql, basketID, productID) + if err != nil { + return nil, err + } + + return item, nil +} + +func (s *BasketService) AddItem(ctx context.Context, itemID int, basketID string, qty int) error { + var price float64 = 0 + pricingAPI := api.NewPricingAPI(SERVICE_USER_AGENT, s.redis) + + productPrice, err := pricingAPI.GetProductPrice(itemID) + if err == nil { + price = productPrice.Price + } + + sql := `INSERT INTO basket.basket_item(basket_id,product_id,price,quantity) VALUES($1,$2,$3,$4)` + if _, err := s.dbConn.Exec(ctx, sql, basketID, itemID, price, qty); err != nil { + return err + } + + return nil +} + +func (s *BasketService) RemoveItem(ctx context.Context, itemID int, basketID string) error { + sql := `DELETE FROM basket.basket_item WHERE basket_id=$1 AND product_id=$2` + if _, err := s.dbConn.Exec(ctx, sql, basketID, itemID); err != nil { + return err + } + + return nil +} + +func (s *BasketService) UpdateItem(ctx context.Context, item *model.BasketItemModel, qty int) error { + var price float64 = 0 + pricingAPI := api.NewPricingAPI(SERVICE_USER_AGENT, s.redis) + + productPrice, err := pricingAPI.GetProductPrice(item.ProductID) + if err == nil { + price = productPrice.Price + } + + sql := `UPDATE basket.basket_item SET quantity=$1, price=$2 WHERE basket_id=$3 AND product_id=$4` + if _, err := s.dbConn.Exec(ctx, sql, qty, price, item.BasketID, item.ProductID); err != nil { return err } @@ -60,8 +126,8 @@ func (s *BasketService) AddProduct(productId, basketID string, qty int) error { func (s *BasketService) Checkout(reqID, basketID string) (string, error) { s.log.Log("Creating initial order from basket#:%s", basketID) - msg := &event.BasketCheckoutEvent{Event: event.NewEvent(reqID), BasketID: basketID} - amqp.Publish(s.ebCh, "api-events", "basket.order.basketCheckout", msg) + msg := &event.BasketCheckoutEvent{Event: event.NewEvent(reqID), BasketID: basketID} // FIXME: send more info... + rabbitmq.Publish(s.ebCh, "api-events", "basket.order.basketCheckout", msg) return basketID, nil } diff --git a/src/internal/app/ui/basket.go b/src/internal/app/ui/basket.go new file mode 100644 index 0000000..bf76ae3 --- /dev/null +++ b/src/internal/app/ui/basket.go @@ -0,0 +1,88 @@ +package ui + +import ( + "context" + + def "git.pbiernat.dev/egommerce/api-entities/http" + "git.pbiernat.dev/egommerce/api-entities/model" + "git.pbiernat.dev/egommerce/basket-service/internal/app/service" +) + +func AddProductToBasket(srv *service.BasketService, productID, qty int, basketID, reqID string) (*model.BasketModel, error) { + ctx := context.Background() + basket, err := srv.FetchFromDB(ctx, basketID) + if err != nil { + srv.Log("Basket#:%s not found. Creating new one...", basketID) + basket, err = srv.CreateBasket(ctx, basketID) + if err != nil { + ctx.Done() // FIXME + srv.Log("Creating basket error: %v", err) + return nil, err + } + } + + item, err := srv.FetchItem(ctx, basket.ID, productID) + if err != nil { + err := srv.AddItem(ctx, productID, basket.ID, qty) + ctx.Done() // FIXME + if err != nil { + srv.Log("Error adding product to basket: %v", err) + return nil, err + } + return basket, nil + } + + qty = item.Quantity + qty + err = srv.UpdateItem(ctx, item, qty) + if err != nil { + srv.Log("UpdateItem error: %v", err) + } + ctx.Done() // FIXME + + return basket, nil +} + +func RemoveProductFromBasket(srv *service.BasketService, productID, qty int, basketID, reqID string) (*model.BasketModel, error) { + ctx := context.Background() + basket, err := srv.FetchFromDB(ctx, basketID) + if err != nil { + ctx.Done() // FIXME + return nil, err + } + + item, err := srv.FetchItem(ctx, basket.ID, productID) + if err != nil { + ctx.Done() // FIXME + return nil, err + } + + if item.Quantity <= qty { + err = srv.RemoveItem(ctx, item.ProductID, item.BasketID) + if err != nil { + ctx.Done() // FIXME + return nil, err + } + } else { + qty = item.Quantity - qty + err = srv.UpdateItem(ctx, item, qty) + if err != nil { + srv.Log("UpdateItem error: %v", err) + } + } + ctx.Done() // FIXME + + return basket, nil +} + +func CheckoutBasket(srv *service.BasketService, basketID, reqID string) (*def.BasketCheckoutResponse, error) { + // ctx := context.Background() + res := &def.BasketCheckoutResponse{} + basketID, err := srv.Checkout(reqID, basketID) + if err != nil { + return res, err + } + res.ID = basketID + // ctx.Done() // FIXME + + return res, nil +}