/
05_person_loader.go
122 lines (99 loc) · 2.79 KB
/
05_person_loader.go
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
package advantages
import (
"database/sql"
"encoding/json"
"errors"
"net/http"
)
var (
// thrown when the supplied order does not exist in the database
errNotFound = errors.New("order not found")
)
// Loads orders based on supplied owner and order ID
type OrderLoader interface {
loadOrder(owner Owner, orderID int) (Order, error)
}
// NewLoadOrderHandler creates a new instance of LoadOrderHandler
func NewLoadOrderHandler(loader OrderLoader) *LoadOrderHandler {
return &LoadOrderHandler{
loader: loader,
}
}
// LoadOrderHandler is a HTTP handler that loads orders based on the current user and supplied user ID
type LoadOrderHandler struct {
loader OrderLoader
}
// ServeHTTP implements http.Handler
func (l *LoadOrderHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
// extract user from supplied authentication credentials
currentUser, err := l.authenticateUser(request)
if err != nil {
response.WriteHeader(http.StatusUnauthorized)
return
}
// extract order ID from request
orderID, err := l.extractOrderID(request)
if err != nil {
response.WriteHeader(http.StatusBadRequest)
return
}
// load order using the current user as a request-scoped dependency
// (with method injection)
order, err := l.loader.loadOrder(currentUser, orderID)
if err != nil {
response.WriteHeader(http.StatusInternalServerError)
return
}
// output order
encoder := json.NewEncoder(response)
err = encoder.Encode(order)
if err != nil {
response.WriteHeader(http.StatusInternalServerError)
return
}
response.WriteHeader(http.StatusOK)
}
// AuthenticatedLoader will load orders for based on the supplied owner
type AuthenticatedLoader struct {
// This pool is expensive to create. We will want to create it once and then reuse it.
db *sql.DB
}
// load the order from the database based on owner and order ID
func (a *AuthenticatedLoader) loadByOwner(owner Owner, orderID int) (*Order, error) {
order, err := a.load(orderID)
if err != nil {
return nil, err
}
if order.OwnerID != owner.ID() {
// Return not found so we do not leak information to hackers
return nil, errNotFound
}
// happy path
return order, nil
}
func (a *AuthenticatedLoader) load(orderID int) (*Order, error) {
// load order from DB
return &Order{OwnerID: 1}, nil
}
type Owner interface {
ID() int
}
type Order struct {
OwnerID int
// other order details
}
type User struct {
id int
// other attributes
}
func (u *User) ID() int {
return u.id
}
// Extract the user from the request (e.g. from a JWT token).
func (l *LoadOrderHandler) authenticateUser(request *http.Request) (*User, error) {
return &User{id: 1}, nil
}
// Extract the order ID from the request (e.g. from the URL or HTTP POST body)
func (l *LoadOrderHandler) extractOrderID(request *http.Request) (int, error) {
return 2, nil
}