Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}