Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions api/components/parameters/Page.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name: page
in: query
schema:
type: integer
minimum: 1
default: 1

8 changes: 8 additions & 0 deletions api/components/parameters/PageSize.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name: pageSize
in: query
schema:
type: integer
minimum: 1
maximum: 100
default: 20

7 changes: 7 additions & 0 deletions api/components/parameters/ProductID.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name: id
in: path
required: true
schema:
type: integer
format: int64

6 changes: 6 additions & 0 deletions api/components/parameters/Q.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
name: q
in: query
schema:
type: string
minLength: 3

7 changes: 7 additions & 0 deletions api/components/requestBodies/ProductCreate.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
description: Product creation payload
required: true
content:
application/json:
schema:
$ref: '../../schemas/ProductCreate.yaml'

5 changes: 5 additions & 0 deletions api/components/responses/Error.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
description: Error response
content:
application/json:
schema:
$ref: '../../schemas/Error.yaml'
17 changes: 6 additions & 11 deletions api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ openapi: 3.0.0
info:
title: Marketplace Demo API
version: 1.0.0
tags:
- name: Products
description: Product query and management endpoints

paths:
/products/{id}:
$ref: './paths/products_by_id.yaml'
$ref: './paths/products/item.yaml'
/products/search:
$ref: './paths/products_search.yaml'
$ref: './paths/products/search.yaml'
/products:
$ref: './paths/products_create.yaml'
$ref: './paths/products/collection.yaml'

components:
schemas:
Expand All @@ -21,11 +24,3 @@ components:
$ref: './schemas/ProductList.yaml'
Error:
$ref: './schemas/Error.yaml'

responses:
ErrorResponse:
description: Error response
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
post:
tags: [Products]
operationId: CreateProduct
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ProductCreate'
$ref: '../../components/requestBodies/ProductCreate.yaml'
responses:
'201':
description: Created product
Expand All @@ -14,5 +11,4 @@ post:
schema:
$ref: '#/components/schemas/Product'
'400':
$ref: '#/components/responses/ErrorResponse'

$ref: '../../components/responses/Error.yaml'
48 changes: 48 additions & 0 deletions api/paths/products/item.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
get:
tags: [Products]
operationId: GetProductByID
parameters:
- $ref: '../../components/parameters/ProductID.yaml'
responses:
'200':
description: Single product
content:
application/json:
schema:
$ref: '#/components/schemas/Product'
'400':
$ref: '../../components/responses/Error.yaml'
'404':
$ref: '../../components/responses/Error.yaml'

put:
tags: [Products]
operationId: UpdateProduct
parameters:
- $ref: '../../components/parameters/ProductID.yaml'
requestBody:
$ref: '../../components/requestBodies/ProductCreate.yaml'
responses:
'200':
description: Updated product
content:
application/json:
schema:
$ref: '#/components/schemas/Product'
'400':
$ref: '../../components/responses/Error.yaml'
'404':
$ref: '../../components/responses/Error.yaml'

delete:
tags: [Products]
operationId: DeleteProductByID
parameters:
- $ref: '../../components/parameters/ProductID.yaml'
responses:
'204':
description: Deleted
'400':
$ref: '../../components/responses/Error.yaml'
'404':
$ref: '../../components/responses/Error.yaml'
16 changes: 16 additions & 0 deletions api/paths/products/search.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
get:
tags: [Products]
operationId: SearchProducts
parameters:
- $ref: '../../components/parameters/Q.yaml'
- $ref: '../../components/parameters/Page.yaml'
- $ref: '../../components/parameters/PageSize.yaml'
responses:
'200':
description: List of products
content:
application/json:
schema:
$ref: '#/components/schemas/ProductList'
'400':
$ref: '../../components/responses/Error.yaml'
38 changes: 0 additions & 38 deletions api/paths/products_by_id.yaml

This file was deleted.

27 changes: 0 additions & 27 deletions api/paths/products_search.yaml

This file was deleted.

9 changes: 0 additions & 9 deletions api/responses/ErrorResponse.yaml

This file was deleted.

9 changes: 0 additions & 9 deletions api/responses/ProductListResponse.yaml

This file was deleted.

9 changes: 0 additions & 9 deletions api/responses/ProductResponse.yaml

This file was deleted.

107 changes: 70 additions & 37 deletions apps/product-query-svc/adapters/inbound/http/handler_product_write.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package httpadapter

