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
8 changes: 8 additions & 0 deletions api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ info:
tags:
- name: Products
description: Product query and management endpoints
- name: Users
description: User account retrieval endpoints

paths:
/products/{id}:
Expand All @@ -13,6 +15,10 @@ paths:
$ref: './paths/products/search.yaml'
/products:
$ref: './paths/products/collection.yaml'
/users/{id}:
$ref: './paths/users/item.yaml'



components:
schemas:
Expand All @@ -22,5 +28,7 @@ components:
$ref: './schemas/ProductCreate.yaml'
ProductList:
$ref: './schemas/ProductList.yaml'
User:
$ref: './schemas/User.yaml'
Error:
$ref: './schemas/Error.yaml'
6 changes: 3 additions & 3 deletions api/paths/products/item.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ get:
tags: [Products]
operationId: GetProductByID
parameters:
- $ref: '../../components/parameters/ProductID.yaml'
- $ref: '../../components/parameters/ID.yaml'
responses:
'200':
description: Single product
Expand All @@ -19,7 +19,7 @@ put:
tags: [Products]
operationId: UpdateProduct
parameters:
- $ref: '../../components/parameters/ProductID.yaml'
- $ref: '../../components/parameters/ID.yaml'
requestBody:
$ref: '../../components/requestBodies/ProductCreate.yaml'
responses:
Expand All @@ -38,7 +38,7 @@ delete:
tags: [Products]
operationId: DeleteProductByID
parameters:
- $ref: '../../components/parameters/ProductID.yaml'
- $ref: '../../components/parameters/ID.yaml'
responses:
'204':
description: Deleted
Expand Down
17 changes: 17 additions & 0 deletions api/paths/users/item.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
get:
tags: [Users]
operationId: GetUserByID
parameters:
- $ref: '../../components/parameters/ID.yaml'
responses:
'200':
description: Single user
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'400':
$ref: '../../components/responses/Error.yaml'
'404':
$ref: '../../components/responses/Error.yaml'

21 changes: 21 additions & 0 deletions api/schemas/User.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
type: object
description: User profile returned by the API.
properties:
id:
type: integer
format: int64
readOnly: true
name:
type: string
minLength: 1
maxLength: 120
email:
type: string
format: email
maxLength: 254
createdAt:
type: string
format: date-time
readOnly: true

required: [id, name, email, createdAt]
Original file line number Diff line number Diff line change
@@ -1,29 +1,18 @@
package httpadapter

import (
"net/http"
"errors"

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

type Server struct{ svc ports.ProductService }

func NewServer(s ports.ProductService) *Server { return &Server{svc: s} }
import "net/http"

func (s *Server) GetProductByID(w http.ResponseWriter, r *http.Request, id int64) {
p, err := s.svc.GetProduct(r.Context(), id)
if err != nil {
if errors.Is(err, domain.ErrNotFound) {
writeError(w, http.StatusNotFound, "NOT_FOUND", err.Error())
} else {
writeError(w, http.StatusInternalServerError, "INTERNAL", err.Error())
}
return
}
out := Product{Id: p.ID, Name: p.Name, Price: float32(p.Price) / 100.0}
writeJSON(w, http.StatusOK, out)
if id <= 0 {
writeError(w, http.StatusBadRequest, "INVALID_ID", "id must be a positive integer")
return
}
p, err := s.products.GetProduct(r.Context(), id)
if err != nil {
writeDomainError(w, err)
return
}
writeJSON(w, http.StatusOK, presentProduct(p))
}

func (s *Server) SearchProducts(w http.ResponseWriter, r *http.Request, params SearchProductsParams) {
Expand All @@ -44,21 +33,11 @@ func (s *Server) SearchProducts(w http.ResponseWriter, r *http.Request, params S
if params.PageSize != nil {
pageSize = *params.PageSize
}
items, total, err := s.svc.SearchProducts(r.Context(), q, page, pageSize)
items, total, err := s.products.SearchProducts(r.Context(), q, page, pageSize)
if err != nil {
writeError(w, http.StatusInternalServerError, "INTERNAL", err.Error())
writeDomainError(w, err)
return
}
var out []Product
for _, it := range items {
out = append(out, Product{Id: it.ID, Name: it.Name, Price: float32(it.Price) / 100.0})
}
resp := ProductList{Items: out, Page: page, PageSize: pageSize, Total: total}
resp := ProductList{Items: presentProducts(items), Page: page, PageSize: pageSize, Total: total}
writeJSON(w, http.StatusOK, resp)
}

// Healthz 健康检查
func (s *Server) Health(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok"))
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package httpadapter

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

"github.com/fightingBald/GoTuto/apps/product-query-svc/domain"
Expand All @@ -15,12 +13,8 @@ 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())
if err := s.products.DeleteProduct(r.Context(), id); err != nil {
writeDomainError(w, err)
return
}
w.WriteHeader(http.StatusNoContent)
Expand All @@ -33,23 +27,20 @@ func (s *Server) CreateProduct(w http.ResponseWriter, r *http.Request) {
writeError(w, http.StatusBadRequest, "INVALID_JSON", "invalid request body")
return
}
cents := int64(math.Round(float64(in.Price) * 100.0))
cents := amountToCents(in.Price)
p, err := domain.NewProduct(in.Name, cents, nil)
if err != nil {
writeError(w, http.StatusBadRequest, "VALIDATION", err.Error())
writeDomainError(w, err)
return
}
id, err := s.svc.CreateProduct(r.Context(), p)
id, err := s.products.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())
writeDomainError(w, err)
return
}
out := Product{Id: id, Name: p.Name, Price: float32(cents) / 100.0}
writeJSON(w, http.StatusCreated, out)
created := *p
created.ID = id
writeJSON(w, http.StatusCreated, presentProduct(&created))
}

// UpdateProduct implements OpenAPI operation: PUT /products/{id}
Expand All @@ -63,25 +54,17 @@ func (s *Server) UpdateProduct(w http.ResponseWriter, r *http.Request, id int64)
writeError(w, http.StatusBadRequest, "INVALID_JSON", "invalid request body")
return
}
cents := int64(math.Round(float64(in.Price) * 100.0))
cents := amountToCents(in.Price)
p, err := domain.NewProduct(in.Name, cents, nil)
if err != nil {
writeError(w, http.StatusBadRequest, "VALIDATION", err.Error())
writeDomainError(w, err)
return
}
p.ID = id
updated, err := s.svc.UpdateProduct(r.Context(), p)
updated, err := s.products.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())
}
writeDomainError(w, err)
return
}
out := Product{Id: updated.ID, Name: updated.Name, Price: float32(updated.Price) / 100.0}
writeJSON(w, http.StatusOK, out)
writeJSON(w, http.StatusOK, presentProduct(updated))
}
16 changes: 16 additions & 0 deletions apps/product-query-svc/adapters/inbound/http/handler_user_read.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package httpadapter

import "net/http"

func (s *Server) GetUserByID(w http.ResponseWriter, r *http.Request, id int64) {
if id <= 0 {
writeError(w, http.StatusBadRequest, "INVALID_ID", "id must be a positive integer")
return
}
u, err := s.users.GetUser(r.Context(), id)
if err != nil {
writeDomainError(w, err)
return
}
writeJSON(w, http.StatusOK, presentUser(u))
}
Loading