From 4e3a4ca2236d8887c2cf9e133ddeddecdae0d54d Mon Sep 17 00:00:00 2001 From: Paul Yu Date: Wed, 6 Dec 2023 09:07:59 -0800 Subject: [PATCH 1/5] refactor: decoupling data access from web handlers --- src/makeline-service/main.go | 240 ++----------------------- src/makeline-service/orderservice.go | 258 +++++++++++++++++++++++++++ 2 files changed, 273 insertions(+), 225 deletions(-) create mode 100644 src/makeline-service/orderservice.go diff --git a/src/makeline-service/main.go b/src/makeline-service/main.go index 94ab30e9..947eb9f9 100644 --- a/src/makeline-service/main.go +++ b/src/makeline-service/main.go @@ -1,240 +1,56 @@ package main import ( - "context" - "encoding/json" "log" - "math/rand" "net/http" "os" - "strconv" - "time" - amqp "github.com/Azure/go-amqp" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" - "go.mongodb.org/mongo-driver/bson" ) -// Fetch orders from the order queue and store them in database +// Fetches orders from the order queue and stores them in database func fetchOrders(c *gin.Context) { - var orders []order - - // Get order queue connection string from environment variable - orderQueueUri := os.Getenv("ORDER_QUEUE_URI") - if orderQueueUri == "" { - log.Printf("ORDER_QUEUE_URI is not set") - c.AbortWithStatus(http.StatusInternalServerError) - return - } - - // Get queue name from environment variable - orderQueueName := os.Getenv("ORDER_QUEUE_NAME") - if orderQueueName == "" { - log.Printf("ORDER_QUEUE_NAME is not set") - c.AbortWithStatus(http.StatusInternalServerError) - return - } - - // Get queue username from environment variable - orderQueueUsername := os.Getenv("ORDER_QUEUE_USERNAME") - if orderQueueName == "" { - log.Printf("ORDER_QUEUE_USERNAME is not set") - c.AbortWithStatus(http.StatusInternalServerError) - return - } - - // Get queue password from environment variable - orderQueuePassword := os.Getenv("ORDER_QUEUE_PASSWORD") - if orderQueuePassword == "" { - log.Printf("ORDER_QUEUE_PASSWORD is not set") - c.AbortWithStatus(http.StatusInternalServerError) - return - } - - ctx := context.Background() - - // Connect to order queue - conn, err := amqp.Dial(ctx, orderQueueUri, &amqp.ConnOptions{ - SASLType: amqp.SASLTypePlain(orderQueueUsername, orderQueuePassword), - }) + // Get orders from the queue + orders, err := getOrdersFromQueue() if err != nil { - log.Printf("%s: %s", "Failed to connect to order queue", err) + log.Printf("Failed to fetch orders from queue: %s", err) c.AbortWithStatus(http.StatusInternalServerError) return } - defer conn.Close() - - session, err := conn.NewSession(ctx, nil) - if err != nil { - log.Printf("Unable to create a new session") - } - - { - // create a receiver - receiver, err := session.NewReceiver(ctx, orderQueueName, nil) - if err != nil { - log.Printf("Creating receiver link: %s", err) - c.AbortWithStatus(http.StatusInternalServerError) - return - } - defer func() { - ctx, cancel := context.WithTimeout(ctx, 1*time.Second) - receiver.Close(ctx) - cancel() - }() - - for { - log.Printf("getting orders") - - ctx, cancel := context.WithTimeout(ctx, 1*time.Second) - defer cancel() - - // receive next message - msg, err := receiver.Receive(ctx, nil) - if err != nil { - if err.Error() == "context deadline exceeded" { - log.Printf("No more orders for you: %v", err.Error()) - break - } else { - c.AbortWithStatus(http.StatusInternalServerError) - return - } - } - - messageBody := string(msg.GetData()) - log.Printf("Message received: %s\n", messageBody) - - // Create a random string to use as the order key - orderKey := strconv.Itoa(rand.Intn(100000)) - - // Deserialize msg data to order and add to []order slice - var order order - err = json.Unmarshal(msg.GetData(), &order) - - if err != nil { - log.Printf("Failed to deserialize message: %s", err) - c.AbortWithStatus(http.StatusInternalServerError) - return - } - - // add orderkey to order - order.OrderID = orderKey - - // set the status to pending - order.Status = Pending - - // Add order to []order slice - orders = append(orders, order) - - // accept message - if err = receiver.AcceptMessage(context.TODO(), msg); err != nil { - log.Printf("Failure accepting message: %s", err) - // remove the order from the slice so that we pick it up on the next run - orders = orders[:len(orders)-1] - } - } - } // Save orders to database - ctx = context.TODO() - - // Connect to MongoDB - collection, err := connectToMongoDB() + insertOrdersToDB(orders) if err != nil { - log.Printf("Failed to connect to MongoDB: %s", err) + log.Printf("Failed to save orders to database: %s", err) c.AbortWithStatus(http.StatusInternalServerError) return - } else { - log.Printf("Connected to MongoDB") - } - - defer collection.Database().Client().Disconnect(context.Background()) - - var ordersInterface []interface{} - for _, o := range orders { - ordersInterface = append(ordersInterface, interface{}(o)) - } - - if len(ordersInterface) == 0 { - log.Printf("No orders to insert into database") - } else { - // Insert orders - insertResult, err := collection.InsertMany(ctx, ordersInterface) - if err != nil { - log.Printf("Failed to insert order: %s", err) - c.AbortWithStatus(http.StatusInternalServerError) - return - } - - log.Printf("Inserted %v documents into database\n", len(insertResult.InsertedIDs)) } - // Return all pending orders - orders = nil - cursor, err := collection.Find(ctx, bson.M{"status": Pending}) + // Return the orders to be processed + orders, err = getPendingOrdersFromDB() if err != nil { - log.Printf("Failed to find records: %s", err) - c.AbortWithStatus(http.StatusInternalServerError) - return - } - defer cursor.Close(ctx) - - // Check if there was an error during iteration - if err := cursor.Err(); err != nil { - log.Printf("Failed to find records: %s", err) + log.Printf("Failed to get pending orders from database: %s", err) c.AbortWithStatus(http.StatusInternalServerError) return } - // Iterate over the cursor and decode each document - for cursor.Next(ctx) { - var pendingOrder order - if err := cursor.Decode(&pendingOrder); err != nil { - log.Printf("Failed to decode order: %s", err) - c.AbortWithStatus(http.StatusInternalServerError) - return - } - orders = append(orders, pendingOrder) - } - - // Return the pending orders c.IndentedJSON(http.StatusOK, orders) } -// Get order from database +// Gets a single order from database by order ID func getOrder(c *gin.Context) { - // TODO: Validate order ID - orderId := c.Param("id") - - // Read order from database - var ctx = context.TODO() - - // Connect to MongoDB - collection, err := connectToMongoDB() + order, err := getOrderFromDB(c.Param("id")) if err != nil { - log.Printf("Failed to connect to MongoDB: %s", err) - c.AbortWithStatus(http.StatusInternalServerError) - return - } else { - log.Printf("Connected to MongoDB") - } - - defer collection.Database().Client().Disconnect(context.Background()) - - // Find the order by orderId - singleResult := collection.FindOne(ctx, bson.M{"orderid": orderId}) - var order order - if singleResult.Decode(&order) != nil { - log.Printf("Failed to decode order: %s", err) + log.Printf("Failed to get order from database: %s", err) c.AbortWithStatus(http.StatusInternalServerError) return } - // return the order to be processed c.IndentedJSON(http.StatusOK, order) } +// Updates the status of an order func updateOrder(c *gin.Context) { // unmarsal the order from the request body var order order @@ -244,39 +60,13 @@ func updateOrder(c *gin.Context) { return } - // Read order from database - var ctx = context.TODO() - - // Connect to MongoDB - collection, err := connectToMongoDB() - if err != nil { - log.Printf("Failed to connect to MongoDB: %s", err) - c.AbortWithStatus(http.StatusInternalServerError) - return - } else { - log.Printf("Connected to MongoDB") - } - - defer collection.Database().Client().Disconnect(context.Background()) - - log.Printf("Updating order: %v", order) - - // Update the order - updateResult, err := collection.UpdateMany( - ctx, - bson.M{"orderid": order.OrderID}, - bson.D{ - {Key: "$set", Value: bson.D{{Key: "status", Value: order.Status}}}, - }, - ) + err := updateOrderStatus(order) if err != nil { - log.Printf("Failed to update order: %s", err) + log.Printf("Failed to update order status: %s", err) c.AbortWithStatus(http.StatusInternalServerError) return } - log.Printf("Matched %v documents and updated %v documents.\n", updateResult.MatchedCount, updateResult.ModifiedCount) - c.SetAccepted("202") } diff --git a/src/makeline-service/orderservice.go b/src/makeline-service/orderservice.go new file mode 100644 index 00000000..5fcd929c --- /dev/null +++ b/src/makeline-service/orderservice.go @@ -0,0 +1,258 @@ +package main + +import ( + "context" + "encoding/json" + "errors" + "log" + "math/rand" + "os" + "strconv" + "time" + + amqp "github.com/Azure/go-amqp" + "go.mongodb.org/mongo-driver/bson" +) + +func getOrdersFromQueue() ([]order, error) { + var orders []order + + // Get order queue connection string from environment variable + orderQueueUri := os.Getenv("ORDER_QUEUE_URI") + if orderQueueUri == "" { + log.Printf("ORDER_QUEUE_URI is not set") + return nil, errors.New("ORDER_QUEUE_URI is not set") + } + + // Get queue name from environment variable + orderQueueName := os.Getenv("ORDER_QUEUE_NAME") + if orderQueueName == "" { + log.Printf("ORDER_QUEUE_NAME is not set") + return nil, errors.New("ORDER_QUEUE_URI is not set") + } + + // Get queue username from environment variable + orderQueueUsername := os.Getenv("ORDER_QUEUE_USERNAME") + if orderQueueName == "" { + log.Printf("ORDER_QUEUE_USERNAME is not set") + return nil, errors.New("ORDER_QUEUE_URI is not set") + } + + // Get queue password from environment variable + orderQueuePassword := os.Getenv("ORDER_QUEUE_PASSWORD") + if orderQueuePassword == "" { + log.Printf("ORDER_QUEUE_PASSWORD is not set") + return nil, errors.New("ORDER_QUEUE_URI is not set") + } + + ctx := context.Background() + + // Connect to order queue + conn, err := amqp.Dial(ctx, orderQueueUri, &amqp.ConnOptions{ + SASLType: amqp.SASLTypePlain(orderQueueUsername, orderQueuePassword), + }) + if err != nil { + log.Printf("%s: %s", "Failed to connect to order queue", err) + return nil, errors.New("ORDER_QUEUE_URI is not set") + } + defer conn.Close() + + session, err := conn.NewSession(ctx, nil) + if err != nil { + log.Printf("Unable to create a new session") + } + + { + // create a receiver + receiver, err := session.NewReceiver(ctx, orderQueueName, nil) + if err != nil { + log.Printf("Creating receiver link: %s", err) + return nil, errors.New("ORDER_QUEUE_URI is not set") + } + defer func() { + ctx, cancel := context.WithTimeout(ctx, 1*time.Second) + receiver.Close(ctx) + cancel() + }() + + for { + log.Printf("getting orders") + + ctx, cancel := context.WithTimeout(ctx, 1*time.Second) + defer cancel() + + // receive next message + msg, err := receiver.Receive(ctx, nil) + if err != nil { + if err.Error() == "context deadline exceeded" { + log.Printf("No more orders for you: %v", err.Error()) + break + } else { + return nil, errors.New("ORDER_QUEUE_URI is not set") + } + } + + messageBody := string(msg.GetData()) + log.Printf("Message received: %s\n", messageBody) + + // Create a random string to use as the order key + orderKey := strconv.Itoa(rand.Intn(100000)) + + // Deserialize msg data to order and add to []order slice + var order order + err = json.Unmarshal(msg.GetData(), &order) + + if err != nil { + log.Printf("Failed to deserialize message: %s", err) + return nil, errors.New("ORDER_QUEUE_URI is not set") + } + + // add orderkey to order + order.OrderID = orderKey + + // set the status to pending + order.Status = Pending + + // Add order to []order slice + orders = append(orders, order) + + // accept message + if err = receiver.AcceptMessage(context.TODO(), msg); err != nil { + log.Printf("Failure accepting message: %s", err) + // remove the order from the slice so that we pick it up on the next run + orders = orders[:len(orders)-1] + } + } + } + return orders, nil +} + +func insertOrdersToDB(orders []order) error { + ctx := context.TODO() + + collection, err := connectToMongoDB() + if err != nil { + log.Printf("Failed to connect to MongoDB: %s", err) + return err + } else { + log.Printf("Connected to MongoDB") + } + + defer collection.Database().Client().Disconnect(context.Background()) + + var ordersInterface []interface{} + for _, o := range orders { + ordersInterface = append(ordersInterface, interface{}(o)) + } + + if len(ordersInterface) == 0 { + log.Printf("No orders to insert into database") + } else { + // Insert orders + insertResult, err := collection.InsertMany(ctx, ordersInterface) + if err != nil { + log.Printf("Failed to insert order: %s", err) + return err + } + + log.Printf("Inserted %v documents into database\n", len(insertResult.InsertedIDs)) + } + return nil +} + +func getPendingOrdersFromDB() ([]order, error) { + ctx := context.TODO() + + collection, err := connectToMongoDB() + if err != nil { + log.Printf("Failed to connect to MongoDB: %s", err) + return nil, err + } else { + log.Printf("Connected to MongoDB") + } + + defer collection.Database().Client().Disconnect(context.Background()) + + var orders []order + cursor, err := collection.Find(ctx, bson.M{"status": Pending}) + if err != nil { + log.Printf("Failed to find records: %s", err) + return nil, err + } + defer cursor.Close(ctx) + + // Check if there was an error during iteration + if err := cursor.Err(); err != nil { + log.Printf("Failed to find records: %s", err) + return nil, err + } + + // Iterate over the cursor and decode each document + for cursor.Next(ctx) { + var pendingOrder order + if err := cursor.Decode(&pendingOrder); err != nil { + log.Printf("Failed to decode order: %s", err) + return nil, err + } + orders = append(orders, pendingOrder) + } + + return orders, nil +} + +func getOrderFromDB(orderId string) (order, error) { + var ctx = context.TODO() + + collection, err := connectToMongoDB() + if err != nil { + log.Printf("Failed to connect to MongoDB: %s", err) + return order{}, err + } else { + log.Printf("Connected to MongoDB") + } + + defer collection.Database().Client().Disconnect(context.Background()) + + // Find the order by orderId + singleResult := collection.FindOne(ctx, bson.M{"orderid": orderId}) + var order order + if singleResult.Decode(&order) != nil { + log.Printf("Failed to decode order: %s", err) + return order, err + } + + return order, nil +} + +func updateOrderStatus(order order) error { + var ctx = context.TODO() + + // Connect to MongoDB + collection, err := connectToMongoDB() + if err != nil { + log.Printf("Failed to connect to MongoDB: %s", err) + return err + } else { + log.Printf("Connected to MongoDB") + } + + defer collection.Database().Client().Disconnect(context.Background()) + + log.Printf("Updating order: %v", order) + + // Update the order + updateResult, err := collection.UpdateMany( + ctx, + bson.M{"orderid": order.OrderID}, + bson.D{ + {Key: "$set", Value: bson.D{{Key: "status", Value: order.Status}}}, + }, + ) + if err != nil { + log.Printf("Failed to update order: %s", err) + return err + } + + log.Printf("Matched %v documents and updated %v documents.\n", updateResult.MatchedCount, updateResult.ModifiedCount) + return nil +} From 2ddf70ccd95a3c861613498753e92602d04564fe Mon Sep 17 00:00:00 2001 From: Paul Yu Date: Wed, 6 Dec 2023 14:26:38 -0800 Subject: [PATCH 2/5] refactor: interface for order crud --- src/makeline-service/main.go | 99 +++++----- src/makeline-service/mongodb.go | 108 ++++++++++- src/makeline-service/orderqueue.go | 127 +++++++++++++ src/makeline-service/orders.go | 37 ++++ src/makeline-service/orderservice.go | 258 --------------------------- 5 files changed, 323 insertions(+), 306 deletions(-) create mode 100644 src/makeline-service/orderqueue.go create mode 100644 src/makeline-service/orders.go delete mode 100644 src/makeline-service/orderservice.go diff --git a/src/makeline-service/main.go b/src/makeline-service/main.go index 947eb9f9..d7fdf0d0 100644 --- a/src/makeline-service/main.go +++ b/src/makeline-service/main.go @@ -9,8 +9,47 @@ import ( "github.com/gin-gonic/gin" ) +func main() { + var orderService *OrderService + + if os.Getenv("ORDER_DB_API") == "cosmosdbsql" { + log.Printf("Using CosmosDB SQL API") + } else { + log.Printf("Using MongoDB API") + orderService = NewOrderService(NewMongoDBOrderRepo()) + } + + router := gin.Default() + router.Use(cors.Default()) + router.Use(OrderMiddleware(orderService)) + router.GET("/order/fetch", fetchOrders) + router.GET("/order/:id", getOrder) + router.PUT("/order", updateOrder) + router.GET("/health", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "status": "ok", + "version": os.Getenv("APP_VERSION"), + }) + }) + router.Run(":3001") +} + +func OrderMiddleware(orderService *OrderService) gin.HandlerFunc { + return func(c *gin.Context) { + c.Set("orderService", orderService) + c.Next() + } +} + // Fetches orders from the order queue and stores them in database func fetchOrders(c *gin.Context) { + client, ok := c.MustGet("orderService").(*OrderService) + if !ok { + log.Printf("Failed to get order service") + c.AbortWithStatus(http.StatusInternalServerError) + return + } + // Get orders from the queue orders, err := getOrdersFromQueue() if err != nil { @@ -20,7 +59,7 @@ func fetchOrders(c *gin.Context) { } // Save orders to database - insertOrdersToDB(orders) + err = client.repo.InsertOrders(orders) if err != nil { log.Printf("Failed to save orders to database: %s", err) c.AbortWithStatus(http.StatusInternalServerError) @@ -28,7 +67,7 @@ func fetchOrders(c *gin.Context) { } // Return the orders to be processed - orders, err = getPendingOrdersFromDB() + orders, err = client.repo.GetPendingOrders() if err != nil { log.Printf("Failed to get pending orders from database: %s", err) c.AbortWithStatus(http.StatusInternalServerError) @@ -40,7 +79,14 @@ func fetchOrders(c *gin.Context) { // Gets a single order from database by order ID func getOrder(c *gin.Context) { - order, err := getOrderFromDB(c.Param("id")) + client, ok := c.MustGet("orderService").(*OrderService) + if !ok { + log.Printf("Failed to get order service") + c.AbortWithStatus(http.StatusInternalServerError) + return + } + + order, err := client.repo.GetOrder(c.Param("id")) if err != nil { log.Printf("Failed to get order from database: %s", err) c.AbortWithStatus(http.StatusInternalServerError) @@ -52,15 +98,22 @@ func getOrder(c *gin.Context) { // Updates the status of an order func updateOrder(c *gin.Context) { + client, ok := c.MustGet("orderService").(*OrderService) + if !ok { + log.Printf("Failed to get order service") + c.AbortWithStatus(http.StatusInternalServerError) + return + } + // unmarsal the order from the request body - var order order + var order Order if err := c.BindJSON(&order); err != nil { log.Printf("Failed to unmarshal order: %s", err) c.AbortWithStatus(http.StatusInternalServerError) return } - err := updateOrderStatus(order) + err := client.repo.UpdateOrder(order) if err != nil { log.Printf("Failed to update order status: %s", err) c.AbortWithStatus(http.StatusInternalServerError) @@ -69,39 +122,3 @@ func updateOrder(c *gin.Context) { c.SetAccepted("202") } - -func main() { - router := gin.Default() - router.Use(cors.Default()) - router.GET("/order/fetch", fetchOrders) - router.GET("/order/:id", getOrder) - router.PUT("/order", updateOrder) - router.GET("/health", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "status": "ok", - "version": os.Getenv("APP_VERSION"), - }) - }) - router.Run(":3001") -} - -type order struct { - OrderID string `json:"orderId"` - CustomerID string `json:"customerId"` - Items []item `json:"items"` - Status status `json:"status"` -} - -type status int - -const ( - Pending status = iota - Processing - Complete -) - -type item struct { - Product int `json:"productId"` - Quantity int `json:"quantity"` - Price float64 `json:"price"` -} diff --git a/src/makeline-service/mongodb.go b/src/makeline-service/mongodb.go index 843d1689..96431198 100644 --- a/src/makeline-service/mongodb.go +++ b/src/makeline-service/mongodb.go @@ -4,33 +4,37 @@ import ( "context" "crypto/tls" "log" - "net/http" "os" + "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) -func connectToMongoDB() (*mongo.Collection, error) { +type MongoDBOrderRepo struct { + db *mongo.Collection +} + +func NewMongoDBOrderRepo() *MongoDBOrderRepo { // Get database uri from environment variable mongoUri := os.Getenv("ORDER_DB_URI") if mongoUri == "" { log.Printf("ORDER_DB_URI is not set") - return nil, http.ErrAbortHandler + return nil } // get database name from environment variable mongoDb := os.Getenv("ORDER_DB_NAME") if mongoDb == "" { log.Printf("ORDER_DB_NAME is not set") - return nil, http.ErrAbortHandler + return nil } // get database collection name from environment variable mongoCollection := os.Getenv("ORDER_DB_COLLECTION_NAME") if mongoCollection == "" { log.Printf("ORDER_DB_COLLECTION_NAME is not set") - return nil, http.ErrAbortHandler + return nil } // get database username from environment variable @@ -59,7 +63,7 @@ func connectToMongoDB() (*mongo.Collection, error) { mongoClient, err := mongo.Connect(ctx, clientOptions) if err != nil { log.Printf("failed to connect to mongodb: %s", err) - return nil, err + return nil } err = mongoClient.Ping(ctx, nil) @@ -71,6 +75,96 @@ func connectToMongoDB() (*mongo.Collection, error) { // get a handle for the collection collection := mongoClient.Database(mongoDb).Collection(mongoCollection) + //defer collection.Database().Client().Disconnect(context.Background()) + + return &MongoDBOrderRepo{collection} +} + +func (r *MongoDBOrderRepo) GetPendingOrders() ([]Order, error) { + ctx := context.TODO() + + var orders []Order + cursor, err := r.db.Find(ctx, bson.M{"status": Pending}) + if err != nil { + log.Printf("Failed to find records: %s", err) + return nil, err + } + defer cursor.Close(ctx) + + // Check if there was an error during iteration + if err := cursor.Err(); err != nil { + log.Printf("Failed to find records: %s", err) + return nil, err + } + + // Iterate over the cursor and decode each document + for cursor.Next(ctx) { + var pendingOrder Order + if err := cursor.Decode(&pendingOrder); err != nil { + log.Printf("Failed to decode order: %s", err) + return nil, err + } + orders = append(orders, pendingOrder) + } + + return orders, nil +} + +func (r *MongoDBOrderRepo) GetOrder(id string) (Order, error) { + var ctx = context.TODO() + + singleResult := r.db.FindOne(ctx, bson.M{"orderid": id}) + var order Order + err := singleResult.Decode(&order) + if err != nil { + log.Printf("Failed to decode order: %s", err) + return order, err + } + + return order, nil +} + +func (r *MongoDBOrderRepo) InsertOrders(orders []Order) error { + ctx := context.TODO() + + var ordersInterface []interface{} + for _, o := range orders { + ordersInterface = append(ordersInterface, interface{}(o)) + } + + if len(ordersInterface) == 0 { + log.Printf("No orders to insert into database") + } else { + // Insert orders + insertResult, err := r.db.InsertMany(ctx, ordersInterface) + if err != nil { + log.Printf("Failed to insert order: %s", err) + return err + } + + log.Printf("Inserted %v documents into database\n", len(insertResult.InsertedIDs)) + } + return nil +} + +func (r *MongoDBOrderRepo) UpdateOrder(order Order) error { + var ctx = context.TODO() + + log.Printf("Updating order: %v", order) + + // Update the order + updateResult, err := r.db.UpdateMany( + ctx, + bson.M{"orderid": order.OrderID}, + bson.D{ + {Key: "$set", Value: bson.D{{Key: "status", Value: order.Status}}}, + }, + ) + if err != nil { + log.Printf("Failed to update order: %s", err) + return err + } - return collection, nil + log.Printf("Matched %v documents and updated %v documents.\n", updateResult.MatchedCount, updateResult.ModifiedCount) + return nil } diff --git a/src/makeline-service/orderqueue.go b/src/makeline-service/orderqueue.go new file mode 100644 index 00000000..bdb43482 --- /dev/null +++ b/src/makeline-service/orderqueue.go @@ -0,0 +1,127 @@ +package main + +import ( + "context" + "encoding/json" + "errors" + "log" + "math/rand" + "os" + "strconv" + "time" + + "github.com/Azure/go-amqp" +) + +func getOrdersFromQueue() ([]Order, error) { + var orders []Order + + // Get order queue connection string from environment variable + orderQueueUri := os.Getenv("ORDER_QUEUE_URI") + if orderQueueUri == "" { + log.Printf("ORDER_QUEUE_URI is not set") + return nil, errors.New("ORDER_QUEUE_URI is not set") + } + + // Get queue name from environment variable + orderQueueName := os.Getenv("ORDER_QUEUE_NAME") + if orderQueueName == "" { + log.Printf("ORDER_QUEUE_NAME is not set") + return nil, errors.New("ORDER_QUEUE_URI is not set") + } + + // Get queue username from environment variable + orderQueueUsername := os.Getenv("ORDER_QUEUE_USERNAME") + if orderQueueName == "" { + log.Printf("ORDER_QUEUE_USERNAME is not set") + return nil, errors.New("ORDER_QUEUE_URI is not set") + } + + // Get queue password from environment variable + orderQueuePassword := os.Getenv("ORDER_QUEUE_PASSWORD") + if orderQueuePassword == "" { + log.Printf("ORDER_QUEUE_PASSWORD is not set") + return nil, errors.New("ORDER_QUEUE_URI is not set") + } + + ctx := context.Background() + + // Connect to order queue + conn, err := amqp.Dial(ctx, orderQueueUri, &amqp.ConnOptions{ + SASLType: amqp.SASLTypePlain(orderQueueUsername, orderQueuePassword), + }) + if err != nil { + log.Printf("%s: %s", "Failed to connect to order queue", err) + return nil, errors.New("ORDER_QUEUE_URI is not set") + } + defer conn.Close() + + session, err := conn.NewSession(ctx, nil) + if err != nil { + log.Printf("Unable to create a new session") + } + + { + // create a receiver + receiver, err := session.NewReceiver(ctx, orderQueueName, nil) + if err != nil { + log.Printf("Creating receiver link: %s", err) + return nil, errors.New("ORDER_QUEUE_URI is not set") + } + defer func() { + ctx, cancel := context.WithTimeout(ctx, 1*time.Second) + receiver.Close(ctx) + cancel() + }() + + for { + log.Printf("getting orders") + + ctx, cancel := context.WithTimeout(ctx, 1*time.Second) + defer cancel() + + // receive next message + msg, err := receiver.Receive(ctx, nil) + if err != nil { + if err.Error() == "context deadline exceeded" { + log.Printf("No more orders for you: %v", err.Error()) + break + } else { + return nil, errors.New("ORDER_QUEUE_URI is not set") + } + } + + messageBody := string(msg.GetData()) + log.Printf("Message received: %s\n", messageBody) + + // Create a random string to use as the order key + orderKey := strconv.Itoa(rand.Intn(100000)) + + // Deserialize msg data to order and add to []order slice + var order Order + err = json.Unmarshal(msg.GetData(), &order) + + if err != nil { + log.Printf("Failed to deserialize message: %s", err) + return nil, errors.New("ORDER_QUEUE_URI is not set") + } + + // add orderkey to order + order.OrderID = orderKey + + // set the status to pending + order.Status = Pending + + // Add order to []order slice + orders = append(orders, order) + + // accept message + if err = receiver.AcceptMessage(context.TODO(), msg); err != nil { + log.Printf("Failure accepting message: %s", err) + // remove the order from the slice so that we pick it up on the next run + orders = orders[:len(orders)-1] + } + } + } + return orders, nil +} diff --git a/src/makeline-service/orders.go b/src/makeline-service/orders.go new file mode 100644 index 00000000..14859065 --- /dev/null +++ b/src/makeline-service/orders.go @@ -0,0 +1,37 @@ +package main + +type Order struct { + OrderID string `json:"orderId"` + CustomerID string `json:"customerId"` + Items []Item `json:"items"` + Status Status `json:"status"` +} + +type Status int + +const ( + Pending Status = iota + Processing + Complete +) + +type Item struct { + Product int `json:"productId"` + Quantity int `json:"quantity"` + Price float64 `json:"price"` +} + +type OrderRepo interface { + GetPendingOrders() ([]Order, error) + GetOrder(id string) (Order, error) + InsertOrders(orders []Order) error + UpdateOrder(order Order) error +} + +type OrderService struct { + repo OrderRepo +} + +func NewOrderService(repo OrderRepo) *OrderService { + return &OrderService{repo} +} diff --git a/src/makeline-service/orderservice.go b/src/makeline-service/orderservice.go deleted file mode 100644 index 5fcd929c..00000000 --- a/src/makeline-service/orderservice.go +++ /dev/null @@ -1,258 +0,0 @@ -package main - -import ( - "context" - "encoding/json" - "errors" - "log" - "math/rand" - "os" - "strconv" - "time" - - amqp "github.com/Azure/go-amqp" - "go.mongodb.org/mongo-driver/bson" -) - -func getOrdersFromQueue() ([]order, error) { - var orders []order - - // Get order queue connection string from environment variable - orderQueueUri := os.Getenv("ORDER_QUEUE_URI") - if orderQueueUri == "" { - log.Printf("ORDER_QUEUE_URI is not set") - return nil, errors.New("ORDER_QUEUE_URI is not set") - } - - // Get queue name from environment variable - orderQueueName := os.Getenv("ORDER_QUEUE_NAME") - if orderQueueName == "" { - log.Printf("ORDER_QUEUE_NAME is not set") - return nil, errors.New("ORDER_QUEUE_URI is not set") - } - - // Get queue username from environment variable - orderQueueUsername := os.Getenv("ORDER_QUEUE_USERNAME") - if orderQueueName == "" { - log.Printf("ORDER_QUEUE_USERNAME is not set") - return nil, errors.New("ORDER_QUEUE_URI is not set") - } - - // Get queue password from environment variable - orderQueuePassword := os.Getenv("ORDER_QUEUE_PASSWORD") - if orderQueuePassword == "" { - log.Printf("ORDER_QUEUE_PASSWORD is not set") - return nil, errors.New("ORDER_QUEUE_URI is not set") - } - - ctx := context.Background() - - // Connect to order queue - conn, err := amqp.Dial(ctx, orderQueueUri, &amqp.ConnOptions{ - SASLType: amqp.SASLTypePlain(orderQueueUsername, orderQueuePassword), - }) - if err != nil { - log.Printf("%s: %s", "Failed to connect to order queue", err) - return nil, errors.New("ORDER_QUEUE_URI is not set") - } - defer conn.Close() - - session, err := conn.NewSession(ctx, nil) - if err != nil { - log.Printf("Unable to create a new session") - } - - { - // create a receiver - receiver, err := session.NewReceiver(ctx, orderQueueName, nil) - if err != nil { - log.Printf("Creating receiver link: %s", err) - return nil, errors.New("ORDER_QUEUE_URI is not set") - } - defer func() { - ctx, cancel := context.WithTimeout(ctx, 1*time.Second) - receiver.Close(ctx) - cancel() - }() - - for { - log.Printf("getting orders") - - ctx, cancel := context.WithTimeout(ctx, 1*time.Second) - defer cancel() - - // receive next message - msg, err := receiver.Receive(ctx, nil) - if err != nil { - if err.Error() == "context deadline exceeded" { - log.Printf("No more orders for you: %v", err.Error()) - break - } else { - return nil, errors.New("ORDER_QUEUE_URI is not set") - } - } - - messageBody := string(msg.GetData()) - log.Printf("Message received: %s\n", messageBody) - - // Create a random string to use as the order key - orderKey := strconv.Itoa(rand.Intn(100000)) - - // Deserialize msg data to order and add to []order slice - var order order - err = json.Unmarshal(msg.GetData(), &order) - - if err != nil { - log.Printf("Failed to deserialize message: %s", err) - return nil, errors.New("ORDER_QUEUE_URI is not set") - } - - // add orderkey to order - order.OrderID = orderKey - - // set the status to pending - order.Status = Pending - - // Add order to []order slice - orders = append(orders, order) - - // accept message - if err = receiver.AcceptMessage(context.TODO(), msg); err != nil { - log.Printf("Failure accepting message: %s", err) - // remove the order from the slice so that we pick it up on the next run - orders = orders[:len(orders)-1] - } - } - } - return orders, nil -} - -func insertOrdersToDB(orders []order) error { - ctx := context.TODO() - - collection, err := connectToMongoDB() - if err != nil { - log.Printf("Failed to connect to MongoDB: %s", err) - return err - } else { - log.Printf("Connected to MongoDB") - } - - defer collection.Database().Client().Disconnect(context.Background()) - - var ordersInterface []interface{} - for _, o := range orders { - ordersInterface = append(ordersInterface, interface{}(o)) - } - - if len(ordersInterface) == 0 { - log.Printf("No orders to insert into database") - } else { - // Insert orders - insertResult, err := collection.InsertMany(ctx, ordersInterface) - if err != nil { - log.Printf("Failed to insert order: %s", err) - return err - } - - log.Printf("Inserted %v documents into database\n", len(insertResult.InsertedIDs)) - } - return nil -} - -func getPendingOrdersFromDB() ([]order, error) { - ctx := context.TODO() - - collection, err := connectToMongoDB() - if err != nil { - log.Printf("Failed to connect to MongoDB: %s", err) - return nil, err - } else { - log.Printf("Connected to MongoDB") - } - - defer collection.Database().Client().Disconnect(context.Background()) - - var orders []order - cursor, err := collection.Find(ctx, bson.M{"status": Pending}) - if err != nil { - log.Printf("Failed to find records: %s", err) - return nil, err - } - defer cursor.Close(ctx) - - // Check if there was an error during iteration - if err := cursor.Err(); err != nil { - log.Printf("Failed to find records: %s", err) - return nil, err - } - - // Iterate over the cursor and decode each document - for cursor.Next(ctx) { - var pendingOrder order - if err := cursor.Decode(&pendingOrder); err != nil { - log.Printf("Failed to decode order: %s", err) - return nil, err - } - orders = append(orders, pendingOrder) - } - - return orders, nil -} - -func getOrderFromDB(orderId string) (order, error) { - var ctx = context.TODO() - - collection, err := connectToMongoDB() - if err != nil { - log.Printf("Failed to connect to MongoDB: %s", err) - return order{}, err - } else { - log.Printf("Connected to MongoDB") - } - - defer collection.Database().Client().Disconnect(context.Background()) - - // Find the order by orderId - singleResult := collection.FindOne(ctx, bson.M{"orderid": orderId}) - var order order - if singleResult.Decode(&order) != nil { - log.Printf("Failed to decode order: %s", err) - return order, err - } - - return order, nil -} - -func updateOrderStatus(order order) error { - var ctx = context.TODO() - - // Connect to MongoDB - collection, err := connectToMongoDB() - if err != nil { - log.Printf("Failed to connect to MongoDB: %s", err) - return err - } else { - log.Printf("Connected to MongoDB") - } - - defer collection.Database().Client().Disconnect(context.Background()) - - log.Printf("Updating order: %v", order) - - // Update the order - updateResult, err := collection.UpdateMany( - ctx, - bson.M{"orderid": order.OrderID}, - bson.D{ - {Key: "$set", Value: bson.D{{Key: "status", Value: order.Status}}}, - }, - ) - if err != nil { - log.Printf("Failed to update order: %s", err) - return err - } - - log.Printf("Matched %v documents and updated %v documents.\n", updateResult.MatchedCount, updateResult.ModifiedCount) - return nil -} From 3e6b568cb7cd69860040b7eac21980401c78a6a0 Mon Sep 17 00:00:00 2001 From: Paul Yu Date: Wed, 6 Dec 2023 16:03:05 -0800 Subject: [PATCH 3/5] feat: adding support for azure cosmosdb sql api resolves #84 --- src/makeline-service/README.md | 60 ++++++-- src/makeline-service/cosmosdb.go | 196 ++++++++++++++++++++++++ src/makeline-service/docker-compose.yml | 2 +- src/makeline-service/go.mod | 14 +- src/makeline-service/go.sum | 42 +++++ src/makeline-service/main.go | 61 +++++++- src/makeline-service/mongodb.go | 35 +---- 7 files changed, 354 insertions(+), 56 deletions(-) create mode 100644 src/makeline-service/cosmosdb.go diff --git a/src/makeline-service/README.md b/src/makeline-service/README.md index b4b9bb8b..628c3c07 100644 --- a/src/makeline-service/README.md +++ b/src/makeline-service/README.md @@ -35,22 +35,25 @@ export ORDER_QUEUE_NAME=orders To run this against Azure Service Bus, you will need to create a Service Bus namespace and a queue. You can do this using the Azure CLI. ```bash -az group create --name --location -az servicebus namespace create --name --resource-group -az servicebus queue create --name orders --namespace-name --resource-group +RGNAME= +LOCNAME= + +az group create --name $RGNAME --location $LOCNAME +az servicebus namespace create --name --resource-group $RGNAME +az servicebus queue create --name orders --namespace-name --resource-group $RGNAME ``` Once you have created the Service Bus namespace and queue, you will need to create a shared access policy with the **Listen** permission for the namespace. ```bash -az servicebus namespace authorization-rule create --name listener --namespace-name --resource-group --rights Listen +az servicebus namespace authorization-rule create --name listener --namespace-name --resource-group $RGNAME --rights Listen ``` Next, get the connection information for the Azure Service Bus queue and save the values to environment variables. ```bash -HOSTNAME=$(az servicebus namespace show --name --resource-group --query serviceBusEndpoint -o tsv | sed 's/https:\/\///;s/:443\///') -PASSWORD=$(az servicebus namespace authorization-rule keys list --namespace-name --resource-group --name listener --query primaryKey -o tsv) +HOSTNAME=$(az servicebus namespace show --name --resource-group $RGNAME --query serviceBusEndpoint -o tsv | sed 's/https:\/\///;s/:443\///') +PASSWORD=$(az servicebus namespace authorization-rule keys list --namespace-name --resource-group $RGNAME --name listener --query primaryKey -o tsv) ``` Finally, set the environment variables. @@ -83,28 +86,53 @@ export ORDER_DB_COLLECTION_NAME=orders To run this against Azure CosmosDB, you will need to create the CosmosDB account, the database, and collection. You can do this using the Azure CLI. ```bash -az group create --name --location -az cosmosdb create --name --resource-group --kind MongoDB -az cosmosdb mongodb database create --account-name --name orderdb --resource-group -az cosmosdb mongodb collection create --account-name --database-name orderdb --name orders --resource-group +RGNAME= +LOCNAME= +COSMOSDBNAME= + +az group create --name $RGNAME --location $LOCNAME +az cosmosdb create --name $COSMOSDBNAME --resource-group $RGNAME --kind MongoDB # or --kind GlobalDocumentDB (for SQL API) + +# if database requires MongoDB API +# create the database and collection +az cosmosdb mongodb database create --account-name $COSMOSDBNAME --name orderdb --resource-group $RGNAME +az cosmosdb mongodb collection create --account-name $COSMOSDBNAME --database-name orderdb --name orders --resource-group $RGNAME + +# if database requires SQL API +# create the database and container +COSMOSDBPARTITIONKEY= +az cosmosdb sql database create --account-name $COSMOSDBNAME --name orderdb --resource-group $RGNAME +az cosmosdb sql container create --account-name $COSMOSDBNAME --database-name orderdb --name orders --resource-group $RGNAME --partition-key-path /$COSMOSDBPARTITIONKEY ``` Next, get the connection information for the Azure Service Bus queue and save the values to environment variables. ```bash -COSMOSDBNAME= -USERNAME= -PASSWORD=$(az cosmosdb keys list --name --resource-group --query primaryMasterKey -o tsv) +COSMOSDBUSERNAME=$COSMOSDBNAME +COSMOSDBPASSWORD=$(az cosmosdb keys list --name $COSMOSDBNAME --resource-group $RGNAME --query primaryMasterKey -o tsv) ``` Finally, set the environment variables. ```bash +# if database requires MongoDB API +# set the following environment variables +export ORDER_DB_API=mongodb export ORDER_DB_URI=mongodb://$COSMOSDBNAME.mongo.cosmos.azure.com:10255/?retryWrites=false export ORDER_DB_NAME=orderdb export ORDER_DB_COLLECTION_NAME=orders -export ORDER_DB_USERNAME=$USERNAME -export ORDER_DB_PASSWORD=$PASSWORD +export ORDER_DB_USERNAME=$COSMOSDBUSERNAME +export ORDER_DB_PASSWORD=$COSMOSDBPASSWORD + +# if database requires SQL API +# set the following environment variables +export ORDER_DB_API=cosmosdbsql +export ORDER_DB_URI=https://$COSMOSDBNAME.documents.azure.com:443/ +export ORDER_DB_NAME=orderdb +export ORDER_DB_CONTAINER_NAME=orders +export ORDER_DB_PASSWORD=$COSMOSDBPASSWORD +export ORDER_DB_PARTITION_KEY=$COSMOSDBPARTITIONKEY +export ORDER_DB_PARTITION_VALUE="pets" ``` > NOTE: With Azure CosmosDB, you must ensure the orderdb database and an unsharded orders collection exist before running the app. Otherwise you will get a "server selection error". @@ -182,4 +210,4 @@ db.orders.find() # get completed orders db.orders.findOne({status: 1}) -``` \ No newline at end of file +``` diff --git a/src/makeline-service/cosmosdb.go b/src/makeline-service/cosmosdb.go new file mode 100644 index 00000000..2989985e --- /dev/null +++ b/src/makeline-service/cosmosdb.go @@ -0,0 +1,196 @@ +package main + +import ( + "context" + "encoding/json" + "log" + "strings" + + "github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos" + "github.com/gofrs/uuid" +) + +type PartitionKey struct { + Key string + Value string +} + +type CosmosDBOrderRepo struct { + db *azcosmos.ContainerClient + partitionKey PartitionKey +} + +func NewCosmosDBOrderRepo(cosmosDbEndpoint string, dbName string, containerName string, cosmosDbKey string, partitionKey PartitionKey) (*CosmosDBOrderRepo, error) { + cred, err := azcosmos.NewKeyCredential(cosmosDbKey) + if err != nil { + log.Printf("failed to create cosmosdb key credential: %v\n", err) + return nil, err + } + + // create a cosmos client + client, err := azcosmos.NewClientWithKey(cosmosDbEndpoint, cred, nil) + if err != nil { + log.Printf("failed to create cosmosdb client: %v\n", err) + return nil, err + } + + // create a cosmos container + container, err := client.NewContainer(dbName, containerName) + if err != nil { + log.Printf("failed to create cosmosdb container: %v\n", err) + return nil, err + } + + return &CosmosDBOrderRepo{container, partitionKey}, nil +} + +func (r *CosmosDBOrderRepo) GetPendingOrders() ([]Order, error) { + var orders []Order + + pk := azcosmos.NewPartitionKeyString(r.partitionKey.Value) + opt := &azcosmos.QueryOptions{ + QueryParameters: []azcosmos.QueryParameter{ + {Name: "@status", Value: Pending}, + }, + } + queryPager := r.db.NewQueryItemsPager("SELECT * FROM o WHERE o.status = @status", pk, opt) + + for queryPager.More() { + queryResponse, err := queryPager.NextPage(context.Background()) + if err != nil { + log.Printf("failed to get next page: %v\n", err) + return nil, err + } + + for _, item := range queryResponse.Items { + var order Order + err := json.Unmarshal(item, &order) + if err != nil { + log.Printf("failed to deserialize order: %v\n", err) + return nil, err + } + orders = append(orders, order) + } + } + return orders, nil +} + +func (r *CosmosDBOrderRepo) GetOrder(id string) (Order, error) { + pk := azcosmos.NewPartitionKeyString(r.partitionKey.Value) + opt := &azcosmos.QueryOptions{ + QueryParameters: []azcosmos.QueryParameter{ + {Name: "@orderId", Value: id}, + }, + } + queryPager := r.db.NewQueryItemsPager("SELECT * FROM o WHERE o.orderId = @orderId", pk, opt) + + for queryPager.More() { + queryResponse, err := queryPager.NextPage(context.Background()) + if err != nil { + log.Printf("failed to get next page: %v\n", err) + return Order{}, err + } + + for _, item := range queryResponse.Items { + var order Order + err := json.Unmarshal(item, &order) + if err != nil { + log.Printf("failed to deserialize order: %v\n", err) + return Order{}, err + } + return order, nil + } + } + return Order{}, nil +} + +func (r *CosmosDBOrderRepo) InsertOrders(orders []Order) error { + var counter = 0 + + for _, o := range orders { + pk := azcosmos.NewPartitionKeyString(r.partitionKey.Value) + + marshalledOrder, err := json.Marshal(o) + if err != nil { + log.Printf("failed to marshal order: %v\n", err) + return err + } + + var order map[string]interface{} + err = json.Unmarshal(marshalledOrder, &order) + if err != nil { + log.Printf("failed to unmarshal order: %v\n", err) + return err + } + + // add id with value of uuid.NewV4() to marhsalled order + uuidWithHyphen, err := uuid.NewV4() + if err != nil { + log.Printf("failed to generate uuid: %v\n", err) + return err + } + uuid := strings.Replace(uuidWithHyphen.String(), "-", "", -1) + order["id"] = uuid + + order[r.partitionKey.Key] = r.partitionKey.Value + + marshalledOrder, err = json.Marshal(order) + if err != nil { + log.Printf("failed to marshal order: %v\n", err) + return err + } + + _, err = r.db.CreateItem(context.Background(), pk, marshalledOrder, nil) + if err != nil { + log.Printf("failed to create item: %v\n", err) + return err + } + + // increment counter for each order inserted + counter++ + } + + log.Printf("Inserted %v documents into database\n", counter) + + return nil +} + +func (r *CosmosDBOrderRepo) UpdateOrder(order Order) error { + var existingOrderId string + pk := azcosmos.NewPartitionKeyString(r.partitionKey.Value) + opt := &azcosmos.QueryOptions{ + QueryParameters: []azcosmos.QueryParameter{ + {Name: "@orderId", Value: order.OrderID}, + }, + } + queryPager := r.db.NewQueryItemsPager("SELECT * FROM o WHERE o.orderId = @orderId", pk, opt) + + for queryPager.More() { + queryResponse, err := queryPager.NextPage(context.Background()) + if err != nil { + break + } + + for _, item := range queryResponse.Items { + var order map[string]interface{} + err = json.Unmarshal(item, &order) + if err != nil { + log.Printf("failed to deserialize order: %v\n", err) + return err + } + existingOrderId = order["id"].(string) + break + } + } + + patch := azcosmos.PatchOperations{} + patch.AppendReplace("/status", order.Status) + + _, err := r.db.PatchItem(context.Background(), pk, existingOrderId, patch, nil) + if err != nil { + log.Printf("failed to replace item: %v\n", err) + return err + } + + return nil +} diff --git a/src/makeline-service/docker-compose.yml b/src/makeline-service/docker-compose.yml index cc9d385b..d72589a9 100644 --- a/src/makeline-service/docker-compose.yml +++ b/src/makeline-service/docker-compose.yml @@ -61,7 +61,7 @@ services: restart: always environment: - ORDER_SERVICE_URL=http://orderservice:3000/ - - ORDERS_PER_HOUR=3600 + - ORDERS_PER_HOUR=60 networks: - backend_services depends_on: diff --git a/src/makeline-service/go.mod b/src/makeline-service/go.mod index 598b1d81..4ff38fed 100644 --- a/src/makeline-service/go.mod +++ b/src/makeline-service/go.mod @@ -3,13 +3,18 @@ module aks-store-demo/makeline-service go 1.20 require ( + github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v0.3.6 github.com/Azure/go-amqp v1.0.1 github.com/gin-contrib/cors v1.4.0 github.com/gin-gonic/gin v1.9.1 + github.com/gofrs/uuid v4.4.0+incompatible go.mongodb.org/mongo-driver v1.11.7 ) require ( + github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 // indirect github.com/bytedance/sonic v1.9.1 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect @@ -18,6 +23,7 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.14.0 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/gofrs/uuid/v5 v5.0.0 // indirect github.com/golang/snappy v0.0.1 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.13.6 // indirect @@ -36,11 +42,11 @@ require ( github.com/xdg-go/stringprep v1.0.3 // indirect github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/crypto v0.14.0 // indirect - golang.org/x/net v0.17.0 // indirect + golang.org/x/crypto v0.16.0 // indirect + golang.org/x/net v0.19.0 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/src/makeline-service/go.sum b/src/makeline-service/go.sum index 10d4742c..3927435b 100644 --- a/src/makeline-service/go.sum +++ b/src/makeline-service/go.sum @@ -1,3 +1,18 @@ +github.com/Azure/azure-sdk-for-go v57.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= +github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.0/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0 h1:fb8kj/Dh4CSwgsOzHeZY4Xh68cFVbzXx+ONXGMY//4w= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0/go.mod h1:uReU2sSxZExRPBAg3qKzmAucSi51+SP1OhohieR821Q= +github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v0.2.0 h1:B5u4aPfGVgnbj413imyq4MpoenRkwi3RahF3sb8WeGs= +github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v0.2.0/go.mod h1:7tY5tA4vXkkkd77crrj/ZZo2gHm43Z6URd3umMb+iHM= +github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v0.3.6 h1:oBqQLSI1pZwGOdXJAoJJSzmff9tlfD4KroVfjQQmd0g= +github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v0.3.6/go.mod h1:Beh5cHIXJ0oWEDWk9lNFtuklCojLLQ5hl+LqSNTTs0I= +github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0 h1:d81/ng9rET2YqdVkVwkb6EXeRrLJIwyGnJcAlAWKwhs= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 h1:6oNBlSdi1QqM1PNW7FPA6xOGA5UNsXnkaYZz9vdPGhA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI= github.com/Azure/go-amqp v1.0.1 h1:Jf8OQCKzRDMZ3pCiH4onM7yrhl5curkRSGkRLTyP35o= github.com/Azure/go-amqp v1.0.1/go.mod h1:+bg0x3ce5+Q3ahCEXnCsGG3ETpDQe3MEVnOuT2ywPwc= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= @@ -10,6 +25,7 @@ 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/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= @@ -34,6 +50,10 @@ github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QX github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M= +github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -41,6 +61,8 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= @@ -67,6 +89,7 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= @@ -92,6 +115,7 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= @@ -113,16 +137,28 @@ go.mongodb.org/mongo-driver v1.11.7/go.mod h1:G9TgswdsWjX4tmDA5zfs2+6AEPpYJwqbly golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -132,12 +168,17 @@ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= @@ -149,6 +190,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/makeline-service/main.go b/src/makeline-service/main.go index d7fdf0d0..74271739 100644 --- a/src/makeline-service/main.go +++ b/src/makeline-service/main.go @@ -9,14 +9,28 @@ import ( "github.com/gin-gonic/gin" ) +// Valid database API types +const ( + AZURE_COSMOS_DB_SQL_API = "cosmosdbsql" +) + func main() { var orderService *OrderService - if os.Getenv("ORDER_DB_API") == "cosmosdbsql" { - log.Printf("Using CosmosDB SQL API") - } else { + // Get the database API type + apiType := os.Getenv("ORDER_DB_API") + switch apiType { + case "cosmosdbsql": + log.Printf("Using Azure CosmosDB SQL API") + default: log.Printf("Using MongoDB API") - orderService = NewOrderService(NewMongoDBOrderRepo()) + } + + // Initialize the database + orderService, err := initDatabase(apiType) + if err != nil { + log.Printf("Failed to initialize database: %s", err) + os.Exit(1) } router := gin.Default() @@ -34,6 +48,7 @@ func main() { router.Run(":3001") } +// OrderMiddleware is a middleware function that injects the order service into the request context func OrderMiddleware(orderService *OrderService) gin.HandlerFunc { return func(c *gin.Context) { c.Set("orderService", orderService) @@ -122,3 +137,41 @@ func updateOrder(c *gin.Context) { c.SetAccepted("202") } + +// Gets an environment variable or exits if it is not set +func getEnvVar(varName string) string { + value := os.Getenv(varName) + if value == "" { + log.Printf("%s is not set", varName) + os.Exit(1) + } + return value +} + +// Initializes the database based on the API type +func initDatabase(apiType string) (*OrderService, error) { + dbURI := getEnvVar("ORDER_DB_URI") + dbName := getEnvVar("ORDER_DB_NAME") + + switch apiType { + case AZURE_COSMOS_DB_SQL_API: + containerName := getEnvVar("ORDER_DB_CONTAINER_NAME") + dbPassword := os.Getenv("ORDER_DB_PASSWORD") + dbPartitionKey := getEnvVar("ORDER_DB_PARTITION_KEY") + dbPartitionValue := getEnvVar("ORDER_DB_PARTITION_VALUE") + cosmosRepo, err := NewCosmosDBOrderRepo(dbURI, dbName, containerName, dbPassword, PartitionKey{dbPartitionKey, dbPartitionValue}) + if err != nil { + return nil, err + } + return NewOrderService(cosmosRepo), nil + default: + collectionName := getEnvVar("ORDER_DB_COLLECTION_NAME") + dbUsername := os.Getenv("ORDER_DB_USERNAME") + dbPassword := os.Getenv("ORDER_DB_PASSWORD") + mongoRepo, err := NewMongoDBOrderRepo(dbURI, dbName, collectionName, dbUsername, dbPassword) + if err != nil { + return nil, err + } + return NewOrderService(mongoRepo), nil + } +} diff --git a/src/makeline-service/mongodb.go b/src/makeline-service/mongodb.go index 96431198..0f2f8cfc 100644 --- a/src/makeline-service/mongodb.go +++ b/src/makeline-service/mongodb.go @@ -4,7 +4,6 @@ import ( "context" "crypto/tls" "log" - "os" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" @@ -15,34 +14,7 @@ type MongoDBOrderRepo struct { db *mongo.Collection } -func NewMongoDBOrderRepo() *MongoDBOrderRepo { - // Get database uri from environment variable - mongoUri := os.Getenv("ORDER_DB_URI") - if mongoUri == "" { - log.Printf("ORDER_DB_URI is not set") - return nil - } - - // get database name from environment variable - mongoDb := os.Getenv("ORDER_DB_NAME") - if mongoDb == "" { - log.Printf("ORDER_DB_NAME is not set") - return nil - } - - // get database collection name from environment variable - mongoCollection := os.Getenv("ORDER_DB_COLLECTION_NAME") - if mongoCollection == "" { - log.Printf("ORDER_DB_COLLECTION_NAME is not set") - return nil - } - - // get database username from environment variable - mongoUser := os.Getenv("ORDER_DB_USERNAME") - - // get database password from environment variable - mongoPassword := os.Getenv("ORDER_DB_PASSWORD") - +func NewMongoDBOrderRepo(mongoUri string, mongoDb string, mongoCollection string, mongoUser string, mongoPassword string) (*MongoDBOrderRepo, error) { // create a context ctx := context.Background() @@ -63,12 +35,13 @@ func NewMongoDBOrderRepo() *MongoDBOrderRepo { mongoClient, err := mongo.Connect(ctx, clientOptions) if err != nil { log.Printf("failed to connect to mongodb: %s", err) - return nil + return nil, err } err = mongoClient.Ping(ctx, nil) if err != nil { log.Printf("failed to ping database: %s", err) + return nil, err } else { log.Printf("pong from database") } @@ -77,7 +50,7 @@ func NewMongoDBOrderRepo() *MongoDBOrderRepo { collection := mongoClient.Database(mongoDb).Collection(mongoCollection) //defer collection.Database().Client().Disconnect(context.Background()) - return &MongoDBOrderRepo{collection} + return &MongoDBOrderRepo{collection}, nil } func (r *MongoDBOrderRepo) GetPendingOrders() ([]Order, error) { From 3e548e636a7bbcc4af2abb530fed982a61ab7526 Mon Sep 17 00:00:00 2001 From: Paul Yu Date: Fri, 8 Dec 2023 09:09:46 -0800 Subject: [PATCH 4/5] docs: update readme --- src/makeline-service/README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/makeline-service/README.md b/src/makeline-service/README.md index 628c3c07..032d2f89 100644 --- a/src/makeline-service/README.md +++ b/src/makeline-service/README.md @@ -85,22 +85,25 @@ export ORDER_DB_COLLECTION_NAME=orders To run this against Azure CosmosDB, you will need to create the CosmosDB account, the database, and collection. You can do this using the Azure CLI. +> Azure CosmosDB supports multiple APIs. This app supports both the MongoDB and SQL APIs. You will need to create the database and collection based on the API you want to use. + ```bash RGNAME= LOCNAME= COSMOSDBNAME= az group create --name $RGNAME --location $LOCNAME -az cosmosdb create --name $COSMOSDBNAME --resource-group $RGNAME --kind MongoDB # or --kind GlobalDocumentDB (for SQL API) # if database requires MongoDB API # create the database and collection +az cosmosdb create --name $COSMOSDBNAME --resource-group $RGNAME --kind MongoDB az cosmosdb mongodb database create --account-name $COSMOSDBNAME --name orderdb --resource-group $RGNAME az cosmosdb mongodb collection create --account-name $COSMOSDBNAME --database-name orderdb --name orders --resource-group $RGNAME # if database requires SQL API # create the database and container -COSMOSDBPARTITIONKEY= +COSMOSDBPARTITIONKEY=storeId +az cosmosdb create --name $COSMOSDBNAME --resource-group $RGNAME --kind GlobalDocumentDB az cosmosdb sql database create --account-name $COSMOSDBNAME --name orderdb --resource-group $RGNAME az cosmosdb sql container create --account-name $COSMOSDBNAME --database-name orderdb --name orders --resource-group $RGNAME --partition-key-path /$COSMOSDBPARTITIONKEY ``` From d1bbc9b93a7dd0da5c75483f69d38353966d9f9a Mon Sep 17 00:00:00 2001 From: Paul Yu Date: Fri, 8 Dec 2023 09:10:51 -0800 Subject: [PATCH 5/5] bug: fix bson doc filter --- src/makeline-service/go.mod | 2 +- src/makeline-service/go.sum | 2 ++ src/makeline-service/main.go | 54 +++++++++++++++++++++++++++++++-- src/makeline-service/mongodb.go | 10 ++++-- 4 files changed, 62 insertions(+), 6 deletions(-) diff --git a/src/makeline-service/go.mod b/src/makeline-service/go.mod index 4ff38fed..43f78e68 100644 --- a/src/makeline-service/go.mod +++ b/src/makeline-service/go.mod @@ -21,7 +21,7 @@ require ( github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/go-playground/validator/v10 v10.16.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/gofrs/uuid/v5 v5.0.0 // indirect github.com/golang/snappy v0.0.1 // indirect diff --git a/src/makeline-service/go.sum b/src/makeline-service/go.sum index 3927435b..f9ea26b1 100644 --- a/src/makeline-service/go.sum +++ b/src/makeline-service/go.sum @@ -47,6 +47,8 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91 github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= +github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= diff --git a/src/makeline-service/main.go b/src/makeline-service/main.go index 74271739..418c7758 100644 --- a/src/makeline-service/main.go +++ b/src/makeline-service/main.go @@ -4,11 +4,15 @@ import ( "log" "net/http" "os" + "strconv" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" ) +var validate *validator.Validate + // Valid database API types const ( AZURE_COSMOS_DB_SQL_API = "cosmosdbsql" @@ -101,7 +105,23 @@ func getOrder(c *gin.Context) { return } - order, err := client.repo.GetOrder(c.Param("id")) + err := validate.Var(c.Param("id"), "required,numeric") + if err != nil { + log.Printf("Failed to validate order id: %s", err) + c.AbortWithStatus(http.StatusBadRequest) + return + } + + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + log.Printf("Failed to convert order id to int: %s", err) + c.AbortWithStatus(http.StatusBadRequest) + return + } + + orderId := strconv.FormatInt(int64(id), 10) + + order, err := client.repo.GetOrder(orderId) if err != nil { log.Printf("Failed to get order from database: %s", err) c.AbortWithStatus(http.StatusInternalServerError) @@ -128,7 +148,37 @@ func updateOrder(c *gin.Context) { return } - err := client.repo.UpdateOrder(order) + err := validate.Struct(order) + validationErrors := err.(validator.ValidationErrors) + if err != nil { + log.Printf("Failed to validate order: %s", validationErrors) + c.AbortWithStatus(http.StatusBadRequest) + return + } + err = validate.Var(order.OrderID, "required,numeric") + if err != nil { + log.Printf("Failed to validate order id: %s", err) + c.AbortWithStatus(http.StatusBadRequest) + return + } + + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + log.Printf("Failed to convert order id to int: %s", err) + c.AbortWithStatus(http.StatusBadRequest) + return + } + + sanitizedOrderId := strconv.FormatInt(int64(id), 10) + + sanitizedOrder := Order{ + OrderID: sanitizedOrderId, + CustomerID: order.CustomerID, + Items: order.Items, + Status: order.Status, + } + + err = client.repo.UpdateOrder(sanitizedOrder) if err != nil { log.Printf("Failed to update order status: %s", err) c.AbortWithStatus(http.StatusInternalServerError) diff --git a/src/makeline-service/mongodb.go b/src/makeline-service/mongodb.go index 0f2f8cfc..8bc8f439 100644 --- a/src/makeline-service/mongodb.go +++ b/src/makeline-service/mongodb.go @@ -86,7 +86,10 @@ func (r *MongoDBOrderRepo) GetPendingOrders() ([]Order, error) { func (r *MongoDBOrderRepo) GetOrder(id string) (Order, error) { var ctx = context.TODO() - singleResult := r.db.FindOne(ctx, bson.M{"orderid": id}) + filter := bson.D{{Key: "orderid", Value: bson.D{{Key: "$eq", Value: id}}}} + + singleResult := r.db.FindOne(ctx, filter) + var order Order err := singleResult.Decode(&order) if err != nil { @@ -123,12 +126,13 @@ func (r *MongoDBOrderRepo) InsertOrders(orders []Order) error { func (r *MongoDBOrderRepo) UpdateOrder(order Order) error { var ctx = context.TODO() - log.Printf("Updating order: %v", order) + filter := bson.D{{Key: "orderid", Value: bson.D{{Key: "$eq", Value: order.OrderID}}}} // Update the order + log.Printf("Updating order: %v", order) updateResult, err := r.db.UpdateMany( ctx, - bson.M{"orderid": order.OrderID}, + filter, bson.D{ {Key: "$set", Value: bson.D{{Key: "status", Value: order.Status}}}, },