mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 10:42:57 +00:00
265 lines
7.5 KiB
Go
265 lines
7.5 KiB
Go
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)
|
|
}
|