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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

sharedmembers.gno

5.13 Kb · 196 lines
  1// Package sharedmembers is the Remix Lab reference shared membership
  2// service: one membership set that several apps consult.
  3//
  4// Other realms get read-only access through RoleOf, Size and Snapshot,
  5// which together implement core.MembershipReader (membership.read/v1).
  6// Mutations happen only through this realm's own authorized entry points;
  7// reading a member's role never grants the reader any power over that
  8// member.
  9package sharedmembers
 10
 11import (
 12	"strconv"
 13	"strings"
 14
 15	"gno.land/p/g1lnkytfqcjwllws63gvf0mv9yt04aswy4y9amhm/remixlab/core/v1"
 16	"gno.land/p/g1lnkytfqcjwllws63gvf0mv9yt04aswy4y9amhm/remixlab/membership/v1"
 17	"gno.land/p/g1lnkytfqcjwllws63gvf0mv9yt04aswy4y9amhm/remixlab/policies/v1"
 18	"gno.land/p/g1lnkytfqcjwllws63gvf0mv9yt04aswy4y9amhm/remixlab/render/v1"
 19)
 20
 21const (
 22	pkgPath  = "gno.land/r/g1lnkytfqcjwllws63gvf0mv9yt04aswy4y9amhm/remixlab/sharedmembers"
 23	pageSize = 25
 24
 25	// InterfaceVersion is the read interface this realm provides.
 26	InterfaceVersion = "membership.read/v1"
 27)
 28
 29const (
 30	opInvite  = "members.invite"
 31	opRemove  = "members.remove"
 32	opSetRole = "members.set_role"
 33)
 34
 35var (
 36	members *membership.Set
 37	policy  *policies.Policy
 38)
 39
 40func init(cur realm) {
 41	deployer := cur.Previous()
 42	if deployer.IsUser() && deployer.Address().IsValid() {
 43		setup(deployer.Address())
 44	}
 45}
 46
 47func setup(owner address) {
 48	members = membership.New(membership.Config{
 49		JoinMode:   membership.JoinInvitation,
 50		MaxMembers: 100,
 51	}, owner)
 52	policy = policies.New(members)
 53	policy.Define(opInvite, policies.RuleOwner, policies.RuleReviewer)
 54	policy.Define(opRemove, policies.RuleOwner, policies.RuleOwner)
 55	policy.Define(opSetRole, policies.RuleOwner, policies.RuleOwner)
 56}
 57
 58// --- membership.read/v1 ---
 59
 60// RoleOf returns addr's role.
 61func RoleOf(addr address) core.Role {
 62	if members == nil {
 63		return core.RoleNone
 64	}
 65	return members.RoleOf(addr)
 66}
 67
 68// Size returns the number of members.
 69func Size() int {
 70	if members == nil {
 71		return 0
 72	}
 73	return members.Size()
 74}
 75
 76// Snapshot returns members with at least minRole, or ok=false when more
 77// than max qualify or the service is not initialized. The returned slice is
 78// a fresh copy.
 79func Snapshot(minRole core.Role, max int) ([]address, bool) {
 80	if members == nil {
 81		return nil, false
 82	}
 83	return members.Snapshot(minRole, max)
 84}
 85
 86// Members pages through members ordered by address.
 87func Members(offset, limit int) []core.MemberView {
 88	if members == nil {
 89		return nil
 90	}
 91	if limit < 1 || limit > pageSize {
 92		limit = pageSize
 93	}
 94	return members.List(offset, limit)
 95}
 96
 97// --- Authorized mutations ---
 98
 99// Invite lets addr join with role. Inviting a reviewer or owner also
100// requires the set-role permission.
101func Invite(cur realm, addr address, role string) {
102	actor := authorize(cur, opInvite)
103	r, err := core.ParseRole(role)
104	must(err)
105	if r > core.RoleMember {
106		must(policy.Authorize(opSetRole, actor))
107	}
108	must(members.Invite(addr, r))
109}
110
111// RevokeInvite cancels a pending invitation. Revoking an invitation for a
112// reviewer or owner also requires the set-role permission.
113func RevokeInvite(cur realm, addr address) {
114	actor := authorize(cur, opInvite)
115	if r, ok := members.InviteRole(addr); ok && r > core.RoleMember {
116		must(policy.Authorize(opSetRole, actor))
117	}
118	must(members.RevokeInvite(addr))
119}
120
121// Invites pages through pending invitations ordered by address.
122func Invites(offset, limit int) []core.MemberView {
123	if members == nil {
124		return nil
125	}
126	if limit < 1 || limit > pageSize {
127		limit = pageSize
128	}
129	return members.Invites(offset, limit)
130}
131
132// Join accepts the caller's pending invitation.
133func Join(cur realm) {
134	ready()
135	must(members.Join(cur.Previous().Address()))
136}
137
138// Leave removes the caller.
139func Leave(cur realm) {
140	ready()
141	must(members.Leave(cur.Previous().Address()))
142}
143
144// RemoveMember removes addr.
145func RemoveMember(cur realm, addr address) {
146	authorize(cur, opRemove)
147	must(members.Remove(addr))
148}
149
150// SetRole changes addr's role.
151func SetRole(cur realm, addr address, role string) {
152	authorize(cur, opSetRole)
153	r, err := core.ParseRole(role)
154	must(err)
155	must(members.SetRole(addr, r))
156}
157
158// Render lists the members.
159func Render(path string) string {
160	if members == nil {
161		return render.Heading(1, "Shared membership") + "This service is not initialized.\n"
162	}
163	_, q := render.ParsePath(path)
164	pages := render.PageCount(members.Size(), pageSize)
165	page := render.Page(q, pages)
166	var rows [][]string
167	for _, m := range members.List((page-1)*pageSize, pageSize) {
168		rows = append(rows, render.Row(m.Address.String(), m.Role.String()))
169	}
170	var sb strings.Builder
171	sb.WriteString(render.Heading(1, "Shared membership"))
172	sb.WriteString("Apps read this list through " + render.Code(InterfaceVersion) + ". ")
173	sb.WriteString(strconv.Itoa(members.Size()) + " members.\n\n")
174	sb.WriteString(render.Table([]string{"Address", "Role"}, rows))
175	sb.WriteString(render.Pager(pkgPath, "", page, pages))
176	return sb.String()
177}
178
179func authorize(cur realm, op string) address {
180	ready()
181	actor := cur.Previous().Address()
182	must(policy.Authorize(op, actor))
183	return actor
184}
185
186func ready() {
187	if members == nil {
188		panic(core.Errorf(core.ErrInvalidState, "service is not initialized").Error())
189	}
190}
191
192func must(err error) {
193	if err != nil {
194		panic(err.Error())
195	}
196}