Skip to content

Commit bb1afe9

Browse files
authored
Merge pull request #26 from linuxfoundation/andrest50/groupsio-member
[LFXV2-459] feat: add GroupsIO mailing list member put/remove handlers
2 parents ede0abf + 9e2d1b4 commit bb1afe9

File tree

5 files changed

+247
-1
lines changed

5 files changed

+247
-1
lines changed

CLAUDE.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,28 @@ attachment-123
208208

209209
Simple attachment UID string.
210210

211+
### GroupsIO Mailing List Member Put Message (`lfx.put_member.groupsio_mailing_list`)
212+
213+
```json
214+
{
215+
"username": "user-123",
216+
"mailing_list_uid": "mailing-list-456"
217+
}
218+
```
219+
220+
Adds a member to a GroupsIO mailing list. This is an idempotent operation that creates the `member` relation between the user and the mailing list.
221+
222+
### GroupsIO Mailing List Member Remove Message (`lfx.remove_member.groupsio_mailing_list`)
223+
224+
```json
225+
{
226+
"username": "user-123",
227+
"mailing_list_uid": "mailing-list-456"
228+
}
229+
```
230+
231+
Removes a member from a GroupsIO mailing list. This deletes the `member` relation between the user and the mailing list.
232+
211233
### Past Meeting Recording Update Message (`lfx.update_access.past_meeting_recording`)
212234

