Skip to content

Commit 3c912d7

Browse files
authored
Merge pull request #35 from linuxfoundation/andrest50/project-settings-event
[LFXV2-787] Add NATS event for project settings updates
2 parents db85f95 + 54bbb0f commit 3c912d7

File tree

8 files changed

+135
-0
lines changed

8 files changed

+135
-0
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,20 @@ This service handles the following NATS subjects for inter-service communication
3030
- `lfx.projects-api.get_logo`: Get a project logo URL from a given project UID
3131
- `lfx.projects-api.slug_to_uid`: Get a project UID from a given project slug
3232

33+
### NATS Events Published
34+
35+
This service publishes the following NATS events:
36+
37+
- `lfx.projects-api.project_settings.updated`: Published when project settings are updated. Contains both the old and new settings to allow downstream services to react to changes. Message format:
38+
39+
```json
40+
{
41+
"project_uid": "string",
42+
"old_settings": { /* ProjectSettings object */ },
43+
"new_settings": { /* ProjectSettings object */ }
44+
}
45+
```
46+
3347
### Project Tags
3448

3549
The LFX v2 Project Service generates a set of tags for projects and project settings that are sent to the indexer-service. These tags enable searchability and discoverability of projects through OpenSearch.

internal/domain/message.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ type MessageHandler interface {
2323
type MessageBuilder interface {
2424
SendIndexerMessage(ctx context.Context, subject string, message any, sync bool) error
2525
SendAccessMessage(ctx context.Context, subject string, message any, sync bool) error
26+
SendProjectEventMessage(ctx context.Context, subject string, message any) error
2627
}

internal/domain/mock.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ func (m *MockMessageBuilder) SendAccessMessage(ctx context.Context, subject stri
122122
return args.Error(0)
123123
}
124124

125+
func (m *MockMessageBuilder) SendProjectEventMessage(ctx context.Context, subject string, message any) error {
126+
args := m.Called(ctx, subject, message)
127+
return args.Error(0)
128+
}
129+
125130
// MockMessage implements Message for testing
126131
type MockMessage struct {
127132
mock.Mock

internal/domain/models/message.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,11 @@ type ProjectAccessData struct {
5353
type ProjectAccessMessage struct {
5454
Data ProjectAccessData `json:"data"`
5555
}
56+
57+
// ProjectSettingsUpdatedMessage is a NATS message published when project settings are updated.
58+
// It contains both the before and after states to allow downstream services to react to changes.
59+
type ProjectSettingsUpdatedMessage struct {
60+
ProjectUID string `json:"project_uid"`
61+
OldSettings ProjectSettings `json:"old_settings"`
62+
NewSettings ProjectSettings `json:"new_settings"`
63+
}

internal/infrastructure/nats/message.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,3 +171,22 @@ func (m *MessageBuilder) SendAccessMessage(ctx context.Context, subject string,
171171
return fmt.Errorf("unsupported access message type: %T", message)
172172
}
173173
}
174+
175+
// SendProjectEventMessage sends project event messages to NATS asynchronously.
176+
// This is used for publishing events like project settings updates, project creation, etc.
177+
func (m *MessageBuilder) SendProjectEventMessage(ctx context.Context, subject string, message any) error {
178+
messageBytes, err := json.Marshal(message)
179+
if err != nil {
180+
slog.ErrorContext(ctx, "error marshalling project event message into JSON", constants.ErrKey, err, "subject", subject)
181+
return err
182+
}
183+
184+
err = m.publishMessage(subject, messageBytes)
185+
if err != nil {
186+
slog.ErrorContext(ctx, "error publishing project event message to NATS", constants.ErrKey, err, "subject", subject)
187+
return err
188+
}
189+
190+
slog.DebugContext(ctx, "published project event message to NATS", "subject", subject)
191+
return nil
192+
}

internal/infrastructure/nats/message_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,3 +385,77 @@ func TestMessageBuilder_PublishAccessMessage_Sync(t *testing.T) {
385385
})
386386
}
387387
}
388+
389+
func TestMessageBuilder_SendProjectEventMessage(t *testing.T) {
390+
tests := []struct {
391+
name string
392+
subject string
393+
message interface{}
394+
setupMocks func(*MockNATSConn)
395+
wantErr bool
396+
}{
397+
{
398+
name: "successful send project settings updated message",
399+
subject: constants.ProjectSettingsUpdatedSubject,
400+
message: models.ProjectSettingsUpdatedMessage{
401+
ProjectUID: "test-project-uid",
402+
OldSettings: models.ProjectSettings{
403+
UID: "test-project-uid",
404+
MissionStatement: "old mission",
405+
},
406+
NewSettings: models.ProjectSettings{
407+
UID: "test-project-uid",
408+
MissionStatement: "new mission",
409+
},
410+
},
411+
setupMocks: func(mockConn *MockNATSConn) {
412+
mockConn.On("Publish", constants.ProjectSettingsUpdatedSubject, mock.MatchedBy(func(data []byte) bool {
413+
var msg models.ProjectSettingsUpdatedMessage
414+
err := json.Unmarshal(data, &msg)
415+
if err != nil {
416+
return false
417+
}
418+
return msg.ProjectUID == "test-project-uid" &&
419+
msg.OldSettings.MissionStatement == "old mission" &&
420+
msg.NewSettings.MissionStatement == "new mission"
421+
})).Return(nil)
422+
},
423+
wantErr: false,
424+
},
425+
{
426+
name: "nats publish error",
427+
subject: constants.ProjectSettingsUpdatedSubject,
428+
message: models.ProjectSettingsUpdatedMessage{
429+
ProjectUID: "test-project-uid",
430+
OldSettings: models.ProjectSettings{UID: "test"},
431+
NewSettings: models.ProjectSettings{UID: "test"},
432+
},
433+
setupMocks: func(mockConn *MockNATSConn) {
434+
mockConn.On("Publish", constants.ProjectSettingsUpdatedSubject, mock.AnythingOfType("[]uint8")).Return(errors.New("nats error"))
435+
},
436+
wantErr: true,
437+
},
438+
}
439+
440+
for _, tt := range tests {
441+
t.Run(tt.name, func(t *testing.T) {
442+
mockConn := &MockNATSConn{}
443+
tt.setupMocks(mockConn)
444+
445+
mb := &MessageBuilder{
446+
NatsConn: mockConn,
447+
}
448+
449+
ctx := context.Background()
450+
err := mb.SendProjectEventMessage(ctx, tt.subject, tt.message)
451+
452+
if tt.wantErr {
453+
assert.Error(t, err)
454+
} else {
455+
assert.NoError(t, err)
456+
}
457+
458+
mockConn.AssertExpectations(t)
459+
})
460+
}
461+
}

