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}