refactor(marketplace): enforce unified api response envelope
Some checks failed
Veza CI / Rust Services (Chat & Stream) (push) Failing after 0s
Veza CI / Frontend (Web) (push) Failing after 0s
Veza CI / Backend (Go) (push) Failing after 0s

This commit is contained in:
okinrev 2025-12-06 17:39:04 +01:00
parent e7ae13736b
commit af0e42c656
5 changed files with 56 additions and 95 deletions

View file

@ -64,19 +64,13 @@ const docTemplate = `{
"403": {
"description": "No license",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
}
}
@ -119,21 +113,15 @@ const docTemplate = `{
}
},
"400": {
"description": "Bad Request",
"description": "Validation Error",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
}
}
@ -214,21 +202,15 @@ const docTemplate = `{
}
},
"400": {
"description": "Bad Request",
"description": "Validation Error",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
}
}

View file

@ -58,19 +58,13 @@
"403": {
"description": "No license",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
}
}
@ -113,21 +107,15 @@
}
},
"400": {
"description": "Bad Request",
"description": "Validation Error",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
}
}
@ -208,21 +196,15 @@
}
},
"400": {
"description": "Bad Request",
"description": "Validation Error",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
}
}

View file

@ -558,15 +558,11 @@ paths:
"403":
description: No license
schema:
additionalProperties:
type: string
type: object
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"404":
description: Not Found
schema:
additionalProperties:
type: string
type: object
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
security:
- BearerAuth: []
summary: Get download URL
@ -592,17 +588,13 @@ paths:
schema:
$ref: '#/definitions/veza-backend-api_internal_core_marketplace.Order'
"400":
description: Bad Request
description: Validation Error
schema:
additionalProperties:
type: string
type: object
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"401":
description: Unauthorized
schema:
additionalProperties:
type: string
type: object
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
security:
- BearerAuth: []
summary: Create a new order
@ -653,17 +645,13 @@ paths:
schema:
$ref: '#/definitions/veza-backend-api_internal_core_marketplace.Product'
"400":
description: Bad Request
description: Validation Error
schema:
additionalProperties:
type: string
type: object
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"401":
description: Unauthorized
schema:
additionalProperties:
type: string
type: object
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
security:
- BearerAuth: []
summary: Create a new product

View file

@ -67,3 +67,11 @@ func NewUnauthorizedError(message string) *AppError {
Message: message,
}
}
// NewForbiddenError crée une nouvelle erreur "forbidden"
func NewForbiddenError(message string) *AppError {
return &AppError{
Code: ErrCodeForbidden,
Message: message,
}
}

View file

@ -1,12 +1,11 @@
package handlers
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"go.uber.org/zap"
"veza-backend-api/internal/core/marketplace"
"veza-backend-api/internal/response"
)
// MarketplaceHandler gère les opérations de la marketplace
@ -43,8 +42,9 @@ type CreateProductRequest struct {
// @Security BearerAuth
// @Param product body CreateProductRequest true "Product info"
// @Success 201 {object} marketplace.Product
// @Failure 400 {object} map[string]string
// @Failure 401 {object} map[string]string
// @Success 201 {object} marketplace.Product
// @Failure 400 {object} response.APIResponse "Validation Error"
// @Failure 401 {object} response.APIResponse "Unauthorized"
// @Router /api/v1/marketplace/products [post]
func (h *MarketplaceHandler) CreateProduct(c *gin.Context) {
userID := c.MustGet("user_id").(uuid.UUID)
@ -68,7 +68,7 @@ func (h *MarketplaceHandler) CreateProduct(c *gin.Context) {
if req.TrackID != "" {
trackUUID, err := uuid.Parse(req.TrackID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid track_id format"})
response.BadRequest(c, "Invalid track_id format")
return
}
product.TrackID = &trackUUID
@ -76,18 +76,18 @@ func (h *MarketplaceHandler) CreateProduct(c *gin.Context) {
if err := h.service.CreateProduct(c.Request.Context(), product); err != nil {
if err == marketplace.ErrInvalidSeller {
c.JSON(http.StatusForbidden, gin.H{"error": "You do not own this track"})
response.Forbidden(c, "You do not own this track")
return
}
if err == marketplace.ErrTrackNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "Track not found"})
response.NotFound(c, "Track not found")
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create product"})
response.InternalServerError(c, "Failed to create product")
return
}
RespondSuccess(c, http.StatusCreated, product)
response.Created(c, product)
}
// CreateOrderRequest DTO pour la création de commande
@ -106,8 +106,9 @@ type CreateOrderRequest struct {
// @Security BearerAuth
// @Param order body CreateOrderRequest true "Order items"
// @Success 201 {object} marketplace.Order
// @Failure 400 {object} map[string]string
// @Failure 401 {object} map[string]string
// @Success 201 {object} marketplace.Order
// @Failure 400 {object} response.APIResponse "Validation Error"
// @Failure 401 {object} response.APIResponse "Unauthorized"
// @Router /api/v1/marketplace/orders [post]
func (h *MarketplaceHandler) CreateOrder(c *gin.Context) {
buyerID := c.MustGet("user_id").(uuid.UUID)
@ -122,7 +123,7 @@ func (h *MarketplaceHandler) CreateOrder(c *gin.Context) {
for _, item := range req.Items {
pid, err := uuid.Parse(item.ProductID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid product_id: " + item.ProductID})
response.BadRequest(c, "Invalid product_id: "+item.ProductID)
return
}
items = append(items, marketplace.NewOrderItem{ProductID: pid})
@ -130,11 +131,11 @@ func (h *MarketplaceHandler) CreateOrder(c *gin.Context) {
order, err := h.service.CreateOrder(c.Request.Context(), buyerID, items)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
response.InternalServerError(c, err.Error())
return
}
RespondSuccess(c, http.StatusCreated, order)
response.Created(c, order)
}
// GetDownloadURL récupère l'URL de téléchargement pour un achat
@ -146,8 +147,8 @@ func (h *MarketplaceHandler) CreateOrder(c *gin.Context) {
// @Security BearerAuth
// @Param product_id path string true "Product ID"
// @Success 200 {object} map[string]string
// @Failure 403 {object} map[string]string "No license"
// @Failure 404 {object} map[string]string
// @Failure 403 {object} response.APIResponse "No license"
// @Failure 404 {object} response.APIResponse "Not Found"
// @Router /api/v1/marketplace/download/{product_id} [get]
func (h *MarketplaceHandler) GetDownloadURL(c *gin.Context) {
userID := c.MustGet("user_id").(uuid.UUID)
@ -155,25 +156,25 @@ func (h *MarketplaceHandler) GetDownloadURL(c *gin.Context) {
productID, err := uuid.Parse(productIDStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid product_id"})
response.BadRequest(c, "Invalid product_id")
return
}
url, err := h.service.GetDownloadURL(c.Request.Context(), userID, productID)
if err != nil {
if err == marketplace.ErrNoLicense {
c.JSON(http.StatusForbidden, gin.H{"error": "No valid license for this product"})
response.Forbidden(c, "No valid license for this product")
return
}
if err == marketplace.ErrTrackNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "Track file not found"})
response.NotFound(c, "Track file not found")
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get download URL"})
response.InternalServerError(c, "Failed to get download URL")
return
}
RespondSuccess(c, http.StatusOK, gin.H{"url": url})
response.Success(c, gin.H{"url": url})
}
// ListProducts liste les produits
@ -198,9 +199,9 @@ func (h *MarketplaceHandler) ListProducts(c *gin.Context) {
products, err := h.service.ListProducts(c.Request.Context(), filters)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list products"})
response.InternalServerError(c, "Failed to list products")
return
}
RespondSuccess(c, http.StatusOK, products)
response.Success(c, products)
}