diff options
author | 2025-01-23 16:47:30 -0800 | |
---|---|---|
committer | 2025-01-23 16:47:30 -0800 | |
commit | 5b765d734ee70f0a8a0790444d60969a727567f8 (patch) | |
tree | f76e05a6e5b22df17160be595c40e964bdbe5f22 /internal/db | |
parent | [feature] Serve bot accounts over AP as Service instead of Person (#3672) (diff) | |
download | gotosocial-5b765d734ee70f0a8a0790444d60969a727567f8.tar.xz |
[feature] Push notifications (#3587)
* Update push subscription API model to be Mastodon 4.0 compatible
* Add webpush-go dependency
# Conflicts:
# go.sum
* Single-row table for storing instance's VAPID key pair
* Generate VAPID key pair during startup
* Add VAPID public key to instance info API
* Return VAPID public key when registering an app
* Store Web Push subscriptions in DB
* Add Web Push sender (similar to email sender)
* Add no-op push senders to most processor tests
* Test Web Push notifications from workers
* Delete Web Push subscriptions when account is deleted
* Implement push subscription API
* Linter fixes
* Update Swagger
* Fix enum to int migration
* Fix GetVAPIDKeyPair
* Create web push subscriptions table with indexes
* Log Web Push server error messages
* Send instance URL as Web Push JWT subject
* Accept any 2xx code as a success
* Fix malformed VAPID sub claim
* Use packed notification flags
* Remove unused date columns
* Add notification type for update notifications
Not used yet
* Make GetVAPIDKeyPair idempotent
and remove PutVAPIDKeyPair
* Post-rebase fixes
* go mod tidy
* Special-case 400 errors other than 408/429
Most client errors should remove the subscription.
* Improve titles, trim body to reasonable length
* Disallow cleartext HTTP for Web Push servers
* Fix lint
* Remove redundant index on unique column
Also removes redundant unique and notnull tags on ID column since these are implied by pk
* Make realsender.go more readable
* Use Tobi's style for wrapping errors
* Restore treating all 5xx codes as temporary problems
* Always load target account settings
* Stub `policy` and `standard`
* webpush.Sender: take type converter as ctor param
* Move webpush.MockSender and noopSender into testrig
Diffstat (limited to 'internal/db')
-rw-r--r-- | internal/db/application.go | 3 | ||||
-rw-r--r-- | internal/db/bundb/application.go | 10 | ||||
-rw-r--r-- | internal/db/bundb/bundb.go | 5 | ||||
-rw-r--r-- | internal/db/bundb/migrations/20241121121623_enum_strings_to_ints.go | 4 | ||||
-rw-r--r-- | internal/db/bundb/migrations/20241124012635_add_vapid_key_pairs.go | 51 | ||||
-rw-r--r-- | internal/db/bundb/migrations/20241124012636_add_web_push_subscriptions.go | 61 | ||||
-rw-r--r-- | internal/db/bundb/notification_test.go | 2 | ||||
-rw-r--r-- | internal/db/bundb/webpush.go | 270 | ||||
-rw-r--r-- | internal/db/bundb/webpush_test.go | 81 | ||||
-rw-r--r-- | internal/db/db.go | 1 | ||||
-rw-r--r-- | internal/db/webpush.go | 54 |
11 files changed, 539 insertions, 3 deletions
diff --git a/internal/db/application.go b/internal/db/application.go index b71e593c2..5a4068431 100644 --- a/internal/db/application.go +++ b/internal/db/application.go @@ -48,6 +48,9 @@ type Application interface { // GetAllTokens ... GetAllTokens(ctx context.Context) ([]*gtsmodel.Token, error) + // GetTokenByID ... + GetTokenByID(ctx context.Context, id string) (*gtsmodel.Token, error) + // GetTokenByCode ... GetTokenByCode(ctx context.Context, code string) (*gtsmodel.Token, error) diff --git a/internal/db/bundb/application.go b/internal/db/bundb/application.go index cbba499b0..92fc5ea2b 100644 --- a/internal/db/bundb/application.go +++ b/internal/db/bundb/application.go @@ -174,6 +174,16 @@ func (a *applicationDB) GetAllTokens(ctx context.Context) ([]*gtsmodel.Token, er return tokens, nil } +func (a *applicationDB) GetTokenByID(ctx context.Context, code string) (*gtsmodel.Token, error) { + return a.getTokenBy( + "ID", + func(t *gtsmodel.Token) error { + return a.db.NewSelect().Model(t).Where("? = ?", bun.Ident("id"), code).Scan(ctx) + }, + code, + ) +} + func (a *applicationDB) GetTokenByCode(ctx context.Context, code string) (*gtsmodel.Token, error) { return a.getTokenBy( "Code", diff --git a/internal/db/bundb/bundb.go b/internal/db/bundb/bundb.go index cf612fd2e..c9dd7866d 100644 --- a/internal/db/bundb/bundb.go +++ b/internal/db/bundb/bundb.go @@ -88,6 +88,7 @@ type DBService struct { db.Timeline db.User db.Tombstone + db.WebPush db.WorkerTask db *bun.DB } @@ -301,6 +302,10 @@ func NewBunDBService(ctx context.Context, state *state.State) (db.DB, error) { db: db, state: state, }, + WebPush: &webPushDB{ + db: db, + state: state, + }, WorkerTask: &workerTaskDB{ db: db, }, diff --git a/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints.go b/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints.go index 6767c6809..5f3eb1409 100644 --- a/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints.go +++ b/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints.go @@ -149,10 +149,10 @@ func notificationEnumMapping[T ~string]() map[T]new_gtsmodel.NotificationType { T(old_gtsmodel.NotificationFollowRequest): new_gtsmodel.NotificationFollowRequest, T(old_gtsmodel.NotificationMention): new_gtsmodel.NotificationMention, T(old_gtsmodel.NotificationReblog): new_gtsmodel.NotificationReblog, - T(old_gtsmodel.NotificationFave): new_gtsmodel.NotificationFave, + T(old_gtsmodel.NotificationFave): new_gtsmodel.NotificationFavourite, T(old_gtsmodel.NotificationPoll): new_gtsmodel.NotificationPoll, T(old_gtsmodel.NotificationStatus): new_gtsmodel.NotificationStatus, - T(old_gtsmodel.NotificationSignup): new_gtsmodel.NotificationSignup, + T(old_gtsmodel.NotificationSignup): new_gtsmodel.NotificationAdminSignup, T(old_gtsmodel.NotificationPendingFave): new_gtsmodel.NotificationPendingFave, T(old_gtsmodel.NotificationPendingReply): new_gtsmodel.NotificationPendingReply, T(old_gtsmodel.NotificationPendingReblog): new_gtsmodel.NotificationPendingReblog, diff --git a/internal/db/bundb/migrations/20241124012635_add_vapid_key_pairs.go b/internal/db/bundb/migrations/20241124012635_add_vapid_key_pairs.go new file mode 100644 index 000000000..c1a32f6be --- /dev/null +++ b/internal/db/bundb/migrations/20241124012635_add_vapid_key_pairs.go @@ -0,0 +1,51 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +package migrations + +import ( + "context" + + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/uptrace/bun" +) + +func init() { + up := func(ctx context.Context, db *bun.DB) error { + return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { + if _, err := tx. + NewCreateTable(). + Model(>smodel.VAPIDKeyPair{}). + IfNotExists(). + Exec(ctx); err != nil { + return err + } + + return nil + }) + } + + down := func(ctx context.Context, db *bun.DB) error { + return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { + return nil + }) + } + + if err := Migrations.Register(up, down); err != nil { + panic(err) + } +} diff --git a/internal/db/bundb/migrations/20241124012636_add_web_push_subscriptions.go b/internal/db/bundb/migrations/20241124012636_add_web_push_subscriptions.go new file mode 100644 index 000000000..87d966903 --- /dev/null +++ b/internal/db/bundb/migrations/20241124012636_add_web_push_subscriptions.go @@ -0,0 +1,61 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +package migrations + +import ( + "context" + + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/uptrace/bun" +) + +func init() { + up := func(ctx context.Context, db *bun.DB) error { + return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { + if _, err := tx. + NewCreateTable(). + Model(>smodel.WebPushSubscription{}). + IfNotExists(). + Exec(ctx); err != nil { + return err + } + + if _, err := tx. + NewCreateIndex(). + Model(>smodel.WebPushSubscription{}). + Index("web_push_subscriptions_account_id_idx"). + Column("account_id"). + IfNotExists(). + Exec(ctx); err != nil { + return err + } + + return nil + }) + } + + down := func(ctx context.Context, db *bun.DB) error { + return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { + return nil + }) + } + + if err := Migrations.Register(up, down); err != nil { + panic(err) + } +} diff --git a/internal/db/bundb/notification_test.go b/internal/db/bundb/notification_test.go index 8e2fb8031..8cc778071 100644 --- a/internal/db/bundb/notification_test.go +++ b/internal/db/bundb/notification_test.go @@ -66,7 +66,7 @@ func (suite *NotificationTestSuite) spamNotifs() { notif := >smodel.Notification{ ID: notifID, - NotificationType: gtsmodel.NotificationFave, + NotificationType: gtsmodel.NotificationFavourite, CreatedAt: time.Now(), TargetAccountID: targetAccountID, OriginAccountID: originAccountID, diff --git a/internal/db/bundb/webpush.go b/internal/db/bundb/webpush.go new file mode 100644 index 000000000..c61209573 --- /dev/null +++ b/internal/db/bundb/webpush.go @@ -0,0 +1,270 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +package bundb + +import ( + "context" + "errors" + + webpushgo "github.com/SherClockHolmes/webpush-go" + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/state" + "github.com/superseriousbusiness/gotosocial/internal/util/xslices" + "github.com/uptrace/bun" +) + +type webPushDB struct { + db *bun.DB + state *state.State +} + +func (w *webPushDB) GetVAPIDKeyPair(ctx context.Context) (*gtsmodel.VAPIDKeyPair, error) { + var err error + + vapidKeyPair, err := w.getVAPIDKeyPair(ctx) + if err != nil { + return nil, err + } + if vapidKeyPair != nil { + return vapidKeyPair, nil + } + + // If there aren't any, generate new ones. + vapidKeyPair = >smodel.VAPIDKeyPair{} + if vapidKeyPair.Private, vapidKeyPair.Public, err = webpushgo.GenerateVAPIDKeys(); err != nil { + return nil, gtserror.Newf("error generating VAPID key pair: %w", err) + } + + // Store the keys in the database. + if _, err = w.db.NewInsert(). + Model(vapidKeyPair). + Exec(ctx); // nocollapse + err != nil { + if errors.Is(err, db.ErrAlreadyExists) { + // Multiple concurrent attempts to generate new keys, and this one didn't win. + // Get the results of the one that did. + return w.getVAPIDKeyPair(ctx) + } + return nil, err + } + + // Cache the keys. + w.state.Caches.DB.VAPIDKeyPair.Store(vapidKeyPair) + + return vapidKeyPair, nil +} + +// getVAPIDKeyPair gets an existing VAPID key pair from cache or DB. +// If there is no existing VAPID key pair, it returns nil, with no error. +func (w *webPushDB) getVAPIDKeyPair(ctx context.Context) (*gtsmodel.VAPIDKeyPair, error) { + // Look for cached keys. + vapidKeyPair := w.state.Caches.DB.VAPIDKeyPair.Load() + if vapidKeyPair != nil { + return vapidKeyPair, nil + } + + // Look for previously generated keys in the database. + vapidKeyPair = >smodel.VAPIDKeyPair{} + if err := w.db.NewSelect(). + Model(vapidKeyPair). + Limit(1). + Scan(ctx); // nocollapse + err != nil { + if errors.Is(err, db.ErrNoEntries) { + return nil, nil + } + return nil, err + } + + return vapidKeyPair, nil +} + +func (w *webPushDB) DeleteVAPIDKeyPair(ctx context.Context) error { + // Delete any existing keys. + if _, err := w.db.NewTruncateTable(). + Model((*gtsmodel.VAPIDKeyPair)(nil)). + Exec(ctx); // nocollapse + err != nil { + return err + } + + // Clear the key cache. + w.state.Caches.DB.VAPIDKeyPair.Store(nil) + + return nil +} + +func (w *webPushDB) GetWebPushSubscriptionByTokenID(ctx context.Context, tokenID string) (*gtsmodel.WebPushSubscription, error) { + subscription, err := w.state.Caches.DB.WebPushSubscription.LoadOne( + "TokenID", + func() (*gtsmodel.WebPushSubscription, error) { + var subscription gtsmodel.WebPushSubscription + err := w.db. + NewSelect(). + Model(&subscription). + Where("? = ?", bun.Ident("token_id"), tokenID). + Scan(ctx) + return &subscription, err + }, + tokenID, + ) + if err != nil { + return nil, err + } + return subscription, nil +} + +func (w *webPushDB) PutWebPushSubscription(ctx context.Context, subscription *gtsmodel.WebPushSubscription) error { + return w.state.Caches.DB.WebPushSubscription.Store(subscription, func() error { + _, err := w.db.NewInsert(). + Model(subscription). + Exec(ctx) + return err + }) +} + +func (w *webPushDB) UpdateWebPushSubscription(ctx context.Context, subscription *gtsmodel.WebPushSubscription, columns ...string) error { + // Update database. + result, err := w.db. + NewUpdate(). + Model(subscription). + Column(columns...). + Where("? = ?", bun.Ident("id"), subscription.ID). + Exec(ctx) + if err != nil { + return err + } + rowsAffected, err := result.RowsAffected() + if err != nil { + return gtserror.Newf("error getting updated row count: %w", err) + } + if rowsAffected == 0 { + return db.ErrNoEntries + } + + // Update cache. + w.state.Caches.DB.WebPushSubscription.Put(subscription) + + return nil +} + +func (w *webPushDB) DeleteWebPushSubscriptionByTokenID(ctx context.Context, tokenID string) error { + // Deleted partial model for cache invalidation. + var deleted gtsmodel.WebPushSubscription + + // Delete subscription, returning subset of columns used by invalidation hook. + if _, err := w.db.NewDelete(). + Model(&deleted). + Where("? = ?", bun.Ident("token_id"), tokenID). + Returning("?", bun.Ident("account_id")). + Exec(ctx); // nocollapse + err != nil && !errors.Is(err, db.ErrNoEntries) { + return err + } + + // Invalidate cached subscription by token ID. + w.state.Caches.DB.WebPushSubscription.Invalidate("TokenID", tokenID) + + // Call invalidate hook directly. + w.state.Caches.OnInvalidateWebPushSubscription(&deleted) + + return nil +} + +func (w *webPushDB) GetWebPushSubscriptionsByAccountID(ctx context.Context, accountID string) ([]*gtsmodel.WebPushSubscription, error) { + // Fetch IDs of all subscriptions created by this account. + subscriptionIDs, err := loadPagedIDs(&w.state.Caches.DB.WebPushSubscriptionIDs, accountID, nil, func() ([]string, error) { + // Subscription IDs not in cache. Perform DB query. + var subscriptionIDs []string + if _, err := w.db. + NewSelect(). + Model((*gtsmodel.WebPushSubscription)(nil)). + Column("id"). + Where("? = ?", bun.Ident("account_id"), accountID). + Order("id DESC"). + Exec(ctx, &subscriptionIDs); // nocollapse + err != nil && !errors.Is(err, db.ErrNoEntries) { + return nil, err + } + return subscriptionIDs, nil + }) + if err != nil { + return nil, err + } + if len(subscriptionIDs) == 0 { + return nil, nil + } + + // Get each subscription by ID from the cache or DB. + subscriptions, err := w.state.Caches.DB.WebPushSubscription.LoadIDs("ID", + subscriptionIDs, + func(uncached []string) ([]*gtsmodel.WebPushSubscription, error) { + subscriptions := make([]*gtsmodel.WebPushSubscription, 0, len(uncached)) + if err := w.db. + NewSelect(). + Model(&subscriptions). + Where("? IN (?)", bun.Ident("id"), bun.In(uncached)). + Scan(ctx); // nocollapse + err != nil { + return nil, err + } + return subscriptions, nil + }, + ) + if err != nil { + return nil, err + } + + // Put the subscription structs in the same order as the filter IDs. + xslices.OrderBy( + subscriptions, + subscriptionIDs, + func(subscription *gtsmodel.WebPushSubscription) string { + return subscription.ID + }, + ) + + return subscriptions, nil +} + +func (w *webPushDB) DeleteWebPushSubscriptionsByAccountID(ctx context.Context, accountID string) error { + // Deleted partial models for cache invalidation. + var deleted []*gtsmodel.WebPushSubscription + + // Delete subscriptions, returning subset of columns. + if _, err := w.db.NewDelete(). + Model(&deleted). + Where("? = ?", bun.Ident("account_id"), accountID). + Returning("?", bun.Ident("account_id")). + Exec(ctx); // nocollapse + err != nil && !errors.Is(err, db.ErrNoEntries) { + return err + } + + // Invalidate cached subscriptions by account ID. + w.state.Caches.DB.WebPushSubscription.Invalidate("AccountID", accountID) + + // Call invalidate hooks directly in case those entries weren't cached. + for _, subscription := range deleted { + w.state.Caches.OnInvalidateWebPushSubscription(subscription) + } + + return nil +} diff --git a/internal/db/bundb/webpush_test.go b/internal/db/bundb/webpush_test.go new file mode 100644 index 000000000..8ca83955a --- /dev/null +++ b/internal/db/bundb/webpush_test.go @@ -0,0 +1,81 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +package bundb_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/suite" +) + +type WebPushTestSuite struct { + BunDBStandardTestSuite +} + +// Get the text fixture VAPID key pair. +func (suite *WebPushTestSuite) TestGetVAPIDKeyPair() { + ctx := context.Background() + + vapidKeyPair, err := suite.db.GetVAPIDKeyPair(ctx) + suite.NoError(err) + if !suite.NotNil(vapidKeyPair) { + suite.FailNow("Got a nil VAPID key pair, can't continue") + } + suite.NotEmpty(vapidKeyPair.Private) + suite.NotEmpty(vapidKeyPair.Public) + + // Get it again. It should be the same one. + vapidKeyPair2, err := suite.db.GetVAPIDKeyPair(ctx) + suite.NoError(err) + if suite.NotNil(vapidKeyPair2) { + suite.Equal(vapidKeyPair.Private, vapidKeyPair2.Private) + suite.Equal(vapidKeyPair.Public, vapidKeyPair2.Public) + } +} + +// Generate a VAPID key pair when there isn't one. +func (suite *WebPushTestSuite) TestGenerateVAPIDKeyPair() { + ctx := context.Background() + + // Delete the text fixture VAPID key pair. + if err := suite.db.DeleteVAPIDKeyPair(ctx); !suite.NoError(err) { + suite.FailNow("Test setup failed: DB error deleting fixture VAPID key pair: %v", err) + } + + // Get a new one. + vapidKeyPair, err := suite.db.GetVAPIDKeyPair(ctx) + suite.NoError(err) + if !suite.NotNil(vapidKeyPair) { + suite.FailNow("Got a nil VAPID key pair, can't continue") + } + suite.NotEmpty(vapidKeyPair.Private) + suite.NotEmpty(vapidKeyPair.Public) + + // Get it again. It should be the same one. + vapidKeyPair2, err := suite.db.GetVAPIDKeyPair(ctx) + suite.NoError(err) + if suite.NotNil(vapidKeyPair2) { + suite.Equal(vapidKeyPair.Private, vapidKeyPair2.Private) + suite.Equal(vapidKeyPair.Public, vapidKeyPair2.Public) + } +} + +func TestWebPushTestSuite(t *testing.T) { + suite.Run(t, new(WebPushTestSuite)) +} diff --git a/internal/db/db.go b/internal/db/db.go index 11dd2e507..16796ae49 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -58,5 +58,6 @@ type DB interface { Timeline User Tombstone + WebPush WorkerTask } diff --git a/internal/db/webpush.go b/internal/db/webpush.go new file mode 100644 index 000000000..22bf449de --- /dev/null +++ b/internal/db/webpush.go @@ -0,0 +1,54 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +package db + +import ( + "context" + + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +// WebPush contains functions related to Web Push notifications. +type WebPush interface { + // GetVAPIDKeyPair retrieves the server's existing VAPID key pair, if there is one. + // If there isn't one, it generates a new one, stores it, and returns that. + GetVAPIDKeyPair(ctx context.Context) (*gtsmodel.VAPIDKeyPair, error) + + // DeleteVAPIDKeyPair deletes the server's VAPID key pair. + DeleteVAPIDKeyPair(ctx context.Context) error + + // GetWebPushSubscriptionByTokenID retrieves an access token's Web Push subscription. + // There may not be one, in which case an error will be returned. + GetWebPushSubscriptionByTokenID(ctx context.Context, tokenID string) (*gtsmodel.WebPushSubscription, error) + + // PutWebPushSubscription creates an access token's Web Push subscription. + PutWebPushSubscription(ctx context.Context, subscription *gtsmodel.WebPushSubscription) error + + // UpdateWebPushSubscription updates an access token's Web Push subscription. + // There may not be one, in which case an error will be returned. + UpdateWebPushSubscription(ctx context.Context, subscription *gtsmodel.WebPushSubscription, columns ...string) error + + // DeleteWebPushSubscriptionByTokenID deletes an access token's Web Push subscription, if there is one. + DeleteWebPushSubscriptionByTokenID(ctx context.Context, tokenID string) error + + // GetWebPushSubscriptionsByAccountID retrieves an account's list of Web Push subscriptions. + GetWebPushSubscriptionsByAccountID(ctx context.Context, accountID string) ([]*gtsmodel.WebPushSubscription, error) + + // DeleteWebPushSubscriptionsByAccountID deletes an account's list of Web Push subscriptions. + DeleteWebPushSubscriptionsByAccountID(ctx context.Context, accountID string) error +} |