Skip to content

Commit 6c4cefd

Browse files
authored
feat(spanner): Implement read and delete operations for notification channels (#2027)
Adds adapter methods for retrieving (Get and List) and deleting user notification channels. Creation and update methods are omitted at this stage as channels are currently provisioned automatically. This lays the groundwork for managing user communication preferences.
1 parent b545639 commit 6c4cefd

2 files changed

Lines changed: 433 additions & 0 deletions

File tree

lib/gcpspanner/spanneradapters/backend.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,11 @@ 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+
GetNotificationChannel(
145+
ctx context.Context, channelID string, userID string) (*gcpspanner.NotificationChannel, error)
146+
ListNotificationChannels(ctx context.Context, req gcpspanner.ListNotificationChannelsRequest) (
147+
[]gcpspanner.NotificationChannel, *string, error)
148+
DeleteNotificationChannel(ctx context.Context, channelID string, userID string) error
144149
}
145150

146151
// Backend converts queries to spanner to usable entities for the backend
@@ -457,6 +462,93 @@ func (s *Backend) ListBaselineStatusCounts(
457462
}, nil
458463
}
459464

465+
func (s *Backend) GetNotificationChannel(ctx context.Context,
466+
userID, channelID string) (*backend.NotificationChannelResponse, error) {
467+
channel, err := s.client.GetNotificationChannel(ctx, channelID, userID)
468+
if err != nil {
469+
if errors.Is(err, gcpspanner.ErrMissingRequiredRole) {
470+
return nil, errors.Join(err, backendtypes.ErrUserNotAuthorizedForAction)
471+
} else if errors.Is(err, gcpspanner.ErrQueryReturnedNoResults) {
472+
return nil, errors.Join(err, backendtypes.ErrEntityDoesNotExist)
473+
}
474+
475+
return nil, err
476+
}
477+
478+
return toBackendNotificationChannel(channel), nil
479+
}
480+
481+
func (s *Backend) DeleteNotificationChannel(ctx context.Context, userID, channelID string) error {
482+
err := s.client.DeleteNotificationChannel(ctx, channelID, userID)
483+
if err != nil {
484+
if errors.Is(err, gcpspanner.ErrMissingRequiredRole) {
485+
return errors.Join(err, backendtypes.ErrUserNotAuthorizedForAction)
486+
} else if errors.Is(err, gcpspanner.ErrQueryReturnedNoResults) {
487+
return errors.Join(err, backendtypes.ErrEntityDoesNotExist)
488+
}
489+
490+
return err
491+
}
492+
493+
return nil
494+
}
495+
496+
func (s *Backend) ListNotificationChannels(ctx context.Context,
497+
userID string, pageSize int, pageToken *string) (*backend.NotificationChannelPage, error) {
498+
listReq := gcpspanner.ListNotificationChannelsRequest{
499+
UserID: userID,
500+
PageSize: pageSize,
501+
PageToken: pageToken,
502+
}
503+
channels, _, err := s.client.ListNotificationChannels(ctx, listReq)
504+
if err != nil {
505+
if errors.Is(err, gcpspanner.ErrInvalidCursorFormat) {
506+
return nil, errors.Join(err, backendtypes.ErrInvalidPageToken)
507+
}
508+
509+
return nil, err
510+
}
511+
512+
backendChannels := make([]backend.NotificationChannelResponse, 0, len(channels))
513+
for i := range channels {
514+
backendChannels = append(backendChannels, *toBackendNotificationChannel(&channels[i]))
515+
}
516+
517+
return &backend.NotificationChannelPage{
518+
Data: &backendChannels,
519+
Metadata: &backend.PageMetadata{
520+
NextPageToken: nil,
521+
},
522+
}, nil
523+
}
524+
525+
// toBackendNotificationChannel is a helper function to convert spanner
526+
// notification channel to backend notification channel.
527+
func toBackendNotificationChannel(channel *gcpspanner.NotificationChannel) *backend.NotificationChannelResponse {
528+
if channel == nil {
529+
return nil
530+
}
531+
// Convert spanner channel to backend channel
532+
// This can be expanded to handle different channel types.
533+
var value string
534+
if channel.EmailConfig != nil {
535+
value = channel.EmailConfig.Address
536+
}
537+
538+
return &backend.NotificationChannelResponse{
539+
Id: channel.ID,
540+
Name: channel.Name,
541+
Type: backend.NotificationChannelResponseType(channel.Type),
542+
Value: value,
543+
// For now, assume all channels are enabled.
544+
// We currently do not disable channels.
545+
// TODO: https://github.com/GoogleChrome/webstatus.dev/issues/2021
546+
Status: backend.NotificationChannelStatusEnabled,
547+
CreatedAt: channel.CreatedAt,
548+
UpdatedAt: channel.UpdatedAt,
549+
}
550+
}
551+
460552
func (s *Backend) CreateUserSavedSearch(ctx context.Context, userID string,
461553
savedSearch backend.SavedSearch) (*backend.SavedSearchResponse, error) {
462554
output, err := s.client.CreateNewUserSavedSearch(ctx, gcpspanner.CreateUserSavedSearchRequest{

0 commit comments

Comments
 (0)