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}