forked from kataras/iris
-
Notifications
You must be signed in to change notification settings - Fork 0
/
user_repository.go
174 lines (144 loc) · 4.35 KB
/
user_repository.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
package repositories
import (
"errors"
"sync"
"github.com/kataras/iris/_examples/mvc/login/datamodels"
)
// Query represents the visitor and action queries.
type Query func(datamodels.User) bool
// UserRepository handles the basic operations of a user entity/model.
// It's an interface in order to be testable, i.e a memory user repository or
// a connected to an sql database.
type UserRepository interface {
Exec(query Query, action Query, limit int, mode int) (ok bool)
Select(query Query) (user datamodels.User, found bool)
SelectMany(query Query, limit int) (results []datamodels.User)
InsertOrUpdate(user datamodels.User) (updatedUser datamodels.User, err error)
Delete(query Query, limit int) (deleted bool)
}
// NewUserRepository returns a new user memory-based repository,
// the one and only repository type in our example.
func NewUserRepository(source map[int64]datamodels.User) UserRepository {
return &userMemoryRepository{source: source}
}
// userMemoryRepository is a "UserRepository"
// which manages the users using the memory data source (map).
type userMemoryRepository struct {
source map[int64]datamodels.User
mu sync.RWMutex
}
const (
// ReadOnlyMode will RLock(read) the data .
ReadOnlyMode = iota
// ReadWriteMode will Lock(read/write) the data.
ReadWriteMode
)
func (r *userMemoryRepository) Exec(query Query, action Query, actionLimit int, mode int) (ok bool) {
loops := 0
if mode == ReadOnlyMode {
r.mu.RLock()
defer r.mu.RUnlock()
} else {
r.mu.Lock()
defer r.mu.Unlock()
}
for _, user := range r.source {
ok = query(user)
if ok {
if action(user) {
loops++
if actionLimit >= loops {
break // break
}
}
}
}
return
}
// Select receives a query function
// which is fired for every single user model inside
// our imaginary data source.
// When that function returns true then it stops the iteration.
//
// It returns the query's return last known boolean value
// and the last known user model
// to help callers to reduce the LOC.
//
// It's actually a simple but very clever prototype function
// I'm using everywhere since I firstly think of it,
// hope you'll find it very useful as well.
func (r *userMemoryRepository) Select(query Query) (user datamodels.User, found bool) {
found = r.Exec(query, func(m datamodels.User) bool {
user = m
return true
}, 1, ReadOnlyMode)
// set an empty datamodels.User if not found at all.
if !found {
user = datamodels.User{}
}
return
}
// SelectMany same as Select but returns one or more datamodels.User as a slice.
// If limit <=0 then it returns everything.
func (r *userMemoryRepository) SelectMany(query Query, limit int) (results []datamodels.User) {
r.Exec(query, func(m datamodels.User) bool {
results = append(results, m)
return true
}, limit, ReadOnlyMode)
return
}
// InsertOrUpdate adds or updates a user to the (memory) storage.
//
// Returns the new user and an error if any.
func (r *userMemoryRepository) InsertOrUpdate(user datamodels.User) (datamodels.User, error) {
id := user.ID
if id == 0 { // Create new action
var lastID int64
// find the biggest ID in order to not have duplications
// in productions apps you can use a third-party
// library to generate a UUID as string.
r.mu.RLock()
for _, item := range r.source {
if item.ID > lastID {
lastID = item.ID
}
}
r.mu.RUnlock()
id = lastID + 1
user.ID = id
// map-specific thing
r.mu.Lock()
r.source[id] = user
r.mu.Unlock()
return user, nil
}
// Update action based on the user.ID,
// here we will allow updating the poster and genre if not empty.
// Alternatively we could do pure replace instead:
// r.source[id] = user
// and comment the code below;
current, exists := r.Select(func(m datamodels.User) bool {
return m.ID == id
})
if !exists { // ID is not a real one, return an error.
return datamodels.User{}, errors.New("failed to update a nonexistent user")
}
// or comment these and r.source[id] = user for pure replace
if user.Username != "" {
current.Username = user.Username
}
if user.Firstname != "" {
current.Firstname = user.Firstname
}
// map-specific thing
r.mu.Lock()
r.source[id] = current
r.mu.Unlock()
return user, nil
}
func (r *userMemoryRepository) Delete(query Query, limit int) bool {
return r.Exec(query, func(m datamodels.User) bool {
delete(r.source, m.ID)
return true
}, limit, ReadWriteMode)
}