Skip to content

Commit a2d7a08

Browse files
committed
feat(api): Refactor subscription model and add API placeholders
This commit introduces two main changes to improve the robustness of the user subscription feature and prepare for future API expansion. **Forward-Compatible Subscription Triggers** To prevent errors when a subscription trigger is deprecated or removed, the data model has been refactored to be forward-compatible. - The OpenAPI specification now uses a `SubscriptionTriggerResponseItem` schema. This object returns the trigger's `value` and an optional `raw_value`. - If a trigger stored in the database is no longer a valid API enum, its `value` is now set to 'unknown', and the original database string is preserved in `raw_value`. This ensures the API does not fail when encountering old data. - The backend adapter and its tests have been updated to handle this new structure. **API Endpoint Placeholders** As part of the effort to split up a large pull request (#2041), this commit adds placeholder paths to `openapi.yaml` for future subscription management endpoints. These paths are for planning purposes and do not have functional implementations in this change: - `/v1/users/me/subscriptions` - `/v1/users/me/subscriptions/{subscription_id}` **Other Changes** - A specific error for unauthorized subscription creation attempts has been added.
1 parent 9088392 commit a2d7a08

File tree

3 files changed

+429
-64
lines changed

3 files changed

+429
-64
lines changed

lib/gcpspanner/spanneradapters/backend.go

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1332,18 +1332,75 @@ func (s *Backend) GetIDFromFeatureKey(
13321332
return id, nil
13331333
}
13341334

1335+
func backendTriggersToSpannerTriggers(backendTriggers []backend.SubscriptionTriggerWritable) []string {
1336+
triggers := make([]string, 0, len(backendTriggers))
1337+
for _, trigger := range backendTriggers {
1338+
triggers = append(triggers, string(trigger))
1339+
}
1340+
1341+
return triggers
1342+
}
1343+
1344+
func attemptToStoreSubscriptionTrigger(t backend.SubscriptionTriggerWritable) backend.SubscriptionTriggerResponseValue {
1345+
ret := backend.SubscriptionTriggerResponseValue{}
1346+
err := ret.FromSubscriptionTriggerWritable(t)
1347+
if err != nil {
1348+
slog.Warn("unable to convert trigger from database. skipping", "err", err, "value", t)
1349+
}
1350+
1351+
return ret
1352+
}
1353+
1354+
func attemptToStoreSubscriptionTriggerUnknown() backend.SubscriptionTriggerResponseValue {
1355+
ret := backend.SubscriptionTriggerResponseValue{}
1356+
err := ret.FromEnumUnknown(backend.EnumUnknownValue)
1357+
if err != nil {
1358+
slog.Warn("unable to convert trigger from database. skipping", "err", err)
1359+
}
1360+
1361+
return ret
1362+
}
1363+
1364+
func spannerTriggersToBackendTriggers(spannerTriggers []string) []backend.SubscriptionTriggerResponseItem {
1365+
triggers := make([]backend.SubscriptionTriggerResponseItem, 0, len(spannerTriggers))
1366+
for _, trigger := range spannerTriggers {
1367+
input := backend.SubscriptionTriggerWritable(trigger)
1368+
switch input {
1369+
case backend.SubscriptionTriggerFeatureAnyBrowserImplementationComplete,
1370+
backend.SubscriptionTriggerFeatureBaselineLimitedToNewly,
1371+
backend.SubscriptionTriggerFeatureBaselineRegressionNewlyToLimited:
1372+
triggers = append(triggers, backend.SubscriptionTriggerResponseItem{
1373+
Value: attemptToStoreSubscriptionTrigger(input),
1374+
RawValue: nil,
1375+
})
1376+
default:
1377+
value := trigger
1378+
triggers = append(triggers, backend.SubscriptionTriggerResponseItem{
1379+
Value: attemptToStoreSubscriptionTriggerUnknown(),
1380+
RawValue: &value,
1381+
})
1382+
}
1383+
}
1384+
1385+
return triggers
1386+
}
1387+
13351388
func (s *Backend) CreateSavedSearchSubscription(ctx context.Context,
13361389
userID string, req backend.Subscription) (*backend.SubscriptionResponse, error) {
13371390
createReq := gcpspanner.CreateSavedSearchSubscriptionRequest{
13381391
UserID: userID,
13391392
ChannelID: req.ChannelId,
13401393
SavedSearchID: req.SavedSearchId,
1341-
Triggers: req.Triggers,
1394+
Triggers: backendTriggersToSpannerTriggers(req.Triggers),
13421395
Frequency: string(req.Frequency),
13431396
}
13441397

13451398
id, err := s.client.CreateSavedSearchSubscription(ctx, createReq)
13461399
if err != nil {
1400+
if errors.Is(err, gcpspanner.ErrMissingRequiredRole) {
1401+
return nil, errors.Join(err, backendtypes.ErrUserNotAuthorizedForAction)
1402+
}
1403+
13471404
return nil, err
13481405
}
13491406

@@ -1415,7 +1472,9 @@ func (s *Backend) UpdateSavedSearchSubscription(ctx context.Context,
14151472
for _, field := range req.UpdateMask {
14161473
switch field {
14171474
case backend.UpdateSubscriptionRequestMaskTriggers:
1418-
updateReq.Triggers = gcpspanner.OptionallySet[[]string]{Value: *req.Triggers, IsSet: true}
1475+
updateReq.Triggers = gcpspanner.OptionallySet[[]string]{
1476+
Value: backendTriggersToSpannerTriggers(*req.Triggers),
1477+
IsSet: true}
14191478
case backend.UpdateSubscriptionRequestMaskFrequency:
14201479
updateReq.Frequency = gcpspanner.OptionallySet[string]{Value: string(*req.Frequency), IsSet: true}
14211480
}
@@ -1465,8 +1524,8 @@ func toBackendSubscription(sub *gcpspanner.SavedSearchSubscription) *backend.Sub
14651524
Id: sub.ID,
14661525
ChannelId: sub.ChannelID,
14671526
SavedSearchId: sub.SavedSearchID,
1468-
Triggers: sub.Triggers,
1469-
Frequency: backend.SubscriptionResponseFrequency(sub.Frequency),
1527+
Triggers: spannerTriggersToBackendTriggers(sub.Triggers),
1528+
Frequency: backend.SubscriptionFrequency(sub.Frequency),
14701529
CreatedAt: sub.CreatedAt,
14711530
UpdatedAt: sub.UpdatedAt,
14721531
}

0 commit comments

Comments
 (0)