mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(coderd): add webpush package (#17091)
* Adds `codersdk.ExperimentWebPush` (`web-push`) * Adds a `coderd/webpush` package that allows sending native push notifications via `github.com/SherClockHolmes/webpush-go` * Adds database tables to store push notification subscriptions. * Adds an API endpoint that allows users to subscribe/unsubscribe, and send a test notification (404 without experiment, excluded from API docs) * Adds server CLI command to regenerate VAPID keys (note: regenerating the VAPID keypair requires deleting all existing subscriptions) --------- Co-authored-by: Kyle Carberry <kyle@carberry.com>
This commit is contained in:
co-authored by
Kyle Carberry
parent
006600ea3e
commit
06e5d9ef21
@@ -283,6 +283,8 @@ var (
|
||||
Site: rbac.Permissions(map[string][]policy.Action{
|
||||
rbac.ResourceNotificationMessage.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
rbac.ResourceInboxNotification.Type: {policy.ActionCreate},
|
||||
rbac.ResourceWebpushSubscription.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
rbac.ResourceDeploymentConfig.Type: {policy.ActionRead, policy.ActionUpdate}, // To read and upsert VAPID keys
|
||||
}),
|
||||
Org: map[string][]rbac.Permission{},
|
||||
User: []rbac.Permission{},
|
||||
@@ -1176,6 +1178,13 @@ func (q *querier) DeleteAllTailnetTunnels(ctx context.Context, arg database.Dele
|
||||
return q.db.DeleteAllTailnetTunnels(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) DeleteAllWebpushSubscriptions(ctx context.Context) error {
|
||||
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceWebpushSubscription); err != nil {
|
||||
return err
|
||||
}
|
||||
return q.db.DeleteAllWebpushSubscriptions(ctx)
|
||||
}
|
||||
|
||||
func (q *querier) DeleteApplicationConnectAPIKeysByUserID(ctx context.Context, userID uuid.UUID) error {
|
||||
// TODO: This is not 100% correct because it omits apikey IDs.
|
||||
err := q.authorizeContext(ctx, policy.ActionDelete,
|
||||
@@ -1381,6 +1390,20 @@ func (q *querier) DeleteTailnetTunnel(ctx context.Context, arg database.DeleteTa
|
||||
return q.db.DeleteTailnetTunnel(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) DeleteWebpushSubscriptionByUserIDAndEndpoint(ctx context.Context, arg database.DeleteWebpushSubscriptionByUserIDAndEndpointParams) error {
|
||||
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceWebpushSubscription.WithOwner(arg.UserID.String())); err != nil {
|
||||
return err
|
||||
}
|
||||
return q.db.DeleteWebpushSubscriptionByUserIDAndEndpoint(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) DeleteWebpushSubscriptions(ctx context.Context, ids []uuid.UUID) error {
|
||||
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceSystem); err != nil {
|
||||
return err
|
||||
}
|
||||
return q.db.DeleteWebpushSubscriptions(ctx, ids)
|
||||
}
|
||||
|
||||
func (q *querier) DeleteWorkspaceAgentPortShare(ctx context.Context, arg database.DeleteWorkspaceAgentPortShareParams) error {
|
||||
w, err := q.db.GetWorkspaceByID(ctx, arg.WorkspaceID)
|
||||
if err != nil {
|
||||
@@ -2663,6 +2686,20 @@ func (q *querier) GetUsersByIDs(ctx context.Context, ids []uuid.UUID) ([]databas
|
||||
return q.db.GetUsersByIDs(ctx, ids)
|
||||
}
|
||||
|
||||
func (q *querier) GetWebpushSubscriptionsByUserID(ctx context.Context, userID uuid.UUID) ([]database.WebpushSubscription, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceWebpushSubscription.WithOwner(userID.String())); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetWebpushSubscriptionsByUserID(ctx, userID)
|
||||
}
|
||||
|
||||
func (q *querier) GetWebpushVAPIDKeys(ctx context.Context) (database.GetWebpushVAPIDKeysRow, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceDeploymentConfig); err != nil {
|
||||
return database.GetWebpushVAPIDKeysRow{}, err
|
||||
}
|
||||
return q.db.GetWebpushVAPIDKeys(ctx)
|
||||
}
|
||||
|
||||
func (q *querier) GetWorkspaceAgentAndLatestBuildByAuthToken(ctx context.Context, authToken uuid.UUID) (database.GetWorkspaceAgentAndLatestBuildByAuthTokenRow, error) {
|
||||
// This is a system function
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
@@ -3420,6 +3457,13 @@ func (q *querier) InsertVolumeResourceMonitor(ctx context.Context, arg database.
|
||||
return q.db.InsertVolumeResourceMonitor(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) InsertWebpushSubscription(ctx context.Context, arg database.InsertWebpushSubscriptionParams) (database.WebpushSubscription, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionCreate, rbac.ResourceWebpushSubscription.WithOwner(arg.UserID.String())); err != nil {
|
||||
return database.WebpushSubscription{}, err
|
||||
}
|
||||
return q.db.InsertWebpushSubscription(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) InsertWorkspace(ctx context.Context, arg database.InsertWorkspaceParams) (database.WorkspaceTable, error) {
|
||||
obj := rbac.ResourceWorkspace.WithOwner(arg.OwnerID.String()).InOrg(arg.OrganizationID)
|
||||
tpl, err := q.GetTemplateByID(ctx, arg.TemplateID)
|
||||
@@ -4670,6 +4714,13 @@ func (q *querier) UpsertTemplateUsageStats(ctx context.Context) error {
|
||||
return q.db.UpsertTemplateUsageStats(ctx)
|
||||
}
|
||||
|
||||
func (q *querier) UpsertWebpushVAPIDKeys(ctx context.Context, arg database.UpsertWebpushVAPIDKeysParams) error {
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceDeploymentConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
return q.db.UpsertWebpushVAPIDKeys(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpsertWorkspaceAgentPortShare(ctx context.Context, arg database.UpsertWorkspaceAgentPortShareParams) (database.WorkspaceAgentPortShare, error) {
|
||||
workspace, err := q.db.GetWorkspaceByID(ctx, arg.WorkspaceID)
|
||||
if err != nil {
|
||||
|
||||
@@ -4531,6 +4531,22 @@ func (s *MethodTestSuite) TestSystemFunctions() {
|
||||
s.Run("UpsertOAuth2GithubDefaultEligible", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args(true).Asserts(rbac.ResourceDeploymentConfig, policy.ActionUpdate)
|
||||
}))
|
||||
s.Run("GetWebpushVAPIDKeys", s.Subtest(func(db database.Store, check *expects) {
|
||||
require.NoError(s.T(), db.UpsertWebpushVAPIDKeys(context.Background(), database.UpsertWebpushVAPIDKeysParams{
|
||||
VapidPublicKey: "test",
|
||||
VapidPrivateKey: "test",
|
||||
}))
|
||||
check.Args().Asserts(rbac.ResourceDeploymentConfig, policy.ActionRead).Returns(database.GetWebpushVAPIDKeysRow{
|
||||
VapidPublicKey: "test",
|
||||
VapidPrivateKey: "test",
|
||||
})
|
||||
}))
|
||||
s.Run("UpsertWebpushVAPIDKeys", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args(database.UpsertWebpushVAPIDKeysParams{
|
||||
VapidPublicKey: "test",
|
||||
VapidPrivateKey: "test",
|
||||
}).Asserts(rbac.ResourceDeploymentConfig, policy.ActionUpdate)
|
||||
}))
|
||||
}
|
||||
|
||||
func (s *MethodTestSuite) TestNotifications() {
|
||||
@@ -4568,6 +4584,39 @@ func (s *MethodTestSuite) TestNotifications() {
|
||||
}).Asserts(rbac.ResourceNotificationMessage, policy.ActionRead)
|
||||
}))
|
||||
|
||||
// webpush subscriptions
|
||||
s.Run("GetWebpushSubscriptionsByUserID", s.Subtest(func(db database.Store, check *expects) {
|
||||
user := dbgen.User(s.T(), db, database.User{})
|
||||
check.Args(user.ID).Asserts(rbac.ResourceWebpushSubscription.WithOwner(user.ID.String()), policy.ActionRead)
|
||||
}))
|
||||
s.Run("InsertWebpushSubscription", s.Subtest(func(db database.Store, check *expects) {
|
||||
user := dbgen.User(s.T(), db, database.User{})
|
||||
check.Args(database.InsertWebpushSubscriptionParams{
|
||||
UserID: user.ID,
|
||||
}).Asserts(rbac.ResourceWebpushSubscription.WithOwner(user.ID.String()), policy.ActionCreate)
|
||||
}))
|
||||
s.Run("DeleteWebpushSubscriptions", s.Subtest(func(db database.Store, check *expects) {
|
||||
user := dbgen.User(s.T(), db, database.User{})
|
||||
push := dbgen.WebpushSubscription(s.T(), db, database.InsertWebpushSubscriptionParams{
|
||||
UserID: user.ID,
|
||||
})
|
||||
check.Args([]uuid.UUID{push.ID}).Asserts(rbac.ResourceSystem, policy.ActionDelete)
|
||||
}))
|
||||
s.Run("DeleteWebpushSubscriptionByUserIDAndEndpoint", s.Subtest(func(db database.Store, check *expects) {
|
||||
user := dbgen.User(s.T(), db, database.User{})
|
||||
push := dbgen.WebpushSubscription(s.T(), db, database.InsertWebpushSubscriptionParams{
|
||||
UserID: user.ID,
|
||||
})
|
||||
check.Args(database.DeleteWebpushSubscriptionByUserIDAndEndpointParams{
|
||||
UserID: user.ID,
|
||||
Endpoint: push.Endpoint,
|
||||
}).Asserts(rbac.ResourceWebpushSubscription.WithOwner(user.ID.String()), policy.ActionDelete)
|
||||
}))
|
||||
s.Run("DeleteAllWebpushSubscriptions", s.Subtest(func(_ database.Store, check *expects) {
|
||||
check.Args().
|
||||
Asserts(rbac.ResourceWebpushSubscription, policy.ActionDelete)
|
||||
}))
|
||||
|
||||
// Notification templates
|
||||
s.Run("GetNotificationTemplateByID", s.Subtest(func(db database.Store, check *expects) {
|
||||
dbtestutil.DisableForeignKeysAndTriggers(s.T(), db)
|
||||
|
||||
@@ -479,6 +479,18 @@ func NotificationInbox(t testing.TB, db database.Store, orig database.InsertInbo
|
||||
return notification
|
||||
}
|
||||
|
||||
func WebpushSubscription(t testing.TB, db database.Store, orig database.InsertWebpushSubscriptionParams) database.WebpushSubscription {
|
||||
subscription, err := db.InsertWebpushSubscription(genCtx, database.InsertWebpushSubscriptionParams{
|
||||
CreatedAt: takeFirst(orig.CreatedAt, dbtime.Now()),
|
||||
UserID: takeFirst(orig.UserID, uuid.New()),
|
||||
Endpoint: takeFirst(orig.Endpoint, testutil.GetRandomName(t)),
|
||||
EndpointP256dhKey: takeFirst(orig.EndpointP256dhKey, testutil.GetRandomName(t)),
|
||||
EndpointAuthKey: takeFirst(orig.EndpointAuthKey, testutil.GetRandomName(t)),
|
||||
})
|
||||
require.NoError(t, err, "insert webpush subscription")
|
||||
return subscription
|
||||
}
|
||||
|
||||
func Group(t testing.TB, db database.Store, orig database.Group) database.Group {
|
||||
t.Helper()
|
||||
|
||||
|
||||
@@ -246,6 +246,7 @@ type data struct {
|
||||
templates []database.TemplateTable
|
||||
templateUsageStats []database.TemplateUsageStat
|
||||
userConfigs []database.UserConfig
|
||||
webpushSubscriptions []database.WebpushSubscription
|
||||
workspaceAgents []database.WorkspaceAgent
|
||||
workspaceAgentMetadata []database.WorkspaceAgentMetadatum
|
||||
workspaceAgentLogs []database.WorkspaceAgentLog
|
||||
@@ -289,6 +290,8 @@ type data struct {
|
||||
lastLicenseID int32
|
||||
defaultProxyDisplayName string
|
||||
defaultProxyIconURL string
|
||||
webpushVAPIDPublicKey string
|
||||
webpushVAPIDPrivateKey string
|
||||
userStatusChanges []database.UserStatusChange
|
||||
telemetryItems []database.TelemetryItem
|
||||
presets []database.TemplateVersionPreset
|
||||
@@ -1853,6 +1856,14 @@ func (*FakeQuerier) DeleteAllTailnetTunnels(_ context.Context, arg database.Dele
|
||||
return ErrUnimplemented
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) DeleteAllWebpushSubscriptions(_ context.Context) error {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
q.webpushSubscriptions = make([]database.WebpushSubscription, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) DeleteApplicationConnectAPIKeysByUserID(_ context.Context, userID uuid.UUID) error {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
@@ -2422,6 +2433,38 @@ func (*FakeQuerier) DeleteTailnetTunnel(_ context.Context, arg database.DeleteTa
|
||||
return database.DeleteTailnetTunnelRow{}, ErrUnimplemented
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) DeleteWebpushSubscriptionByUserIDAndEndpoint(_ context.Context, arg database.DeleteWebpushSubscriptionByUserIDAndEndpointParams) error {
|
||||
err := validateDatabaseType(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
for i, subscription := range q.webpushSubscriptions {
|
||||
if subscription.UserID == arg.UserID && subscription.Endpoint == arg.Endpoint {
|
||||
q.webpushSubscriptions[i] = q.webpushSubscriptions[len(q.webpushSubscriptions)-1]
|
||||
q.webpushSubscriptions = q.webpushSubscriptions[:len(q.webpushSubscriptions)-1]
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) DeleteWebpushSubscriptions(_ context.Context, ids []uuid.UUID) error {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
for i, subscription := range q.webpushSubscriptions {
|
||||
if slices.Contains(ids, subscription.ID) {
|
||||
q.webpushSubscriptions[i] = q.webpushSubscriptions[len(q.webpushSubscriptions)-1]
|
||||
q.webpushSubscriptions = q.webpushSubscriptions[:len(q.webpushSubscriptions)-1]
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) DeleteWorkspaceAgentPortShare(_ context.Context, arg database.DeleteWorkspaceAgentPortShareParams) error {
|
||||
err := validateDatabaseType(arg)
|
||||
if err != nil {
|
||||
@@ -6717,6 +6760,34 @@ func (q *FakeQuerier) GetUsersByIDs(_ context.Context, ids []uuid.UUID) ([]datab
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetWebpushSubscriptionsByUserID(_ context.Context, userID uuid.UUID) ([]database.WebpushSubscription, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
out := make([]database.WebpushSubscription, 0)
|
||||
for _, subscription := range q.webpushSubscriptions {
|
||||
if subscription.UserID == userID {
|
||||
out = append(out, subscription)
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetWebpushVAPIDKeys(_ context.Context) (database.GetWebpushVAPIDKeysRow, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
if q.webpushVAPIDPublicKey == "" && q.webpushVAPIDPrivateKey == "" {
|
||||
return database.GetWebpushVAPIDKeysRow{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
return database.GetWebpushVAPIDKeysRow{
|
||||
VapidPublicKey: q.webpushVAPIDPublicKey,
|
||||
VapidPrivateKey: q.webpushVAPIDPrivateKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetWorkspaceAgentAndLatestBuildByAuthToken(_ context.Context, authToken uuid.UUID) (database.GetWorkspaceAgentAndLatestBuildByAuthTokenRow, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
@@ -9144,6 +9215,27 @@ func (q *FakeQuerier) InsertVolumeResourceMonitor(_ context.Context, arg databas
|
||||
return monitor, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) InsertWebpushSubscription(_ context.Context, arg database.InsertWebpushSubscriptionParams) (database.WebpushSubscription, error) {
|
||||
err := validateDatabaseType(arg)
|
||||
if err != nil {
|
||||
return database.WebpushSubscription{}, err
|
||||
}
|
||||
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
newSub := database.WebpushSubscription{
|
||||
ID: uuid.New(),
|
||||
UserID: arg.UserID,
|
||||
CreatedAt: arg.CreatedAt,
|
||||
Endpoint: arg.Endpoint,
|
||||
EndpointP256dhKey: arg.EndpointP256dhKey,
|
||||
EndpointAuthKey: arg.EndpointAuthKey,
|
||||
}
|
||||
q.webpushSubscriptions = append(q.webpushSubscriptions, newSub)
|
||||
return newSub, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) InsertWorkspace(_ context.Context, arg database.InsertWorkspaceParams) (database.WorkspaceTable, error) {
|
||||
if err := validateDatabaseType(arg); err != nil {
|
||||
return database.WorkspaceTable{}, err
|
||||
@@ -12458,6 +12550,20 @@ TemplateUsageStatsInsertLoop:
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) UpsertWebpushVAPIDKeys(_ context.Context, arg database.UpsertWebpushVAPIDKeysParams) error {
|
||||
err := validateDatabaseType(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
q.webpushVAPIDPublicKey = arg.VapidPublicKey
|
||||
q.webpushVAPIDPrivateKey = arg.VapidPrivateKey
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) UpsertWorkspaceAgentPortShare(_ context.Context, arg database.UpsertWorkspaceAgentPortShareParams) (database.WorkspaceAgentPortShare, error) {
|
||||
err := validateDatabaseType(arg)
|
||||
if err != nil {
|
||||
|
||||
@@ -221,6 +221,13 @@ func (m queryMetricsStore) DeleteAllTailnetTunnels(ctx context.Context, arg data
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) DeleteAllWebpushSubscriptions(ctx context.Context) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.DeleteAllWebpushSubscriptions(ctx)
|
||||
m.queryLatencies.WithLabelValues("DeleteAllWebpushSubscriptions").Observe(time.Since(start).Seconds())
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) DeleteApplicationConnectAPIKeysByUserID(ctx context.Context, userID uuid.UUID) error {
|
||||
start := time.Now()
|
||||
err := m.s.DeleteApplicationConnectAPIKeysByUserID(ctx, userID)
|
||||
@@ -410,6 +417,20 @@ func (m queryMetricsStore) DeleteTailnetTunnel(ctx context.Context, arg database
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) DeleteWebpushSubscriptionByUserIDAndEndpoint(ctx context.Context, arg database.DeleteWebpushSubscriptionByUserIDAndEndpointParams) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.DeleteWebpushSubscriptionByUserIDAndEndpoint(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("DeleteWebpushSubscriptionByUserIDAndEndpoint").Observe(time.Since(start).Seconds())
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) DeleteWebpushSubscriptions(ctx context.Context, ids []uuid.UUID) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.DeleteWebpushSubscriptions(ctx, ids)
|
||||
m.queryLatencies.WithLabelValues("DeleteWebpushSubscriptions").Observe(time.Since(start).Seconds())
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) DeleteWorkspaceAgentPortShare(ctx context.Context, arg database.DeleteWorkspaceAgentPortShareParams) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.DeleteWorkspaceAgentPortShare(ctx, arg)
|
||||
@@ -1502,6 +1523,20 @@ func (m queryMetricsStore) GetUsersByIDs(ctx context.Context, ids []uuid.UUID) (
|
||||
return users, err
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetWebpushSubscriptionsByUserID(ctx context.Context, userID uuid.UUID) ([]database.WebpushSubscription, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetWebpushSubscriptionsByUserID(ctx, userID)
|
||||
m.queryLatencies.WithLabelValues("GetWebpushSubscriptionsByUserID").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetWebpushVAPIDKeys(ctx context.Context) (database.GetWebpushVAPIDKeysRow, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetWebpushVAPIDKeys(ctx)
|
||||
m.queryLatencies.WithLabelValues("GetWebpushVAPIDKeys").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetWorkspaceAgentAndLatestBuildByAuthToken(ctx context.Context, authToken uuid.UUID) (database.GetWorkspaceAgentAndLatestBuildByAuthTokenRow, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetWorkspaceAgentAndLatestBuildByAuthToken(ctx, authToken)
|
||||
@@ -2146,6 +2181,13 @@ func (m queryMetricsStore) InsertVolumeResourceMonitor(ctx context.Context, arg
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) InsertWebpushSubscription(ctx context.Context, arg database.InsertWebpushSubscriptionParams) (database.WebpushSubscription, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.InsertWebpushSubscription(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("InsertWebpushSubscription").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) InsertWorkspace(ctx context.Context, arg database.InsertWorkspaceParams) (database.WorkspaceTable, error) {
|
||||
start := time.Now()
|
||||
workspace, err := m.s.InsertWorkspace(ctx, arg)
|
||||
@@ -3014,6 +3056,13 @@ func (m queryMetricsStore) UpsertTemplateUsageStats(ctx context.Context) error {
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) UpsertWebpushVAPIDKeys(ctx context.Context, arg database.UpsertWebpushVAPIDKeysParams) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.UpsertWebpushVAPIDKeys(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("UpsertWebpushVAPIDKeys").Observe(time.Since(start).Seconds())
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) UpsertWorkspaceAgentPortShare(ctx context.Context, arg database.UpsertWorkspaceAgentPortShareParams) (database.WorkspaceAgentPortShare, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.UpsertWorkspaceAgentPortShare(ctx, arg)
|
||||
|
||||
@@ -318,6 +318,20 @@ func (mr *MockStoreMockRecorder) DeleteAllTailnetTunnels(ctx, arg any) *gomock.C
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAllTailnetTunnels", reflect.TypeOf((*MockStore)(nil).DeleteAllTailnetTunnels), ctx, arg)
|
||||
}
|
||||
|
||||
// DeleteAllWebpushSubscriptions mocks base method.
|
||||
func (m *MockStore) DeleteAllWebpushSubscriptions(ctx context.Context) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteAllWebpushSubscriptions", ctx)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteAllWebpushSubscriptions indicates an expected call of DeleteAllWebpushSubscriptions.
|
||||
func (mr *MockStoreMockRecorder) DeleteAllWebpushSubscriptions(ctx any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAllWebpushSubscriptions", reflect.TypeOf((*MockStore)(nil).DeleteAllWebpushSubscriptions), ctx)
|
||||
}
|
||||
|
||||
// DeleteApplicationConnectAPIKeysByUserID mocks base method.
|
||||
func (m *MockStore) DeleteApplicationConnectAPIKeysByUserID(ctx context.Context, userID uuid.UUID) error {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -702,6 +716,34 @@ func (mr *MockStoreMockRecorder) DeleteTailnetTunnel(ctx, arg any) *gomock.Call
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteTailnetTunnel", reflect.TypeOf((*MockStore)(nil).DeleteTailnetTunnel), ctx, arg)
|
||||
}
|
||||
|
||||
// DeleteWebpushSubscriptionByUserIDAndEndpoint mocks base method.
|
||||
func (m *MockStore) DeleteWebpushSubscriptionByUserIDAndEndpoint(ctx context.Context, arg database.DeleteWebpushSubscriptionByUserIDAndEndpointParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteWebpushSubscriptionByUserIDAndEndpoint", ctx, arg)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteWebpushSubscriptionByUserIDAndEndpoint indicates an expected call of DeleteWebpushSubscriptionByUserIDAndEndpoint.
|
||||
func (mr *MockStoreMockRecorder) DeleteWebpushSubscriptionByUserIDAndEndpoint(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteWebpushSubscriptionByUserIDAndEndpoint", reflect.TypeOf((*MockStore)(nil).DeleteWebpushSubscriptionByUserIDAndEndpoint), ctx, arg)
|
||||
}
|
||||
|
||||
// DeleteWebpushSubscriptions mocks base method.
|
||||
func (m *MockStore) DeleteWebpushSubscriptions(ctx context.Context, ids []uuid.UUID) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteWebpushSubscriptions", ctx, ids)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteWebpushSubscriptions indicates an expected call of DeleteWebpushSubscriptions.
|
||||
func (mr *MockStoreMockRecorder) DeleteWebpushSubscriptions(ctx, ids any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteWebpushSubscriptions", reflect.TypeOf((*MockStore)(nil).DeleteWebpushSubscriptions), ctx, ids)
|
||||
}
|
||||
|
||||
// DeleteWorkspaceAgentPortShare mocks base method.
|
||||
func (m *MockStore) DeleteWorkspaceAgentPortShare(ctx context.Context, arg database.DeleteWorkspaceAgentPortShareParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -3142,6 +3184,36 @@ func (mr *MockStoreMockRecorder) GetUsersByIDs(ctx, ids any) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUsersByIDs", reflect.TypeOf((*MockStore)(nil).GetUsersByIDs), ctx, ids)
|
||||
}
|
||||
|
||||
// GetWebpushSubscriptionsByUserID mocks base method.
|
||||
func (m *MockStore) GetWebpushSubscriptionsByUserID(ctx context.Context, userID uuid.UUID) ([]database.WebpushSubscription, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetWebpushSubscriptionsByUserID", ctx, userID)
|
||||
ret0, _ := ret[0].([]database.WebpushSubscription)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetWebpushSubscriptionsByUserID indicates an expected call of GetWebpushSubscriptionsByUserID.
|
||||
func (mr *MockStoreMockRecorder) GetWebpushSubscriptionsByUserID(ctx, userID any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWebpushSubscriptionsByUserID", reflect.TypeOf((*MockStore)(nil).GetWebpushSubscriptionsByUserID), ctx, userID)
|
||||
}
|
||||
|
||||
// GetWebpushVAPIDKeys mocks base method.
|
||||
func (m *MockStore) GetWebpushVAPIDKeys(ctx context.Context) (database.GetWebpushVAPIDKeysRow, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetWebpushVAPIDKeys", ctx)
|
||||
ret0, _ := ret[0].(database.GetWebpushVAPIDKeysRow)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetWebpushVAPIDKeys indicates an expected call of GetWebpushVAPIDKeys.
|
||||
func (mr *MockStoreMockRecorder) GetWebpushVAPIDKeys(ctx any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWebpushVAPIDKeys", reflect.TypeOf((*MockStore)(nil).GetWebpushVAPIDKeys), ctx)
|
||||
}
|
||||
|
||||
// GetWorkspaceAgentAndLatestBuildByAuthToken mocks base method.
|
||||
func (m *MockStore) GetWorkspaceAgentAndLatestBuildByAuthToken(ctx context.Context, authToken uuid.UUID) (database.GetWorkspaceAgentAndLatestBuildByAuthTokenRow, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -4527,6 +4599,21 @@ func (mr *MockStoreMockRecorder) InsertVolumeResourceMonitor(ctx, arg any) *gomo
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertVolumeResourceMonitor", reflect.TypeOf((*MockStore)(nil).InsertVolumeResourceMonitor), ctx, arg)
|
||||
}
|
||||
|
||||
// InsertWebpushSubscription mocks base method.
|
||||
func (m *MockStore) InsertWebpushSubscription(ctx context.Context, arg database.InsertWebpushSubscriptionParams) (database.WebpushSubscription, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "InsertWebpushSubscription", ctx, arg)
|
||||
ret0, _ := ret[0].(database.WebpushSubscription)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// InsertWebpushSubscription indicates an expected call of InsertWebpushSubscription.
|
||||
func (mr *MockStoreMockRecorder) InsertWebpushSubscription(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertWebpushSubscription", reflect.TypeOf((*MockStore)(nil).InsertWebpushSubscription), ctx, arg)
|
||||
}
|
||||
|
||||
// InsertWorkspace mocks base method.
|
||||
func (m *MockStore) InsertWorkspace(ctx context.Context, arg database.InsertWorkspaceParams) (database.WorkspaceTable, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -6347,6 +6434,20 @@ func (mr *MockStoreMockRecorder) UpsertTemplateUsageStats(ctx any) *gomock.Call
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertTemplateUsageStats", reflect.TypeOf((*MockStore)(nil).UpsertTemplateUsageStats), ctx)
|
||||
}
|
||||
|
||||
// UpsertWebpushVAPIDKeys mocks base method.
|
||||
func (m *MockStore) UpsertWebpushVAPIDKeys(ctx context.Context, arg database.UpsertWebpushVAPIDKeysParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpsertWebpushVAPIDKeys", ctx, arg)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UpsertWebpushVAPIDKeys indicates an expected call of UpsertWebpushVAPIDKeys.
|
||||
func (mr *MockStoreMockRecorder) UpsertWebpushVAPIDKeys(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertWebpushVAPIDKeys", reflect.TypeOf((*MockStore)(nil).UpsertWebpushVAPIDKeys), ctx, arg)
|
||||
}
|
||||
|
||||
// UpsertWorkspaceAgentPortShare mocks base method.
|
||||
func (m *MockStore) UpsertWorkspaceAgentPortShare(ctx context.Context, arg database.UpsertWorkspaceAgentPortShareParams) (database.WorkspaceAgentPortShare, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
Generated
+15
@@ -1614,6 +1614,15 @@ CREATE TABLE user_status_changes (
|
||||
|
||||
COMMENT ON TABLE user_status_changes IS 'Tracks the history of user status changes';
|
||||
|
||||
CREATE TABLE webpush_subscriptions (
|
||||
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
||||
user_id uuid NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
endpoint text NOT NULL,
|
||||
endpoint_p256dh_key text NOT NULL,
|
||||
endpoint_auth_key text NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE workspace_agent_devcontainers (
|
||||
id uuid NOT NULL,
|
||||
workspace_agent_id uuid NOT NULL,
|
||||
@@ -2305,6 +2314,9 @@ ALTER TABLE ONLY user_status_changes
|
||||
ALTER TABLE ONLY users
|
||||
ADD CONSTRAINT users_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY webpush_subscriptions
|
||||
ADD CONSTRAINT webpush_subscriptions_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY workspace_agent_devcontainers
|
||||
ADD CONSTRAINT workspace_agent_devcontainers_pkey PRIMARY KEY (id);
|
||||
|
||||
@@ -2745,6 +2757,9 @@ ALTER TABLE ONLY user_links
|
||||
ALTER TABLE ONLY user_status_changes
|
||||
ADD CONSTRAINT user_status_changes_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id);
|
||||
|
||||
ALTER TABLE ONLY webpush_subscriptions
|
||||
ADD CONSTRAINT webpush_subscriptions_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY workspace_agent_devcontainers
|
||||
ADD CONSTRAINT workspace_agent_devcontainers_workspace_agent_id_fkey FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE;
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ const (
|
||||
ForeignKeyUserLinksOauthRefreshTokenKeyID ForeignKeyConstraint = "user_links_oauth_refresh_token_key_id_fkey" // ALTER TABLE ONLY user_links ADD CONSTRAINT user_links_oauth_refresh_token_key_id_fkey FOREIGN KEY (oauth_refresh_token_key_id) REFERENCES dbcrypt_keys(active_key_digest);
|
||||
ForeignKeyUserLinksUserID ForeignKeyConstraint = "user_links_user_id_fkey" // ALTER TABLE ONLY user_links ADD CONSTRAINT user_links_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
ForeignKeyUserStatusChangesUserID ForeignKeyConstraint = "user_status_changes_user_id_fkey" // ALTER TABLE ONLY user_status_changes ADD CONSTRAINT user_status_changes_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id);
|
||||
ForeignKeyWebpushSubscriptionsUserID ForeignKeyConstraint = "webpush_subscriptions_user_id_fkey" // ALTER TABLE ONLY webpush_subscriptions ADD CONSTRAINT webpush_subscriptions_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
ForeignKeyWorkspaceAgentDevcontainersWorkspaceAgentID ForeignKeyConstraint = "workspace_agent_devcontainers_workspace_agent_id_fkey" // ALTER TABLE ONLY workspace_agent_devcontainers ADD CONSTRAINT workspace_agent_devcontainers_workspace_agent_id_fkey FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE;
|
||||
ForeignKeyWorkspaceAgentLogSourcesWorkspaceAgentID ForeignKeyConstraint = "workspace_agent_log_sources_workspace_agent_id_fkey" // ALTER TABLE ONLY workspace_agent_log_sources ADD CONSTRAINT workspace_agent_log_sources_workspace_agent_id_fkey FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE;
|
||||
ForeignKeyWorkspaceAgentMemoryResourceMonitorsAgentID ForeignKeyConstraint = "workspace_agent_memory_resource_monitors_agent_id_fkey" // ALTER TABLE ONLY workspace_agent_memory_resource_monitors ADD CONSTRAINT workspace_agent_memory_resource_monitors_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE;
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
DROP TABLE IF EXISTS webpush_subscriptions;
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
-- webpush_subscriptions is a table that stores push notification
|
||||
-- subscriptions for users. These are acquired via the Push API in the browser.
|
||||
CREATE TABLE IF NOT EXISTS webpush_subscriptions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES users ON DELETE CASCADE,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
-- endpoint is called by coderd to send a push notification to the user.
|
||||
endpoint TEXT NOT NULL,
|
||||
-- endpoint_p256dh_key is the public key for the endpoint.
|
||||
endpoint_p256dh_key TEXT NOT NULL,
|
||||
-- endpoint_auth_key is the authentication key for the endpoint.
|
||||
endpoint_auth_key TEXT NOT NULL
|
||||
);
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
-- VAPID keys lited from coderd/notifications_test.go.
|
||||
INSERT INTO webpush_subscriptions (id, user_id, created_at, endpoint, endpoint_p256dh_key, endpoint_auth_key) VALUES (gen_random_uuid(), (SELECT id FROM users LIMIT 1), NOW(), 'https://example.com', 'BNNL5ZaTfK81qhXOx23+wewhigUeFb632jN6LvRWCFH1ubQr77FE/9qV1FuojuRmHP42zmf34rXgW80OvUVDgTk=', 'zqbxT6JKstKSY9JKibZLSQ==');
|
||||
@@ -3240,6 +3240,15 @@ type VisibleUser struct {
|
||||
AvatarURL string `db:"avatar_url" json:"avatar_url"`
|
||||
}
|
||||
|
||||
type WebpushSubscription struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
UserID uuid.UUID `db:"user_id" json:"user_id"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
Endpoint string `db:"endpoint" json:"endpoint"`
|
||||
EndpointP256dhKey string `db:"endpoint_p256dh_key" json:"endpoint_p256dh_key"`
|
||||
EndpointAuthKey string `db:"endpoint_auth_key" json:"endpoint_auth_key"`
|
||||
}
|
||||
|
||||
// Joins in the display name information such as username, avatar, and organization name.
|
||||
type Workspace struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
|
||||
@@ -69,6 +69,11 @@ type sqlcQuerier interface {
|
||||
DeleteAPIKeysByUserID(ctx context.Context, userID uuid.UUID) error
|
||||
DeleteAllTailnetClientSubscriptions(ctx context.Context, arg DeleteAllTailnetClientSubscriptionsParams) error
|
||||
DeleteAllTailnetTunnels(ctx context.Context, arg DeleteAllTailnetTunnelsParams) error
|
||||
// Deletes all existing webpush subscriptions.
|
||||
// This should be called when the VAPID keypair is regenerated, as the old
|
||||
// keypair will no longer be valid and all existing subscriptions will need to
|
||||
// be recreated.
|
||||
DeleteAllWebpushSubscriptions(ctx context.Context) error
|
||||
DeleteApplicationConnectAPIKeysByUserID(ctx context.Context, userID uuid.UUID) error
|
||||
DeleteCoordinator(ctx context.Context, id uuid.UUID) error
|
||||
DeleteCryptoKey(ctx context.Context, arg DeleteCryptoKeyParams) (CryptoKey, error)
|
||||
@@ -104,6 +109,8 @@ type sqlcQuerier interface {
|
||||
DeleteTailnetClientSubscription(ctx context.Context, arg DeleteTailnetClientSubscriptionParams) error
|
||||
DeleteTailnetPeer(ctx context.Context, arg DeleteTailnetPeerParams) (DeleteTailnetPeerRow, error)
|
||||
DeleteTailnetTunnel(ctx context.Context, arg DeleteTailnetTunnelParams) (DeleteTailnetTunnelRow, error)
|
||||
DeleteWebpushSubscriptionByUserIDAndEndpoint(ctx context.Context, arg DeleteWebpushSubscriptionByUserIDAndEndpointParams) error
|
||||
DeleteWebpushSubscriptions(ctx context.Context, ids []uuid.UUID) error
|
||||
DeleteWorkspaceAgentPortShare(ctx context.Context, arg DeleteWorkspaceAgentPortShareParams) error
|
||||
DeleteWorkspaceAgentPortSharesByTemplate(ctx context.Context, templateID uuid.UUID) error
|
||||
// Disable foreign keys and triggers for all tables.
|
||||
@@ -340,6 +347,8 @@ type sqlcQuerier interface {
|
||||
// to look up references to actions. eg. a user could build a workspace
|
||||
// for another user, then be deleted... we still want them to appear!
|
||||
GetUsersByIDs(ctx context.Context, ids []uuid.UUID) ([]User, error)
|
||||
GetWebpushSubscriptionsByUserID(ctx context.Context, userID uuid.UUID) ([]WebpushSubscription, error)
|
||||
GetWebpushVAPIDKeys(ctx context.Context) (GetWebpushVAPIDKeysRow, error)
|
||||
GetWorkspaceAgentAndLatestBuildByAuthToken(ctx context.Context, authToken uuid.UUID) (GetWorkspaceAgentAndLatestBuildByAuthTokenRow, error)
|
||||
GetWorkspaceAgentByID(ctx context.Context, id uuid.UUID) (WorkspaceAgent, error)
|
||||
GetWorkspaceAgentByInstanceID(ctx context.Context, authInstanceID string) (WorkspaceAgent, error)
|
||||
@@ -453,6 +462,7 @@ type sqlcQuerier interface {
|
||||
InsertUserGroupsByName(ctx context.Context, arg InsertUserGroupsByNameParams) error
|
||||
InsertUserLink(ctx context.Context, arg InsertUserLinkParams) (UserLink, error)
|
||||
InsertVolumeResourceMonitor(ctx context.Context, arg InsertVolumeResourceMonitorParams) (WorkspaceAgentVolumeResourceMonitor, error)
|
||||
InsertWebpushSubscription(ctx context.Context, arg InsertWebpushSubscriptionParams) (WebpushSubscription, error)
|
||||
InsertWorkspace(ctx context.Context, arg InsertWorkspaceParams) (WorkspaceTable, error)
|
||||
InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspaceAgentParams) (WorkspaceAgent, error)
|
||||
InsertWorkspaceAgentDevcontainers(ctx context.Context, arg InsertWorkspaceAgentDevcontainersParams) ([]WorkspaceAgentDevcontainer, error)
|
||||
@@ -597,6 +607,7 @@ type sqlcQuerier interface {
|
||||
// used to store the data, and the minutes are summed for each user and template
|
||||
// combination. The result is stored in the template_usage_stats table.
|
||||
UpsertTemplateUsageStats(ctx context.Context) error
|
||||
UpsertWebpushVAPIDKeys(ctx context.Context, arg UpsertWebpushVAPIDKeysParams) error
|
||||
UpsertWorkspaceAgentPortShare(ctx context.Context, arg UpsertWorkspaceAgentPortShareParams) (WorkspaceAgentPortShare, error)
|
||||
//
|
||||
// The returned boolean, new_or_stale, can be used to deduce if a new session
|
||||
|
||||
@@ -3988,6 +3988,19 @@ func (q *sqlQuerier) BulkMarkNotificationMessagesSent(ctx context.Context, arg B
|
||||
return result.RowsAffected()
|
||||
}
|
||||
|
||||
const deleteAllWebpushSubscriptions = `-- name: DeleteAllWebpushSubscriptions :exec
|
||||
TRUNCATE TABLE webpush_subscriptions
|
||||
`
|
||||
|
||||
// Deletes all existing webpush subscriptions.
|
||||
// This should be called when the VAPID keypair is regenerated, as the old
|
||||
// keypair will no longer be valid and all existing subscriptions will need to
|
||||
// be recreated.
|
||||
func (q *sqlQuerier) DeleteAllWebpushSubscriptions(ctx context.Context) error {
|
||||
_, err := q.db.ExecContext(ctx, deleteAllWebpushSubscriptions)
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteOldNotificationMessages = `-- name: DeleteOldNotificationMessages :exec
|
||||
DELETE
|
||||
FROM notification_messages
|
||||
@@ -4003,6 +4016,31 @@ func (q *sqlQuerier) DeleteOldNotificationMessages(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteWebpushSubscriptionByUserIDAndEndpoint = `-- name: DeleteWebpushSubscriptionByUserIDAndEndpoint :exec
|
||||
DELETE FROM webpush_subscriptions
|
||||
WHERE user_id = $1 AND endpoint = $2
|
||||
`
|
||||
|
||||
type DeleteWebpushSubscriptionByUserIDAndEndpointParams struct {
|
||||
UserID uuid.UUID `db:"user_id" json:"user_id"`
|
||||
Endpoint string `db:"endpoint" json:"endpoint"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) DeleteWebpushSubscriptionByUserIDAndEndpoint(ctx context.Context, arg DeleteWebpushSubscriptionByUserIDAndEndpointParams) error {
|
||||
_, err := q.db.ExecContext(ctx, deleteWebpushSubscriptionByUserIDAndEndpoint, arg.UserID, arg.Endpoint)
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteWebpushSubscriptions = `-- name: DeleteWebpushSubscriptions :exec
|
||||
DELETE FROM webpush_subscriptions
|
||||
WHERE id = ANY($1::uuid[])
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) DeleteWebpushSubscriptions(ctx context.Context, ids []uuid.UUID) error {
|
||||
_, err := q.db.ExecContext(ctx, deleteWebpushSubscriptions, pq.Array(ids))
|
||||
return err
|
||||
}
|
||||
|
||||
const enqueueNotificationMessage = `-- name: EnqueueNotificationMessage :exec
|
||||
INSERT INTO notification_messages (id, notification_template_id, user_id, method, payload, targets, created_by, created_at)
|
||||
VALUES ($1,
|
||||
@@ -4255,6 +4293,76 @@ func (q *sqlQuerier) GetUserNotificationPreferences(ctx context.Context, userID
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getWebpushSubscriptionsByUserID = `-- name: GetWebpushSubscriptionsByUserID :many
|
||||
SELECT id, user_id, created_at, endpoint, endpoint_p256dh_key, endpoint_auth_key
|
||||
FROM webpush_subscriptions
|
||||
WHERE user_id = $1::uuid
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetWebpushSubscriptionsByUserID(ctx context.Context, userID uuid.UUID) ([]WebpushSubscription, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getWebpushSubscriptionsByUserID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []WebpushSubscription
|
||||
for rows.Next() {
|
||||
var i WebpushSubscription
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.UserID,
|
||||
&i.CreatedAt,
|
||||
&i.Endpoint,
|
||||
&i.EndpointP256dhKey,
|
||||
&i.EndpointAuthKey,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const insertWebpushSubscription = `-- name: InsertWebpushSubscription :one
|
||||
INSERT INTO webpush_subscriptions (user_id, created_at, endpoint, endpoint_p256dh_key, endpoint_auth_key)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING id, user_id, created_at, endpoint, endpoint_p256dh_key, endpoint_auth_key
|
||||
`
|
||||
|
||||
type InsertWebpushSubscriptionParams struct {
|
||||
UserID uuid.UUID `db:"user_id" json:"user_id"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
Endpoint string `db:"endpoint" json:"endpoint"`
|
||||
EndpointP256dhKey string `db:"endpoint_p256dh_key" json:"endpoint_p256dh_key"`
|
||||
EndpointAuthKey string `db:"endpoint_auth_key" json:"endpoint_auth_key"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertWebpushSubscription(ctx context.Context, arg InsertWebpushSubscriptionParams) (WebpushSubscription, error) {
|
||||
row := q.db.QueryRowContext(ctx, insertWebpushSubscription,
|
||||
arg.UserID,
|
||||
arg.CreatedAt,
|
||||
arg.Endpoint,
|
||||
arg.EndpointP256dhKey,
|
||||
arg.EndpointAuthKey,
|
||||
)
|
||||
var i WebpushSubscription
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.UserID,
|
||||
&i.CreatedAt,
|
||||
&i.Endpoint,
|
||||
&i.EndpointP256dhKey,
|
||||
&i.EndpointAuthKey,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updateNotificationTemplateMethodByID = `-- name: UpdateNotificationTemplateMethodByID :one
|
||||
UPDATE notification_templates
|
||||
SET method = $1::notification_method
|
||||
@@ -8561,6 +8669,24 @@ func (q *sqlQuerier) GetRuntimeConfig(ctx context.Context, key string) (string,
|
||||
return value, err
|
||||
}
|
||||
|
||||
const getWebpushVAPIDKeys = `-- name: GetWebpushVAPIDKeys :one
|
||||
SELECT
|
||||
COALESCE((SELECT value FROM site_configs WHERE key = 'webpush_vapid_public_key'), '') :: text AS vapid_public_key,
|
||||
COALESCE((SELECT value FROM site_configs WHERE key = 'webpush_vapid_private_key'), '') :: text AS vapid_private_key
|
||||
`
|
||||
|
||||
type GetWebpushVAPIDKeysRow struct {
|
||||
VapidPublicKey string `db:"vapid_public_key" json:"vapid_public_key"`
|
||||
VapidPrivateKey string `db:"vapid_private_key" json:"vapid_private_key"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetWebpushVAPIDKeys(ctx context.Context) (GetWebpushVAPIDKeysRow, error) {
|
||||
row := q.db.QueryRowContext(ctx, getWebpushVAPIDKeys)
|
||||
var i GetWebpushVAPIDKeysRow
|
||||
err := row.Scan(&i.VapidPublicKey, &i.VapidPrivateKey)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const insertDERPMeshKey = `-- name: InsertDERPMeshKey :exec
|
||||
INSERT INTO site_configs (key, value) VALUES ('derp_mesh_key', $1)
|
||||
`
|
||||
@@ -8729,6 +8855,25 @@ func (q *sqlQuerier) UpsertRuntimeConfig(ctx context.Context, arg UpsertRuntimeC
|
||||
return err
|
||||
}
|
||||
|
||||
const upsertWebpushVAPIDKeys = `-- name: UpsertWebpushVAPIDKeys :exec
|
||||
INSERT INTO site_configs (key, value)
|
||||
VALUES
|
||||
('webpush_vapid_public_key', $1 :: text),
|
||||
('webpush_vapid_private_key', $2 :: text)
|
||||
ON CONFLICT (key)
|
||||
DO UPDATE SET value = EXCLUDED.value WHERE site_configs.key = EXCLUDED.key
|
||||
`
|
||||
|
||||
type UpsertWebpushVAPIDKeysParams struct {
|
||||
VapidPublicKey string `db:"vapid_public_key" json:"vapid_public_key"`
|
||||
VapidPrivateKey string `db:"vapid_private_key" json:"vapid_private_key"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpsertWebpushVAPIDKeys(ctx context.Context, arg UpsertWebpushVAPIDKeysParams) error {
|
||||
_, err := q.db.ExecContext(ctx, upsertWebpushVAPIDKeys, arg.VapidPublicKey, arg.VapidPrivateKey)
|
||||
return err
|
||||
}
|
||||
|
||||
const cleanTailnetCoordinators = `-- name: CleanTailnetCoordinators :exec
|
||||
DELETE
|
||||
FROM tailnet_coordinators
|
||||
|
||||
@@ -189,3 +189,28 @@ WHERE
|
||||
INSERT INTO notification_report_generator_logs (notification_template_id, last_generated_at) VALUES (@notification_template_id, @last_generated_at)
|
||||
ON CONFLICT (notification_template_id) DO UPDATE set last_generated_at = EXCLUDED.last_generated_at
|
||||
WHERE notification_report_generator_logs.notification_template_id = EXCLUDED.notification_template_id;
|
||||
|
||||
-- name: GetWebpushSubscriptionsByUserID :many
|
||||
SELECT *
|
||||
FROM webpush_subscriptions
|
||||
WHERE user_id = @user_id::uuid;
|
||||
|
||||
-- name: InsertWebpushSubscription :one
|
||||
INSERT INTO webpush_subscriptions (user_id, created_at, endpoint, endpoint_p256dh_key, endpoint_auth_key)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING *;
|
||||
|
||||
-- name: DeleteWebpushSubscriptions :exec
|
||||
DELETE FROM webpush_subscriptions
|
||||
WHERE id = ANY(@ids::uuid[]);
|
||||
|
||||
-- name: DeleteWebpushSubscriptionByUserIDAndEndpoint :exec
|
||||
DELETE FROM webpush_subscriptions
|
||||
WHERE user_id = @user_id AND endpoint = @endpoint;
|
||||
|
||||
-- name: DeleteAllWebpushSubscriptions :exec
|
||||
-- Deletes all existing webpush subscriptions.
|
||||
-- This should be called when the VAPID keypair is regenerated, as the old
|
||||
-- keypair will no longer be valid and all existing subscriptions will need to
|
||||
-- be recreated.
|
||||
TRUNCATE TABLE webpush_subscriptions;
|
||||
|
||||
@@ -131,3 +131,16 @@ SET value = CASE
|
||||
ELSE 'false'
|
||||
END
|
||||
WHERE site_configs.key = 'oauth2_github_default_eligible';
|
||||
|
||||
-- name: UpsertWebpushVAPIDKeys :exec
|
||||
INSERT INTO site_configs (key, value)
|
||||
VALUES
|
||||
('webpush_vapid_public_key', @vapid_public_key :: text),
|
||||
('webpush_vapid_private_key', @vapid_private_key :: text)
|
||||
ON CONFLICT (key)
|
||||
DO UPDATE SET value = EXCLUDED.value WHERE site_configs.key = EXCLUDED.key;
|
||||
|
||||
-- name: GetWebpushVAPIDKeys :one
|
||||
SELECT
|
||||
COALESCE((SELECT value FROM site_configs WHERE key = 'webpush_vapid_public_key'), '') :: text AS vapid_public_key,
|
||||
COALESCE((SELECT value FROM site_configs WHERE key = 'webpush_vapid_private_key'), '') :: text AS vapid_private_key;
|
||||
|
||||
@@ -71,6 +71,7 @@ const (
|
||||
UniqueUserLinksPkey UniqueConstraint = "user_links_pkey" // ALTER TABLE ONLY user_links ADD CONSTRAINT user_links_pkey PRIMARY KEY (user_id, login_type);
|
||||
UniqueUserStatusChangesPkey UniqueConstraint = "user_status_changes_pkey" // ALTER TABLE ONLY user_status_changes ADD CONSTRAINT user_status_changes_pkey PRIMARY KEY (id);
|
||||
UniqueUsersPkey UniqueConstraint = "users_pkey" // ALTER TABLE ONLY users ADD CONSTRAINT users_pkey PRIMARY KEY (id);
|
||||
UniqueWebpushSubscriptionsPkey UniqueConstraint = "webpush_subscriptions_pkey" // ALTER TABLE ONLY webpush_subscriptions ADD CONSTRAINT webpush_subscriptions_pkey PRIMARY KEY (id);
|
||||
UniqueWorkspaceAgentDevcontainersPkey UniqueConstraint = "workspace_agent_devcontainers_pkey" // ALTER TABLE ONLY workspace_agent_devcontainers ADD CONSTRAINT workspace_agent_devcontainers_pkey PRIMARY KEY (id);
|
||||
UniqueWorkspaceAgentLogSourcesPkey UniqueConstraint = "workspace_agent_log_sources_pkey" // ALTER TABLE ONLY workspace_agent_log_sources ADD CONSTRAINT workspace_agent_log_sources_pkey PRIMARY KEY (workspace_agent_id, id);
|
||||
UniqueWorkspaceAgentMemoryResourceMonitorsPkey UniqueConstraint = "workspace_agent_memory_resource_monitors_pkey" // ALTER TABLE ONLY workspace_agent_memory_resource_monitors ADD CONSTRAINT workspace_agent_memory_resource_monitors_pkey PRIMARY KEY (agent_id);
|
||||
|
||||
Reference in New Issue
Block a user