[BE-API-037] be-api: Implement marketplace product update endpoint
This commit is contained in:
parent
3e4e2bb174
commit
f6fa8d933a
4 changed files with 149 additions and 2 deletions
|
|
@ -2780,7 +2780,7 @@
|
|||
"description": "PUT /api/v1/marketplace/products/:id to update product details",
|
||||
"owner": "backend",
|
||||
"estimated_hours": 3,
|
||||
"status": "todo",
|
||||
"status": "completed",
|
||||
"files_involved": [],
|
||||
"implementation_steps": [
|
||||
{
|
||||
|
|
@ -2801,7 +2801,9 @@
|
|||
"Unit tests",
|
||||
"Integration tests"
|
||||
],
|
||||
"notes": ""
|
||||
"notes": "",
|
||||
"completed_at": "2025-12-24T14:49:39.604875",
|
||||
"implementation_notes": "Implemented PUT /api/v1/marketplace/products/:id endpoint. Added UpdateProduct method to marketplace service that validates ownership and updates allowed fields (title, description, price, status). Added UpdateProduct handler with validation. Route registered with ownership middleware to ensure only product owner can update."
|
||||
},
|
||||
{
|
||||
"id": "BE-API-038",
|
||||
|
|
|
|||
|
|
@ -232,6 +232,24 @@ func (r *APIRouter) setupMarketplaceRoutes(router *gin.RouterGroup) {
|
|||
createGroup := protected.Group("")
|
||||
createGroup.Use(r.config.AuthMiddleware.RequireContentCreatorRole())
|
||||
createGroup.POST("/products", marketHandler.CreateProduct)
|
||||
|
||||
// BE-API-037: Update product endpoint (requires ownership)
|
||||
// Resolver: Load product from DB to get its seller_id
|
||||
productOwnerResolver := func(c *gin.Context) (uuid.UUID, error) {
|
||||
productIDStr := c.Param("id")
|
||||
productID, err := uuid.Parse(productIDStr)
|
||||
if err != nil {
|
||||
return uuid.Nil, err
|
||||
}
|
||||
// Load product to get seller ID
|
||||
product, err := marketService.GetProduct(c.Request.Context(), productID)
|
||||
if err != nil {
|
||||
return uuid.Nil, err
|
||||
}
|
||||
return product.SellerID, nil
|
||||
}
|
||||
protected.PUT("/products/:id", r.config.AuthMiddleware.RequireOwnershipOrAdmin("product", productOwnerResolver), marketHandler.UpdateProduct)
|
||||
|
||||
protected.POST("/orders", marketHandler.CreateOrder)
|
||||
protected.GET("/download/:product_id", marketHandler.GetDownloadURL)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ type MarketplaceService interface {
|
|||
// Product Management
|
||||
CreateProduct(ctx context.Context, product *Product) error
|
||||
GetProduct(ctx context.Context, id uuid.UUID) (*Product, error)
|
||||
UpdateProduct(ctx context.Context, id uuid.UUID, sellerID uuid.UUID, updates map[string]interface{}) (*Product, error)
|
||||
ListProducts(ctx context.Context, filters map[string]interface{}) ([]Product, error)
|
||||
|
||||
// Purchasing
|
||||
|
|
@ -110,6 +111,50 @@ func (s *Service) GetProduct(ctx context.Context, id uuid.UUID) (*Product, error
|
|||
return &product, nil
|
||||
}
|
||||
|
||||
// UpdateProduct updates an existing product
|
||||
// BE-API-037: Implement marketplace product update endpoint
|
||||
// Validates that the seller owns the product
|
||||
func (s *Service) UpdateProduct(ctx context.Context, id uuid.UUID, sellerID uuid.UUID, updates map[string]interface{}) (*Product, error) {
|
||||
var product Product
|
||||
if err := s.db.First(&product, "id = ?", id).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, ErrProductNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Verify ownership
|
||||
if product.SellerID != sellerID {
|
||||
return nil, ErrInvalidSeller
|
||||
}
|
||||
|
||||
// Update allowed fields
|
||||
if title, ok := updates["title"].(string); ok && title != "" {
|
||||
product.Title = title
|
||||
}
|
||||
if description, ok := updates["description"].(string); ok {
|
||||
product.Description = description
|
||||
}
|
||||
if price, ok := updates["price"].(float64); ok && price > 0 {
|
||||
product.Price = price
|
||||
}
|
||||
if status, ok := updates["status"].(string); ok {
|
||||
product.Status = ProductStatus(status)
|
||||
}
|
||||
|
||||
// Save updates
|
||||
if err := s.db.Save(&product).Error; err != nil {
|
||||
s.logger.Error("Failed to update product", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.logger.Info("Product updated successfully",
|
||||
zap.String("product_id", product.ID.String()),
|
||||
zap.String("seller_id", sellerID.String()))
|
||||
|
||||
return &product, nil
|
||||
}
|
||||
|
||||
// ListProducts retrieves products based on filters
|
||||
func (s *Service) ListProducts(ctx context.Context, filters map[string]interface{}) ([]Product, error) {
|
||||
var products []Product
|
||||
|
|
|
|||
|
|
@ -227,3 +227,85 @@ func (h *MarketplaceHandler) ListProducts(c *gin.Context) {
|
|||
|
||||
response.Success(c, products)
|
||||
}
|
||||
|
||||
// UpdateProductRequest DTO pour la mise à jour de produit
|
||||
// BE-API-037: Implement marketplace product update endpoint
|
||||
// MOD-P1-001: Ajout tags validate pour validation systématique
|
||||
type UpdateProductRequest struct {
|
||||
Title *string `json:"title,omitempty" binding:"omitempty,min=3,max=200" validate:"omitempty,min=3,max=200"`
|
||||
Description *string `json:"description,omitempty" binding:"omitempty,max=2000" validate:"omitempty,max=2000"`
|
||||
Price *float64 `json:"price,omitempty" binding:"omitempty,min=0,gt=0" validate:"omitempty,min=0,gt=0"`
|
||||
Status *string `json:"status,omitempty" binding:"omitempty,oneof=draft active archived" validate:"omitempty,oneof=draft active archived"`
|
||||
}
|
||||
|
||||
// UpdateProduct gère la mise à jour d'un produit
|
||||
// BE-API-037: PUT /api/v1/marketplace/products/:id to update product details
|
||||
// @Summary Update a product
|
||||
// @Description Update product details (only seller can update)
|
||||
// @Tags Marketplace
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "Product ID"
|
||||
// @Param product body UpdateProductRequest true "Product updates"
|
||||
// @Success 200 {object} marketplace.Product
|
||||
// @Failure 400 {object} response.APIResponse "Validation Error"
|
||||
// @Failure 401 {object} response.APIResponse "Unauthorized"
|
||||
// @Failure 403 {object} response.APIResponse "Forbidden - Not product owner"
|
||||
// @Failure 404 {object} response.APIResponse "Product not found"
|
||||
// @Router /api/v1/marketplace/products/{id} [put]
|
||||
func (h *MarketplaceHandler) UpdateProduct(c *gin.Context) {
|
||||
userID, ok := GetUserIDUUID(c)
|
||||
if !ok {
|
||||
return // Erreur déjà envoyée par GetUserIDUUID
|
||||
}
|
||||
|
||||
productIDStr := c.Param("id")
|
||||
productID, err := uuid.Parse(productIDStr)
|
||||
if err != nil {
|
||||
response.BadRequest(c, "Invalid product id")
|
||||
return
|
||||
}
|
||||
|
||||
var req UpdateProductRequest
|
||||
if appErr := h.commonHandler.BindAndValidateJSON(c, &req); appErr != nil {
|
||||
RespondWithAppError(c, appErr)
|
||||
return
|
||||
}
|
||||
|
||||
// Build updates map
|
||||
updates := make(map[string]interface{})
|
||||
if req.Title != nil {
|
||||
updates["title"] = *req.Title
|
||||
}
|
||||
if req.Description != nil {
|
||||
updates["description"] = *req.Description
|
||||
}
|
||||
if req.Price != nil {
|
||||
updates["price"] = *req.Price
|
||||
}
|
||||
if req.Status != nil {
|
||||
updates["status"] = *req.Status
|
||||
}
|
||||
|
||||
if len(updates) == 0 {
|
||||
response.BadRequest(c, "No fields to update")
|
||||
return
|
||||
}
|
||||
|
||||
product, err := h.service.UpdateProduct(c.Request.Context(), productID, userID, updates)
|
||||
if err != nil {
|
||||
if err == marketplace.ErrProductNotFound {
|
||||
response.NotFound(c, "Product not found")
|
||||
return
|
||||
}
|
||||
if err == marketplace.ErrInvalidSeller {
|
||||
response.Forbidden(c, "You do not own this product")
|
||||
return
|
||||
}
|
||||
response.InternalServerError(c, "Failed to update product")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, product)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue