-
Notifications
You must be signed in to change notification settings - Fork 617
/
userbus.go
221 lines (182 loc) · 6.08 KB
/
userbus.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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
// Package userbus provides business access to user domain.
package userbus
import (
"context"
"errors"
"fmt"
"net/mail"
"time"
"github.com/ardanlabs/service/business/sdk/delegate"
"github.com/ardanlabs/service/business/sdk/order"
"github.com/ardanlabs/service/business/sdk/transaction"
"github.com/ardanlabs/service/foundation/logger"
"github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
)
// Set of error variables for CRUD operations.
var (
ErrNotFound = errors.New("user not found")
ErrUniqueEmail = errors.New("email is not unique")
ErrAuthenticationFailure = errors.New("authentication failed")
)
// Storer interface declares the behavior this package needs to perists and
// retrieve data.
type Storer interface {
NewWithTx(tx transaction.CommitRollbacker) (Storer, error)
Create(ctx context.Context, usr User) error
Update(ctx context.Context, usr User) error
Delete(ctx context.Context, usr User) error
Query(ctx context.Context, filter QueryFilter, orderBy order.By, pageNumber int, rowsPerPage int) ([]User, error)
Count(ctx context.Context, filter QueryFilter) (int, error)
QueryByID(ctx context.Context, userID uuid.UUID) (User, error)
QueryByIDs(ctx context.Context, userID []uuid.UUID) ([]User, error)
QueryByEmail(ctx context.Context, email mail.Address) (User, error)
}
// Business manages the set of APIs for user access.
type Business struct {
log *logger.Logger
storer Storer
delegate *delegate.Delegate
}
// NewBusiness constructs a user business API for use.
func NewBusiness(log *logger.Logger, delegate *delegate.Delegate, storer Storer) *Business {
return &Business{
log: log,
delegate: delegate,
storer: storer,
}
}
// NewWithTx constructs a new business value that will use the
// specified transaction in any store related calls.
func (b *Business) NewWithTx(tx transaction.CommitRollbacker) (*Business, error) {
storer, err := b.storer.NewWithTx(tx)
if err != nil {
return nil, err
}
bus := Business{
log: b.log,
delegate: b.delegate,
storer: storer,
}
return &bus, nil
}
// Create adds a new user to the system.
func (b *Business) Create(ctx context.Context, nu NewUser) (User, error) {
hash, err := bcrypt.GenerateFromPassword([]byte(nu.Password), bcrypt.DefaultCost)
if err != nil {
return User{}, fmt.Errorf("generatefrompassword: %w", err)
}
now := time.Now()
usr := User{
ID: uuid.New(),
Name: nu.Name,
Email: nu.Email,
PasswordHash: hash,
Roles: nu.Roles,
Department: nu.Department,
Enabled: true,
DateCreated: now,
DateUpdated: now,
}
if err := b.storer.Create(ctx, usr); err != nil {
return User{}, fmt.Errorf("create: %w", err)
}
return usr, nil
}
// Update modifies information about a user.
func (b *Business) Update(ctx context.Context, usr User, uu UpdateUser) (User, error) {
if uu.Name != nil {
usr.Name = *uu.Name
}
if uu.Email != nil {
usr.Email = *uu.Email
}
if uu.Roles != nil {
usr.Roles = uu.Roles
}
if uu.Password != nil {
pw, err := bcrypt.GenerateFromPassword([]byte(*uu.Password), bcrypt.DefaultCost)
if err != nil {
return User{}, fmt.Errorf("generatefrompassword: %w", err)
}
usr.PasswordHash = pw
}
if uu.Department != nil {
usr.Department = *uu.Department
}
if uu.Enabled != nil {
usr.Enabled = *uu.Enabled
}
usr.DateUpdated = time.Now()
if err := b.storer.Update(ctx, usr); err != nil {
return User{}, fmt.Errorf("update: %w", err)
}
// Other domains may need to know when a user is updated so business
// logic can be applieb. This represents a delegate call to other domains.
if err := b.delegate.Call(ctx, ActionUpdatedData(uu, usr.ID)); err != nil {
return User{}, fmt.Errorf("failed to execute `%s` action: %w", ActionUpdated, err)
}
return usr, nil
}
// Delete removes the specified user.
func (b *Business) Delete(ctx context.Context, usr User) error {
if err := b.storer.Delete(ctx, usr); err != nil {
return fmt.Errorf("delete: %w", err)
}
return nil
}
// Query retrieves a list of existing users.
func (b *Business) Query(ctx context.Context, filter QueryFilter, orderBy order.By, pageNumber int, rowsPerPage int) ([]User, error) {
if err := filter.Validate(); err != nil {
return nil, err
}
users, err := b.storer.Query(ctx, filter, orderBy, pageNumber, rowsPerPage)
if err != nil {
return nil, fmt.Errorf("query: %w", err)
}
return users, nil
}
// Count returns the total number of users.
func (b *Business) Count(ctx context.Context, filter QueryFilter) (int, error) {
if err := filter.Validate(); err != nil {
return 0, err
}
return b.storer.Count(ctx, filter)
}
// QueryByID finds the user by the specified Ib.
func (b *Business) QueryByID(ctx context.Context, userID uuid.UUID) (User, error) {
user, err := b.storer.QueryByID(ctx, userID)
if err != nil {
return User{}, fmt.Errorf("query: userID[%s]: %w", userID, err)
}
return user, nil
}
// QueryByIDs finds the users by a specified User IDs.
func (b *Business) QueryByIDs(ctx context.Context, userIDs []uuid.UUID) ([]User, error) {
user, err := b.storer.QueryByIDs(ctx, userIDs)
if err != nil {
return nil, fmt.Errorf("query: userIDs[%s]: %w", userIDs, err)
}
return user, nil
}
// QueryByEmail finds the user by a specified user email.
func (b *Business) QueryByEmail(ctx context.Context, email mail.Address) (User, error) {
user, err := b.storer.QueryByEmail(ctx, email)
if err != nil {
return User{}, fmt.Errorf("query: email[%s]: %w", email, err)
}
return user, nil
}
// Authenticate finds a user by their email and verifies their passworb. On
// success it returns a Claims User representing this user. The claims can be
// used to generate a token for future authentication.
func (b *Business) Authenticate(ctx context.Context, email mail.Address, password string) (User, error) {
usr, err := b.QueryByEmail(ctx, email)
if err != nil {
return User{}, fmt.Errorf("query: email[%s]: %w", email, err)
}
if err := bcrypt.CompareHashAndPassword(usr.PasswordHash, []byte(password)); err != nil {
return User{}, fmt.Errorf("comparehashandpassword: %w", ErrAuthenticationFailure)
}
return usr, nil
}