213235
```json

charts/lfx-v2-fga-sync/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ apiVersion: v2
55
name: lfx-v2-fga-sync
66
description: LFX Platform V2 FGA Sync chart
77
type: application
8-
version: 0.2.10
8+
version: 0.2.11
99
appVersion: "latest"

handler_groupsio_member.go

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
// Copyright The Linux Foundation and each contributor to LFX.
2+
// SPDX-License-Identifier: MIT
3+
4+
// The fga-sync service.
5+
package main
6+
7+
import (
8+
"context"
9+
"encoding/json"
10+
"errors"
11+
12+
"github.com/linuxfoundation/lfx-v2-fga-sync/pkg/constants"
13+
"github.com/openfga/go-sdk/client" // Only for client types, not the full SDK
14+
)
15+
16+
// groupsIOMailingListMemberStub represents the structure of GroupsIO mailing list member data for FGA sync.
17+
type groupsIOMailingListMemberStub struct {
18+
// UID is the mailing list member ID.
19+
UID string `json:"uid"`
20+
// Username is the username (i.e. LFID) of the member. This is the identity of the user object in FGA.
21+
Username string `json:"username"`
22+
// MailingListUID is the mailing list ID for the mailing list the member belongs to.
23+
MailingListUID string `json:"mailing_list_uid"`
24+
}
25+
26+
// groupsIOMailingListMemberOperation defines the type of operation to perform on a mailing list member
27+
type groupsIOMailingListMemberOperation int
28+
29+
const (
30+
groupsIOMailingListMemberPut groupsIOMailingListMemberOperation = iota
31+
groupsIOMailingListMemberRemove
32+
)
33+
34+
// processGroupsIOMailingListMemberMessage handles the complete message processing flow
35+
// for mailing list member operations
36+
func (h *HandlerService) processGroupsIOMailingListMemberMessage(
37+
message INatsMsg,
38+
operation groupsIOMailingListMemberOperation,
39+
) error {
40+
ctx := context.Background()
41+
42+
// Log the operation type
43+
operationType := constants.OperationPut
44+
responseMsg := "sent groupsio mailing list member put response"
45+
if operation == groupsIOMailingListMemberRemove {
46+
operationType = constants.OperationRemove
47+
responseMsg = "sent groupsio mailing list member remove response"
48+
}
49+
50+
logger.With("message", string(message.Data())).InfoContext(ctx, "handling groupsio mailing list member "+operationType)
51+
52+
// Parse the event data.
53+
member := new(groupsIOMailingListMemberStub)
54+
err := json.Unmarshal(message.Data(), member)
55+
if err != nil {
56+
logger.With(errKey, err).ErrorContext(ctx, "event data parse error")
57+
return err
58+
}
59+
60+
funcLogger := logger.With("mailing_list_member", member)
61+
62+
// Validate required fields.
63+
if member.Username == "" {
64+
funcLogger.ErrorContext(ctx, "groupsio mailing list member username not found")
65+
return errors.New("groupsio mailing list member username not found")
66+
}
67+
if member.MailingListUID == "" {
68+
funcLogger.ErrorContext(ctx, "groupsio mailing list UID not found")
69+
return errors.New("groupsio mailing list UID not found")
70+
}
71+
72+
// Perform the FGA operation
73+
err = h.handleGroupsIOMailingListMemberOperation(ctx, member, operation)
74+
if err != nil {
75+
return err
76+
}
77+
78+
// Send reply if requested
79+
if message.Reply() != "" {
80+
if err = message.Respond([]byte("OK")); err != nil {
81+
funcLogger.With(errKey, err).WarnContext(ctx, "failed to send reply")
82+
return err
83+
}
84+
85+
funcLogger.InfoContext(ctx, responseMsg,
86+
"mailing_list", constants.ObjectTypeGroupsIOMailingList+member.MailingListUID,
87+
"member", constants.ObjectTypeUser+member.Username,
88+
)
89+
}
90+
91+
return nil
92+
}
93+
94+
// handleGroupsIOMailingListMemberOperation handles the FGA operation for putting/removing mailing list members
95+
func (h *HandlerService) handleGroupsIOMailingListMemberOperation(
96+
ctx context.Context,
97+
member *groupsIOMailingListMemberStub,
98+
operation groupsIOMailingListMemberOperation,
99+
) error {
100+
mailingListObject := constants.ObjectTypeGroupsIOMailingList + member.MailingListUID
101+
userPrincipal := constants.ObjectTypeUser + member.Username
102+
103+
switch operation {
104+
case groupsIOMailingListMemberPut:
105+
return h.putGroupsIOMailingListMember(ctx, userPrincipal, mailingListObject)
106+
case groupsIOMailingListMemberRemove:
107+
return h.removeGroupsIOMailingListMember(ctx, userPrincipal, mailingListObject)
108+
default:
109+
return errors.New("unknown groupsio mailing list member operation")
110+
}
111+
}
112+
113+
// putGroupsIOMailingListMember implements idempotent put operation for mailing list member relations
114+
func (h *HandlerService) putGroupsIOMailingListMember(
115+
ctx context.Context,
116+
userPrincipal,
117+
mailingListObject string,
118+
) error {
119+
// Read existing relations for this user on this mailing list
120+
existingTuples, err := h.fgaService.ReadObjectTuples(ctx, mailingListObject)
121+
if err != nil {
122+
logger.ErrorContext(ctx, "failed to read existing groupsio mailing list tuples",
123+
errKey, err,
124+
"user", userPrincipal,
125+
"mailing_list", mailingListObject,
126+
)
127+
return err
128+
}
129+
130+
// Check if the member relation already exists
131+
var hasMemberRelation bool
132+
for _, tuple := range existingTuples {
133+
if tuple.Key.User == userPrincipal && tuple.Key.Relation == constants.RelationMember {
134+
hasMemberRelation = true
135+
break
136+
}
137+
}
138+
139+
// Only write if the relation doesn't already exist
140+
if !hasMemberRelation {
141+
tuples := []client.ClientTupleKey{
142+
h.fgaService.TupleKey(userPrincipal, constants.RelationMember, mailingListObject),
143+
}
144+
145+
err = h.fgaService.WriteTuples(ctx, tuples)
146+
if err != nil {
147+
logger.ErrorContext(ctx, "failed to put groupsio mailing list member tuple",
148+
errKey, err,
149+
"user", userPrincipal,
150+
"relation", constants.RelationMember,
151+
"mailing_list", mailingListObject,
152+
)
153+
return err
154+
}
155+
156+
logger.With(
157+
"user", userPrincipal,
158+
"relation", constants.RelationMember,
159+
"mailing_list", mailingListObject,
160+
).InfoContext(ctx, "put member to groupsio mailing list")
161+
} else {
162+
logger.With(
163+
"user", userPrincipal,
164+
"relation", constants.RelationMember,
165+
"mailing_list", mailingListObject,
166+
).InfoContext(ctx, "member already has correct relation - no changes needed")
167+
}
168+
169+
return nil
170+
}
171+
172+
// removeGroupsIOMailingListMember removes the member relation for a user from a mailing list
173+
func (h *HandlerService) removeGroupsIOMailingListMember(
174+
ctx context.Context,
175+
userPrincipal,
176+
mailingListObject string,
177+
) error {
178+
err := h.fgaService.DeleteTuple(ctx, userPrincipal, constants.RelationMember, mailingListObject)
179+
if err != nil {
180+
logger.ErrorContext(ctx, "failed to remove groupsio mailing list member tuple",
181+
errKey, err,
182+
"user", userPrincipal,
183+
"relation", constants.RelationMember,
184+
"mailing_list", mailingListObject,
185+
)
186+
return err
187+
}
188+
189+
logger.With(
190+
"user", userPrincipal,
191+
"relation", constants.RelationMember,
192+
"mailing_list", mailingListObject,
193+
).InfoContext(ctx, "removed member from groupsio mailing list")
194+
195+
return nil
196+
}
197+
198+
// groupsIOMailingListMemberPutHandler handles putting a member to a GroupsIO mailing list (idempotent create/update).
199+
func (h *HandlerService) groupsIOMailingListMemberPutHandler(message INatsMsg) error {
200+
return h.processGroupsIOMailingListMemberMessage(message, groupsIOMailingListMemberPut)
201+
}
202+
203+
// groupsIOMailingListMemberRemoveHandler handles removing a member from a GroupsIO mailing list.
204+
func (h *HandlerService) groupsIOMailingListMemberRemoveHandler(message INatsMsg) error {
205+
return h.processGroupsIOMailingListMemberMessage(message, groupsIOMailingListMemberRemove)
206+
}

main.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,16 @@ func createQueueSubscriptions(handlerService HandlerService) error {
410410
handler: handlerService.groupsIOMailingListDeleteAllAccessHandler,
411411
description: "groups.io MailingList delete all access",
412412
},
413+
{
414+
subject: constants.GroupsIOMailingListPutMemberSubject,
415+
handler: handlerService.groupsIOMailingListMemberPutHandler,
416+
description: "groups.io mailing list member put",
417+
},
418+
{
419+
subject: constants.GroupsIOMailingListRemoveMemberSubject,
420+
handler: handlerService.groupsIOMailingListMemberRemoveHandler,
421+
description: "groups.io mailing list member remove",
422+
},
413423
{
414424
subject: constants.PastMeetingRecordingUpdateAccessSubject,
415425
handler: handlerService.pastMeetingRecordingUpdateAccessHandler,

pkg/constants/nats.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,14 @@ const (
9595
// The subject is of the form: lfx.delete_all_access.groupsio_mailing_list
9696
GroupsIOMailingListDeleteAllAccessSubject = "lfx.delete_all_access.groupsio_mailing_list"
9797

98+
// GroupsIOMailingListPutMemberSubject is the subject for adding groups.io mailing list members.
99+
// The subject is of the form: lfx.put_member.groupsio_mailing_list
100+
GroupsIOMailingListPutMemberSubject = "lfx.put_member.groupsio_mailing_list"
101+
102+
// GroupsIOMailingListRemoveMemberSubject is the subject for removing groups.io mailing list members.
103+
// The subject is of the form: lfx.remove_member.groupsio_mailing_list
104+
GroupsIOMailingListRemoveMemberSubject = "lfx.remove_member.groupsio_mailing_list"
105+
98106
// PastMeetingRecordingUpdateAccessSubject is the subject for the past meeting recording access control updates.
99107
// The subject is of the form: lfx.update_access.past_meeting_recording
100108
PastMeetingRecordingUpdateAccessSubject = "lfx.update_access.past_meeting_recording"

0 commit comments

Comments
 (0)