first commit
This commit is contained in:
665
app/shop/controllers/shop.go
Normal file
665
app/shop/controllers/shop.go
Normal file
@@ -0,0 +1,665 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
|
||||
shopModels "goaresv3/app/shop/models"
|
||||
"goaresv3/config"
|
||||
)
|
||||
|
||||
type UpsertProductCategoryRequest struct {
|
||||
Title string `json:"title" binding:"required,max=254"`
|
||||
Slug string `json:"slug" binding:"required,max=254"`
|
||||
Description string `json:"description"`
|
||||
Keywords string `json:"keywords"`
|
||||
ParentID *uint `json:"parent_id"`
|
||||
}
|
||||
|
||||
type UpsertProductTagRequest struct {
|
||||
Name string `json:"name" binding:"required,max=254"`
|
||||
}
|
||||
|
||||
type UpsertProductRequest struct {
|
||||
Title string `json:"title" binding:"required,max=254"`
|
||||
Images string `json:"images" binding:"required"`
|
||||
Price float64 `json:"price"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Quality int `json:"quality"`
|
||||
Format string `json:"format" binding:"omitempty,max=10"`
|
||||
Content string `json:"content"`
|
||||
Slug string `json:"slug" binding:"required,max=254"`
|
||||
CategoryIDs []uint `json:"category_ids"`
|
||||
TagIDs []uint `json:"tag_ids"`
|
||||
}
|
||||
|
||||
type UpsertCartItemRequest struct {
|
||||
ProductID uint `json:"product_id" binding:"required"`
|
||||
Quantity int `json:"quantity" binding:"required,min=1"`
|
||||
}
|
||||
|
||||
func parseShopID(c *gin.Context, key string) (uint, bool) {
|
||||
id, err := strconv.ParseUint(strings.TrimSpace(c.Param(key)), 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||||
return 0, false
|
||||
}
|
||||
return uint(id), true
|
||||
}
|
||||
|
||||
// ListProductCategories godoc
|
||||
// @Summary List product categories
|
||||
// @Description Returns all categories with children.
|
||||
// @Tags Shop
|
||||
// @Produce json
|
||||
// @Success 200 {array} map[string]interface{}
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /api/v1/shop/categories [get]
|
||||
func ListProductCategories(c *gin.Context) {
|
||||
var items []shopModels.ProductCategory
|
||||
if err := config.DB.Preload("Children").Order("id DESC").Find(&items).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch categories"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, items)
|
||||
}
|
||||
|
||||
// CreateProductCategory godoc
|
||||
// @Summary Create product category
|
||||
// @Description Creates a new shop category.
|
||||
// @Tags Shop
|
||||
// @Security BearerAuth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body UpsertProductCategoryRequest true "category payload"
|
||||
// @Success 201 {object} map[string]interface{}
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 409 {object} map[string]string
|
||||
// @Router /api/v1/shop/categories [post]
|
||||
func CreateProductCategory(c *gin.Context) {
|
||||
var req UpsertProductCategoryRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
item := shopModels.ProductCategory{
|
||||
Title: req.Title,
|
||||
Slug: req.Slug,
|
||||
Description: req.Description,
|
||||
Keywords: req.Keywords,
|
||||
ParentID: req.ParentID,
|
||||
}
|
||||
if err := config.DB.Create(&item).Error; err != nil {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "failed to create category"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusCreated, item)
|
||||
}
|
||||
|
||||
// UpdateProductCategory godoc
|
||||
// @Summary Update product category
|
||||
// @Description Updates a category by id.
|
||||
// @Tags Shop
|
||||
// @Security BearerAuth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "category id"
|
||||
// @Param request body UpsertProductCategoryRequest true "category payload"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 404 {object} map[string]string
|
||||
// @Failure 409 {object} map[string]string
|
||||
// @Router /api/v1/shop/categories/{id} [put]
|
||||
func UpdateProductCategory(c *gin.Context) {
|
||||
id, ok := parseShopID(c, "id")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var req UpsertProductCategoryRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
var item shopModels.ProductCategory
|
||||
if err := config.DB.First(&item, id).Error; errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "category not found"})
|
||||
return
|
||||
} else if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch category"})
|
||||
return
|
||||
}
|
||||
if err := config.DB.Model(&item).Updates(map[string]any{
|
||||
"title": req.Title,
|
||||
"slug": req.Slug,
|
||||
"description": req.Description,
|
||||
"keywords": req.Keywords,
|
||||
"parent_id": req.ParentID,
|
||||
}).Error; err != nil {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "failed to update category"})
|
||||
return
|
||||
}
|
||||
_ = config.DB.First(&item, id).Error
|
||||
c.JSON(http.StatusOK, item)
|
||||
}
|
||||
|
||||
// DeleteProductCategory godoc
|
||||
// @Summary Delete product category
|
||||
// @Description Deletes a category by id.
|
||||
// @Tags Shop
|
||||
// @Security BearerAuth
|
||||
// @Produce json
|
||||
// @Param id path int true "category id"
|
||||
// @Success 200 {object} map[string]string
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 404 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /api/v1/shop/categories/{id} [delete]
|
||||
func DeleteProductCategory(c *gin.Context) {
|
||||
id, ok := parseShopID(c, "id")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
res := config.DB.Delete(&shopModels.ProductCategory{}, id)
|
||||
if res.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete category"})
|
||||
return
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "category not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "category deleted"})
|
||||
}
|
||||
|
||||
// ListProductTags godoc
|
||||
// @Summary List product tags
|
||||
// @Description Returns all product tags.
|
||||
// @Tags Shop
|
||||
// @Produce json
|
||||
// @Success 200 {array} map[string]interface{}
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /api/v1/shop/tags [get]
|
||||
func ListProductTags(c *gin.Context) {
|
||||
var items []shopModels.ProductTag
|
||||
if err := config.DB.Order("id DESC").Find(&items).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch tags"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, items)
|
||||
}
|
||||
|
||||
// CreateProductTag godoc
|
||||
// @Summary Create product tag
|
||||
// @Description Creates a new product tag.
|
||||
// @Tags Shop
|
||||
// @Security BearerAuth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body UpsertProductTagRequest true "tag payload"
|
||||
// @Success 201 {object} map[string]interface{}
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 409 {object} map[string]string
|
||||
// @Router /api/v1/shop/tags [post]
|
||||
func CreateProductTag(c *gin.Context) {
|
||||
var req UpsertProductTagRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
item := shopModels.ProductTag{Name: req.Name}
|
||||
if err := config.DB.Create(&item).Error; err != nil {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "failed to create tag"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusCreated, item)
|
||||
}
|
||||
|
||||
// UpdateProductTag godoc
|
||||
// @Summary Update product tag
|
||||
// @Description Updates a tag by id.
|
||||
// @Tags Shop
|
||||
// @Security BearerAuth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "tag id"
|
||||
// @Param request body UpsertProductTagRequest true "tag payload"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 404 {object} map[string]string
|
||||
// @Failure 409 {object} map[string]string
|
||||
// @Router /api/v1/shop/tags/{id} [put]
|
||||
func UpdateProductTag(c *gin.Context) {
|
||||
id, ok := parseShopID(c, "id")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var req UpsertProductTagRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
var item shopModels.ProductTag
|
||||
if err := config.DB.First(&item, id).Error; errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "tag not found"})
|
||||
return
|
||||
} else if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch tag"})
|
||||
return
|
||||
}
|
||||
if err := config.DB.Model(&item).Update("name", req.Name).Error; err != nil {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "failed to update tag"})
|
||||
return
|
||||
}
|
||||
_ = config.DB.First(&item, id).Error
|
||||
c.JSON(http.StatusOK, item)
|
||||
}
|
||||
|
||||
// DeleteProductTag godoc
|
||||
// @Summary Delete product tag
|
||||
// @Description Deletes a tag by id.
|
||||
// @Tags Shop
|
||||
// @Security BearerAuth
|
||||
// @Produce json
|
||||
// @Param id path int true "tag id"
|
||||
// @Success 200 {object} map[string]string
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 404 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /api/v1/shop/tags/{id} [delete]
|
||||
func DeleteProductTag(c *gin.Context) {
|
||||
id, ok := parseShopID(c, "id")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
res := config.DB.Delete(&shopModels.ProductTag{}, id)
|
||||
if res.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete tag"})
|
||||
return
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "tag not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "tag deleted"})
|
||||
}
|
||||
|
||||
// ListProducts godoc
|
||||
// @Summary List products
|
||||
// @Description Returns all products with categories and tags.
|
||||
// @Tags Shop
|
||||
// @Produce json
|
||||
// @Success 200 {array} map[string]interface{}
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /api/v1/shop/products [get]
|
||||
func ListProducts(c *gin.Context) {
|
||||
var items []shopModels.Product
|
||||
if err := config.DB.Preload("Categories").Preload("Tags").Order("id DESC").Find(&items).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch products"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, items)
|
||||
}
|
||||
|
||||
// GetProduct godoc
|
||||
// @Summary Get product
|
||||
// @Description Returns product details by id.
|
||||
// @Tags Shop
|
||||
// @Produce json
|
||||
// @Param id path int true "product id"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 404 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /api/v1/shop/products/{id} [get]
|
||||
func GetProduct(c *gin.Context) {
|
||||
id, ok := parseShopID(c, "id")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var item shopModels.Product
|
||||
if err := config.DB.Preload("Categories").Preload("Tags").First(&item, id).Error; errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "product not found"})
|
||||
return
|
||||
} else if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch product"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, item)
|
||||
}
|
||||
|
||||
// CreateProduct godoc
|
||||
// @Summary Create product
|
||||
// @Description Creates a new product and assigns category/tag relations.
|
||||
// @Tags Shop
|
||||
// @Security BearerAuth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body UpsertProductRequest true "product payload"
|
||||
// @Success 201 {object} map[string]interface{}
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 409 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /api/v1/shop/products [post]
|
||||
func CreateProduct(c *gin.Context) {
|
||||
var req UpsertProductRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
item := shopModels.Product{
|
||||
Title: req.Title,
|
||||
Images: req.Images,
|
||||
Price: req.Price,
|
||||
Width: req.Width,
|
||||
Height: req.Height,
|
||||
Quality: req.Quality,
|
||||
Format: req.Format,
|
||||
Content: req.Content,
|
||||
Slug: req.Slug,
|
||||
}
|
||||
if err := config.DB.Create(&item).Error; err != nil {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "failed to create product"})
|
||||
return
|
||||
}
|
||||
if err := assignProductRelations(item.ID, req.CategoryIDs, req.TagIDs); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to assign product relations"})
|
||||
return
|
||||
}
|
||||
_ = config.DB.Preload("Categories").Preload("Tags").First(&item, item.ID).Error
|
||||
c.JSON(http.StatusCreated, item)
|
||||
}
|
||||
|
||||
// UpdateProduct godoc
|
||||
// @Summary Update product
|
||||
// @Description Updates a product and reassigns category/tag relations.
|
||||
// @Tags Shop
|
||||
// @Security BearerAuth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "product id"
|
||||
// @Param request body UpsertProductRequest true "product payload"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 404 {object} map[string]string
|
||||
// @Failure 409 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /api/v1/shop/products/{id} [put]
|
||||
func UpdateProduct(c *gin.Context) {
|
||||
id, ok := parseShopID(c, "id")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var req UpsertProductRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
var item shopModels.Product
|
||||
if err := config.DB.First(&item, id).Error; errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "product not found"})
|
||||
return
|
||||
} else if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch product"})
|
||||
return
|
||||
}
|
||||
if err := config.DB.Model(&item).Updates(map[string]any{
|
||||
"title": req.Title,
|
||||
"images": req.Images,
|
||||
"price": req.Price,
|
||||
"width": req.Width,
|
||||
"height": req.Height,
|
||||
"quality": req.Quality,
|
||||
"format": req.Format,
|
||||
"content": req.Content,
|
||||
"slug": req.Slug,
|
||||
}).Error; err != nil {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "failed to update product"})
|
||||
return
|
||||
}
|
||||
if err := assignProductRelations(item.ID, req.CategoryIDs, req.TagIDs); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to assign product relations"})
|
||||
return
|
||||
}
|
||||
_ = config.DB.Preload("Categories").Preload("Tags").First(&item, id).Error
|
||||
c.JSON(http.StatusOK, item)
|
||||
}
|
||||
|
||||
// DeleteProduct godoc
|
||||
// @Summary Delete product
|
||||
// @Description Deletes a product by id.
|
||||
// @Tags Shop
|
||||
// @Security BearerAuth
|
||||
// @Produce json
|
||||
// @Param id path int true "product id"
|
||||
// @Success 200 {object} map[string]string
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 404 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /api/v1/shop/products/{id} [delete]
|
||||
func DeleteProduct(c *gin.Context) {
|
||||
id, ok := parseShopID(c, "id")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
res := config.DB.Delete(&shopModels.Product{}, id)
|
||||
if res.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete product"})
|
||||
return
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "product not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "product deleted"})
|
||||
}
|
||||
|
||||
// GetMyCart godoc
|
||||
// @Summary Get my cart
|
||||
// @Description Returns the authenticated user's cart with items.
|
||||
// @Tags Shop
|
||||
// @Security BearerAuth
|
||||
// @Produce json
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /api/v1/shop/cart [get]
|
||||
func GetMyCart(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
var cart shopModels.Cart
|
||||
err := config.DB.Preload("Items.Product").Where("user_id = ?", userID).First(&cart).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.JSON(http.StatusOK, gin.H{"items": []any{}})
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch cart"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, cart)
|
||||
}
|
||||
|
||||
// AddCartItem godoc
|
||||
// @Summary Add item to my cart
|
||||
// @Description Creates or increments a cart item for authenticated user.
|
||||
// @Tags Shop
|
||||
// @Security BearerAuth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body UpsertCartItemRequest true "cart item payload"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Success 201 {object} map[string]interface{}
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /api/v1/shop/cart/items [post]
|
||||
func AddCartItem(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
var req UpsertCartItemRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
cart, err := ensureCart(userID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get cart"})
|
||||
return
|
||||
}
|
||||
|
||||
var item shopModels.CartItem
|
||||
err = config.DB.Where("cart_id = ? AND product_id = ?", cart.ID, req.ProductID).First(&item).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
item = shopModels.CartItem{CartID: cart.ID, ProductID: req.ProductID, Quantity: req.Quantity}
|
||||
if err := config.DB.Create(&item).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to add cart item"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusCreated, item)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch cart item"})
|
||||
return
|
||||
}
|
||||
if err := config.DB.Model(&item).Update("quantity", item.Quantity+req.Quantity).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update cart item"})
|
||||
return
|
||||
}
|
||||
_ = config.DB.First(&item, item.ID).Error
|
||||
c.JSON(http.StatusOK, item)
|
||||
}
|
||||
|
||||
// UpdateCartItem godoc
|
||||
// @Summary Update my cart item
|
||||
// @Description Updates a cart item owned by authenticated user.
|
||||
// @Tags Shop
|
||||
// @Security BearerAuth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param itemId path int true "cart item id"
|
||||
// @Param request body UpsertCartItemRequest true "cart item payload"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 404 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /api/v1/shop/cart/items/{itemId} [put]
|
||||
func UpdateCartItem(c *gin.Context) {
|
||||
itemID, ok := parseShopID(c, "itemId")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
userID := c.GetUint("user_id")
|
||||
var req UpsertCartItemRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
var item shopModels.CartItem
|
||||
err := config.DB.Joins("JOIN carts ON carts.id = cart_items.cart_id").
|
||||
Where("cart_items.id = ? AND carts.user_id = ?", itemID, userID).
|
||||
First(&item).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "cart item not found"})
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch cart item"})
|
||||
return
|
||||
}
|
||||
if err := config.DB.Model(&item).Updates(map[string]any{
|
||||
"product_id": req.ProductID,
|
||||
"quantity": req.Quantity,
|
||||
}).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update cart item"})
|
||||
return
|
||||
}
|
||||
_ = config.DB.First(&item, itemID).Error
|
||||
c.JSON(http.StatusOK, item)
|
||||
}
|
||||
|
||||
// DeleteCartItem godoc
|
||||
// @Summary Delete item from my cart
|
||||
// @Description Deletes a cart item owned by authenticated user.
|
||||
// @Tags Shop
|
||||
// @Security BearerAuth
|
||||
// @Produce json
|
||||
// @Param itemId path int true "cart item id"
|
||||
// @Success 200 {object} map[string]string
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 404 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /api/v1/shop/cart/items/{itemId} [delete]
|
||||
func DeleteCartItem(c *gin.Context) {
|
||||
itemID, ok := parseShopID(c, "itemId")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
userID := c.GetUint("user_id")
|
||||
var item shopModels.CartItem
|
||||
err := config.DB.Joins("JOIN carts ON carts.id = cart_items.cart_id").
|
||||
Where("cart_items.id = ? AND carts.user_id = ?", itemID, userID).
|
||||
First(&item).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "cart item not found"})
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch cart item"})
|
||||
return
|
||||
}
|
||||
if err := config.DB.Delete(&item).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete cart item"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "cart item deleted"})
|
||||
}
|
||||
|
||||
func ensureCart(userID uint) (*shopModels.Cart, error) {
|
||||
var cart shopModels.Cart
|
||||
err := config.DB.Where("user_id = ?", userID).First(&cart).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
cart = shopModels.Cart{UserID: userID}
|
||||
if createErr := config.DB.Create(&cart).Error; createErr != nil {
|
||||
return nil, createErr
|
||||
}
|
||||
return &cart, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cart, nil
|
||||
}
|
||||
|
||||
func assignProductRelations(productID uint, categoryIDs, tagIDs []uint) error {
|
||||
var p shopModels.Product
|
||||
if err := config.DB.First(&p, productID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if categoryIDs != nil {
|
||||
var categories []shopModels.ProductCategory
|
||||
if len(categoryIDs) > 0 {
|
||||
if err := config.DB.Where("id IN ?", categoryIDs).Find(&categories).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := config.DB.Model(&p).Association("Categories").Replace(categories); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if tagIDs != nil {
|
||||
var tags []shopModels.ProductTag
|
||||
if len(tagIDs) > 0 {
|
||||
if err := config.DB.Where("id IN ?", tagIDs).Find(&tags).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := config.DB.Model(&p).Association("Tags").Replace(tags); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
20
app/shop/models/cart.go
Normal file
20
app/shop/models/cart.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Cart struct {
|
||||
gorm.Model
|
||||
UserID uint `gorm:"not null;index" json:"user_id"`
|
||||
Items []CartItem `gorm:"foreignKey:CartID" json:"items,omitempty"`
|
||||
}
|
||||
|
||||
type CartItem struct {
|
||||
gorm.Model
|
||||
CartID uint `gorm:"not null;index" json:"cart_id"`
|
||||
Cart *Cart `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;foreignKey:CartID" json:"cart,omitempty"`
|
||||
ProductID uint `gorm:"not null;index" json:"product_id"`
|
||||
Product *Product `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;foreignKey:ProductID" json:"product,omitempty"`
|
||||
Quantity int `gorm:"default:1" json:"quantity"`
|
||||
}
|
||||
53
app/shop/models/product.go
Normal file
53
app/shop/models/product.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Minimal, temiz GORM modelleri
|
||||
|
||||
type ProductCategory struct {
|
||||
gorm.Model
|
||||
Title string `gorm:"type:varchar(254);not null" json:"title"`
|
||||
Slug string `gorm:"type:varchar(254);not null;uniqueIndex" json:"slug"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Keywords string `json:"keywords,omitempty"`
|
||||
ParentID *uint `json:"parent_id,omitempty"`
|
||||
Parent *ProductCategory `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;foreignKey:ParentID" json:"parent,omitempty"`
|
||||
Children []ProductCategory `gorm:"foreignKey:ParentID" json:"children,omitempty"`
|
||||
Products []Product `gorm:"many2many:product_product_categories;" json:"products,omitempty"`
|
||||
}
|
||||
|
||||
type ProductTag struct {
|
||||
gorm.Model
|
||||
Name string `gorm:"type:varchar(254);not null" json:"name"`
|
||||
Products []Product `gorm:"many2many:product_product_tags;" json:"products,omitempty"`
|
||||
}
|
||||
|
||||
type Product struct {
|
||||
gorm.Model
|
||||
Title string `gorm:"type:varchar(254);not null" json:"title" form:"title"`
|
||||
Images string `gorm:"type:text;not null" json:"images" form:"images"`
|
||||
Price float64 `gorm:"type:decimal(10,2);default:0.0" json:"price" form:"price"`
|
||||
Width int `gorm:"default:0" json:"width" form:"width"`
|
||||
Height int `gorm:"default:0" json:"height" form:"height"`
|
||||
Quality int `gorm:"default:0" json:"quality" form:"quality"`
|
||||
Format string `gorm:"type:varchar(10);default:avif" json:"format" form:"format"`
|
||||
Content string `gorm:"type:text" json:"content,omitempty" form:"content"`
|
||||
Slug string `gorm:"type:varchar(254);not null;uniqueIndex" json:"slug" form:"slug"`
|
||||
Categories []ProductCategory `gorm:"many2many:product_product_categories;" json:"categories,omitempty" form:"product_category"`
|
||||
Tags []ProductTag `gorm:"many2many:product_product_tags;" json:"tags,omitempty" form:"tags"`
|
||||
}
|
||||
|
||||
type ProductCategoryView struct {
|
||||
gorm.Model
|
||||
CategoryID uint `json:"category_id"`
|
||||
IPAddress string `gorm:"type:varchar(45)" json:"ip_address,omitempty"`
|
||||
}
|
||||
|
||||
type ProductComment struct {
|
||||
gorm.Model
|
||||
UserID uint `json:"user_id"`
|
||||
ProductID uint `json:"product_id"`
|
||||
Body string `gorm:"type:text" json:"body,omitempty"`
|
||||
}
|
||||
Reference in New Issue
Block a user