Skip to content

Commit

Permalink
Merge pull request #3 from thejasmeetsingh/development
Browse files Browse the repository at this point in the history
Master PR
  • Loading branch information
thejasmeetsingh authored Jan 1, 2024
2 parents 683cbe5 + 8ea2125 commit 1ca0127
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 60 deletions.
76 changes: 73 additions & 3 deletions src/product_service/api/crud.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,43 @@
package api

import (
"encoding/json"
"fmt"
"time"

"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/redis/go-redis/v9"
log "github.com/sirupsen/logrus"
"github.com/thejasmeetsingh/go-ecommerce/product_service/internal/database"
)

func structToJson(product database.Product) ([]byte, error) {
return json.Marshal(product)
}

func JsonToStruct(data []byte) (database.Product, error) {
var product database.Product
err := json.Unmarshal(data, &product)
if err != nil {
return product, err
}
return product, nil
}

func retreiveProductFromCache(client *redis.Client, ctx *gin.Context, key string) ([]byte, error) {
return client.Get(ctx, key).Bytes()
}

func storeProductToCache(client *redis.Client, ctx *gin.Context, product database.Product) error {
key := product.ID.String()
value, err := structToJson(product)
if err != nil {
return err
}
return client.Set(ctx, key, value, 1*time.Hour).Err()
}

