members.gno
8.61 Kb · 382 lines
1package basedao
2
3import (
4 "chain"
5
6 "gno.land/p/onbloc/json"
7 "gno.land/p/samcrew/avl"
8)
9
10type MembersStore struct {
11 Roles *avl.Tree // role name -> *Role
12 Members *avl.Tree // string -> *avl.Tree [roles -> struct{}]
13}
14
15type Member struct {
16 Address string
17 Roles []string
18}
19
20type RoleInfo struct {
21 Name string
22 Description string
23 Color string
24}
25
26type Role struct {
27 Name string
28 Description string
29 Color string
30 Members *avl.Tree // string -> struct{}
31}
32
33const EventAddMember = "BaseDAOAddMember"
34const EventRemoveMember = "BaseDAORemoveMember"
35
36func NewMembersStore(initialRoles []RoleInfo, initialMembers []Member) *MembersStore {
37 res := &MembersStore{
38 Roles: avl.NewTree(),
39 Members: avl.NewTree(),
40 }
41 res.setRoles(initialRoles)
42 res.setMembers(initialMembers)
43 return res
44}
45
46func (m *MembersStore) HasRole(member string, role string) bool {
47 rolesRaw, ok := m.Members.Get(member)
48 if !ok {
49 return false
50 }
51 roles, ok := rolesRaw.(*avl.Tree)
52 if !ok {
53 return false
54 }
55 return roles.Has(role)
56}
57
58func (m *MembersStore) IsMember(member string) bool {
59 return m.Members.Has(member)
60}
61
62func (m *MembersStore) RoleInfo(role string) RoleInfo {
63 roleDataRaw, ok := m.Roles.Get(role)
64 if !ok {
65 panic("role does not exist")
66 }
67 roleData, ok := roleDataRaw.(*Role)
68 if !ok {
69 panic("a value of memberstore.roles is not a Role, should not happen")
70 }
71 return RoleInfo{
72 Name: roleData.Name,
73 Description: roleData.Description,
74 Color: roleData.Color,
75 }
76}
77
78func (m *MembersStore) MembersCount() uint64 {
79 return uint64(m.Members.Size())
80}
81
82func (m *MembersStore) GetMembers() []string {
83 members := make([]string, 0, m.Members.Size())
84 m.Members.Iterate("", "", func(key string, value interface{}) bool {
85 members = append(members, key)
86 return false
87 })
88 return members
89}
90
91func (m *MembersStore) GetRoles() []string {
92 i := 0
93 res := make([]string, m.Roles.Size())
94 m.Roles.Iterate("", "", func(key string, value interface{}) bool {
95 res[i] = key
96 i++
97 return false
98 })
99 return res
100}
101
102func (m *MembersStore) GetMemberRoles(member string) []string {
103 rolesRaw, ok := m.Members.Get(member)
104 if !ok {
105 return []string{}
106 }
107 roles, ok := rolesRaw.(*avl.Tree)
108 if !ok {
109 return []string{}
110 }
111 i := 0
112 res := make([]string, roles.Size())
113 roles.Iterate("", "", func(key string, value interface{}) bool {
114 res[i] = key
115 i++
116 return false
117 })
118 return res
119}
120
121func (m *MembersStore) CountMemberRoles(member string) int {
122 rolesRaw, ok := m.Members.Get(member)
123 if !ok {
124 return 0
125 }
126 roles, ok := rolesRaw.(*avl.Tree)
127 if !ok {
128 return 0
129 }
130 return roles.Size()
131}
132
133func (m *MembersStore) GetMembersWithRole(role string) []string {
134 roleDataRaw, ok := m.Roles.Get(role)
135 if !ok {
136 return []string{}
137 }
138 roleData, ok := roleDataRaw.(*Role)
139 if !ok {
140 return []string{}
141 }
142 i := 0
143 res := make([]string, roleData.Members.Size())
144 roleData.Members.Iterate("", "", func(key string, value interface{}) bool {
145 res[i] = key
146 i++
147 return false
148 })
149 return res
150}
151
152func (m *MembersStore) CountMembersWithRole(role string) uint32 {
153 return uint32(len(m.GetMembersWithRole(role)))
154}
155
156func (m *MembersStore) GetMembersWithoutRole() []string {
157 var res []string
158 m.Members.Iterate("", "", func(key string, value interface{}) bool {
159 roles := m.GetMemberRoles(key)
160 if len(roles) == 0 {
161 res = append(res, key)
162 }
163 return false
164 })
165 return res
166}
167
168func (m *MembersStore) CountMembersWithoutRole() uint32 {
169 return uint32(len(m.GetMembersWithoutRole()))
170}
171
172func (m *MembersStore) setRoles(roles []RoleInfo) {
173 for _, role := range roles {
174 m.AddRole(role)
175 }
176}
177
178func (m *MembersStore) setMembers(members []Member) {
179 for _, member := range members {
180 m.AddMember(member.Address, member.Roles)
181 }
182}
183
184func (m *MembersStore) AddMember(member string, roles []string) {
185 // "" is what CallerID resolves to when it cannot name the caller.
186 if member == "" {
187 panic("member id must not be empty")
188 }
189 if m.IsMember(member) {
190 panic("member already exists")
191 }
192
193 membersRoles := avl.NewTree()
194 for _, role := range roles {
195 if !m.Roles.Has(role) {
196 panic("role: " + role + " does not exist")
197 }
198 membersRoles.Set(role, struct{}{})
199 roleDataRaw, _ := m.Roles.Get(role)
200 roleData := roleDataRaw.(*Role)
201 roleData.Members.Set(member, struct{}{})
202 }
203 m.Members.Set(member, membersRoles)
204
205 chain.Emit(EventAddMember,
206 "address", member,
207 )
208}
209
210func (m *MembersStore) RemoveMember(member string) {
211 if !m.IsMember(member) {
212 panic("member does not exist")
213 }
214
215 // A zero-member DAO is unrecoverable: assertCallerIsMember rejects every
216 // entry point, so not even AddMember can be proposed again.
217 if m.MembersCount() <= 1 {
218 panic("cannot remove the last member")
219 }
220
221 memberRolesRaw, ok := m.Members.Get(member)
222 if !ok {
223 panic("should not happen")
224 }
225 memberRoles, ok := memberRolesRaw.(*avl.Tree)
226 if !ok {
227 panic("a value of memberstore.members is not an avl.Tree, should not happen")
228 }
229
230 memberRoles.Iterate("", "", func(key string, value interface{}) bool {
231 roleRaw, ok := m.Roles.Get(key)
232 if !ok {
233 return false
234 }
235 role, ok := roleRaw.(*Role)
236 if !ok {
237 panic("a value of memberstore.roles is not a Role, should not happen")
238 }
239 role.Members.Remove(member)
240 return false
241 })
242 m.Members.Remove(member)
243
244 chain.Emit(EventRemoveMember,
245 "address", member,
246 )
247}
248
249func (m *MembersStore) AddRole(role RoleInfo) {
250 // An empty role name is not merely useless, it is destructive. Role names
251 // are substituted into rendered prose by RenderWithRolesLinks, and
252 // strings.ReplaceAll with an empty needle matches between every character:
253 // "hello world" comes back as 227 characters of repeated link. Once a DAO is
254 // published there is no way to remove the role from a render path.
255 if role.Name == "" {
256 panic("role name must not be empty")
257 }
258 if m.Roles.Has(role.Name) {
259 panic("role already exists")
260 }
261
262 roleData := &Role{
263 Name: role.Name,
264 Description: role.Description,
265 Color: role.Color,
266 Members: avl.NewTree(),
267 }
268
269 m.Roles.Set(role.Name, roleData)
270}
271
272func (m *MembersStore) RemoveRole(role string) {
273 roleDataRaw, ok := m.Roles.Get(role)
274 if !ok {
275 panic("role does not exist")
276 }
277 roleData, ok := roleDataRaw.(*Role)
278 if !ok {
279 panic("a value of memberstore.roles is not a Role, should not happen")
280 }
281
282 roleData.Members.Iterate("", "", func(key string, value interface{}) bool {
283 memberRaw, ok := m.Members.Get(key)
284 if !ok {
285 return false
286 }
287 member, ok := memberRaw.(*avl.Tree)
288 if !ok {
289 panic("a value of memberstore.members is not an avl.Tree, should not happen")
290 }
291 member.Remove(role)
292 return false
293 })
294 m.Roles.Remove(role)
295}
296
297func (m *MembersStore) AddRoleToMember(member string, role string) {
298 if !m.IsMember(member) {
299 panic("member does not exist")
300 }
301 if !m.Roles.Has(role) {
302 panic("role " + role + " does not exist")
303 }
304 if m.HasRole(member, role) {
305 panic("member already has the role")
306 }
307
308 memberRolesRaw, ok := m.Members.Get(member)
309 if !ok {
310 panic("should not happen")
311 }
312 memberRoles, ok := memberRolesRaw.(*avl.Tree)
313 if !ok {
314 panic("a value of memberstore.members is not an avl.Tree, should not happen")
315 }
316
317 roleDataRaw, ok := m.Roles.Get(role)
318 if !ok {
319 panic("should not happen")
320 }
321 roleData, ok := roleDataRaw.(*Role)
322 if !ok {
323 panic("a value of memberstore.roles is not a Role, should not happen")
324 }
325
326 roleData.Members.Set(member, struct{}{})
327 memberRoles.Set(role, struct{}{})
328}
329
330func (m *MembersStore) RemoveRoleFromMember(member string, role string) {
331 if !m.IsMember(member) {
332 panic("member does not exist")
333 }
334 if !m.Roles.Has(role) {
335 panic("role " + role + " does not exist")
336 }
337 if !m.HasRole(member, role) {
338 panic("member does not have the role")
339 }
340
341 memberRolesRaw, ok := m.Members.Get(member)
342 if !ok {
343 panic("should not happen")
344 }
345 memberRoles, ok := memberRolesRaw.(*avl.Tree)
346 if !ok {
347 panic("a value of memberstore.members is not an avl.Tree, should not happen")
348 }
349
350 roleDataRaw, ok := m.Roles.Get(role)
351 if !ok {
352 panic("should not happen")
353 }
354 roleData, ok := roleDataRaw.(*Role)
355 if !ok {
356 panic("a value of memberstore.roles is not a Role, should not happen")
357 }
358
359 memberRoles.Remove(role)
360 roleData.Members.Remove(member)
361}
362
363func (m *MembersStore) GetMembersJSON() string {
364 // XXX: replace with protoc-gen-gno
365 members := []*json.Node{}
366 for _, memberID := range m.GetMembers() {
367 roles := []*json.Node{}
368 for _, role := range m.GetMemberRoles(memberID) {
369 roles = append(roles, json.StringNode("", role))
370 }
371 members = append(members, json.ObjectNode("", map[string]*json.Node{
372 "address": json.StringNode("", memberID),
373 "roles": json.ArrayNode("", roles),
374 }))
375 }
376 node := json.ArrayNode("", members)
377 bz, err := json.Marshal(node)
378 if err != nil {
379 panic(err)
380 }
381 return string(bz)
382}