user_store.gno
6.44 Kb · 281 lines
1package user
2
3import "gno.land/p/akkadia/v0/ds/btree"
4
5// UserStore owns user records, opaque metadata, and address/nickname indexes.
6//
7// Data shape:
8// - metadatas: userID(uint32) -> metadata(string)
9// - properties: userID(uint32) -> map[string]string
10// - userMap: userID(uint32) -> address(string)
11// - addressIDs: address(string) -> userID(uint32)
12// - nicknames: userID(uint32) -> nickname(string)
13// - nicknameIDs: nickname(string) -> userID(uint32)
14type UserStore struct {
15 metadatas *btree.Uint32BTree
16 properties *btree.Uint32BTree
17 userMap *btree.Uint32BTree
18 addressIDs *btree.StringBTree
19 nicknames *btree.Uint32BTree
20 nicknameIDs *btree.StringBTree
21 nextUserID uint32
22}
23
24var userStore = newUserStore()
25
26func newUserStore() *UserStore {
27 return &UserStore{
28 metadatas: btree.NewUint32BTree(32),
29 properties: btree.NewUint32BTree(32),
30 userMap: btree.NewUint32BTree(32),
31 addressIDs: btree.NewStringBTree(32),
32 nicknames: btree.NewUint32BTree(32),
33 nicknameIDs: btree.NewStringBTree(32),
34 nextUserID: 1,
35 }
36}
37
38func (s *UserStore) Has(addr address) bool {
39 _, found := s.addressIDs.Get(addr.String())
40 return found
41}
42
43func (s *UserStore) Create(addr address, props map[string]string) {
44 addrStr := addr.String()
45 s.assertNotExists(addrStr)
46 nickname := props["nickname"]
47 s.assertNicknameAvailable(nickname)
48
49 id := s.createID(addr)
50 s.setNickname(id, nickname)
51 s.properties.Set(id, copyStringMap(props))
52}
53
54func (s *UserStore) Update(addr address, props map[string]string) map[string]string {
55 addrStr := addr.String()
56 s.assertExists(addrStr)
57
58 id := s.getID(addr)
59 currentVal, found := s.properties.Get(id)
60 if !found {
61 panic("user properties not found: " + addrStr)
62 }
63
64 if nickname, found := props["nickname"]; found {
65 currentNickname := s.getNickname(id)
66 if currentNickname != nickname {
67 s.assertNicknameAvailable(nickname)
68 s.unsetNickname(id)
69 s.setNickname(id, nickname)
70 }
71 }
72
73 current := currentVal.(map[string]string)
74 for key, value := range props {
75 current[key] = value
76 }
77 s.properties.Set(id, current)
78 return copyStringMap(current)
79}
80
81func (s *UserStore) Delete(addr address) {
82 addrStr := addr.String()
83 s.assertExists(addrStr)
84
85 id := s.getID(addr)
86 s.unsetNickname(id)
87 s.userMap.Remove(id)
88 s.addressIDs.Remove(addrStr)
89 s.metadatas.Remove(id)
90 s.properties.Remove(id)
91}
92
93func (s *UserStore) SetMetadata(addr address, metadata string) {
94 id := s.getID(addr)
95 s.metadatas.Set(id, metadata)
96}
97
98func (s *UserStore) Total() int {
99 return s.userMap.Size()
100}
101
102func (s *UserStore) NextID() uint32 {
103 return s.nextUserID
104}
105
106// ==================== DANGER: MUTABLE MIGRATION CAPABILITIES ====================
107//
108// The getters in this section expose mutable store internals.
109// Only authorized migration exporter paths may depend on these capabilities.
110// Runtime reads and test setup must use copy-returning methods or total helpers.
111func (s *UserStore) Metadatas() *btree.Uint32BTree {
112 return s.metadatas
113}
114
115func (s *UserStore) Properties() *btree.Uint32BTree {
116 return s.properties
117}
118
119func (s *UserStore) UserMap() *btree.Uint32BTree {
120 return s.userMap
121}
122
123func (s *UserStore) AddressIDs() *btree.StringBTree {
124 return s.addressIDs
125}
126
127func (s *UserStore) Nicknames() *btree.Uint32BTree {
128 return s.nicknames
129}
130
131func (s *UserStore) NicknameIDs() *btree.StringBTree {
132 return s.nicknameIDs
133}
134
135// ================= END DANGER: MUTABLE MIGRATION CAPABILITIES ==================
136
137func (s *UserStore) List(page, count int) []string {
138 result := make([]string, 0)
139 offset := (page - 1) * count
140
141 s.userMap.IterateByOffset(offset, count, func(id uint32, val interface{}) bool {
142 result = append(result, val.(string))
143 return false
144 })
145
146 return result
147}
148
149func (s *UserStore) AddressByNickname(nickname string) address {
150 v, found := s.nicknameIDs.Get(nickname)
151 if !found {
152 panic("user not found")
153 }
154 return s.addressByID(v.(uint32))
155}
156
157func (s *UserStore) HasNickname(nickname string) bool {
158 _, found := s.nicknameIDs.Get(nickname)
159 return found
160}
161
162func (s *UserStore) NicknameByAddress(addr address) string {
163 id, found := s.findID(addr.String())
164 if !found {
165 return ""
166 }
167 return s.getNickname(id)
168}
169
170func (s *UserStore) ListUserMetadataByAddresses(addrs ...address) []string {
171 result := []string{}
172 for _, addr := range addrs {
173 id, userFound := s.findID(addr.String())
174 if !userFound {
175 result = append(result, "")
176 continue
177 }
178 metadata, found := s.metadatas.Get(id)
179 if !found {
180 result = append(result, "")
181 continue
182 }
183 result = append(result, metadata.(string))
184 }
185 return result
186}
187
188func (s *UserStore) ListUsersByAddresses(addrs ...address) []map[string]string {
189 result := []map[string]string{}
190 for _, addr := range addrs {
191 addrStr := addr.String()
192 id, userFound := s.findID(addrStr)
193 if !userFound {
194 continue
195 }
196 props, found := s.properties.Get(id)
197 if !found {
198 continue
199 }
200 m := copyStringMap(props.(map[string]string))
201 m["address"] = addrStr
202 result = append(result, m)
203 }
204 return result
205}
206
207func (s *UserStore) assertNotExists(addrStr string) {
208 _, found := s.addressIDs.Get(addrStr)
209 if found {
210 panic("user already exists: " + addrStr)
211 }
212}
213
214func (s *UserStore) assertExists(addrStr string) {
215 _, found := s.addressIDs.Get(addrStr)
216 if !found {
217 panic("user not found: " + addrStr)
218 }
219}
220
221func (s *UserStore) assertNicknameAvailable(nickname string) {
222 _, taken := s.nicknameIDs.Get(nickname)
223 if taken {
224 panic("nickname already exists: " + nickname)
225 }
226}
227
228func (s *UserStore) createID(addr address) uint32 {
229 id := s.nextUserID
230 s.nextUserID++
231 s.userMap.Set(id, addr.String())
232 s.addressIDs.Set(addr.String(), id)
233 return id
234}
235
236func (s *UserStore) findID(addrStr string) (uint32, bool) {
237 v, found := s.addressIDs.Get(addrStr)
238 if !found {
239 return 0, false
240 }
241 return v.(uint32), true
242}
243
244func (s *UserStore) getID(addr address) uint32 {
245 addrStr := addr.String()
246 id, found := s.findID(addrStr)
247 if !found {
248 panic("user not found: " + addrStr)
249 }
250 return id
251}
252
253func (s *UserStore) addressByID(id uint32) address {
254 v, found := s.userMap.Get(id)
255 if !found {
256 panic("user not found")
257 }
258 return address(v.(string))
259}
260
261func (s *UserStore) getNickname(id uint32) string {
262 v, found := s.nicknames.Get(id)
263 if !found {
264 return ""
265 }
266 return v.(string)
267}
268
269func (s *UserStore) setNickname(id uint32, nickname string) {
270 s.nicknames.Set(id, nickname)
271 s.nicknameIDs.Set(nickname, id)
272}
273
274func (s *UserStore) unsetNickname(id uint32) {
275 oldNick, found := s.nicknames.Get(id)
276 if !found {
277 return
278 }
279 s.nicknames.Remove(id)
280 s.nicknameIDs.Remove(oldNick.(string))
281}