Repository navigation
Expand file tree
/
Copy pathorders.go
More file actions
127 lines (111 loc) · 3.62 KB
/
Copy pathorders.go
File metadata and controls
127 lines (111 loc) · 3.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package orders
import (
"context"
"errors"
"fmt"
"time"
"example.com/orders-api/gen/serverhttp"
"github.com/devctllabs/go-libs/postgresdb"
"github.com/jackc/pgx/v5"
)
var ErrNotFound = errors.New("order not found")
// Order is a persisted customer order with its total expressed in cents.
type Order struct {
ID int64
CustomerName string
TotalCents int64
CreatedAt time.Time
}
// Store persists and retrieves orders.
type Store interface {
// Create persists an order and returns its assigned ID and creation time.
Create(ctx context.Context, customerName string, totalCents int64) (Order, error)
// Get returns the order with the given ID, or ErrNotFound when it does not exist.
Get(ctx context.Context, id int64) (Order, error)
}
// PostgresStore reads and writes orders through PostgreSQL endpoints.
type PostgresStore struct {
reader *postgresdb.Endpoint
writer *postgresdb.Endpoint
}
// NewPostgresStore creates a store using separate reader and writer endpoints.
func NewPostgresStore(reader, writer *postgresdb.Endpoint) *PostgresStore {
return &PostgresStore{reader: reader, writer: writer}
}
// Create inserts an order and returns its assigned ID and creation time.
func (s *PostgresStore) Create(ctx context.Context, customerName string, totalCents int64) (Order, error) {
const query = `
INSERT INTO orders (customer_name, total_cents)
VALUES ($1, $2)
RETURNING id, customer_name, total_cents, created_at`
var order Order
err := s.writer.QueryRow(ctx, query, customerName, totalCents).Scan(
&order.ID,
&order.CustomerName,
&order.TotalCents,
&order.CreatedAt,
)
if err != nil {
return Order{}, fmt.Errorf("insert order: %w", err)
}
return order, nil
}
// Get retrieves an order by ID, returning ErrNotFound when it does not exist.
func (s *PostgresStore) Get(ctx context.Context, id int64) (Order, error) {
const query = `
SELECT id, customer_name, total_cents, created_at
FROM orders
WHERE id = $1`
var order Order
err := s.reader.QueryRow(ctx, query, id).Scan(
&order.ID,
&order.CustomerName,
&order.TotalCents,
&order.CreatedAt,
)
if errors.Is(err, pgx.ErrNoRows) {
return Order{}, ErrNotFound
}
if err != nil {
return Order{}, fmt.Errorf("select order: %w", err)
}
return order, nil
}
// Handler serves the Orders API using a Store.
type Handler struct {
store Store
}
// NewHandler creates an Orders API handler backed by store.
func NewHandler(store Store) *Handler {
return &Handler{store: store}
}
// CreateOrder validates the request body and persists a new order.
func (h *Handler) CreateOrder(ctx context.Context, request serverhttp.CreateOrderRequestObject) (serverhttp.CreateOrderResponseObject, error) {
if request.Body == nil {
return nil, errors.New("create order body is required")
}
order, err := h.store.Create(ctx, request.Body.CustomerName, request.Body.TotalCents)
if err != nil {
return nil, err
}
return serverhttp.CreateOrder201JSONResponse(toAPIOrder(order)), nil
}
// GetOrder retrieves an order, returning a not-found response when it does not exist.
func (h *Handler) GetOrder(ctx context.Context, request serverhttp.GetOrderRequestObject) (serverhttp.GetOrderResponseObject, error) {
order, err := h.store.Get(ctx, request.Id)
if errors.Is(err, ErrNotFound) {
return serverhttp.GetOrder404JSONResponse{Message: "order not found"}, nil
}
if err != nil {
return nil, err
}
return serverhttp.GetOrder200JSONResponse(toAPIOrder(order)), nil
}
func toAPIOrder(order Order) serverhttp.Order {
return serverhttp.Order{
Id: order.ID,
CustomerName: order.CustomerName,
TotalCents: order.TotalCents,
CreatedAt: order.CreatedAt,
}
}