@@ -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+
460552func (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