Search Apps Documentation Source Content File Folder Download Copy Actions Download

public_invite.gno

4.54 Kb ยท 179 lines
  1package boards2
  2
  3import (
  4	"chain"
  5	"chain/runtime"
  6	"strings"
  7	"time"
  8
  9	"gno.land/p/gnoland/boards"
 10	"gno.land/p/nt/avl"
 11)
 12
 13// Invite contains a user invitation.
 14type Invite struct {
 15	// User is the user to invite.
 16	User address
 17
 18	// Role is the optional role to assign to the user.
 19	Role boards.Role
 20}
 21
 22// InviteMember adds a member to the realm or to a board.
 23//
 24// A role can optionally be specified to be assigned to the new member.
 25func InviteMember(_ realm, boardID boards.ID, user address, role boards.Role) {
 26	inviteMembers(boardID, Invite{
 27		User: user,
 28		Role: role,
 29	})
 30}
 31
 32// InviteMembers adds one or more members to the realm or to a board.
 33//
 34// Board ID is only required when inviting a member to a specific board.
 35func InviteMembers(_ realm, boardID boards.ID, invites ...Invite) {
 36	inviteMembers(boardID, invites...)
 37}
 38
 39// RequestInvite request to be invited to a board.
 40func RequestInvite(_ realm, boardID boards.ID) {
 41	assertMembersUpdateIsEnabled(boardID)
 42
 43	if !runtime.PreviousRealm().IsUser() {
 44		panic("caller must be user")
 45	}
 46
 47	// TODO: Request a fee (returned on accept) or registered user to avoid spam?
 48	// TODO: Make open invite requests optional (per board)
 49
 50	board := mustGetBoard(boardID)
 51	user := runtime.PreviousRealm().Address()
 52	if board.Permissions.HasUser(user) {
 53		panic("caller is already a member")
 54	}
 55
 56	invitee := user.String()
 57	requests, found := getInviteRequests(boardID)
 58	if !found {
 59		requests = avl.NewTree()
 60		requests.Set(invitee, time.Now())
 61		gInviteRequests.Set(boardID.Key(), requests)
 62		return
 63	}
 64
 65	if requests.Has(invitee) {
 66		panic("invite request already exists")
 67	}
 68
 69	requests.Set(invitee, time.Now())
 70}
 71
 72// AcceptInvite accepts a board invite request.
 73func AcceptInvite(_ realm, boardID boards.ID, user address) {
 74	assertMembersUpdateIsEnabled(boardID)
 75	assertInviteRequestExists(boardID, user)
 76
 77	board := mustGetBoard(boardID)
 78	if board.Permissions.HasUser(user) {
 79		panic("user is already a member")
 80	}
 81
 82	caller := runtime.PreviousRealm().Address()
 83	invite := Invite{
 84		User: user,
 85		Role: RoleGuest,
 86	}
 87	args := boards.Args{caller, boardID, []Invite{invite}}
 88	board.Permissions.WithPermission(cross, caller, PermissionMemberInvite, args, func(realm) {
 89		assertMembersUpdateIsEnabled(boardID)
 90
 91		invitee := user.String()
 92		requests, found := getInviteRequests(boardID)
 93		if !found || !requests.Has(invitee) {
 94			panic("invite request not found")
 95		}
 96
 97		if board.Permissions.HasUser(user) {
 98			panic("user is already a member")
 99		}
100
101		board.Permissions.SetUserRoles(cross, user)
102		requests.Remove(invitee)
103
104		chain.Emit(
105			"MembersInvited",
106			"invitedBy", caller.String(),
107			"boardID", board.ID.String(),
108			"members", user.String()+":"+string(RoleGuest), // TODO: Support optional role assign
109		)
110	})
111}
112
113// RevokeInvite revokes a board invite request.
114func RevokeInvite(_ realm, boardID boards.ID, user address) {
115	assertInviteRequestExists(boardID, user)
116
117	board := mustGetBoard(boardID)
118	caller := runtime.PreviousRealm().Address()
119	args := boards.Args{boardID, user, RoleGuest}
120	board.Permissions.WithPermission(cross, caller, PermissionMemberInviteRevoke, args, func(realm) {
121		invitee := user.String()
122		requests, found := getInviteRequests(boardID)
123		if !found || !requests.Has(invitee) {
124			panic("invite request not found")
125		}
126
127		requests.Remove(invitee)
128
129		chain.Emit(
130			"InviteRevoked",
131			"revokedBy", caller.String(),
132			"boardID", board.ID.String(),
133			"user", user.String(),
134		)
135	})
136}
137
138func inviteMembers(boardID boards.ID, invites ...Invite) {
139	assertMembersUpdateIsEnabled(boardID)
140
141	if len(invites) == 0 {
142		panic("one or more user invites are required")
143	}
144
145	perms := mustGetPermissions(boardID)
146	caller := runtime.PreviousRealm().Address()
147	args := boards.Args{caller, boardID, invites}
148	perms.WithPermission(cross, caller, PermissionMemberInvite, args, func(realm) {
149		assertMembersUpdateIsEnabled(boardID)
150
151		users := make([]string, len(invites))
152		for _, v := range invites {
153			assertMemberAddressIsValid(v.User)
154
155			if perms.HasUser(v.User) {
156				panic("user is already a member: " + v.User.String())
157			}
158
159			// NOTE: Permissions implementation should check that role is valid
160			perms.SetUserRoles(cross, v.User, v.Role)
161			users = append(users, v.User.String()+":"+string(v.Role))
162		}
163
164		chain.Emit(
165			"MembersInvited",
166			"invitedBy", caller.String(),
167			"boardID", boardID.String(),
168			"members", strings.Join(users, ","),
169		)
170	})
171}
172
173func assertInviteRequestExists(boardID boards.ID, user address) {
174	invitee := user.String()
175	requests, found := getInviteRequests(boardID)
176	if !found || !requests.Has(invitee) {
177		panic("invite request not found")
178	}
179}