// Create product
func CreateProductDB(apiCfg *APIConfig, ctx *gin.Context, params database.CreateProductParams) (database.Product, error) {
// Begin DB transaction
Expand All @@ -27,6 +56,13 @@ func CreateProductDB(apiCfg *APIConfig, ctx *gin.Context, params database.Create
return database.Product{}, fmt.Errorf("something went wrong")
}

// Store newly product details into cache
err = storeProductToCache(apiCfg.Cache, ctx, dbProduct)
if err != nil {
log.Error(err)
return database.Product{}, fmt.Errorf("something went wrong")
}

// Commit the transaction
err = tx.Commit()
if err != nil {
Expand All @@ -48,11 +84,32 @@ func GetProductListDB(apiCfg *APIConfig, ctx *gin.Context, params database.GetPr

// Get details of a specific product
func GetProductDetailDB(apiCfg *APIConfig, ctx *gin.Context, productID uuid.UUID) (database.Product, error) {
product, err := apiCfg.Queries.GetProductById(ctx, productID)
// Find if product is available in cache or not
data, err := retreiveProductFromCache(apiCfg.Cache, ctx, productID.String())
if err != nil {
product, err := apiCfg.Queries.GetProductById(ctx, productID)
if err != nil {
log.Errorln(err)
return database.Product{}, fmt.Errorf("something went wrong")
}

// store the product details into cache
err = storeProductToCache(apiCfg.Cache, ctx, product)
if err != nil {
log.Error(err)
return database.Product{}, fmt.Errorf("something went wrong")
}

return product, nil
}

// Convert product JSON to struct
product, err := JsonToStruct(data)
if err != nil {
log.Errorln(err)
return database.Product{}, fmt.Errorf("something went wrong")
}

return product, nil
}

Expand All @@ -67,21 +124,28 @@ func UpdateProductDetailDB(apiCfg *APIConfig, ctx *gin.Context, params database.
defer tx.Rollback()
qtx := apiCfg.Queries.WithTx(tx)

dbProduct, err := qtx.UpdateProductDetails(ctx, params)
product, err := qtx.UpdateProductDetails(ctx, params)

if err != nil {
log.Errorln(err)
return database.Product{}, fmt.Errorf("something went wrong")
}

// Store product with latest details to cache
err = storeProductToCache(apiCfg.Cache, ctx, product)
if err != nil {
log.Error(err)
return database.Product{}, fmt.Errorf("something went wrong")
}

// Commit the transaction
err = tx.Commit()
if err != nil {
log.Fatal(err)
return database.Product{}, fmt.Errorf("something went wrong")
}

return dbProduct, nil
return product, nil
}

// Delete a product
Expand All @@ -101,6 +165,12 @@ func DeleteProductDetailDB(apiCfg *APIConfig, ctx *gin.Context, productID uuid.U
return fmt.Errorf("something went wrong")
}

// Remove deleted product from the cache if it is preasent
err = apiCfg.Cache.Del(ctx, productID.String()).Err()
if err != nil {
log.Error(err)
}

// Commit the transaction
err = tx.Commit()
if err != nil {
Expand Down
38 changes: 38 additions & 0 deletions src/product_service/api/internal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package api

import (
"net/http"

"github.com/gin-gonic/gin"
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
)

// API for getting details of a specific product
func (apiCfg *APIConfig) GetProductIDToDetails(c *gin.Context) {
type Parameters struct {
ID string `json:"id" binding:"required"`
}

var params Parameters
err := c.ShouldBindJSON(&params)
if err != nil {
log.Errorln(err)
c.JSON(http.StatusBadRequest, gin.H{"message": "Error while parsing the request data"})
return
}

productID, err := uuid.Parse(params.ID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": "Invalid product ID format"})
return
}

dbProduct, err := GetProductDetailDB(apiCfg, c, productID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
return
}

c.JSON(http.StatusOK, gin.H{"data": DatabaseProductToProduct(dbProduct)})
}
23 changes: 22 additions & 1 deletion src/product_service/api/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ package api

import (
"net/http"
"os"
"strings"

"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"github.com/thejasmeetsingh/go-ecommerce/product_service/shared"
)

func Auth(apiCfg *APIConfig) gin.HandlerFunc {
func JWTAuth(apiCfg *APIConfig) gin.HandlerFunc {
return func(ctx *gin.Context) {
headerAuthToken := ctx.GetHeader("Authorization")

Expand Down Expand Up @@ -42,3 +43,23 @@ func Auth(apiCfg *APIConfig) gin.HandlerFunc {
ctx.Next()
}
}

// Middleware for checking if a valid API secret is passed or not
func InternalAPIAuth(apiCfg *APIConfig) gin.HandlerFunc {
return func(ctx *gin.Context) {
apiSecret := ctx.GetHeader("Secret")
internalAPISecret := os.Getenv("INTERNAL_API_SECRET")

if internalAPISecret == "" {
panic("Internal API secret is not configured")
}

if apiSecret == "" || apiSecret != internalAPISecret {
ctx.JSON(http.StatusForbidden, gin.H{"message": "Invalid API Secret"})
ctx.Abort()
return
}

ctx.Next()
}
}
4 changes: 2 additions & 2 deletions src/product_service/api/products.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func (apiCfg *APIConfig) GetProductDetails(c *gin.Context) {
productID, err := uuid.Parse(productIDStr)

if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": "Invalid product ID"})
c.JSON(http.StatusBadRequest, gin.H{"message": "Invalid product ID format"})
return
}

Expand Down Expand Up @@ -201,7 +201,7 @@ func (apiCfg *APIConfig) DeleteProduct(c *gin.Context) {

// Check if the current user is the product creator or not
if dbProduct.CreatorID != userID {
c.JSON(http.StatusForbidden, gin.H{"message": "You cannot update this product details"})
c.JSON(http.StatusForbidden, gin.H{"message": "You cannot delete this product details"})
return
}

Expand Down
89 changes: 42 additions & 47 deletions src/product_service/api/products_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package api
import (
"net/http/httptest"
"os"
"sync"
"testing"
"time"

Expand All @@ -12,7 +13,10 @@ import (
"github.com/thejasmeetsingh/go-ecommerce/product_service/internal/database"
)

var apiCfg *APIConfig
var (
apiCfg *APIConfig
productID uuid.UUID
)

func TestMain(m *testing.M) {
// Connect to testing DB
Expand All @@ -34,7 +38,7 @@ func TestCreateProduct(t *testing.T) {
ctx, _ := gin.CreateTestContext(w)

// Create a testing product
_, err := CreateProductDB(apiCfg, ctx, database.CreateProductParams{
product, err := CreateProductDB(apiCfg, ctx, database.CreateProductParams{
ID: uuid.New(),
CreatedAt: time.Now().UTC(),
ModifiedAt: time.Now().UTC(),
Expand All @@ -47,54 +51,45 @@ func TestCreateProduct(t *testing.T) {
if err != nil {
t.Errorf("Error caught while creating a product: %s", err.Error())
}
}

// func TestConcurrentSingup(t *testing.T) {
// t.Parallel()

// engine := gin.Default()
// engine.POST("/register/", apiCfg.Singup)

// payload := []byte(`{"email": "john.doe@example1.com", "password": "12345678Uu@"}`)

// // Counter to track successful signups
// var successCounter int
// var mu sync.Mutex // Mutex to protect the counter

// var wg sync.WaitGroup

// // Number of concurrent signups
// numGoroutines := 5

// for i := 0; i < numGoroutines; i++ {
// wg.Add(1)
// go func() {
// defer wg.Done()

// // Create a request for each Goroutine
// req := httptest.NewRequest(http.MethodPost, "/register/", bytes.NewBuffer(payload))
// req.Header.Set("Content-Type", "application/json")
productID = product.ID
}

// // Create a ResponseRecorder to capture the response
// rr := httptest.NewRecorder()
func TestConcurrentProductDeletion(t *testing.T) {
t.Parallel()

// // Call the signup handler
// engine.ServeHTTP(rr, req)
w := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(w)

// // Check the status code for each Goroutine
// if rr.Code == http.StatusOK {
// // Increment the counter if signup is successful
// mu.Lock()
// successCounter++
// mu.Unlock()
// }
// }()
// }
// Counter to track successful product deletion
var successCounter int
var mu sync.Mutex // Mutex to protect the counter

var wg sync.WaitGroup

// Number of concurrent signups
numGoroutines := 5

for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// Delete the product
err := DeleteProductDetailDB(apiCfg, ctx, productID)

if err != nil {
// Increment the counter if product deletion is successful
mu.Lock()
successCounter++
mu.Unlock()
}
}()
}

// // Wait for all Goroutines to finish
// wg.Wait()
// Wait for all Goroutines to finish
wg.Wait()

// if successCounter > 1 {
// t.Errorf("Expected only one account to be created, but got %d", successCounter)
// }
// }
if successCounter > 1 {
t.Errorf("Expected only one product to be deleted, but got %d", successCounter)
}
}
18 changes: 11 additions & 7 deletions src/product_service/api/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@ func GetRoutes(engine *gin.Engine, apiCfg *APIConfig) {
})
})

router := engine.Group("/api/v1/")
router.Use(Auth(apiCfg))
router.POST("product/", apiCfg.CreateProduct)
router.GET("product/:id/", apiCfg.GetProductDetails)
router.PATCH("product/:id/", apiCfg.UpdateProductDetails)
router.DELETE("product/:id/", apiCfg.DeleteProduct)
router.GET("product/", apiCfg.GetProducts)
pubRouter := engine.Group("/api/v1/")
pubRouter.Use(JWTAuth(apiCfg))
pubRouter.POST("product/", apiCfg.CreateProduct)
pubRouter.GET("product/:id/", apiCfg.GetProductDetails)
pubRouter.PATCH("product/:id/", apiCfg.UpdateProductDetails)
pubRouter.DELETE("product/:id/", apiCfg.DeleteProduct)
pubRouter.GET("product/", apiCfg.GetProducts)

pvtRouter := engine.Group("/internal/v1/")
pvtRouter.Use(InternalAPIAuth(apiCfg))
pvtRouter.POST("product-details/", apiCfg.GetProductIDToDetails)
}

0 comments on commit 1ca0127

Please sign in to comment.