package eshop import ( "net/http" "fotbal-club/internal/config" "fotbal-club/internal/models" "github.com/gin-gonic/gin" "gorm.io/gorm" ) type CartController struct { DB *gorm.DB Config *config.Config } func NewCartController(db *gorm.DB, cfg *config.Config) *CartController { return &CartController{ DB: db, Config: cfg, } } type cartContext struct { UserID *uint SessionToken string } func (ctrl *CartController) getCartContext(c *gin.Context) cartContext { var res cartContext if uidVal, ok := c.Get("userID"); ok { switch v := uidVal.(type) { case uint: res.UserID = &v case int: u := uint(v) res.UserID = &u case int64: u := uint(v) res.UserID = &u } } res.SessionToken = c.GetHeader("X-Session-Token") if res.SessionToken == "" { if cookie, err := c.Request.Cookie("eshop_session_token"); err == nil { res.SessionToken = cookie.Value } } return res } func (ctrl *CartController) findOrCreateCart(c *gin.Context) (*models.EshopCart, error) { cc := ctrl.getCartContext(c) q := ctrl.DB.Where("completed = ?", false) if cc.UserID != nil { q = q.Where("user_id = ?", *cc.UserID) } else if cc.SessionToken != "" { q = q.Where("session_token = ?", cc.SessionToken) } var cart models.EshopCart if err := q.Preload("Items").Preload("Items.Product").First(&cart).Error; err != nil { if err != gorm.ErrRecordNotFound { return nil, err } // Create new cart cart = models.EshopCart{ UserID: cc.UserID, SessionToken: cc.SessionToken, Currency: ctrl.Config.StripeCurrency, } if cart.Currency == "" { cart.Currency = "CZK" } if err := ctrl.DB.Create(&cart).Error; err != nil { return nil, err } } return &cart, nil } // GetCart returns the current cart func (ctrl *CartController) GetCart(c *gin.Context) { cartObj, err := ctrl.findOrCreateCart(c) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to load cart"}) return } // Ensure items are fully loaded if err := ctrl.DB. Preload("Items"). Preload("Items.Product"). Preload("Items.Variant"). First(cartObj, cartObj.ID).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to load cart items"}) return } c.JSON(http.StatusOK, cartObj) } // AddItem adds an item to the cart func (ctrl *CartController) AddItem(c *gin.Context) { var body struct { ProductID uint `json:"product_id"` VariantID *uint `json:"variant_id"` Quantity int `json:"quantity"` } if err := c.ShouldBindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request format: " + err.Error()}) return } // Validate required fields if body.ProductID == 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "Product ID is required"}) return } if body.Quantity <= 0 || body.Quantity > 100 { c.JSON(http.StatusBadRequest, gin.H{"error": "Quantity must be between 1 and 100"}) return } // Check if product exists and is active var product models.EshopProduct if err := ctrl.DB.Where("id = ? AND active = ?", body.ProductID, true).First(&product).Error; err != nil { if err == gorm.ErrRecordNotFound { c.JSON(http.StatusNotFound, gin.H{"error": "Product not found or not available"}) } else { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to load product"}) } return } // Validate variant if provided if body.VariantID != nil { var variant models.EshopProductVariant if err := ctrl.DB.Where("id = ? AND product_id = ?", *body.VariantID, body.ProductID).First(&variant).Error; err != nil { if err == gorm.ErrRecordNotFound { c.JSON(http.StatusNotFound, gin.H{"error": "Product variant not found"}) } else { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to load variant"}) } return } // Check stock (negative values mean unlimited) if variant.StockQty >= 0 && variant.StockQty < body.Quantity { c.JSON(http.StatusBadRequest, gin.H{"error": "Insufficient stock for this variant"}) return } } cartObj, err := ctrl.findOrCreateCart(c) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to load cart"}) return } // Upsert cart item var item models.EshopCartItem q := ctrl.DB.Where("cart_id = ? AND product_id = ?", cartObj.ID, body.ProductID) if body.VariantID != nil { q = q.Where("variant_id = ?", *body.VariantID) } if err := q.First(&item).Error; err != nil { if err != gorm.ErrRecordNotFound { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update cart"}) return } item = models.EshopCartItem{ CartID: cartObj.ID, ProductID: body.ProductID, VariantID: body.VariantID, Quantity: body.Quantity, UnitPriceCents: product.PriceCents, Currency: product.Currency, } if item.Currency == "" { item.Currency = cartObj.Currency } if err := ctrl.DB.Create(&item).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to add item"}) return } } else { // Update quantity item.Quantity += body.Quantity if item.Quantity <= 0 { if err := ctrl.DB.Delete(&item).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update cart"}) return } } else { if err := ctrl.DB.Save(&item).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update cart"}) return } } } c.Status(http.StatusNoContent) } // UpdateItem updates quantity of a cart item func (ctrl *CartController) UpdateItem(c *gin.Context) { id := c.Param("id") var body struct { Quantity int `json:"quantity"` } if err := c.ShouldBindJSON(&body); err != nil || body.Quantity < 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid payload"}) return } var item models.EshopCartItem if err := ctrl.DB.First(&item, id).Error; err != nil { if err == gorm.ErrRecordNotFound { c.JSON(http.StatusNotFound, gin.H{"error": "Cart item not found"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to load cart item"}) return } // Check ownership (simplified) - in real app check if item belongs to user's cart // Here we assume if they know the ID, they can edit it (or rely on middleware/session match in findOrCreateCart flow if we enforced it strictly) // Ideally we should check if item.CartID belongs to current session/user. // For MVP let's leave it as is, or add a check: cc := ctrl.getCartContext(c) var cart models.EshopCart if err := ctrl.DB.First(&cart, item.CartID).Error; err == nil { if cc.UserID != nil && (cart.UserID == nil || *cart.UserID != *cc.UserID) { // user mismatch c.JSON(http.StatusForbidden, gin.H{"error": "Unauthorized"}) return } if cc.UserID == nil && cc.SessionToken != "" && cart.SessionToken != cc.SessionToken { // token mismatch c.JSON(http.StatusForbidden, gin.H{"error": "Unauthorized"}) return } } if body.Quantity == 0 { if err := ctrl.DB.Delete(&item).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update cart"}) return } } else { item.Quantity = body.Quantity if err := ctrl.DB.Save(&item).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update cart"}) return } } c.Status(http.StatusNoContent) } // RemoveItem removes an item from the cart func (ctrl *CartController) RemoveItem(c *gin.Context) { id := c.Param("id") if err := ctrl.DB.Delete(&models.EshopCartItem{}, id).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to remove item"}) return } c.Status(http.StatusNoContent) }