internal/service/project_operations.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,15 @@ func (s *ProjectsService) UpdateProjectSettings(ctx context.Context, payload *pr
593593
return s.MessageBuilder.SendAccessMessage(ctx, constants.UpdateAccessProjectSubject, msg, runSync)
594594
})
595595

596+
g.Go(func() error {
597+
msg := models.ProjectSettingsUpdatedMessage{
598+
ProjectUID: *payload.UID,
599+
OldSettings: *existingProjectSettingsDB,
600+
NewSettings: *projectSettingsDB,
601+
}
602+
return s.MessageBuilder.SendProjectEventMessage(ctx, constants.ProjectSettingsUpdatedSubject, msg)
603+
})
604+
596605
if err := g.Wait(); err != nil {
597606
// Return the first error from the goroutines.
598607
return nil, domain.ErrInternal

pkg/constants/nats.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ const (
3737
// DeleteAllAccessProjectSettingsSubject is the subject for the project settings access control deletion.
3838
// The subject is of the form: lfx.delete_all_access.project_settings
3939
DeleteAllAccessProjectSettingsSubject = "lfx.delete_all_access.project_settings"
40+
41+
// ProjectSettingsUpdatedSubject is the subject for project settings change events.
42+
// This event is published when project settings are updated, containing both before and after states.
43+
// The subject is of the form: lfx.projects-api.project_settings.updated
44+
ProjectSettingsUpdatedSubject = "lfx.projects-api.project_settings.updated"
4045
)
4146

4247
// NATS wildcard subjects that the project service handles messages about.

0 commit comments

Comments
 (0)