Skip to content

Commit 7b4b380

Browse files
committed
fix: rename individual vote to vote response and fix FGA access config
Rename IndividualVoteInput to VoteResponseInput and update all related functions and constants to use vote_response naming for consistency with v2 system terminology. Fix FGA access configuration: - Vote: Use references instead of relations for project/committee - Vote response: Keep proper access control with vote reference - Survey response: Use owner relation and survey reference, fix AccessCheckObject Update indexer parent refs to use conditional appending instead of hardcoded template strings to avoid sending empty parent references. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: Andres Tobon <andrest2455@gmail.com>
1 parent d97f9ac commit 7b4b380

File tree

4 files changed

+88
-71
lines changed

4 files changed

+88
-71
lines changed

cmd/lfx-v1-sync-helper/handlers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ func handleKVPut(ctx context.Context, entry jetstream.KeyValueEntry) {
8787
case "itx-poll":
8888
handleVoteUpdate(ctx, key, v1Data)
8989
case "itx-poll-vote":
90-
handleIndividualVoteUpdate(ctx, key, v1Data)
90+
handleVoteResponseUpdate(ctx, key, v1Data)
9191
case "itx-surveys":
9292
handleSurveyUpdate(ctx, key, v1Data)
9393
case "itx-survey-responses":

cmd/lfx-v1-sync-helper/handlers_survey.go

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,7 @@ func sendSurveyResponseIndexerMessage(ctx context.Context, subject string, actio
571571
IndexingConfig: &indexerTypes.IndexingConfig{
572572
ObjectID: "{{ uid }}",
573573
Public: &public,
574-
AccessCheckObject: "survey_response:{{ uid }}",
574+
AccessCheckObject: "survey:{{ uid }}",
575575
AccessCheckRelation: "viewer",
576576
HistoryCheckObject: "survey_response:{{ uid }}",
577577
HistoryCheckRelation: "auditor",
@@ -602,18 +602,11 @@ func sendSurveyResponseAccessMessage(data SurveyResponseInput) error {
602602
relations := map[string][]string{}
603603
references := map[string][]string{}
604604

605-
if data.Username != "" {
606-
relations["writer"] = []string{data.Username}
607-
relations["viewer"] = []string{data.Username}
608-
}
609605
if data.SurveyUID != "" {
610606
references["survey"] = []string{data.SurveyUID}
611607
}
612-
if data.Project.ProjectUID != "" {
613-
references["project"] = []string{data.Project.ProjectUID}
614-
}
615-
if data.CommitteeUID != "" {
616-
references["committee"] = []string{data.CommitteeUID}
608+
if data.Username != "" {
609+
relations["owner"] = []string{data.Username}
617610
}
618611

619612
// Skip sending access message if there are no relations or references

cmd/lfx-v1-sync-helper/handlers_voting.go

Lines changed: 82 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ const (
1919
// IndexVoteSubject is the subject for the vote indexing.
2020
IndexVoteSubject = "lfx.index.vote"
2121

22-
// IndexIndividualVoteSubject is the subject for the individual vote indexing.
23-
IndexIndividualVoteSubject = "lfx.index.individual_vote"
22+
// IndexVoteResponseSubject is the subject for the vote response indexing.
23+
IndexVoteResponseSubject = "lfx.index.vote_response"
2424
)
2525

2626
// sendVoteIndexerMessage sends the message to the NATS server for the vote indexer.
@@ -44,6 +44,17 @@ func sendVoteIndexerMessage(ctx context.Context, subject string, action indexerC
4444

4545
// Construct the indexer message
4646
public := false
47+
nameAndAliases := []string{}
48+
parentRefs := []string{}
49+
if data.Name != "" {
50+
nameAndAliases = append(nameAndAliases, data.Name)
51+
}
52+
if data.ProjectUID != "" {
53+
parentRefs = append(parentRefs, fmt.Sprintf("project:%s", data.ProjectUID))
54+
}
55+
if data.CommitteeUID != "" {
56+
parentRefs = append(parentRefs, fmt.Sprintf("committee:%s", data.CommitteeUID))
57+
}
4758
message := indexerTypes.IndexerMessageEnvelope{
4859
Action: action,
4960
Headers: headers,
@@ -56,8 +67,8 @@ func sendVoteIndexerMessage(ctx context.Context, subject string, action indexerC
5667
HistoryCheckObject: "vote:{{ uid }}",
5768
HistoryCheckRelation: "auditor",
5869
SortName: "{{ name }}",
59-
NameAndAliases: []string{"{{ name }}"},
60-
ParentRefs: []string{"project:{{ project_uid }}", "committee:{{ committee_uid }}"},
70+
NameAndAliases: nameAndAliases,
71+
ParentRefs: parentRefs,
6172
Fulltext: "{{ name }} {{ description }}",
6273
},
6374
}
@@ -79,26 +90,26 @@ func sendVoteIndexerMessage(ctx context.Context, subject string, action indexerC
7990

8091
// sendVoteAccessMessage sends the message to the NATS server for the vote access control.
8192
func sendVoteAccessMessage(vote InputVote) error {
82-
relations := map[string][]string{}
93+
references := map[string][]string{}
8394
if vote.ProjectUID != "" {
84-
relations["project"] = []string{vote.ProjectUID}
95+
references["project"] = []string{vote.ProjectUID}
8596
}
8697
if vote.CommitteeUID != "" {
87-
relations["committee"] = []string{vote.CommitteeUID}
98+
references["committee"] = []string{vote.CommitteeUID}
8899
}
89100

90-
// Skip sending access message if there are no relations
91-
if len(relations) == 0 {
101+
// Skip sending access message if there are no references
102+
if len(references) == 0 {
92103
return nil
93104
}
94105

95106
accessMsg := GenericFGAMessage{
96107
ObjectType: "vote",
97108
Operation: "update_access",
98109
Data: map[string]interface{}{
99-
"uid": vote.UID,
100-
"public": false,
101-
"relations": relations,
110+
"uid": vote.UID,
111+
"public": false,
112+
"references": references,
102113
},
103114
}
104115
accessMsgBytes, err := json.Marshal(accessMsg)
@@ -273,8 +284,8 @@ func handleVoteUpdate(ctx context.Context, key string, v1Data map[string]any) {
273284
funcLogger.InfoContext(ctx, "successfully sent vote indexer and access messages")
274285
}
275286

276-
// sendIndividualVoteIndexerMessage sends the message to the NATS server for the individual vote indexer.
277-
func sendIndividualVoteIndexerMessage(ctx context.Context, subject string, action indexerConstants.MessageAction, data IndividualVoteInput) error {
287+
// sendVoteResponseIndexerMessage sends the message to the NATS server for the vote response indexer.
288+
func sendVoteResponseIndexerMessage(ctx context.Context, subject string, action indexerConstants.MessageAction, data VoteResponseInput) error {
278289
headers := make(map[string]string)
279290

280291
// Extract authorization from context if available
@@ -294,22 +305,35 @@ func sendIndividualVoteIndexerMessage(ctx context.Context, subject string, actio
294305

295306
// Construct the indexer message
296307
public := false
308+
nameAndAliases := []string{}
309+
parentRefs := []string{}
310+
if data.Username != "" {
311+
nameAndAliases = append(nameAndAliases, data.Username)
312+
}
313+
if data.ProjectUID != "" {
314+
parentRefs = append(parentRefs, fmt.Sprintf("project:%s", data.ProjectUID))
315+
}
316+
if data.VoteUID != "" {
317+
parentRefs = append(parentRefs, fmt.Sprintf("vote:%s", data.VoteUID))
318+
}
319+
indexingConfig := &indexerTypes.IndexingConfig{
320+
ObjectID: "{{ uid }}",
321+
Public: &public,
322+
AccessCheckObject: "vote:{{ uid }}",
323+
AccessCheckRelation: "viewer",
324+
HistoryCheckObject: "vote_response:{{ uid }}",
325+
HistoryCheckRelation: "auditor",
326+
SortName: "{{ user_name }}",
327+
NameAndAliases: nameAndAliases,
328+
ParentRefs: parentRefs,
329+
Fulltext: "{{ user_name }}",
330+
}
331+
297332
message := indexerTypes.IndexerMessageEnvelope{
298-
Action: action,
299-
Headers: headers,
300-
Data: data,
301-
IndexingConfig: &indexerTypes.IndexingConfig{
302-
ObjectID: "{{ uid }}",
303-
Public: &public,
304-
AccessCheckObject: "individual_vote:{{ uid }}",
305-
AccessCheckRelation: "viewer",
306-
HistoryCheckObject: "individual_vote:{{ uid }}",
307-
HistoryCheckRelation: "auditor",
308-
SortName: "{{ user_name }}",
309-
NameAndAliases: []string{"{{ user_name }}"},
310-
ParentRefs: []string{"project:{{ project_uid }}", "vote:{{ vote_uid }}"},
311-
Fulltext: "{{ user_name }}",
312-
},
333+
Action: action,
334+
Headers: headers,
335+
Data: data,
336+
IndexingConfig: indexingConfig,
313337
}
314338

315339
messageBytes, err := json.Marshal(message)
@@ -327,8 +351,8 @@ func sendIndividualVoteIndexerMessage(ctx context.Context, subject string, actio
327351
return nil
328352
}
329353

330-
// sendIndividualVoteAccessMessage sends the message to the NATS server for the individual vote access control.
331-
func sendIndividualVoteAccessMessage(data IndividualVoteInput) error {
354+
// sendVoteResponseAccessMessage sends the message to the NATS server for the vote response access control.
355+
func sendVoteResponseAccessMessage(data VoteResponseInput) error {
332356
relations := map[string][]string{}
333357
if data.Username != "" {
334358
relations["writer"] = []string{data.Username}
@@ -349,7 +373,7 @@ func sendIndividualVoteAccessMessage(data IndividualVoteInput) error {
349373
}
350374

351375
accessMsg := GenericFGAMessage{
352-
ObjectType: "individual_vote",
376+
ObjectType: "vote_response",
353377
Operation: "update_access",
354378
Data: map[string]interface{}{
355379
"uid": data.UID,
@@ -371,8 +395,8 @@ func sendIndividualVoteAccessMessage(data IndividualVoteInput) error {
371395
return nil
372396
}
373397

374-
func convertMapToInputIndividualVote(ctx context.Context, v1Data map[string]any) (*IndividualVoteInput, error) {
375-
funcLogger := logger.With("handler", "individual_vote")
398+
func convertMapToInputVoteResponse(ctx context.Context, v1Data map[string]any) (*VoteResponseInput, error) {
399+
funcLogger := logger.With("handler", "vote_response")
376400

377401
// Convert map to JSON bytes
378402
jsonBytes, err := json.Marshal(v1Data)
@@ -386,8 +410,8 @@ func convertMapToInputIndividualVote(ctx context.Context, v1Data map[string]any)
386410
return nil, fmt.Errorf("failed to unmarshal JSON into VoteDB: %w", err)
387411
}
388412

389-
// Convert VoteDB to IndividualVoteInput
390-
individualVote := IndividualVoteInput{
413+
// Convert VoteDB to VoteResponseInput
414+
voteResponse := VoteResponseInput{
391415
UID: voteDB.VoteID, // Use vote_id as UID for v2 system
392416
VoteID: voteDB.VoteID,
393417
VoteUID: voteDB.PollID, // Use poll_id as VoteUID
@@ -450,14 +474,14 @@ func convertMapToInputIndividualVote(ctx context.Context, v1Data map[string]any)
450474
pollAnswerInput.RankedUserChoice = append(pollAnswerInput.RankedUserChoice, rankedChoiceInput)
451475
}
452476

453-
individualVote.PollAnswers = append(individualVote.PollAnswers, pollAnswerInput)
477+
voteResponse.PollAnswers = append(voteResponse.PollAnswers, pollAnswerInput)
454478
}
455479

456480
// Use the v1 project ID to get the v2 project UID.
457481
if voteDB.ProjectID != "" {
458482
projectMappingKey := fmt.Sprintf("project.sfid.%s", voteDB.ProjectID)
459483
if entry, err := mappingsKV.Get(ctx, projectMappingKey); err == nil {
460-
individualVote.ProjectUID = string(entry.Value())
484+
voteResponse.ProjectUID = string(entry.Value())
461485
} else {
462486
funcLogger.With(errKey, err).
463487
With("field", "project_id").
@@ -466,65 +490,65 @@ func convertMapToInputIndividualVote(ctx context.Context, v1Data map[string]any)
466490
}
467491
}
468492

469-
return &individualVote, nil
493+
return &voteResponse, nil
470494
}
471495

472-
// handleIndividualVoteUpdate processes an individual vote update from itx-individual-vote records.
473-
func handleIndividualVoteUpdate(ctx context.Context, key string, v1Data map[string]any) {
496+
// handleVoteResponseUpdate processes a vote response update from itx-poll-vote records.
497+
func handleVoteResponseUpdate(ctx context.Context, key string, v1Data map[string]any) {
474498
// Check if we should skip this sync operation.
475499
if shouldSkipSync(ctx, v1Data) {
476500
return
477501
}
478502

479503
funcLogger := logger.With("key", key)
480504

481-
funcLogger.DebugContext(ctx, "processing individual vote update")
505+
funcLogger.DebugContext(ctx, "processing vote response update")
482506

483-
// Convert v1Data map to IndividualVoteInput struct
484-
individualVote, err := convertMapToInputIndividualVote(ctx, v1Data)
507+
// Convert v1Data map to VoteResponseInput struct
508+
voteResponse, err := convertMapToInputVoteResponse(ctx, v1Data)
485509
if err != nil {
486-
funcLogger.With(errKey, err).ErrorContext(ctx, "failed to convert v1Data to IndividualVoteInput")
510+
funcLogger.With(errKey, err).ErrorContext(ctx, "failed to convert v1Data to VoteResponseInput")
487511
return
488512
}
489513

490514
// Extract the individual vote UID
491-
uid := individualVote.UID
515+
uid := voteResponse.UID
492516
if uid == "" {
493-
funcLogger.ErrorContext(ctx, "missing or invalid uid in v1 individual vote data")
517+
funcLogger.ErrorContext(ctx, "missing or invalid uid in v1 vote response data")
494518
return
495519
}
496-
funcLogger = funcLogger.With("individual_vote_id", uid)
520+
funcLogger = funcLogger.With("vote_response_id", uid)
497521

498522
// Check if parent project exists in mappings before proceeding. Because
499-
// convertMapToInputIndividualVote has already looked up the SFID project ID
523+
// convertMapToInputVoteResponse has already looked up the SFID project ID
500524
// mapping, we don't need to do it again: we can just check if ProjectID (v2
501525
// UID) is set.
502-
if individualVote.ProjectUID == "" {
503-
funcLogger.With("project_id", individualVote.ProjectID).InfoContext(ctx, "skipping individual vote sync - parent project not found in mappings")
526+
if voteResponse.ProjectUID == "" {
527+
funcLogger.With("project_id", voteResponse.ProjectID).InfoContext(ctx, "skipping vote response sync - parent project not found in mappings")
504528
return
505529
}
506530

507-
mappingKey := fmt.Sprintf("individual_vote.%s", uid)
531+
mappingKey := fmt.Sprintf("vote_response.%s", uid)
508532
indexerAction := indexerConstants.ActionCreated
509533
if _, err := mappingsKV.Get(ctx, mappingKey); err == nil {
510534
indexerAction = indexerConstants.ActionUpdated
511535
}
512536

513-
if err := sendIndividualVoteIndexerMessage(ctx, IndexIndividualVoteSubject, indexerAction, *individualVote); err != nil {
514-
funcLogger.With(errKey, err).ErrorContext(ctx, "failed to send individual vote indexer message")
537+
if err := sendVoteResponseIndexerMessage(ctx, IndexVoteResponseSubject, indexerAction, *voteResponse); err != nil {
538+
funcLogger.With(errKey, err).ErrorContext(ctx, "failed to send vote response indexer message")
515539
return
516540
}
517541

518-
if err := sendIndividualVoteAccessMessage(*individualVote); err != nil {
519-
funcLogger.With(errKey, err).ErrorContext(ctx, "failed to send individual vote access message")
542+
if err := sendVoteResponseAccessMessage(*voteResponse); err != nil {
543+
funcLogger.With(errKey, err).ErrorContext(ctx, "failed to send vote response access message")
520544
return
521545
}
522546

523547
if uid != "" {
524548
if _, err := mappingsKV.Put(ctx, mappingKey, []byte("1")); err != nil {
525-
funcLogger.With(errKey, err).WarnContext(ctx, "failed to store individual vote mapping")
549+
funcLogger.With(errKey, err).WarnContext(ctx, "failed to store vote response mapping")
526550
}
527551
}
528552

529-
funcLogger.InfoContext(ctx, "successfully sent individual vote indexer and access messages")
553+
funcLogger.InfoContext(ctx, "successfully sent vote response indexer and access messages")
530554
}

cmd/lfx-v1-sync-helper/models_voting.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,8 @@ type InputVote struct {
176176
AllowAbstain bool `json:"allow_abstain"`
177177
}
178178

179-
// IndividualVoteInput is the input model for an individual vote.
180-
type IndividualVoteInput struct {
179+
// VoteResponseInput is the input model for a vote response.
180+
type VoteResponseInput struct {
181181
UID string `json:"uid"` // new system primary key attribute (same as [VoteID])
182182
VoteID string `json:"vote_id"` // old system primary key attribute
183183
VoteUID string `json:"vote_uid"` // new system poll/vote UID (same as [PollID])

0 commit comments

Comments
 (0)