@@ -53,13 +53,13 @@ func NewStoreEnqueuer(cfg codersdk.NotificationsConfig, store Store, helpers tem
53
53
}
54
54
55
55
// Enqueue queues a notification message for later delivery, assumes no structured input data.
56
- func (s * StoreEnqueuer ) Enqueue (ctx context.Context , userID , templateID uuid.UUID , labels map [string ]string , createdBy string , targets ... uuid.UUID ) (* uuid.UUID , error ) {
56
+ func (s * StoreEnqueuer ) Enqueue (ctx context.Context , userID , templateID uuid.UUID , labels map [string ]string , createdBy string , targets ... uuid.UUID ) ([] uuid.UUID , error ) {
57
57
return s .EnqueueWithData (ctx , userID , templateID , labels , nil , createdBy , targets ... )
58
58
}
59
59
60
60
// Enqueue queues a notification message for later delivery.
61
61
// Messages will be dequeued by a notifier later and dispatched.
62
- func (s * StoreEnqueuer ) EnqueueWithData (ctx context.Context , userID , templateID uuid.UUID , labels map [string ]string , data map [string ]any , createdBy string , targets ... uuid.UUID ) (* uuid.UUID , error ) {
62
+ func (s * StoreEnqueuer ) EnqueueWithData (ctx context.Context , userID , templateID uuid.UUID , labels map [string ]string , data map [string ]any , createdBy string , targets ... uuid.UUID ) ([] uuid.UUID , error ) {
63
63
metadata , err := s .store .FetchNewMessageMetadata (ctx , database.FetchNewMessageMetadataParams {
64
64
UserID : userID ,
65
65
NotificationTemplateID : templateID ,
@@ -85,40 +85,48 @@ func (s *StoreEnqueuer) EnqueueWithData(ctx context.Context, userID, templateID
85
85
return nil , xerrors .Errorf ("failed encoding input labels: %w" , err )
86
86
}
87
87
88
- id := uuid .New ()
89
- err = s .store .EnqueueNotificationMessage (ctx , database.EnqueueNotificationMessageParams {
90
- ID : id ,
91
- UserID : userID ,
92
- NotificationTemplateID : templateID ,
93
- Method : dispatchMethod ,
94
- Payload : input ,
95
- Targets : targets ,
96
- CreatedBy : createdBy ,
97
- CreatedAt : dbtime .Time (s .clock .Now ().UTC ()),
98
- })
99
- if err != nil {
100
- // We have a trigger on the notification_messages table named `inhibit_enqueue_if_disabled` which prevents messages
101
- // from being enqueued if the user has disabled them via notification_preferences. The trigger will fail the insertion
102
- // with the message "cannot enqueue message: user has disabled this notification".
103
- //
104
- // This is more efficient than fetching the user's preferences for each enqueue, and centralizes the business logic.
105
- if strings .Contains (err .Error (), ErrCannotEnqueueDisabledNotification .Error ()) {
106
- return nil , ErrCannotEnqueueDisabledNotification
107
- }
108
-
109
- // If the enqueue fails due to a dedupe hash conflict, this means that a notification has already been enqueued
110
- // today with identical properties. It's far simpler to prevent duplicate sends in this central manner, rather than
111
- // having each notification enqueue handle its own logic.
112
- if database .IsUniqueViolation (err , database .UniqueNotificationMessagesDedupeHashIndex ) {
113
- return nil , ErrDuplicate
88
+ uuids := make ([]uuid.UUID , 0 , 2 )
89
+ // All the enqueued messages are enqueued both on the dispatch method set by the user (or default one) and the inbox.
90
+ // As the inbox is not configurable per the user and is always enabled, we always enqueue the message on the inbox.
91
+ // The logic is done here in order to have two completely separated processing and retries are handled separately.
92
+ for _ , method := range []database.NotificationMethod {dispatchMethod , database .NotificationMethodInbox } {
93
+ id := uuid .New ()
94
+ err = s .store .EnqueueNotificationMessage (ctx , database.EnqueueNotificationMessageParams {
95
+ ID : id ,
96
+ UserID : userID ,
97
+ NotificationTemplateID : templateID ,
98
+ Method : method ,
99
+ Payload : input ,
100
+ Targets : targets ,
101
+ CreatedBy : createdBy ,
102
+ CreatedAt : dbtime .Time (s .clock .Now ().UTC ()),
103
+ })
104
+ if err != nil {
105
+ // We have a trigger on the notification_messages table named `inhibit_enqueue_if_disabled` which prevents messages
106
+ // from being enqueued if the user has disabled them via notification_preferences. The trigger will fail the insertion
107
+ // with the message "cannot enqueue message: user has disabled this notification".
108
+ //
109
+ // This is more efficient than fetching the user's preferences for each enqueue, and centralizes the business logic.
110
+ if strings .Contains (err .Error (), ErrCannotEnqueueDisabledNotification .Error ()) {
111
+ return nil , ErrCannotEnqueueDisabledNotification
112
+ }
113
+
114
+ // If the enqueue fails due to a dedupe hash conflict, this means that a notification has already been enqueued
115
+ // today with identical properties. It's far simpler to prevent duplicate sends in this central manner, rather than
116
+ // having each notification enqueue handle its own logic.
117
+ if database .IsUniqueViolation (err , database .UniqueNotificationMessagesDedupeHashIndex ) {
118
+ return nil , ErrDuplicate
119
+ }
120
+
121
+ s .log .Warn (ctx , "failed to enqueue notification" , slog .F ("template_id" , templateID ), slog .F ("input" , input ), slog .Error (err ))
122
+ return nil , xerrors .Errorf ("enqueue notification: %w" , err )
114
123
}
115
124
116
- s .log .Warn (ctx , "failed to enqueue notification" , slog .F ("template_id" , templateID ), slog .F ("input" , input ), slog .Error (err ))
117
- return nil , xerrors .Errorf ("enqueue notification: %w" , err )
125
+ uuids = append (uuids , id )
118
126
}
119
127
120
- s .log .Debug (ctx , "enqueued notification" , slog .F ("msg_id " , id ))
121
- return & id , nil
128
+ s .log .Debug (ctx , "enqueued notification" , slog .F ("msg_ids " , uuids ))
129
+ return uuids , nil
122
130
}
123
131
124
132
// buildPayload creates the payload that the notification will for variable substitution and/or routing.
@@ -165,12 +173,12 @@ func NewNoopEnqueuer() *NoopEnqueuer {
165
173
return & NoopEnqueuer {}
166
174
}
167
175
168
- func (* NoopEnqueuer ) Enqueue (context.Context , uuid.UUID , uuid.UUID , map [string ]string , string , ... uuid.UUID ) (* uuid.UUID , error ) {
176
+ func (* NoopEnqueuer ) Enqueue (context.Context , uuid.UUID , uuid.UUID , map [string ]string , string , ... uuid.UUID ) ([] uuid.UUID , error ) {
169
177
// nolint:nilnil // irrelevant.
170
178
return nil , nil
171
179
}
172
180
173
- func (* NoopEnqueuer ) EnqueueWithData (context.Context , uuid.UUID , uuid.UUID , map [string ]string , map [string ]any , string , ... uuid.UUID ) (* uuid.UUID , error ) {
181
+ func (* NoopEnqueuer ) EnqueueWithData (context.Context , uuid.UUID , uuid.UUID , map [string ]string , map [string ]any , string , ... uuid.UUID ) ([] uuid.UUID , error ) {
174
182
// nolint:nilnil // irrelevant.
175
183
return nil , nil
176
184
}
0 commit comments