[BE-API-037] be-api: Implement marketplace product update endpoint

This commit is contained in:
senke 2025-12-24 14:49:41 +01:00
parent 3e4e2bb174
commit f6fa8d933a
4 changed files with 149 additions and 2 deletions

View file

@ -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",

View file

@ -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)
}

View file

@ -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

View file

@ -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)
}