Search Apps Documentation Source Content File Folder Download Copy Actions Download

public_invite.gno

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