Skip to content

Commit 1486b4d

Browse files
authored
feat(adapter): Add CRUD adapter layer for subscriptions (#2026)
This layer enables the management of user subscriptions by translating API requests into corresponding database operations for creating, reading, updating, and deleting subscription entities. This is a foundational step towards implementing user-managed notification preferences.
1 parent 6c4cefd commit 1486b4d

File tree

2 files changed

+729
-0
lines changed

2 files changed

+729
-0
lines changed

lib/gcpspanner/spanneradapters/backend.go

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,14 @@ type BackendSpannerClient interface {
141141
AddUserSearchBookmark(ctx context.Context, req gcpspanner.UserSavedSearchBookmark) error
142142
DeleteUserSearchBookmark(ctx context.Context, req gcpspanner.UserSavedSearchBookmark) error
143143
SyncUserProfileInfo(ctx context.Context, userProfile gcpspanner.UserProfile) error
144+
CreateSavedSearchSubscription(
145+
ctx context.Context, req gcpspanner.CreateSavedSearchSubscriptionRequest) (*string, error)
146+
GetSavedSearchSubscription(ctx context.Context, subscriptionID string, userID string) (
147+
*gcpspanner.SavedSearchSubscription, error)
148+
UpdateSavedSearchSubscription(ctx context.Context, req gcpspanner.UpdateSavedSearchSubscriptionRequest) error
149+
DeleteSavedSearchSubscription(ctx context.Context, subscriptionID string, userID string) error
150+
ListSavedSearchSubscriptions(ctx context.Context, req gcpspanner.ListSavedSearchSubscriptionsRequest) (
151+
[]gcpspanner.SavedSearchSubscription, *string, error)
144152
GetNotificationChannel(
145153
ctx context.Context, channelID string, userID string) (*gcpspanner.NotificationChannel, error)
146154
ListNotificationChannels(ctx context.Context, req gcpspanner.ListNotificationChannelsRequest) (
@@ -1323,3 +1331,143 @@ func (s *Backend) GetIDFromFeatureKey(
13231331

13241332
return id, nil
13251333
}
1334+
1335+
func (s *Backend) CreateSavedSearchSubscription(ctx context.Context,
1336+
userID string, req backend.Subscription) (*backend.SubscriptionResponse, error) {
1337+
createReq := gcpspanner.CreateSavedSearchSubscriptionRequest{
1338+
UserID: userID,
1339+
ChannelID: req.ChannelId,
1340+
SavedSearchID: req.SavedSearchId,
1341+
Triggers: req.Triggers,
1342+
Frequency: string(req.Frequency),
1343+
}
1344+
1345+
id, err := s.client.CreateSavedSearchSubscription(ctx, createReq)
1346+
if err != nil {
1347+
return nil, err
1348+
}
1349+
1350+
// Retrieve the newly created subscription to return to the client.
1351+
sub, err := s.client.GetSavedSearchSubscription(ctx, *id, userID)
1352+
if err != nil {
1353+
return nil, err
1354+
}
1355+
1356+
return toBackendSubscription(sub), nil
1357+
1358+
}
1359+
1360+
func (s *Backend) ListSavedSearchSubscriptions(ctx context.Context,
1361+
userID string, pageSize int, pageToken *string) (*backend.SubscriptionPage, error) {
1362+
listReq := gcpspanner.ListSavedSearchSubscriptionsRequest{
1363+
UserID: userID,
1364+
PageSize: pageSize,
1365+
PageToken: pageToken,
1366+
}
1367+
subs, token, err := s.client.ListSavedSearchSubscriptions(ctx, listReq)
1368+
if err != nil {
1369+
return nil, err
1370+
}
1371+
backendSubs := make([]backend.SubscriptionResponse, 0, len(subs))
1372+
for i := range subs {
1373+
backendSubs = append(backendSubs, *toBackendSubscription(&subs[i]))
1374+
}
1375+
1376+
return &backend.SubscriptionPage{
1377+
Data: &backendSubs,
1378+
Metadata: &backend.PageMetadata{
1379+
NextPageToken: token,
1380+
},
1381+
}, nil
1382+
}
1383+
1384+
func (s *Backend) GetSavedSearchSubscription(ctx context.Context,
1385+
userID, subscriptionID string) (*backend.SubscriptionResponse, error) {
1386+
sub, err := s.client.GetSavedSearchSubscription(ctx, subscriptionID, userID)
1387+
if err != nil {
1388+
if errors.Is(err, gcpspanner.ErrMissingRequiredRole) {
1389+
return nil, errors.Join(err, backendtypes.ErrUserNotAuthorizedForAction)
1390+
} else if errors.Is(err, gcpspanner.ErrQueryReturnedNoResults) {
1391+
return nil, errors.Join(err, backendtypes.ErrEntityDoesNotExist)
1392+
}
1393+
1394+
return nil, err
1395+
}
1396+
1397+
return toBackendSubscription(sub), nil
1398+
}
1399+
1400+
func (s *Backend) UpdateSavedSearchSubscription(ctx context.Context,
1401+
userID, subscriptionID string, req backend.UpdateSubscriptionRequest) (*backend.SubscriptionResponse, error) {
1402+
updateReq := gcpspanner.UpdateSavedSearchSubscriptionRequest{
1403+
ID: subscriptionID,
1404+
UserID: userID,
1405+
Triggers: gcpspanner.OptionallySet[[]string]{
1406+
IsSet: false,
1407+
Value: nil,
1408+
},
1409+
Frequency: gcpspanner.OptionallySet[string]{
1410+
IsSet: false,
1411+
Value: "",
1412+
},
1413+
}
1414+
1415+
for _, field := range req.UpdateMask {
1416+
switch field {
1417+
case backend.UpdateSubscriptionRequestMaskTriggers:
1418+
updateReq.Triggers = gcpspanner.OptionallySet[[]string]{Value: *req.Triggers, IsSet: true}
1419+
case backend.UpdateSubscriptionRequestMaskFrequency:
1420+
updateReq.Frequency = gcpspanner.OptionallySet[string]{Value: string(*req.Frequency), IsSet: true}
1421+
}
1422+
}
1423+
1424+
err := s.client.UpdateSavedSearchSubscription(ctx, updateReq)
1425+
if err != nil {
1426+
if errors.Is(err, gcpspanner.ErrMissingRequiredRole) {
1427+
return nil, errors.Join(err, backendtypes.ErrUserNotAuthorizedForAction)
1428+
} else if errors.Is(err, gcpspanner.ErrQueryReturnedNoResults) {
1429+
return nil, errors.Join(err, backendtypes.ErrEntityDoesNotExist)
1430+
}
1431+
1432+
return nil, err
1433+
}
1434+
1435+
// Fetch the updated subscription to return.
1436+
sub, err := s.client.GetSavedSearchSubscription(ctx, subscriptionID, userID)
1437+
if err != nil {
1438+
return nil, err
1439+
}
1440+
1441+
return toBackendSubscription(sub), nil
1442+
}
1443+
1444+
func (s *Backend) DeleteSavedSearchSubscription(ctx context.Context, userID, subscriptionID string) error {
1445+
err := s.client.DeleteSavedSearchSubscription(ctx, subscriptionID, userID)
1446+
if err != nil {
1447+
if errors.Is(err, gcpspanner.ErrMissingRequiredRole) {
1448+
return errors.Join(err, backendtypes.ErrUserNotAuthorizedForAction)
1449+
} else if errors.Is(err, gcpspanner.ErrQueryReturnedNoResults) {
1450+
return errors.Join(err, backendtypes.ErrEntityDoesNotExist)
1451+
}
1452+
1453+
return err
1454+
}
1455+
1456+
return nil
1457+
}
1458+
1459+
func toBackendSubscription(sub *gcpspanner.SavedSearchSubscription) *backend.SubscriptionResponse {
1460+
if sub == nil {
1461+
return nil
1462+
}
1463+
1464+
return &backend.SubscriptionResponse{
1465+
Id: sub.ID,
1466+
ChannelId: sub.ChannelID,
1467+
SavedSearchId: sub.SavedSearchID,
1468+
Triggers: sub.Triggers,
1469+
Frequency: backend.SubscriptionResponseFrequency(sub.Frequency),
1470+
CreatedAt: sub.CreatedAt,
1471+
UpdatedAt: sub.UpdatedAt,
1472+
}
1473+
}

0 commit comments

Comments
 (0)