import (
"encoding/json"
"math"
"net/http"
"errors"
"encoding/json"
"errors"
"math"
"net/http"

"github.com/fightingBald/GoTuto/apps/product-query-svc/domain"
"github.com/fightingBald/GoTuto/apps/product-query-svc/domain"
)

// DeleteProductByID implements OpenAPI operation: DELETE /products/{id}
Expand All @@ -15,40 +15,73 @@ func (s *Server) DeleteProductByID(w http.ResponseWriter, r *http.Request, id in
writeError(w, http.StatusBadRequest, "INVALID_ID", "id must be a positive integer")
return
}
if err := s.svc.DeleteProduct(r.Context(), id); err != nil {
if errors.Is(err, domain.ErrNotFound) {
writeError(w, http.StatusNotFound, "NOT_FOUND", err.Error())
return
}
writeError(w, http.StatusInternalServerError, "INTERNAL", err.Error())
return
}
w.WriteHeader(http.StatusNoContent)
if err := s.svc.DeleteProduct(r.Context(), id); err != nil {
if errors.Is(err, domain.ErrNotFound) {
writeError(w, http.StatusNotFound, "NOT_FOUND", err.Error())
return
}
writeError(w, http.StatusInternalServerError, "INTERNAL", err.Error())
return
}
w.WriteHeader(http.StatusNoContent)
}

// CreateProduct implements POST /products
func (s *Server) CreateProduct(w http.ResponseWriter, r *http.Request) {
var in ProductCreate
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
writeError(w, http.StatusBadRequest, "INVALID_JSON", "invalid request body")
return
}
// 价格从美元浮点转分(四舍五入)
cents := int64(math.Round(float64(in.Price) * 100.0))
p, err := domain.NewProduct(in.Name, cents, nil)
if err != nil {
writeError(w, http.StatusBadRequest, "VALIDATION", err.Error())
return
}
id, err := s.svc.CreateProduct(r.Context(), p)
if err != nil {
if errors.Is(err, domain.ErrValidation) {
writeError(w, http.StatusBadRequest, "VALIDATION", err.Error())
return
}
writeError(w, http.StatusInternalServerError, "INTERNAL", err.Error())
return
}
out := Product{Id: id, Name: p.Name, Price: in.Price}
writeJSON(w, http.StatusCreated, out)
var in CreateProductJSONBody
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
writeError(w, http.StatusBadRequest, "INVALID_JSON", "invalid request body")
return
}
cents := int64(math.Round(float64(in.Price) * 100.0))
p, err := domain.NewProduct(in.Name, cents, nil)
if err != nil {
writeError(w, http.StatusBadRequest, "VALIDATION", err.Error())
return
}
id, err := s.svc.CreateProduct(r.Context(), p)
if err != nil {
if errors.Is(err, domain.ErrValidation) {
writeError(w, http.StatusBadRequest, "VALIDATION", err.Error())
return
}
writeError(w, http.StatusInternalServerError, "INTERNAL", err.Error())
return
}
out := Product{Id: id, Name: p.Name, Price: float32(cents) / 100.0}
writeJSON(w, http.StatusCreated, out)
}

// UpdateProduct implements OpenAPI operation: PUT /products/{id}
func (s *Server) UpdateProduct(w http.ResponseWriter, r *http.Request, id int64) {
if id <= 0 {
writeError(w, http.StatusBadRequest, "INVALID_ID", "id must be a positive integer")
return
}
var in UpdateProductJSONBody
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
writeError(w, http.StatusBadRequest, "INVALID_JSON", "invalid request body")
return
}
cents := int64(math.Round(float64(in.Price) * 100.0))
p, err := domain.NewProduct(in.Name, cents, nil)
if err != nil {
writeError(w, http.StatusBadRequest, "VALIDATION", err.Error())
return
}
p.ID = id
updated, err := s.svc.UpdateProduct(r.Context(), p)
if err != nil {
switch {
case errors.Is(err, domain.ErrValidation):
writeError(w, http.StatusBadRequest, "VALIDATION", err.Error())
case errors.Is(err, domain.ErrNotFound):
writeError(w, http.StatusNotFound, "NOT_FOUND", err.Error())
default:
writeError(w, http.StatusInternalServerError, "INTERNAL", err.Error())
}
return
}
out := Product{Id: updated.ID, Name: updated.Name, Price: float32(updated.Price) / 100.0}
writeJSON(w, http.StatusOK, out)
}
Loading