From 90c7b64a84edf24c0968daa22522193958dd6c60 Mon Sep 17 00:00:00 2001 From: defelmnq Date: Tue, 18 Feb 2025 10:03:52 +0000 Subject: [PATCH 01/37] work on db queries --- coderd/database/dbauthz/dbauthz.go | 24 ++ coderd/database/dbmem/dbmem.go | 44 ++++ coderd/database/dbmetrics/querymetrics.go | 42 +++ coderd/database/dbmock/dbmock.go | 89 +++++++ coderd/database/dump.sql | 20 ++ .../000295_notifications_inbox.down.sql | 1 + .../000295_notifications_inbox.up.sql | 15 ++ coderd/database/models.go | 13 + coderd/database/querier.go | 6 + coderd/database/queries.sql.go | 240 ++++++++++++++++++ .../database/queries/notificationsinbox.sql | 35 +++ coderd/database/unique_constraint.go | 1 + 12 files changed, 530 insertions(+) create mode 100644 coderd/database/migrations/000295_notifications_inbox.down.sql create mode 100644 coderd/database/migrations/000295_notifications_inbox.up.sql create mode 100644 coderd/database/queries/notificationsinbox.sql diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 9e616dd79dcbc..3c09502c42964 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -1417,6 +1417,14 @@ func (q *querier) FavoriteWorkspace(ctx context.Context, id uuid.UUID) error { return update(q.log, q.auth, fetch, q.db.FavoriteWorkspace)(ctx, id) } +func (q *querier) FetchInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.NotificationsInbox, error) { + panic("not implemented") +} + +func (q *querier) FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx context.Context, arg database.FetchInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]database.NotificationsInbox, error) { + panic("not implemented") +} + func (q *querier) FetchMemoryResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) (database.WorkspaceAgentMemoryResourceMonitor, error) { workspace, err := q.db.GetWorkspaceByAgentID(ctx, agentID) if err != nil { @@ -1438,6 +1446,14 @@ func (q *querier) FetchNewMessageMetadata(ctx context.Context, arg database.Fetc return q.db.FetchNewMessageMetadata(ctx, arg) } +func (q *querier) FetchUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.NotificationsInbox, error) { + panic("not implemented") +} + +func (q *querier) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx context.Context, arg database.FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]database.NotificationsInbox, error) { + panic("not implemented") +} + func (q *querier) FetchVolumesResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) ([]database.WorkspaceAgentVolumeResourceMonitor, error) { workspace, err := q.db.GetWorkspaceByAgentID(ctx, agentID) if err != nil { @@ -3077,6 +3093,10 @@ func (q *querier) InsertGroupMember(ctx context.Context, arg database.InsertGrou return update(q.log, q.auth, fetch, q.db.InsertGroupMember)(ctx, arg) } +func (q *querier) InsertInboxNotification(ctx context.Context, arg database.InsertInboxNotificationParams) (database.NotificationsInbox, error) { + panic("not implemented") +} + func (q *querier) InsertLicense(ctx context.Context, arg database.InsertLicenseParams) (database.License, error) { if err := q.authorizeContext(ctx, policy.ActionCreate, rbac.ResourceLicense); err != nil { return database.License{}, err @@ -3564,6 +3584,10 @@ func (q *querier) RevokeDBCryptKey(ctx context.Context, activeKeyDigest string) return q.db.RevokeDBCryptKey(ctx, activeKeyDigest) } +func (q *querier) SetInboxNotificationAsRead(ctx context.Context, arg database.SetInboxNotificationAsReadParams) error { + panic("not implemented") +} + func (q *querier) TryAcquireLock(ctx context.Context, id int64) (bool, error) { return q.db.TryAcquireLock(ctx, id) } diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 7f56ea5f463e5..bab75e1e7f5c6 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -2363,6 +2363,19 @@ func (q *FakeQuerier) FavoriteWorkspace(_ context.Context, arg uuid.UUID) error return nil } +func (q *FakeQuerier) FetchInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.NotificationsInbox, error) { + panic("not implemented") +} + +func (q *FakeQuerier) FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx context.Context, arg database.FetchInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]database.NotificationsInbox, error) { + err := validateDatabaseType(arg) + if err != nil { + return nil, err + } + + panic("not implemented") +} + func (q *FakeQuerier) FetchMemoryResourceMonitorsByAgentID(_ context.Context, agentID uuid.UUID) (database.WorkspaceAgentMemoryResourceMonitor, error) { for _, monitor := range q.workspaceAgentMemoryResourceMonitors { if monitor.AgentID == agentID { @@ -2405,6 +2418,19 @@ func (q *FakeQuerier) FetchNewMessageMetadata(_ context.Context, arg database.Fe }, nil } +func (q *FakeQuerier) FetchUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.NotificationsInbox, error) { + panic("not implemented") +} + +func (q *FakeQuerier) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx context.Context, arg database.FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]database.NotificationsInbox, error) { + err := validateDatabaseType(arg) + if err != nil { + return nil, err + } + + panic("not implemented") +} + func (q *FakeQuerier) FetchVolumesResourceMonitorsByAgentID(_ context.Context, agentID uuid.UUID) ([]database.WorkspaceAgentVolumeResourceMonitor, error) { monitors := []database.WorkspaceAgentVolumeResourceMonitor{} @@ -7959,6 +7985,15 @@ func (q *FakeQuerier) InsertGroupMember(_ context.Context, arg database.InsertGr return nil } +func (q *FakeQuerier) InsertInboxNotification(ctx context.Context, arg database.InsertInboxNotificationParams) (database.NotificationsInbox, error) { + err := validateDatabaseType(arg) + if err != nil { + return database.NotificationsInbox{}, err + } + + panic("not implemented") +} + func (q *FakeQuerier) InsertLicense( _ context.Context, arg database.InsertLicenseParams, ) (database.License, error) { @@ -9443,6 +9478,15 @@ func (q *FakeQuerier) RevokeDBCryptKey(_ context.Context, activeKeyDigest string return sql.ErrNoRows } +func (q *FakeQuerier) SetInboxNotificationAsRead(ctx context.Context, arg database.SetInboxNotificationAsReadParams) error { + err := validateDatabaseType(arg) + if err != nil { + return err + } + + panic("not implemented") +} + func (*FakeQuerier) TryAcquireLock(_ context.Context, _ int64) (bool, error) { return false, xerrors.New("TryAcquireLock must only be called within a transaction") } diff --git a/coderd/database/dbmetrics/querymetrics.go b/coderd/database/dbmetrics/querymetrics.go index 665c10658a5bc..9ab50064564ec 100644 --- a/coderd/database/dbmetrics/querymetrics.go +++ b/coderd/database/dbmetrics/querymetrics.go @@ -434,6 +434,20 @@ func (m queryMetricsStore) FavoriteWorkspace(ctx context.Context, arg uuid.UUID) return r0 } +func (m queryMetricsStore) FetchInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.NotificationsInbox, error) { + start := time.Now() + r0, r1 := m.s.FetchInboxNotificationsByUserID(ctx, userID) + m.queryLatencies.WithLabelValues("FetchInboxNotificationsByUserID").Observe(time.Since(start).Seconds()) + return r0, r1 +} + +func (m queryMetricsStore) FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx context.Context, arg database.FetchInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]database.NotificationsInbox, error) { + start := time.Now() + r0, r1 := m.s.FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx, arg) + m.queryLatencies.WithLabelValues("FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m queryMetricsStore) FetchMemoryResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) (database.WorkspaceAgentMemoryResourceMonitor, error) { start := time.Now() r0, r1 := m.s.FetchMemoryResourceMonitorsByAgentID(ctx, agentID) @@ -448,6 +462,20 @@ func (m queryMetricsStore) FetchNewMessageMetadata(ctx context.Context, arg data return r0, r1 } +func (m queryMetricsStore) FetchUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.NotificationsInbox, error) { + start := time.Now() + r0, r1 := m.s.FetchUnreadInboxNotificationsByUserID(ctx, userID) + m.queryLatencies.WithLabelValues("FetchUnreadInboxNotificationsByUserID").Observe(time.Since(start).Seconds()) + return r0, r1 +} + +func (m queryMetricsStore) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx context.Context, arg database.FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]database.NotificationsInbox, error) { + start := time.Now() + r0, r1 := m.s.FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx, arg) + m.queryLatencies.WithLabelValues("FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m queryMetricsStore) FetchVolumesResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) ([]database.WorkspaceAgentVolumeResourceMonitor, error) { start := time.Now() r0, r1 := m.s.FetchVolumesResourceMonitorsByAgentID(ctx, agentID) @@ -1869,6 +1897,13 @@ func (m queryMetricsStore) InsertGroupMember(ctx context.Context, arg database.I return err } +func (m queryMetricsStore) InsertInboxNotification(ctx context.Context, arg database.InsertInboxNotificationParams) (database.NotificationsInbox, error) { + start := time.Now() + r0, r1 := m.s.InsertInboxNotification(ctx, arg) + m.queryLatencies.WithLabelValues("InsertInboxNotification").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m queryMetricsStore) InsertLicense(ctx context.Context, arg database.InsertLicenseParams) (database.License, error) { start := time.Now() license, err := m.s.InsertLicense(ctx, arg) @@ -2247,6 +2282,13 @@ func (m queryMetricsStore) RevokeDBCryptKey(ctx context.Context, activeKeyDigest return r0 } +func (m queryMetricsStore) SetInboxNotificationAsRead(ctx context.Context, arg database.SetInboxNotificationAsReadParams) error { + start := time.Now() + r0 := m.s.SetInboxNotificationAsRead(ctx, arg) + m.queryLatencies.WithLabelValues("SetInboxNotificationAsRead").Observe(time.Since(start).Seconds()) + return r0 +} + func (m queryMetricsStore) TryAcquireLock(ctx context.Context, pgTryAdvisoryXactLock int64) (bool, error) { start := time.Now() ok, err := m.s.TryAcquireLock(ctx, pgTryAdvisoryXactLock) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index c7711505d7d51..c4bdb73d24be3 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -771,6 +771,36 @@ func (mr *MockStoreMockRecorder) FavoriteWorkspace(ctx, id any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FavoriteWorkspace", reflect.TypeOf((*MockStore)(nil).FavoriteWorkspace), ctx, id) } +// FetchInboxNotificationsByUserID mocks base method. +func (m *MockStore) FetchInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.NotificationsInbox, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FetchInboxNotificationsByUserID", ctx, userID) + ret0, _ := ret[0].([]database.NotificationsInbox) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FetchInboxNotificationsByUserID indicates an expected call of FetchInboxNotificationsByUserID. +func (mr *MockStoreMockRecorder) FetchInboxNotificationsByUserID(ctx, userID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchInboxNotificationsByUserID", reflect.TypeOf((*MockStore)(nil).FetchInboxNotificationsByUserID), ctx, userID) +} + +// FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID mocks base method. +func (m *MockStore) FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx context.Context, arg database.FetchInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]database.NotificationsInbox, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID", ctx, arg) + ret0, _ := ret[0].([]database.NotificationsInbox) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID indicates an expected call of FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID. +func (mr *MockStoreMockRecorder) FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx, arg any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID", reflect.TypeOf((*MockStore)(nil).FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID), ctx, arg) +} + // FetchMemoryResourceMonitorsByAgentID mocks base method. func (m *MockStore) FetchMemoryResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) (database.WorkspaceAgentMemoryResourceMonitor, error) { m.ctrl.T.Helper() @@ -801,6 +831,36 @@ func (mr *MockStoreMockRecorder) FetchNewMessageMetadata(ctx, arg any) *gomock.C return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchNewMessageMetadata", reflect.TypeOf((*MockStore)(nil).FetchNewMessageMetadata), ctx, arg) } +// FetchUnreadInboxNotificationsByUserID mocks base method. +func (m *MockStore) FetchUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.NotificationsInbox, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FetchUnreadInboxNotificationsByUserID", ctx, userID) + ret0, _ := ret[0].([]database.NotificationsInbox) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FetchUnreadInboxNotificationsByUserID indicates an expected call of FetchUnreadInboxNotificationsByUserID. +func (mr *MockStoreMockRecorder) FetchUnreadInboxNotificationsByUserID(ctx, userID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchUnreadInboxNotificationsByUserID", reflect.TypeOf((*MockStore)(nil).FetchUnreadInboxNotificationsByUserID), ctx, userID) +} + +// FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID mocks base method. +func (m *MockStore) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx context.Context, arg database.FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]database.NotificationsInbox, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID", ctx, arg) + ret0, _ := ret[0].([]database.NotificationsInbox) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID indicates an expected call of FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID. +func (mr *MockStoreMockRecorder) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx, arg any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID", reflect.TypeOf((*MockStore)(nil).FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID), ctx, arg) +} + // FetchVolumesResourceMonitorsByAgentID mocks base method. func (m *MockStore) FetchVolumesResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) ([]database.WorkspaceAgentVolumeResourceMonitor, error) { m.ctrl.T.Helper() @@ -3961,6 +4021,21 @@ func (mr *MockStoreMockRecorder) InsertGroupMember(ctx, arg any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertGroupMember", reflect.TypeOf((*MockStore)(nil).InsertGroupMember), ctx, arg) } +// InsertInboxNotification mocks base method. +func (m *MockStore) InsertInboxNotification(ctx context.Context, arg database.InsertInboxNotificationParams) (database.NotificationsInbox, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InsertInboxNotification", ctx, arg) + ret0, _ := ret[0].(database.NotificationsInbox) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// InsertInboxNotification indicates an expected call of InsertInboxNotification. +func (mr *MockStoreMockRecorder) InsertInboxNotification(ctx, arg any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertInboxNotification", reflect.TypeOf((*MockStore)(nil).InsertInboxNotification), ctx, arg) +} + // InsertLicense mocks base method. func (m *MockStore) InsertLicense(ctx context.Context, arg database.InsertLicenseParams) (database.License, error) { m.ctrl.T.Helper() @@ -4789,6 +4864,20 @@ func (mr *MockStoreMockRecorder) RevokeDBCryptKey(ctx, activeKeyDigest any) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RevokeDBCryptKey", reflect.TypeOf((*MockStore)(nil).RevokeDBCryptKey), ctx, activeKeyDigest) } +// SetInboxNotificationAsRead mocks base method. +func (m *MockStore) SetInboxNotificationAsRead(ctx context.Context, arg database.SetInboxNotificationAsReadParams) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetInboxNotificationAsRead", ctx, arg) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetInboxNotificationAsRead indicates an expected call of SetInboxNotificationAsRead. +func (mr *MockStoreMockRecorder) SetInboxNotificationAsRead(ctx, arg any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetInboxNotificationAsRead", reflect.TypeOf((*MockStore)(nil).SetInboxNotificationAsRead), ctx, arg) +} + // TryAcquireLock mocks base method. func (m *MockStore) TryAcquireLock(ctx context.Context, pgTryAdvisoryXactLock int64) (bool, error) { m.ctrl.T.Helper() diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index e699b34bd5433..8b5a2ea40e2bd 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -912,6 +912,19 @@ COMMENT ON TABLE notification_templates IS 'Templates from which to create notif COMMENT ON COLUMN notification_templates.method IS 'NULL defers to the deployment-level method'; +CREATE TABLE notifications_inbox ( + id uuid NOT NULL, + user_id uuid NOT NULL, + template_id uuid NOT NULL, + target_id uuid, + title text NOT NULL, + content text NOT NULL, + icon text NOT NULL, + actions jsonb NOT NULL, + read_at timestamp with time zone, + created_at timestamp with time zone DEFAULT now() NOT NULL +); + CREATE TABLE oauth2_provider_app_codes ( id uuid NOT NULL, created_at timestamp with time zone NOT NULL, @@ -2003,6 +2016,9 @@ ALTER TABLE ONLY notification_templates ALTER TABLE ONLY notification_templates ADD CONSTRAINT notification_templates_pkey PRIMARY KEY (id); +ALTER TABLE ONLY notifications_inbox + ADD CONSTRAINT notifications_inbox_pkey PRIMARY KEY (id); + ALTER TABLE ONLY oauth2_provider_app_codes ADD CONSTRAINT oauth2_provider_app_codes_pkey PRIMARY KEY (id); @@ -2214,6 +2230,10 @@ CREATE UNIQUE INDEX idx_custom_roles_name_lower ON custom_roles USING btree (low CREATE INDEX idx_notification_messages_status ON notification_messages USING btree (status); +CREATE INDEX idx_notifications_inbox_user_id_read_at ON notifications_inbox USING btree (user_id, read_at); + +CREATE INDEX idx_notifications_inbox_user_id_template_id_target_id ON notifications_inbox USING btree (user_id, template_id, target_id); + CREATE INDEX idx_organization_member_organization_id_uuid ON organization_members USING btree (organization_id); CREATE INDEX idx_organization_member_user_id_uuid ON organization_members USING btree (user_id); diff --git a/coderd/database/migrations/000295_notifications_inbox.down.sql b/coderd/database/migrations/000295_notifications_inbox.down.sql new file mode 100644 index 0000000000000..21a6935b2bb8d --- /dev/null +++ b/coderd/database/migrations/000295_notifications_inbox.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS notifications_inbox; diff --git a/coderd/database/migrations/000295_notifications_inbox.up.sql b/coderd/database/migrations/000295_notifications_inbox.up.sql new file mode 100644 index 0000000000000..6b674776b5d90 --- /dev/null +++ b/coderd/database/migrations/000295_notifications_inbox.up.sql @@ -0,0 +1,15 @@ +CREATE TABLE notifications_inbox ( + id UUID PRIMARY KEY, + user_id UUID NOT NULL, + template_id UUID NOT NULL, + target_id UUID, + title TEXT NOT NULL, + content TEXT NOT NULL, + icon TEXT NOT NULL, + actions JSONB NOT NULL, + read_at TIMESTAMP WITH TIME ZONE, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_notifications_inbox_user_id_read_at ON notifications_inbox(user_id, read_at); +CREATE INDEX idx_notifications_inbox_user_id_template_id_target_id ON notifications_inbox(user_id, template_id, target_id); diff --git a/coderd/database/models.go b/coderd/database/models.go index 5411591eed51c..6711904b456b3 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -2623,6 +2623,19 @@ type NotificationTemplate struct { EnabledByDefault bool `db:"enabled_by_default" json:"enabled_by_default"` } +type NotificationsInbox struct { + ID uuid.UUID `db:"id" json:"id"` + UserID uuid.UUID `db:"user_id" json:"user_id"` + TemplateID uuid.UUID `db:"template_id" json:"template_id"` + TargetID uuid.NullUUID `db:"target_id" json:"target_id"` + Title string `db:"title" json:"title"` + Content string `db:"content" json:"content"` + Icon string `db:"icon" json:"icon"` + Actions json.RawMessage `db:"actions" json:"actions"` + ReadAt sql.NullTime `db:"read_at" json:"read_at"` + CreatedAt time.Time `db:"created_at" json:"created_at"` +} + // A table used to configure apps that can use Coder as an OAuth2 provider, the reverse of what we are calling external authentication. type OAuth2ProviderApp struct { ID uuid.UUID `db:"id" json:"id"` diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 42b88d855e4c3..a4c8f720bbdc6 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -112,9 +112,13 @@ type sqlcQuerier interface { DisableForeignKeysAndTriggers(ctx context.Context) error EnqueueNotificationMessage(ctx context.Context, arg EnqueueNotificationMessageParams) error FavoriteWorkspace(ctx context.Context, id uuid.UUID) error + FetchInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]NotificationsInbox, error) + FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx context.Context, arg FetchInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]NotificationsInbox, error) FetchMemoryResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) (WorkspaceAgentMemoryResourceMonitor, error) // This is used to build up the notification_message's JSON payload. FetchNewMessageMetadata(ctx context.Context, arg FetchNewMessageMetadataParams) (FetchNewMessageMetadataRow, error) + FetchUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]NotificationsInbox, error) + FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx context.Context, arg FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]NotificationsInbox, error) FetchVolumesResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) ([]WorkspaceAgentVolumeResourceMonitor, error) GetAPIKeyByID(ctx context.Context, id string) (APIKey, error) // there is no unique constraint on empty token names @@ -396,6 +400,7 @@ type sqlcQuerier interface { InsertGitSSHKey(ctx context.Context, arg InsertGitSSHKeyParams) (GitSSHKey, error) InsertGroup(ctx context.Context, arg InsertGroupParams) (Group, error) InsertGroupMember(ctx context.Context, arg InsertGroupMemberParams) error + InsertInboxNotification(ctx context.Context, arg InsertInboxNotificationParams) (NotificationsInbox, error) InsertLicense(ctx context.Context, arg InsertLicenseParams) (License, error) InsertMemoryResourceMonitor(ctx context.Context, arg InsertMemoryResourceMonitorParams) (WorkspaceAgentMemoryResourceMonitor, error) // Inserts any group by name that does not exist. All new groups are given @@ -463,6 +468,7 @@ type sqlcQuerier interface { RemoveUserFromAllGroups(ctx context.Context, userID uuid.UUID) error RemoveUserFromGroups(ctx context.Context, arg RemoveUserFromGroupsParams) ([]uuid.UUID, error) RevokeDBCryptKey(ctx context.Context, activeKeyDigest string) error + SetInboxNotificationAsRead(ctx context.Context, arg SetInboxNotificationAsReadParams) error // Non blocking lock. Returns true if the lock was acquired, false otherwise. // // This must be called from within a transaction. The lock will be automatically diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 58722dc152005..dc952c9b18be0 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -4298,6 +4298,246 @@ func (q *sqlQuerier) UpsertNotificationReportGeneratorLog(ctx context.Context, a return err } +const fetchInboxNotificationsByUserID = `-- name: FetchInboxNotificationsByUserID :many +SELECT id, user_id, template_id, target_id, title, content, icon, actions, read_at, created_at FROM notifications_inbox WHERE user_id = $1 ORDER BY created_at DESC +` + +func (q *sqlQuerier) FetchInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]NotificationsInbox, error) { + rows, err := q.db.QueryContext(ctx, fetchInboxNotificationsByUserID, userID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []NotificationsInbox + for rows.Next() { + var i NotificationsInbox + if err := rows.Scan( + &i.ID, + &i.UserID, + &i.TemplateID, + &i.TargetID, + &i.Title, + &i.Content, + &i.Icon, + &i.Actions, + &i.ReadAt, + &i.CreatedAt, + ); 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 fetchInboxNotificationsByUserIDAndTemplateIDAndTargetID = `-- name: FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID :many +SELECT id, user_id, template_id, target_id, title, content, icon, actions, read_at, created_at FROM notifications_inbox WHERE user_id = $1 AND template_id = $2 AND target_id = $3 ORDER BY created_at DESC +` + +type FetchInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams struct { + UserID uuid.UUID `db:"user_id" json:"user_id"` + TemplateID uuid.UUID `db:"template_id" json:"template_id"` + TargetID uuid.NullUUID `db:"target_id" json:"target_id"` +} + +func (q *sqlQuerier) FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx context.Context, arg FetchInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]NotificationsInbox, error) { + rows, err := q.db.QueryContext(ctx, fetchInboxNotificationsByUserIDAndTemplateIDAndTargetID, arg.UserID, arg.TemplateID, arg.TargetID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []NotificationsInbox + for rows.Next() { + var i NotificationsInbox + if err := rows.Scan( + &i.ID, + &i.UserID, + &i.TemplateID, + &i.TargetID, + &i.Title, + &i.Content, + &i.Icon, + &i.Actions, + &i.ReadAt, + &i.CreatedAt, + ); 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 fetchUnreadInboxNotificationsByUserID = `-- name: FetchUnreadInboxNotificationsByUserID :many +SELECT id, user_id, template_id, target_id, title, content, icon, actions, read_at, created_at FROM notifications_inbox WHERE user_id = $1 AND read_at IS NULL ORDER BY created_at DESC +` + +func (q *sqlQuerier) FetchUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]NotificationsInbox, error) { + rows, err := q.db.QueryContext(ctx, fetchUnreadInboxNotificationsByUserID, userID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []NotificationsInbox + for rows.Next() { + var i NotificationsInbox + if err := rows.Scan( + &i.ID, + &i.UserID, + &i.TemplateID, + &i.TargetID, + &i.Title, + &i.Content, + &i.Icon, + &i.Actions, + &i.ReadAt, + &i.CreatedAt, + ); 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 fetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID = `-- name: FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID :many +SELECT id, user_id, template_id, target_id, title, content, icon, actions, read_at, created_at FROM notifications_inbox WHERE user_id = $1 AND template_id = $2 AND target_id = $3 AND read_at IS NULL ORDER BY created_at DESC +` + +type FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams struct { + UserID uuid.UUID `db:"user_id" json:"user_id"` + TemplateID uuid.UUID `db:"template_id" json:"template_id"` + TargetID uuid.NullUUID `db:"target_id" json:"target_id"` +} + +func (q *sqlQuerier) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx context.Context, arg FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]NotificationsInbox, error) { + rows, err := q.db.QueryContext(ctx, fetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID, arg.UserID, arg.TemplateID, arg.TargetID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []NotificationsInbox + for rows.Next() { + var i NotificationsInbox + if err := rows.Scan( + &i.ID, + &i.UserID, + &i.TemplateID, + &i.TargetID, + &i.Title, + &i.Content, + &i.Icon, + &i.Actions, + &i.ReadAt, + &i.CreatedAt, + ); 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 insertInboxNotification = `-- name: InsertInboxNotification :one +INSERT INTO + notifications_inbox ( + id, + user_id, + template_id, + target_id, + title, + content, + icon, + actions, + created_at + ) +VALUES + ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id, user_id, template_id, target_id, title, content, icon, actions, read_at, created_at +` + +type InsertInboxNotificationParams struct { + ID uuid.UUID `db:"id" json:"id"` + UserID uuid.UUID `db:"user_id" json:"user_id"` + TemplateID uuid.UUID `db:"template_id" json:"template_id"` + TargetID uuid.NullUUID `db:"target_id" json:"target_id"` + Title string `db:"title" json:"title"` + Content string `db:"content" json:"content"` + Icon string `db:"icon" json:"icon"` + Actions json.RawMessage `db:"actions" json:"actions"` + CreatedAt time.Time `db:"created_at" json:"created_at"` +} + +func (q *sqlQuerier) InsertInboxNotification(ctx context.Context, arg InsertInboxNotificationParams) (NotificationsInbox, error) { + row := q.db.QueryRowContext(ctx, insertInboxNotification, + arg.ID, + arg.UserID, + arg.TemplateID, + arg.TargetID, + arg.Title, + arg.Content, + arg.Icon, + arg.Actions, + arg.CreatedAt, + ) + var i NotificationsInbox + err := row.Scan( + &i.ID, + &i.UserID, + &i.TemplateID, + &i.TargetID, + &i.Title, + &i.Content, + &i.Icon, + &i.Actions, + &i.ReadAt, + &i.CreatedAt, + ) + return i, err +} + +const setInboxNotificationAsRead = `-- name: SetInboxNotificationAsRead :exec +UPDATE + notifications_inbox +SET + read_at = $1 +WHERE + id = $2 +` + +type SetInboxNotificationAsReadParams struct { + ReadAt sql.NullTime `db:"read_at" json:"read_at"` + ID uuid.UUID `db:"id" json:"id"` +} + +func (q *sqlQuerier) SetInboxNotificationAsRead(ctx context.Context, arg SetInboxNotificationAsReadParams) error { + _, err := q.db.ExecContext(ctx, setInboxNotificationAsRead, arg.ReadAt, arg.ID) + return err +} + const deleteOAuth2ProviderAppByID = `-- name: DeleteOAuth2ProviderAppByID :exec DELETE FROM oauth2_provider_apps WHERE id = $1 ` diff --git a/coderd/database/queries/notificationsinbox.sql b/coderd/database/queries/notificationsinbox.sql new file mode 100644 index 0000000000000..0dadec858f5d2 --- /dev/null +++ b/coderd/database/queries/notificationsinbox.sql @@ -0,0 +1,35 @@ +-- name: FetchUnreadInboxNotificationsByUserID :many +SELECT * FROM notifications_inbox WHERE user_id = $1 AND read_at IS NULL ORDER BY created_at DESC; + +-- name: FetchInboxNotificationsByUserID :many +SELECT * FROM notifications_inbox WHERE user_id = $1 ORDER BY created_at DESC; + +-- name: FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID :many +SELECT * FROM notifications_inbox WHERE user_id = $1 AND template_id = $2 AND target_id = $3 ORDER BY created_at DESC; + +-- name: FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID :many +SELECT * FROM notifications_inbox WHERE user_id = $1 AND template_id = $2 AND target_id = $3 AND read_at IS NULL ORDER BY created_at DESC; + +-- name: InsertInboxNotification :one +INSERT INTO + notifications_inbox ( + id, + user_id, + template_id, + target_id, + title, + content, + icon, + actions, + created_at + ) +VALUES + ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *; + +-- name: SetInboxNotificationAsRead :exec +UPDATE + notifications_inbox +SET + read_at = $1 +WHERE + id = $2; diff --git a/coderd/database/unique_constraint.go b/coderd/database/unique_constraint.go index ce427cf97c3bc..a4ad011a2150d 100644 --- a/coderd/database/unique_constraint.go +++ b/coderd/database/unique_constraint.go @@ -29,6 +29,7 @@ const ( UniqueNotificationReportGeneratorLogsPkey UniqueConstraint = "notification_report_generator_logs_pkey" // ALTER TABLE ONLY notification_report_generator_logs ADD CONSTRAINT notification_report_generator_logs_pkey PRIMARY KEY (notification_template_id); UniqueNotificationTemplatesNameKey UniqueConstraint = "notification_templates_name_key" // ALTER TABLE ONLY notification_templates ADD CONSTRAINT notification_templates_name_key UNIQUE (name); UniqueNotificationTemplatesPkey UniqueConstraint = "notification_templates_pkey" // ALTER TABLE ONLY notification_templates ADD CONSTRAINT notification_templates_pkey PRIMARY KEY (id); + UniqueNotificationsInboxPkey UniqueConstraint = "notifications_inbox_pkey" // ALTER TABLE ONLY notifications_inbox ADD CONSTRAINT notifications_inbox_pkey PRIMARY KEY (id); UniqueOauth2ProviderAppCodesPkey UniqueConstraint = "oauth2_provider_app_codes_pkey" // ALTER TABLE ONLY oauth2_provider_app_codes ADD CONSTRAINT oauth2_provider_app_codes_pkey PRIMARY KEY (id); UniqueOauth2ProviderAppCodesSecretPrefixKey UniqueConstraint = "oauth2_provider_app_codes_secret_prefix_key" // ALTER TABLE ONLY oauth2_provider_app_codes ADD CONSTRAINT oauth2_provider_app_codes_secret_prefix_key UNIQUE (secret_prefix); UniqueOauth2ProviderAppSecretsPkey UniqueConstraint = "oauth2_provider_app_secrets_pkey" // ALTER TABLE ONLY oauth2_provider_app_secrets ADD CONSTRAINT oauth2_provider_app_secrets_pkey PRIMARY KEY (id); From e205049745b33288b0dede3e2c49c9c88b6ab6a7 Mon Sep 17 00:00:00 2001 From: defelmnq Date: Wed, 19 Feb 2025 10:59:11 +0000 Subject: [PATCH 02/37] work on dbauthz --- coderd/apidoc/docs.go | 2 ++ coderd/apidoc/swagger.json | 2 ++ coderd/database/dbauthz/dbauthz.go | 24 ++++++++++++++----- coderd/database/dbmem/dbmem.go | 4 ++++ coderd/database/dbmetrics/querymetrics.go | 7 ++++++ coderd/database/dbmock/dbmock.go | 15 ++++++++++++ coderd/database/modelmethods.go | 6 +++++ coderd/database/querier.go | 1 + coderd/database/queries.sql.go | 22 +++++++++++++++++ .../database/queries/notificationsinbox.sql | 5 +++- coderd/rbac/object_gen.go | 10 ++++++++ coderd/rbac/policy/policy.go | 7 ++++++ codersdk/rbacresources_gen.go | 2 ++ docs/reference/api/members.md | 5 ++++ docs/reference/api/schemas.md | 1 + site/src/api/rbacresourcesGenerated.ts | 5 ++++ site/src/api/typesGenerated.ts | 2 ++ 17 files changed, 113 insertions(+), 7 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 089f98d0f1f49..767ec1339e7da 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -13692,6 +13692,7 @@ const docTemplate = `{ "group_member", "idpsync_settings", "license", + "notification_inbox", "notification_message", "notification_preference", "notification_template", @@ -13728,6 +13729,7 @@ const docTemplate = `{ "ResourceGroupMember", "ResourceIdpsyncSettings", "ResourceLicense", + "ResourceNotificationInbox", "ResourceNotificationMessage", "ResourceNotificationPreference", "ResourceNotificationTemplate", diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index c2e40ac88ebdf..ce91007cc3c5d 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -12385,6 +12385,7 @@ "group_member", "idpsync_settings", "license", + "notification_inbox", "notification_message", "notification_preference", "notification_template", @@ -12421,6 +12422,7 @@ "ResourceGroupMember", "ResourceIdpsyncSettings", "ResourceLicense", + "ResourceNotificationInbox", "ResourceNotificationMessage", "ResourceNotificationPreference", "ResourceNotificationTemplate", diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 3c09502c42964..8e69e2df897a7 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -1418,11 +1418,11 @@ func (q *querier) FavoriteWorkspace(ctx context.Context, id uuid.UUID) error { } func (q *querier) FetchInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.NotificationsInbox, error) { - panic("not implemented") + return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.FetchInboxNotificationsByUserID)(ctx, userID) } func (q *querier) FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx context.Context, arg database.FetchInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]database.NotificationsInbox, error) { - panic("not implemented") + return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID)(ctx, arg) } func (q *querier) FetchMemoryResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) (database.WorkspaceAgentMemoryResourceMonitor, error) { @@ -1447,11 +1447,11 @@ func (q *querier) FetchNewMessageMetadata(ctx context.Context, arg database.Fetc } func (q *querier) FetchUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.NotificationsInbox, error) { - panic("not implemented") + return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.FetchUnreadInboxNotificationsByUserID)(ctx, userID) } func (q *querier) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx context.Context, arg database.FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]database.NotificationsInbox, error) { - panic("not implemented") + return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID)(ctx, arg) } func (q *querier) FetchVolumesResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) ([]database.WorkspaceAgentVolumeResourceMonitor, error) { @@ -1766,6 +1766,10 @@ func (q *querier) GetHungProvisionerJobs(ctx context.Context, hungSince time.Tim return q.db.GetHungProvisionerJobs(ctx, hungSince) } +func (q *querier) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (database.NotificationsInbox, error) { + return fetchWithAction(q.log, q.auth, policy.ActionRead, q.db.GetInboxNotificationByID)(ctx, id) +} + func (q *querier) GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.GetJFrogXrayScanByWorkspaceAndAgentIDParams) (database.JfrogXrayScan, error) { if _, err := fetch(q.log, q.auth, q.db.GetWorkspaceByID)(ctx, arg.WorkspaceID); err != nil { return database.JfrogXrayScan{}, err @@ -3094,7 +3098,7 @@ func (q *querier) InsertGroupMember(ctx context.Context, arg database.InsertGrou } func (q *querier) InsertInboxNotification(ctx context.Context, arg database.InsertInboxNotificationParams) (database.NotificationsInbox, error) { - panic("not implemented") + return insert(q.log, q.auth, rbac.ResourceNotificationInbox.WithOwner(arg.UserID.String()), q.db.InsertInboxNotification)(ctx, arg) } func (q *querier) InsertLicense(ctx context.Context, arg database.InsertLicenseParams) (database.License, error) { @@ -3585,7 +3589,15 @@ func (q *querier) RevokeDBCryptKey(ctx context.Context, activeKeyDigest string) } func (q *querier) SetInboxNotificationAsRead(ctx context.Context, arg database.SetInboxNotificationAsReadParams) error { - panic("not implemented") + fetch := func(ctx context.Context, id uuid.UUID) (database.NotificationsInbox, error) { + return q.db.GetInboxNotificationByID(ctx, id) + } + + update := func(ctx context.Context, arg database.SetInboxNotificationAsReadParams) error { + return q.db.SetInboxNotificationAsRead(ctx, arg) + } + + return update(q.log, q.auth, policy.ActionUpdate, fetch, update)(ctx, arg) } func (q *querier) TryAcquireLock(ctx context.Context, id int64) (bool, error) { diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index bab75e1e7f5c6..57e820d748dc9 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -3358,6 +3358,10 @@ func (q *FakeQuerier) GetHungProvisionerJobs(_ context.Context, hungSince time.T return hungJobs, nil } +func (q *FakeQuerier) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (database.NotificationsInbox, error) { + panic("not implemented") +} + func (q *FakeQuerier) GetJFrogXrayScanByWorkspaceAndAgentID(_ context.Context, arg database.GetJFrogXrayScanByWorkspaceAndAgentIDParams) (database.JfrogXrayScan, error) { err := validateDatabaseType(arg) if err != nil { diff --git a/coderd/database/dbmetrics/querymetrics.go b/coderd/database/dbmetrics/querymetrics.go index 9ab50064564ec..91523b9c0ae67 100644 --- a/coderd/database/dbmetrics/querymetrics.go +++ b/coderd/database/dbmetrics/querymetrics.go @@ -798,6 +798,13 @@ func (m queryMetricsStore) GetHungProvisionerJobs(ctx context.Context, hungSince return jobs, err } +func (m queryMetricsStore) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (database.NotificationsInbox, error) { + start := time.Now() + r0, r1 := m.s.GetInboxNotificationByID(ctx, id) + m.queryLatencies.WithLabelValues("GetInboxNotificationByID").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m queryMetricsStore) GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.GetJFrogXrayScanByWorkspaceAndAgentIDParams) (database.JfrogXrayScan, error) { start := time.Now() r0, r1 := m.s.GetJFrogXrayScanByWorkspaceAndAgentID(ctx, arg) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index c4bdb73d24be3..fbba0ade1d057 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -1626,6 +1626,21 @@ func (mr *MockStoreMockRecorder) GetHungProvisionerJobs(ctx, updatedAt any) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHungProvisionerJobs", reflect.TypeOf((*MockStore)(nil).GetHungProvisionerJobs), ctx, updatedAt) } +// GetInboxNotificationByID mocks base method. +func (m *MockStore) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (database.NotificationsInbox, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetInboxNotificationByID", ctx, id) + ret0, _ := ret[0].(database.NotificationsInbox) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetInboxNotificationByID indicates an expected call of GetInboxNotificationByID. +func (mr *MockStoreMockRecorder) GetInboxNotificationByID(ctx, id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInboxNotificationByID", reflect.TypeOf((*MockStore)(nil).GetInboxNotificationByID), ctx, id) +} + // GetJFrogXrayScanByWorkspaceAndAgentID mocks base method. func (m *MockStore) GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.GetJFrogXrayScanByWorkspaceAndAgentIDParams) (database.JfrogXrayScan, error) { m.ctrl.T.Helper() diff --git a/coderd/database/modelmethods.go b/coderd/database/modelmethods.go index 171c0454563de..ad018b9a0214e 100644 --- a/coderd/database/modelmethods.go +++ b/coderd/database/modelmethods.go @@ -168,6 +168,12 @@ func (TemplateVersion) RBACObject(template Template) rbac.Object { return template.RBACObject() } +func (i NotificationsInbox) RBACObject() rbac.Object { + return rbac.ResourceNotificationInbox. + WithID(i.ID). + WithOwner(i.UserID.String()) +} + // RBACObjectNoTemplate is for orphaned template versions. func (v TemplateVersion) RBACObjectNoTemplate() rbac.Object { return rbac.ResourceTemplate.InOrg(v.OrganizationID) diff --git a/coderd/database/querier.go b/coderd/database/querier.go index a4c8f720bbdc6..c09ad2863d8b8 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -175,6 +175,7 @@ type sqlcQuerier interface { GetGroups(ctx context.Context, arg GetGroupsParams) ([]GetGroupsRow, error) GetHealthSettings(ctx context.Context) (string, error) GetHungProvisionerJobs(ctx context.Context, updatedAt time.Time) ([]ProvisionerJob, error) + GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (NotificationsInbox, error) GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg GetJFrogXrayScanByWorkspaceAndAgentIDParams) (JfrogXrayScan, error) GetLastUpdateCheck(ctx context.Context) (string, error) GetLatestCryptoKeyByFeature(ctx context.Context, feature CryptoKeyFeature) (CryptoKey, error) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index dc952c9b18be0..0bc438238fabb 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -4462,6 +4462,28 @@ func (q *sqlQuerier) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTarget return items, nil } +const getInboxNotificationByID = `-- name: GetInboxNotificationByID :one +SELECT id, user_id, template_id, target_id, title, content, icon, actions, read_at, created_at FROM notifications_inbox WHERE id = $1 +` + +func (q *sqlQuerier) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (NotificationsInbox, error) { + row := q.db.QueryRowContext(ctx, getInboxNotificationByID, id) + var i NotificationsInbox + err := row.Scan( + &i.ID, + &i.UserID, + &i.TemplateID, + &i.TargetID, + &i.Title, + &i.Content, + &i.Icon, + &i.Actions, + &i.ReadAt, + &i.CreatedAt, + ) + return i, err +} + const insertInboxNotification = `-- name: InsertInboxNotification :one INSERT INTO notifications_inbox ( diff --git a/coderd/database/queries/notificationsinbox.sql b/coderd/database/queries/notificationsinbox.sql index 0dadec858f5d2..cfdd4b4ffefc7 100644 --- a/coderd/database/queries/notificationsinbox.sql +++ b/coderd/database/queries/notificationsinbox.sql @@ -10,6 +10,9 @@ SELECT * FROM notifications_inbox WHERE user_id = $1 AND template_id = $2 AND ta -- name: FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID :many SELECT * FROM notifications_inbox WHERE user_id = $1 AND template_id = $2 AND target_id = $3 AND read_at IS NULL ORDER BY created_at DESC; +-- name: GetInboxNotificationByID :one +SELECT * FROM notifications_inbox WHERE id = $1; + -- name: InsertInboxNotification :one INSERT INTO notifications_inbox ( @@ -32,4 +35,4 @@ UPDATE SET read_at = $1 WHERE - id = $2; + id = @id; diff --git a/coderd/rbac/object_gen.go b/coderd/rbac/object_gen.go index e5323225120b5..c947829562d1a 100644 --- a/coderd/rbac/object_gen.go +++ b/coderd/rbac/object_gen.go @@ -129,6 +129,15 @@ var ( Type: "license", } + // ResourceNotificationInbox + // Valid Actions + // - "ActionCreate" :: create notifications inbox + // - "ActionRead" :: read notifications inbox + // - "ActionUpdate" :: update notifications inbox + ResourceNotificationInbox = Object{ + Type: "notification_inbox", + } + // ResourceNotificationMessage // Valid Actions // - "ActionCreate" :: create notification messages @@ -345,6 +354,7 @@ func AllResources() []Objecter { ResourceGroupMember, ResourceIdpsyncSettings, ResourceLicense, + ResourceNotificationInbox, ResourceNotificationMessage, ResourceNotificationPreference, ResourceNotificationTemplate, diff --git a/coderd/rbac/policy/policy.go b/coderd/rbac/policy/policy.go index c06a2117cb4e9..ee3995c5087ab 100644 --- a/coderd/rbac/policy/policy.go +++ b/coderd/rbac/policy/policy.go @@ -287,6 +287,13 @@ var RBACPermissions = map[string]PermissionDefinition{ ActionUpdate: actDef("update notification preferences"), }, }, + "notification_inbox": { + Actions: map[Action]ActionDefinition{ + ActionCreate: actDef("create notifications inbox"), + ActionRead: actDef("read notifications inbox"), + ActionUpdate: actDef("update notifications inbox"), + }, + }, "crypto_key": { Actions: map[Action]ActionDefinition{ ActionRead: actDef("read crypto keys"), diff --git a/codersdk/rbacresources_gen.go b/codersdk/rbacresources_gen.go index f4d7790d40b76..65a848caa6c27 100644 --- a/codersdk/rbacresources_gen.go +++ b/codersdk/rbacresources_gen.go @@ -18,6 +18,7 @@ const ( ResourceGroupMember RBACResource = "group_member" ResourceIdpsyncSettings RBACResource = "idpsync_settings" ResourceLicense RBACResource = "license" + ResourceNotificationInbox RBACResource = "notification_inbox" ResourceNotificationMessage RBACResource = "notification_message" ResourceNotificationPreference RBACResource = "notification_preference" ResourceNotificationTemplate RBACResource = "notification_template" @@ -75,6 +76,7 @@ var RBACResourceActions = map[RBACResource][]RBACAction{ ResourceGroupMember: {ActionRead}, ResourceIdpsyncSettings: {ActionRead, ActionUpdate}, ResourceLicense: {ActionCreate, ActionDelete, ActionRead}, + ResourceNotificationInbox: {ActionCreate, ActionRead, ActionUpdate}, ResourceNotificationMessage: {ActionCreate, ActionDelete, ActionRead, ActionUpdate}, ResourceNotificationPreference: {ActionRead, ActionUpdate}, ResourceNotificationTemplate: {ActionRead, ActionUpdate}, diff --git a/docs/reference/api/members.md b/docs/reference/api/members.md index a3a38457c6631..8251514e78dc2 100644 --- a/docs/reference/api/members.md +++ b/docs/reference/api/members.md @@ -193,6 +193,7 @@ Status Code **200** | `resource_type` | `group_member` | | `resource_type` | `idpsync_settings` | | `resource_type` | `license` | +| `resource_type` | `notification_inbox` | | `resource_type` | `notification_message` | | `resource_type` | `notification_preference` | | `resource_type` | `notification_template` | @@ -356,6 +357,7 @@ Status Code **200** | `resource_type` | `group_member` | | `resource_type` | `idpsync_settings` | | `resource_type` | `license` | +| `resource_type` | `notification_inbox` | | `resource_type` | `notification_message` | | `resource_type` | `notification_preference` | | `resource_type` | `notification_template` | @@ -519,6 +521,7 @@ Status Code **200** | `resource_type` | `group_member` | | `resource_type` | `idpsync_settings` | | `resource_type` | `license` | +| `resource_type` | `notification_inbox` | | `resource_type` | `notification_message` | | `resource_type` | `notification_preference` | | `resource_type` | `notification_template` | @@ -651,6 +654,7 @@ Status Code **200** | `resource_type` | `group_member` | | `resource_type` | `idpsync_settings` | | `resource_type` | `license` | +| `resource_type` | `notification_inbox` | | `resource_type` | `notification_message` | | `resource_type` | `notification_preference` | | `resource_type` | `notification_template` | @@ -915,6 +919,7 @@ Status Code **200** | `resource_type` | `group_member` | | `resource_type` | `idpsync_settings` | | `resource_type` | `license` | +| `resource_type` | `notification_inbox` | | `resource_type` | `notification_message` | | `resource_type` | `notification_preference` | | `resource_type` | `notification_template` | diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index d13a46ed9b365..a68e8dc059104 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -5106,6 +5106,7 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith | `group_member` | | `idpsync_settings` | | `license` | +| `notification_inbox` | | `notification_message` | | `notification_preference` | | `notification_template` | diff --git a/site/src/api/rbacresourcesGenerated.ts b/site/src/api/rbacresourcesGenerated.ts index 437f89ec776a7..71ad1700111fe 100644 --- a/site/src/api/rbacresourcesGenerated.ts +++ b/site/src/api/rbacresourcesGenerated.ts @@ -70,6 +70,11 @@ export const RBACResourceActions: Partial< delete: "delete license", read: "read licenses", }, + notification_inbox: { + create: "create notifications inbox", + read: "read notifications inbox", + update: "update notifications inbox", + }, notification_message: { create: "create notification messages", delete: "delete notification messages", diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 34fe3360601af..9b8c1a2924fda 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1876,6 +1876,7 @@ export type RBACResource = | "group_member" | "idpsync_settings" | "license" + | "notification_inbox" | "notification_message" | "notification_preference" | "notification_template" @@ -1912,6 +1913,7 @@ export const RBACResources: RBACResource[] = [ "group_member", "idpsync_settings", "license", + "notification_inbox", "notification_message", "notification_preference", "notification_template", From ef919696790bdb253b48f0899d57fc98af2d09a7 Mon Sep 17 00:00:00 2001 From: defelmnq Date: Wed, 19 Feb 2025 11:03:45 +0000 Subject: [PATCH 03/37] work on dbauthz --- coderd/database/dbauthz/dbauthz.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 8e69e2df897a7..93d5b1e7b0b44 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -3589,15 +3589,15 @@ func (q *querier) RevokeDBCryptKey(ctx context.Context, activeKeyDigest string) } func (q *querier) SetInboxNotificationAsRead(ctx context.Context, arg database.SetInboxNotificationAsReadParams) error { - fetch := func(ctx context.Context, id uuid.UUID) (database.NotificationsInbox, error) { + fetchFunc := func(ctx context.Context, id uuid.UUID) (database.NotificationsInbox, error) { return q.db.GetInboxNotificationByID(ctx, id) } - update := func(ctx context.Context, arg database.SetInboxNotificationAsReadParams) error { + updateFunc := func(ctx context.Context, arg database.SetInboxNotificationAsReadParams) error { return q.db.SetInboxNotificationAsRead(ctx, arg) } - return update(q.log, q.auth, policy.ActionUpdate, fetch, update)(ctx, arg) + return update(q.log, q.auth, fetchFunc, updateFunc)(ctx, arg) } func (q *querier) TryAcquireLock(ctx context.Context, id int64) (bool, error) { From 72e6de40a8d7750a92bb741172bb74c259435f96 Mon Sep 17 00:00:00 2001 From: defelmnq Date: Wed, 19 Feb 2025 11:26:14 +0000 Subject: [PATCH 04/37] fmt and lint --- coderd/database/dbauthz/dbauthz.go | 17 ++--- coderd/database/dbmem/dbmem.go | 109 +++++++++++++++++++++++------ 2 files changed, 96 insertions(+), 30 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 93d5b1e7b0b44..579d6758d6c4d 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -3588,16 +3588,17 @@ func (q *querier) RevokeDBCryptKey(ctx context.Context, activeKeyDigest string) return q.db.RevokeDBCryptKey(ctx, activeKeyDigest) } -func (q *querier) SetInboxNotificationAsRead(ctx context.Context, arg database.SetInboxNotificationAsReadParams) error { - fetchFunc := func(ctx context.Context, id uuid.UUID) (database.NotificationsInbox, error) { - return q.db.GetInboxNotificationByID(ctx, id) - } +func (*querier) SetInboxNotificationAsRead(_ context.Context, _ database.SetInboxNotificationAsReadParams) error { + panic("implement me") + // fetchFunc := func(ctx context.Context, id uuid.UUID) (database.NotificationsInbox, error) { + // return q.db.GetInboxNotificationByID(ctx, id) + // } - updateFunc := func(ctx context.Context, arg database.SetInboxNotificationAsReadParams) error { - return q.db.SetInboxNotificationAsRead(ctx, arg) - } + // updateFunc := func(ctx context.Context, arg database.SetInboxNotificationAsReadParams) error { + // return q.db.SetInboxNotificationAsRead(ctx, arg) + // } - return update(q.log, q.auth, fetchFunc, updateFunc)(ctx, arg) + // return update(q.log, q.auth, fetchFunc, updateFunc)(ctx, arg) } func (q *querier) TryAcquireLock(ctx context.Context, id int64) (bool, error) { diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 57e820d748dc9..894e25ab28938 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -67,6 +67,7 @@ func New() database.Store { gitSSHKey: make([]database.GitSSHKey, 0), notificationMessages: make([]database.NotificationMessage, 0), notificationPreferences: make([]database.NotificationPreference, 0), + notificationsInbox: make([]database.NotificationsInbox, 0), parameterSchemas: make([]database.ParameterSchema, 0), provisionerDaemons: make([]database.ProvisionerDaemon, 0), provisionerKeys: make([]database.ProvisionerKey, 0), @@ -206,6 +207,7 @@ type data struct { notificationMessages []database.NotificationMessage notificationPreferences []database.NotificationPreference notificationReportGeneratorLogs []database.NotificationReportGeneratorLog + notificationsInbox []database.NotificationsInbox oauth2ProviderApps []database.OAuth2ProviderApp oauth2ProviderAppSecrets []database.OAuth2ProviderAppSecret oauth2ProviderAppCodes []database.OAuth2ProviderAppCode @@ -2363,17 +2365,32 @@ func (q *FakeQuerier) FavoriteWorkspace(_ context.Context, arg uuid.UUID) error return nil } -func (q *FakeQuerier) FetchInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.NotificationsInbox, error) { - panic("not implemented") +func (q *FakeQuerier) FetchInboxNotificationsByUserID(_ context.Context, userID uuid.UUID) ([]database.NotificationsInbox, error) { + q.mutex.RLock() + defer q.mutex.RUnlock() + + notifications := make([]database.NotificationsInbox, 0) + for _, notification := range q.notificationsInbox { + if notification.UserID == userID { + notifications = append(notifications, notification) + } + } + + return notifications, nil } -func (q *FakeQuerier) FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx context.Context, arg database.FetchInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]database.NotificationsInbox, error) { - err := validateDatabaseType(arg) - if err != nil { - return nil, err +func (q *FakeQuerier) FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID(_ context.Context, arg database.FetchInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]database.NotificationsInbox, error) { + q.mutex.RLock() + defer q.mutex.RUnlock() + + notifications := make([]database.NotificationsInbox, 0) + for _, notification := range q.notificationsInbox { + if notification.UserID == arg.UserID && notification.TemplateID == arg.TemplateID && notification.TargetID == arg.TargetID { + notifications = append(notifications, notification) + } } - panic("not implemented") + return notifications, nil } func (q *FakeQuerier) FetchMemoryResourceMonitorsByAgentID(_ context.Context, agentID uuid.UUID) (database.WorkspaceAgentMemoryResourceMonitor, error) { @@ -2418,17 +2435,32 @@ func (q *FakeQuerier) FetchNewMessageMetadata(_ context.Context, arg database.Fe }, nil } -func (q *FakeQuerier) FetchUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.NotificationsInbox, error) { - panic("not implemented") +func (q *FakeQuerier) FetchUnreadInboxNotificationsByUserID(_ context.Context, userID uuid.UUID) ([]database.NotificationsInbox, error) { + q.mutex.RLock() + defer q.mutex.RUnlock() + + notifications := make([]database.NotificationsInbox, 0) + for _, notification := range q.notificationsInbox { + if notification.UserID == userID && !notification.ReadAt.Valid { + notifications = append(notifications, notification) + } + } + + return notifications, nil } -func (q *FakeQuerier) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx context.Context, arg database.FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]database.NotificationsInbox, error) { - err := validateDatabaseType(arg) - if err != nil { - return nil, err +func (q *FakeQuerier) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID(_ context.Context, arg database.FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]database.NotificationsInbox, error) { + q.mutex.RLock() + defer q.mutex.RUnlock() + + notifications := make([]database.NotificationsInbox, 0) + for _, notification := range q.notificationsInbox { + if notification.UserID == arg.UserID && notification.TemplateID == arg.TemplateID && notification.TargetID == arg.TargetID && !notification.ReadAt.Valid { + notifications = append(notifications, notification) + } } - panic("not implemented") + return notifications, nil } func (q *FakeQuerier) FetchVolumesResourceMonitorsByAgentID(_ context.Context, agentID uuid.UUID) ([]database.WorkspaceAgentVolumeResourceMonitor, error) { @@ -3358,8 +3390,17 @@ func (q *FakeQuerier) GetHungProvisionerJobs(_ context.Context, hungSince time.T return hungJobs, nil } -func (q *FakeQuerier) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (database.NotificationsInbox, error) { - panic("not implemented") +func (q *FakeQuerier) GetInboxNotificationByID(_ context.Context, id uuid.UUID) (database.NotificationsInbox, error) { + q.mutex.RLock() + defer q.mutex.RUnlock() + + for _, notification := range q.notificationsInbox { + if notification.ID == id { + return notification, nil + } + } + + return database.NotificationsInbox{}, sql.ErrNoRows } func (q *FakeQuerier) GetJFrogXrayScanByWorkspaceAndAgentID(_ context.Context, arg database.GetJFrogXrayScanByWorkspaceAndAgentIDParams) (database.JfrogXrayScan, error) { @@ -7989,13 +8030,28 @@ func (q *FakeQuerier) InsertGroupMember(_ context.Context, arg database.InsertGr return nil } -func (q *FakeQuerier) InsertInboxNotification(ctx context.Context, arg database.InsertInboxNotificationParams) (database.NotificationsInbox, error) { - err := validateDatabaseType(arg) - if err != nil { +func (q *FakeQuerier) InsertInboxNotification(_ context.Context, arg database.InsertInboxNotificationParams) (database.NotificationsInbox, error) { + if err := validateDatabaseType(arg); err != nil { return database.NotificationsInbox{}, err } - panic("not implemented") + q.mutex.Lock() + defer q.mutex.Unlock() + + notification := database.NotificationsInbox{ + ID: arg.ID, + UserID: arg.UserID, + TemplateID: arg.TemplateID, + TargetID: arg.TargetID, + Title: arg.Title, + Content: arg.Content, + Icon: arg.Icon, + Actions: arg.Actions, + CreatedAt: time.Now(), + } + + q.notificationsInbox = append(q.notificationsInbox, notification) + return notification, nil } func (q *FakeQuerier) InsertLicense( @@ -9482,13 +9538,22 @@ func (q *FakeQuerier) RevokeDBCryptKey(_ context.Context, activeKeyDigest string return sql.ErrNoRows } -func (q *FakeQuerier) SetInboxNotificationAsRead(ctx context.Context, arg database.SetInboxNotificationAsReadParams) error { +func (q *FakeQuerier) SetInboxNotificationAsRead(_ context.Context, arg database.SetInboxNotificationAsReadParams) error { err := validateDatabaseType(arg) if err != nil { return err } - panic("not implemented") + q.mutex.Lock() + defer q.mutex.Unlock() + + for i := range q.notificationsInbox { + if q.notificationsInbox[i].ID == arg.ID { + q.notificationsInbox[i].ReadAt = arg.ReadAt + } + } + + return nil } func (*FakeQuerier) TryAcquireLock(_ context.Context, _ int64) (bool, error) { From 9edb38451f11dcdc6d9f712d17a52ccd9a58a2da Mon Sep 17 00:00:00 2001 From: defelmnq Date: Thu, 20 Feb 2025 09:27:48 +0000 Subject: [PATCH 05/37] change queries to match targets --- coderd/database/dbauthz/dbauthz.go | 23 +++--- coderd/database/dbmem/dbmem.go | 42 ++++++++-- coderd/database/dbmetrics/querymetrics.go | 12 +-- coderd/database/dbmock/dbmock.go | 24 +++--- coderd/database/dump.sql | 4 +- .../000295_notifications_inbox.up.sql | 4 +- coderd/database/models.go | 2 +- coderd/database/querier.go | 4 +- coderd/database/queries.sql.go | 78 +++++++++---------- .../database/queries/notificationsinbox.sql | 30 +++---- 10 files changed, 123 insertions(+), 100 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 579d6758d6c4d..1271e06f527c8 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -1421,8 +1421,8 @@ func (q *querier) FetchInboxNotificationsByUserID(ctx context.Context, userID uu return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.FetchInboxNotificationsByUserID)(ctx, userID) } -func (q *querier) FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx context.Context, arg database.FetchInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]database.NotificationsInbox, error) { - return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID)(ctx, arg) +func (q *querier) FetchInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx context.Context, arg database.FetchInboxNotificationsByUserIDAndTemplateIDAndTargetsParams) ([]database.NotificationsInbox, error) { + return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.FetchInboxNotificationsByUserIDAndTemplateIDAndTargets)(ctx, arg) } func (q *querier) FetchMemoryResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) (database.WorkspaceAgentMemoryResourceMonitor, error) { @@ -1450,8 +1450,8 @@ func (q *querier) FetchUnreadInboxNotificationsByUserID(ctx context.Context, use return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.FetchUnreadInboxNotificationsByUserID)(ctx, userID) } -func (q *querier) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx context.Context, arg database.FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]database.NotificationsInbox, error) { - return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID)(ctx, arg) +func (q *querier) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx context.Context, arg database.FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetsParams) ([]database.NotificationsInbox, error) { + return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets)(ctx, arg) } func (q *querier) FetchVolumesResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) ([]database.WorkspaceAgentVolumeResourceMonitor, error) { @@ -3588,17 +3588,12 @@ func (q *querier) RevokeDBCryptKey(ctx context.Context, activeKeyDigest string) return q.db.RevokeDBCryptKey(ctx, activeKeyDigest) } -func (*querier) SetInboxNotificationAsRead(_ context.Context, _ database.SetInboxNotificationAsReadParams) error { - panic("implement me") - // fetchFunc := func(ctx context.Context, id uuid.UUID) (database.NotificationsInbox, error) { - // return q.db.GetInboxNotificationByID(ctx, id) - // } - - // updateFunc := func(ctx context.Context, arg database.SetInboxNotificationAsReadParams) error { - // return q.db.SetInboxNotificationAsRead(ctx, arg) - // } +func (q *querier) SetInboxNotificationAsRead(ctx context.Context, args database.SetInboxNotificationAsReadParams) error { + fetchFunc := func(ctx context.Context, args database.SetInboxNotificationAsReadParams) (database.NotificationsInbox, error) { + return q.db.GetInboxNotificationByID(ctx, args.ID) + } - // return update(q.log, q.auth, fetchFunc, updateFunc)(ctx, arg) + return update(q.log, q.auth, fetchFunc, q.db.SetInboxNotificationAsRead)(ctx, args) } func (q *querier) TryAcquireLock(ctx context.Context, id int64) (bool, error) { diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 894e25ab28938..70ae3ce30d5f0 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -2379,14 +2379,28 @@ func (q *FakeQuerier) FetchInboxNotificationsByUserID(_ context.Context, userID return notifications, nil } -func (q *FakeQuerier) FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID(_ context.Context, arg database.FetchInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]database.NotificationsInbox, error) { +func (q *FakeQuerier) FetchInboxNotificationsByUserIDAndTemplateIDAndTargets(_ context.Context, arg database.FetchInboxNotificationsByUserIDAndTemplateIDAndTargetsParams) ([]database.NotificationsInbox, error) { q.mutex.RLock() defer q.mutex.RUnlock() notifications := make([]database.NotificationsInbox, 0) for _, notification := range q.notificationsInbox { - if notification.UserID == arg.UserID && notification.TemplateID == arg.TemplateID && notification.TargetID == arg.TargetID { - notifications = append(notifications, notification) + if notification.UserID == arg.UserID && notification.TemplateID == arg.TemplateID { + for _, target := range arg.Targets { + isFound := false + for _, insertedTarget := range notification.Targets { + if insertedTarget == target { + isFound = true + break + } + } + + if !isFound { + continue + } + + notifications = append(notifications, notification) + } } } @@ -2449,14 +2463,28 @@ func (q *FakeQuerier) FetchUnreadInboxNotificationsByUserID(_ context.Context, u return notifications, nil } -func (q *FakeQuerier) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID(_ context.Context, arg database.FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]database.NotificationsInbox, error) { +func (q *FakeQuerier) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets(_ context.Context, arg database.FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetsParams) ([]database.NotificationsInbox, error) { q.mutex.RLock() defer q.mutex.RUnlock() notifications := make([]database.NotificationsInbox, 0) for _, notification := range q.notificationsInbox { - if notification.UserID == arg.UserID && notification.TemplateID == arg.TemplateID && notification.TargetID == arg.TargetID && !notification.ReadAt.Valid { - notifications = append(notifications, notification) + if notification.UserID == arg.UserID && notification.TemplateID == arg.TemplateID && !notification.ReadAt.Valid { + for _, target := range arg.Targets { + isFound := false + for _, insertedTarget := range notification.Targets { + if insertedTarget == target { + isFound = true + break + } + } + + if !isFound { + continue + } + + notifications = append(notifications, notification) + } } } @@ -8042,7 +8070,7 @@ func (q *FakeQuerier) InsertInboxNotification(_ context.Context, arg database.In ID: arg.ID, UserID: arg.UserID, TemplateID: arg.TemplateID, - TargetID: arg.TargetID, + Targets: arg.Targets, Title: arg.Title, Content: arg.Content, Icon: arg.Icon, diff --git a/coderd/database/dbmetrics/querymetrics.go b/coderd/database/dbmetrics/querymetrics.go index 91523b9c0ae67..09af01efcb3e1 100644 --- a/coderd/database/dbmetrics/querymetrics.go +++ b/coderd/database/dbmetrics/querymetrics.go @@ -441,10 +441,10 @@ func (m queryMetricsStore) FetchInboxNotificationsByUserID(ctx context.Context, return r0, r1 } -func (m queryMetricsStore) FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx context.Context, arg database.FetchInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]database.NotificationsInbox, error) { +func (m queryMetricsStore) FetchInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx context.Context, arg database.FetchInboxNotificationsByUserIDAndTemplateIDAndTargetsParams) ([]database.NotificationsInbox, error) { start := time.Now() - r0, r1 := m.s.FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx, arg) - m.queryLatencies.WithLabelValues("FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID").Observe(time.Since(start).Seconds()) + r0, r1 := m.s.FetchInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx, arg) + m.queryLatencies.WithLabelValues("FetchInboxNotificationsByUserIDAndTemplateIDAndTargets").Observe(time.Since(start).Seconds()) return r0, r1 } @@ -469,10 +469,10 @@ func (m queryMetricsStore) FetchUnreadInboxNotificationsByUserID(ctx context.Con return r0, r1 } -func (m queryMetricsStore) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx context.Context, arg database.FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]database.NotificationsInbox, error) { +func (m queryMetricsStore) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx context.Context, arg database.FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetsParams) ([]database.NotificationsInbox, error) { start := time.Now() - r0, r1 := m.s.FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx, arg) - m.queryLatencies.WithLabelValues("FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID").Observe(time.Since(start).Seconds()) + r0, r1 := m.s.FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx, arg) + m.queryLatencies.WithLabelValues("FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets").Observe(time.Since(start).Seconds()) return r0, r1 } diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index fbba0ade1d057..ebb98f25756d4 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -786,19 +786,19 @@ func (mr *MockStoreMockRecorder) FetchInboxNotificationsByUserID(ctx, userID any return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchInboxNotificationsByUserID", reflect.TypeOf((*MockStore)(nil).FetchInboxNotificationsByUserID), ctx, userID) } -// FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID mocks base method. -func (m *MockStore) FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx context.Context, arg database.FetchInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]database.NotificationsInbox, error) { +// FetchInboxNotificationsByUserIDAndTemplateIDAndTargets mocks base method. +func (m *MockStore) FetchInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx context.Context, arg database.FetchInboxNotificationsByUserIDAndTemplateIDAndTargetsParams) ([]database.NotificationsInbox, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID", ctx, arg) + ret := m.ctrl.Call(m, "FetchInboxNotificationsByUserIDAndTemplateIDAndTargets", ctx, arg) ret0, _ := ret[0].([]database.NotificationsInbox) ret1, _ := ret[1].(error) return ret0, ret1 } -// FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID indicates an expected call of FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID. -func (mr *MockStoreMockRecorder) FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx, arg any) *gomock.Call { +// FetchInboxNotificationsByUserIDAndTemplateIDAndTargets indicates an expected call of FetchInboxNotificationsByUserIDAndTemplateIDAndTargets. +func (mr *MockStoreMockRecorder) FetchInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx, arg any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID", reflect.TypeOf((*MockStore)(nil).FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID), ctx, arg) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchInboxNotificationsByUserIDAndTemplateIDAndTargets", reflect.TypeOf((*MockStore)(nil).FetchInboxNotificationsByUserIDAndTemplateIDAndTargets), ctx, arg) } // FetchMemoryResourceMonitorsByAgentID mocks base method. @@ -846,19 +846,19 @@ func (mr *MockStoreMockRecorder) FetchUnreadInboxNotificationsByUserID(ctx, user return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchUnreadInboxNotificationsByUserID", reflect.TypeOf((*MockStore)(nil).FetchUnreadInboxNotificationsByUserID), ctx, userID) } -// FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID mocks base method. -func (m *MockStore) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx context.Context, arg database.FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]database.NotificationsInbox, error) { +// FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets mocks base method. +func (m *MockStore) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx context.Context, arg database.FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetsParams) ([]database.NotificationsInbox, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID", ctx, arg) + ret := m.ctrl.Call(m, "FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets", ctx, arg) ret0, _ := ret[0].([]database.NotificationsInbox) ret1, _ := ret[1].(error) return ret0, ret1 } -// FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID indicates an expected call of FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID. -func (mr *MockStoreMockRecorder) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx, arg any) *gomock.Call { +// FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets indicates an expected call of FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets. +func (mr *MockStoreMockRecorder) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx, arg any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID", reflect.TypeOf((*MockStore)(nil).FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID), ctx, arg) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets", reflect.TypeOf((*MockStore)(nil).FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets), ctx, arg) } // FetchVolumesResourceMonitorsByAgentID mocks base method. diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 8b5a2ea40e2bd..6c6feb67235f4 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -916,7 +916,7 @@ CREATE TABLE notifications_inbox ( id uuid NOT NULL, user_id uuid NOT NULL, template_id uuid NOT NULL, - target_id uuid, + targets uuid[], title text NOT NULL, content text NOT NULL, icon text NOT NULL, @@ -2232,7 +2232,7 @@ CREATE INDEX idx_notification_messages_status ON notification_messages USING btr CREATE INDEX idx_notifications_inbox_user_id_read_at ON notifications_inbox USING btree (user_id, read_at); -CREATE INDEX idx_notifications_inbox_user_id_template_id_target_id ON notifications_inbox USING btree (user_id, template_id, target_id); +CREATE INDEX idx_notifications_inbox_user_id_template_id_targets ON notifications_inbox USING btree (user_id, template_id, targets); CREATE INDEX idx_organization_member_organization_id_uuid ON organization_members USING btree (organization_id); diff --git a/coderd/database/migrations/000295_notifications_inbox.up.sql b/coderd/database/migrations/000295_notifications_inbox.up.sql index 6b674776b5d90..637f34c0a273b 100644 --- a/coderd/database/migrations/000295_notifications_inbox.up.sql +++ b/coderd/database/migrations/000295_notifications_inbox.up.sql @@ -2,7 +2,7 @@ CREATE TABLE notifications_inbox ( id UUID PRIMARY KEY, user_id UUID NOT NULL, template_id UUID NOT NULL, - target_id UUID, + targets UUID[], title TEXT NOT NULL, content TEXT NOT NULL, icon TEXT NOT NULL, @@ -12,4 +12,4 @@ CREATE TABLE notifications_inbox ( ); CREATE INDEX idx_notifications_inbox_user_id_read_at ON notifications_inbox(user_id, read_at); -CREATE INDEX idx_notifications_inbox_user_id_template_id_target_id ON notifications_inbox(user_id, template_id, target_id); +CREATE INDEX idx_notifications_inbox_user_id_template_id_targets ON notifications_inbox(user_id, template_id, targets); diff --git a/coderd/database/models.go b/coderd/database/models.go index 6711904b456b3..ca1dc70ee9ee0 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -2627,7 +2627,7 @@ type NotificationsInbox struct { ID uuid.UUID `db:"id" json:"id"` UserID uuid.UUID `db:"user_id" json:"user_id"` TemplateID uuid.UUID `db:"template_id" json:"template_id"` - TargetID uuid.NullUUID `db:"target_id" json:"target_id"` + Targets []uuid.UUID `db:"targets" json:"targets"` Title string `db:"title" json:"title"` Content string `db:"content" json:"content"` Icon string `db:"icon" json:"icon"` diff --git a/coderd/database/querier.go b/coderd/database/querier.go index c09ad2863d8b8..3099dfc297492 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -113,12 +113,12 @@ type sqlcQuerier interface { EnqueueNotificationMessage(ctx context.Context, arg EnqueueNotificationMessageParams) error FavoriteWorkspace(ctx context.Context, id uuid.UUID) error FetchInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]NotificationsInbox, error) - FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx context.Context, arg FetchInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]NotificationsInbox, error) + FetchInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx context.Context, arg FetchInboxNotificationsByUserIDAndTemplateIDAndTargetsParams) ([]NotificationsInbox, error) FetchMemoryResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) (WorkspaceAgentMemoryResourceMonitor, error) // This is used to build up the notification_message's JSON payload. FetchNewMessageMetadata(ctx context.Context, arg FetchNewMessageMetadataParams) (FetchNewMessageMetadataRow, error) FetchUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]NotificationsInbox, error) - FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx context.Context, arg FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]NotificationsInbox, error) + FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx context.Context, arg FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetsParams) ([]NotificationsInbox, error) FetchVolumesResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) ([]WorkspaceAgentVolumeResourceMonitor, error) GetAPIKeyByID(ctx context.Context, id string) (APIKey, error) // there is no unique constraint on empty token names diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 0bc438238fabb..b119f2206f666 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -4299,7 +4299,7 @@ func (q *sqlQuerier) UpsertNotificationReportGeneratorLog(ctx context.Context, a } const fetchInboxNotificationsByUserID = `-- name: FetchInboxNotificationsByUserID :many -SELECT id, user_id, template_id, target_id, title, content, icon, actions, read_at, created_at FROM notifications_inbox WHERE user_id = $1 ORDER BY created_at DESC +SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM notifications_inbox WHERE user_id = $1 ORDER BY created_at DESC ` func (q *sqlQuerier) FetchInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]NotificationsInbox, error) { @@ -4315,7 +4315,7 @@ func (q *sqlQuerier) FetchInboxNotificationsByUserID(ctx context.Context, userID &i.ID, &i.UserID, &i.TemplateID, - &i.TargetID, + pq.Array(&i.Targets), &i.Title, &i.Content, &i.Icon, @@ -4336,18 +4336,18 @@ func (q *sqlQuerier) FetchInboxNotificationsByUserID(ctx context.Context, userID return items, nil } -const fetchInboxNotificationsByUserIDAndTemplateIDAndTargetID = `-- name: FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID :many -SELECT id, user_id, template_id, target_id, title, content, icon, actions, read_at, created_at FROM notifications_inbox WHERE user_id = $1 AND template_id = $2 AND target_id = $3 ORDER BY created_at DESC +const fetchInboxNotificationsByUserIDAndTemplateIDAndTargets = `-- name: FetchInboxNotificationsByUserIDAndTemplateIDAndTargets :many +SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM notifications_inbox WHERE user_id = $1 AND template_id = $2 AND targets @> $3 ORDER BY created_at DESC ` -type FetchInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams struct { - UserID uuid.UUID `db:"user_id" json:"user_id"` - TemplateID uuid.UUID `db:"template_id" json:"template_id"` - TargetID uuid.NullUUID `db:"target_id" json:"target_id"` +type FetchInboxNotificationsByUserIDAndTemplateIDAndTargetsParams struct { + UserID uuid.UUID `db:"user_id" json:"user_id"` + TemplateID uuid.UUID `db:"template_id" json:"template_id"` + Targets []uuid.UUID `db:"targets" json:"targets"` } -func (q *sqlQuerier) FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx context.Context, arg FetchInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]NotificationsInbox, error) { - rows, err := q.db.QueryContext(ctx, fetchInboxNotificationsByUserIDAndTemplateIDAndTargetID, arg.UserID, arg.TemplateID, arg.TargetID) +func (q *sqlQuerier) FetchInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx context.Context, arg FetchInboxNotificationsByUserIDAndTemplateIDAndTargetsParams) ([]NotificationsInbox, error) { + rows, err := q.db.QueryContext(ctx, fetchInboxNotificationsByUserIDAndTemplateIDAndTargets, arg.UserID, arg.TemplateID, pq.Array(arg.Targets)) if err != nil { return nil, err } @@ -4359,7 +4359,7 @@ func (q *sqlQuerier) FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx &i.ID, &i.UserID, &i.TemplateID, - &i.TargetID, + pq.Array(&i.Targets), &i.Title, &i.Content, &i.Icon, @@ -4381,7 +4381,7 @@ func (q *sqlQuerier) FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx } const fetchUnreadInboxNotificationsByUserID = `-- name: FetchUnreadInboxNotificationsByUserID :many -SELECT id, user_id, template_id, target_id, title, content, icon, actions, read_at, created_at FROM notifications_inbox WHERE user_id = $1 AND read_at IS NULL ORDER BY created_at DESC +SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM notifications_inbox WHERE user_id = $1 AND read_at IS NULL ORDER BY created_at DESC ` func (q *sqlQuerier) FetchUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]NotificationsInbox, error) { @@ -4397,7 +4397,7 @@ func (q *sqlQuerier) FetchUnreadInboxNotificationsByUserID(ctx context.Context, &i.ID, &i.UserID, &i.TemplateID, - &i.TargetID, + pq.Array(&i.Targets), &i.Title, &i.Content, &i.Icon, @@ -4418,18 +4418,18 @@ func (q *sqlQuerier) FetchUnreadInboxNotificationsByUserID(ctx context.Context, return items, nil } -const fetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID = `-- name: FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID :many -SELECT id, user_id, template_id, target_id, title, content, icon, actions, read_at, created_at FROM notifications_inbox WHERE user_id = $1 AND template_id = $2 AND target_id = $3 AND read_at IS NULL ORDER BY created_at DESC +const fetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets = `-- name: FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets :many +SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM notifications_inbox WHERE user_id = $1 AND template_id = $2 AND targets @> $3 AND read_at IS NULL ORDER BY created_at DESC ` -type FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams struct { - UserID uuid.UUID `db:"user_id" json:"user_id"` - TemplateID uuid.UUID `db:"template_id" json:"template_id"` - TargetID uuid.NullUUID `db:"target_id" json:"target_id"` +type FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetsParams struct { + UserID uuid.UUID `db:"user_id" json:"user_id"` + TemplateID uuid.UUID `db:"template_id" json:"template_id"` + Targets []uuid.UUID `db:"targets" json:"targets"` } -func (q *sqlQuerier) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID(ctx context.Context, arg FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetIDParams) ([]NotificationsInbox, error) { - rows, err := q.db.QueryContext(ctx, fetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID, arg.UserID, arg.TemplateID, arg.TargetID) +func (q *sqlQuerier) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx context.Context, arg FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetsParams) ([]NotificationsInbox, error) { + rows, err := q.db.QueryContext(ctx, fetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets, arg.UserID, arg.TemplateID, pq.Array(arg.Targets)) if err != nil { return nil, err } @@ -4441,7 +4441,7 @@ func (q *sqlQuerier) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTarget &i.ID, &i.UserID, &i.TemplateID, - &i.TargetID, + pq.Array(&i.Targets), &i.Title, &i.Content, &i.Icon, @@ -4463,7 +4463,7 @@ func (q *sqlQuerier) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTarget } const getInboxNotificationByID = `-- name: GetInboxNotificationByID :one -SELECT id, user_id, template_id, target_id, title, content, icon, actions, read_at, created_at FROM notifications_inbox WHERE id = $1 +SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM notifications_inbox WHERE id = $1 ` func (q *sqlQuerier) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (NotificationsInbox, error) { @@ -4473,7 +4473,7 @@ func (q *sqlQuerier) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) &i.ID, &i.UserID, &i.TemplateID, - &i.TargetID, + pq.Array(&i.Targets), &i.Title, &i.Content, &i.Icon, @@ -4488,24 +4488,24 @@ const insertInboxNotification = `-- name: InsertInboxNotification :one INSERT INTO notifications_inbox ( id, - user_id, - template_id, - target_id, - title, - content, - icon, - actions, - created_at + user_id, + template_id, + targets, + title, + content, + icon, + actions, + created_at ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id, user_id, template_id, target_id, title, content, icon, actions, read_at, created_at + ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at ` type InsertInboxNotificationParams struct { ID uuid.UUID `db:"id" json:"id"` UserID uuid.UUID `db:"user_id" json:"user_id"` TemplateID uuid.UUID `db:"template_id" json:"template_id"` - TargetID uuid.NullUUID `db:"target_id" json:"target_id"` + Targets []uuid.UUID `db:"targets" json:"targets"` Title string `db:"title" json:"title"` Content string `db:"content" json:"content"` Icon string `db:"icon" json:"icon"` @@ -4518,7 +4518,7 @@ func (q *sqlQuerier) InsertInboxNotification(ctx context.Context, arg InsertInbo arg.ID, arg.UserID, arg.TemplateID, - arg.TargetID, + pq.Array(arg.Targets), arg.Title, arg.Content, arg.Icon, @@ -4530,7 +4530,7 @@ func (q *sqlQuerier) InsertInboxNotification(ctx context.Context, arg InsertInbo &i.ID, &i.UserID, &i.TemplateID, - &i.TargetID, + pq.Array(&i.Targets), &i.Title, &i.Content, &i.Icon, @@ -4543,11 +4543,11 @@ func (q *sqlQuerier) InsertInboxNotification(ctx context.Context, arg InsertInbo const setInboxNotificationAsRead = `-- name: SetInboxNotificationAsRead :exec UPDATE - notifications_inbox + notifications_inbox SET - read_at = $1 + read_at = $1 WHERE - id = $2 + id = $2 ` type SetInboxNotificationAsReadParams struct { diff --git a/coderd/database/queries/notificationsinbox.sql b/coderd/database/queries/notificationsinbox.sql index cfdd4b4ffefc7..94d173004037b 100644 --- a/coderd/database/queries/notificationsinbox.sql +++ b/coderd/database/queries/notificationsinbox.sql @@ -4,11 +4,11 @@ SELECT * FROM notifications_inbox WHERE user_id = $1 AND read_at IS NULL ORDER B -- name: FetchInboxNotificationsByUserID :many SELECT * FROM notifications_inbox WHERE user_id = $1 ORDER BY created_at DESC; --- name: FetchInboxNotificationsByUserIDAndTemplateIDAndTargetID :many -SELECT * FROM notifications_inbox WHERE user_id = $1 AND template_id = $2 AND target_id = $3 ORDER BY created_at DESC; +-- name: FetchInboxNotificationsByUserIDAndTemplateIDAndTargets :many +SELECT * FROM notifications_inbox WHERE user_id = $1 AND template_id = $2 AND targets @> $3 ORDER BY created_at DESC; --- name: FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetID :many -SELECT * FROM notifications_inbox WHERE user_id = $1 AND template_id = $2 AND target_id = $3 AND read_at IS NULL ORDER BY created_at DESC; +-- name: FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets :many +SELECT * FROM notifications_inbox WHERE user_id = $1 AND template_id = $2 AND targets @> $3 AND read_at IS NULL ORDER BY created_at DESC; -- name: GetInboxNotificationByID :one SELECT * FROM notifications_inbox WHERE id = $1; @@ -17,22 +17,22 @@ SELECT * FROM notifications_inbox WHERE id = $1; INSERT INTO notifications_inbox ( id, - user_id, - template_id, - target_id, - title, - content, - icon, - actions, - created_at + user_id, + template_id, + targets, + title, + content, + icon, + actions, + created_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *; -- name: SetInboxNotificationAsRead :exec UPDATE - notifications_inbox + notifications_inbox SET - read_at = $1 + read_at = $1 WHERE - id = @id; + id = $2; From dc74ac4337b172939a0417e821b5d61fbdba94b7 Mon Sep 17 00:00:00 2001 From: defelmnq Date: Thu, 20 Feb 2025 10:48:41 +0000 Subject: [PATCH 06/37] test dbauthz --- coderd/database/dbauthz/dbauthz_test.go | 163 ++++++++++++++++++ coderd/database/dbgen/dbgen.go | 15 ++ coderd/database/queries.sql.go | 4 +- .../database/queries/notificationsinbox.sql | 4 +- 4 files changed, 182 insertions(+), 4 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index c960f06c65f1b..4d5d4f3851939 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -4477,6 +4477,169 @@ func (s *MethodTestSuite) TestNotifications() { Disableds: []bool{true, false}, }).Asserts(rbac.ResourceNotificationPreference.WithOwner(user.ID.String()), policy.ActionUpdate) })) + + s.Run("FetchUnreadInboxNotificationsByUserID", s.Subtest(func(db database.Store, check *expects) { + u := dbgen.User(s.T(), db, database.User{}) + o := dbgen.Organization(s.T(), db, database.Organization{}) + tpl := dbgen.Template(s.T(), db, database.Template{ + OrganizationID: o.ID, + CreatedBy: u.ID, + }) + + notifID := uuid.New() + + notif := dbgen.NotificationInbox(s.T(), db, database.InsertInboxNotificationParams{ + ID: notifID, + UserID: u.ID, + TemplateID: tpl.ID, + Title: "test title", + Content: "test content notification", + Icon: "test icon", + }) + + check.Args(u.ID).Asserts(rbac.ResourceNotificationInbox.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.NotificationsInbox{notif}) + })) + + s.Run("FetchInboxNotificationsByUserID", s.Subtest(func(db database.Store, check *expects) { + u := dbgen.User(s.T(), db, database.User{}) + o := dbgen.Organization(s.T(), db, database.Organization{}) + tpl := dbgen.Template(s.T(), db, database.Template{ + OrganizationID: o.ID, + CreatedBy: u.ID, + }) + + notifID := uuid.New() + + notif := dbgen.NotificationInbox(s.T(), db, database.InsertInboxNotificationParams{ + ID: notifID, + UserID: u.ID, + TemplateID: tpl.ID, + Title: "test title", + Content: "test content notification", + Icon: "test icon", + }) + + check.Args(u.ID).Asserts(rbac.ResourceNotificationInbox.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.NotificationsInbox{notif}) + })) + + s.Run("FetchInboxNotificationsByUserIDAndTemplateIDAndTargets", s.Subtest(func(db database.Store, check *expects) { + u := dbgen.User(s.T(), db, database.User{}) + o := dbgen.Organization(s.T(), db, database.Organization{}) + tpl := dbgen.Template(s.T(), db, database.Template{ + OrganizationID: o.ID, + CreatedBy: u.ID, + }) + + notifID := uuid.New() + + targets := []uuid.UUID{u.ID, tpl.ID} + + notif := dbgen.NotificationInbox(s.T(), db, database.InsertInboxNotificationParams{ + ID: notifID, + UserID: u.ID, + TemplateID: tpl.ID, + Targets: targets, + Title: "test title", + Content: "test content notification", + Icon: "test icon", + }) + + check.Args(database.FetchInboxNotificationsByUserIDAndTemplateIDAndTargetsParams{ + UserID: u.ID, + TemplateID: tpl.ID, + Targets: []uuid.UUID{u.ID}, + }).Asserts(rbac.ResourceNotificationInbox.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.NotificationsInbox{notif}) + })) + + s.Run("FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets", s.Subtest(func(db database.Store, check *expects) { + u := dbgen.User(s.T(), db, database.User{}) + o := dbgen.Organization(s.T(), db, database.Organization{}) + tpl := dbgen.Template(s.T(), db, database.Template{ + OrganizationID: o.ID, + CreatedBy: u.ID, + }) + + notifID := uuid.New() + + targets := []uuid.UUID{u.ID, tpl.ID} + + notif := dbgen.NotificationInbox(s.T(), db, database.InsertInboxNotificationParams{ + ID: notifID, + UserID: u.ID, + TemplateID: tpl.ID, + Targets: targets, + Title: "test title", + Content: "test content notification", + Icon: "test icon", + }) + + check.Args(database.FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetsParams{ + UserID: u.ID, + TemplateID: tpl.ID, + Targets: []uuid.UUID{u.ID}, + }).Asserts(rbac.ResourceNotificationInbox.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.NotificationsInbox{notif}) + })) + + s.Run("GetInboxNotificationByID", s.Subtest(func(db database.Store, check *expects) { + u := dbgen.User(s.T(), db, database.User{}) + o := dbgen.Organization(s.T(), db, database.Organization{}) + tpl := dbgen.Template(s.T(), db, database.Template{ + OrganizationID: o.ID, + CreatedBy: u.ID, + }) + + notifID := uuid.New() + + targets := []uuid.UUID{u.ID, tpl.ID} + + notif := dbgen.NotificationInbox(s.T(), db, database.InsertInboxNotificationParams{ + ID: notifID, + UserID: u.ID, + TemplateID: tpl.ID, + Targets: targets, + Title: "test title", + Content: "test content notification", + Icon: "test icon", + }) + + check.Args(notifID).Asserts(rbac.ResourceNotificationInbox.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns(notif) + })) + + s.Run("InsertInboxNotification", s.Subtest(func(_ database.Store, check *expects) { + owner := uuid.UUID{} + check.Args(database.InsertInboxNotificationParams{}).Asserts(rbac.ResourceNotificationInbox.WithOwner(owner.String()), policy.ActionCreate) + })) + + s.Run("SetInboxNotificationAsRead", s.Subtest(func(db database.Store, check *expects) { + u := dbgen.User(s.T(), db, database.User{}) + o := dbgen.Organization(s.T(), db, database.Organization{}) + tpl := dbgen.Template(s.T(), db, database.Template{ + OrganizationID: o.ID, + CreatedBy: u.ID, + }) + + notifID := uuid.New() + + targets := []uuid.UUID{u.ID, tpl.ID} + readAt := dbtestutil.NowInDefaultTimezone() + + notif := dbgen.NotificationInbox(s.T(), db, database.InsertInboxNotificationParams{ + ID: notifID, + UserID: u.ID, + TemplateID: tpl.ID, + Targets: targets, + Title: "test title", + Content: "test content notification", + Icon: "test icon", + }) + + notif.ReadAt = sql.NullTime{Time: readAt, Valid: true} + + check.Args(database.SetInboxNotificationAsReadParams{ + ID: notifID, + ReadAt: sql.NullTime{Time: readAt, Valid: true}, + }).Asserts(rbac.ResourceNotificationInbox.WithID(notifID).WithOwner(u.ID.String()), policy.ActionUpdate) + })) } func (s *MethodTestSuite) TestOAuth2ProviderApps() { diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go index 9c4ebbe8bb8ca..0eba2fa207ed2 100644 --- a/coderd/database/dbgen/dbgen.go +++ b/coderd/database/dbgen/dbgen.go @@ -450,6 +450,21 @@ func OrganizationMember(t testing.TB, db database.Store, orig database.Organizat return mem } +func NotificationInbox(t testing.TB, db database.Store, orig database.InsertInboxNotificationParams) database.NotificationsInbox { + notification, err := db.InsertInboxNotification(genCtx, database.InsertInboxNotificationParams{ + ID: takeFirst(orig.ID, uuid.New()), + UserID: takeFirst(orig.UserID, uuid.New()), + TemplateID: takeFirst(orig.TemplateID, uuid.New()), + Targets: takeFirstSlice(orig.Targets, []uuid.UUID{}), + Title: takeFirst(orig.Title, testutil.GetRandomName(t)), + Content: takeFirst(orig.Content, testutil.GetRandomName(t)), + Icon: takeFirst(orig.Icon, ""), + CreatedAt: takeFirst(orig.CreatedAt, dbtime.Now()), + }) + require.NoError(t, err, "insert notification") + return notification +} + func Group(t testing.TB, db database.Store, orig database.Group) database.Group { t.Helper() diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index b119f2206f666..56fa40e34572e 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -4337,7 +4337,7 @@ func (q *sqlQuerier) FetchInboxNotificationsByUserID(ctx context.Context, userID } const fetchInboxNotificationsByUserIDAndTemplateIDAndTargets = `-- name: FetchInboxNotificationsByUserIDAndTemplateIDAndTargets :many -SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM notifications_inbox WHERE user_id = $1 AND template_id = $2 AND targets @> $3 ORDER BY created_at DESC +SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM notifications_inbox WHERE user_id = $1 AND template_id = $2 AND targets @> COALESCE($3, ARRAY[]::UUID[]) ORDER BY created_at DESC ` type FetchInboxNotificationsByUserIDAndTemplateIDAndTargetsParams struct { @@ -4419,7 +4419,7 @@ func (q *sqlQuerier) FetchUnreadInboxNotificationsByUserID(ctx context.Context, } const fetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets = `-- name: FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets :many -SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM notifications_inbox WHERE user_id = $1 AND template_id = $2 AND targets @> $3 AND read_at IS NULL ORDER BY created_at DESC +SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM notifications_inbox WHERE user_id = $1 AND template_id = $2 AND targets @> COALESCE($3, ARRAY[]::UUID[]) AND read_at IS NULL ORDER BY created_at DESC ` type FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetsParams struct { diff --git a/coderd/database/queries/notificationsinbox.sql b/coderd/database/queries/notificationsinbox.sql index 94d173004037b..8e7ae2c20d1d1 100644 --- a/coderd/database/queries/notificationsinbox.sql +++ b/coderd/database/queries/notificationsinbox.sql @@ -5,10 +5,10 @@ SELECT * FROM notifications_inbox WHERE user_id = $1 AND read_at IS NULL ORDER B SELECT * FROM notifications_inbox WHERE user_id = $1 ORDER BY created_at DESC; -- name: FetchInboxNotificationsByUserIDAndTemplateIDAndTargets :many -SELECT * FROM notifications_inbox WHERE user_id = $1 AND template_id = $2 AND targets @> $3 ORDER BY created_at DESC; +SELECT * FROM notifications_inbox WHERE user_id = $1 AND template_id = $2 AND targets @> COALESCE($3, ARRAY[]::UUID[]) ORDER BY created_at DESC; -- name: FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets :many -SELECT * FROM notifications_inbox WHERE user_id = $1 AND template_id = $2 AND targets @> $3 AND read_at IS NULL ORDER BY created_at DESC; +SELECT * FROM notifications_inbox WHERE user_id = $1 AND template_id = $2 AND targets @> COALESCE($3, ARRAY[]::UUID[]) AND read_at IS NULL ORDER BY created_at DESC; -- name: GetInboxNotificationByID :one SELECT * FROM notifications_inbox WHERE id = $1; From 6c9e41f91e4b1c51742506d59eb5045a6c88115a Mon Sep 17 00:00:00 2001 From: defelmnq Date: Thu, 20 Feb 2025 11:02:51 +0000 Subject: [PATCH 07/37] move notification migration --- ...cations_inbox.down.sql => 000296_notifications_inbox.down.sql} | 0 ...tifications_inbox.up.sql => 000296_notifications_inbox.up.sql} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename coderd/database/migrations/{000295_notifications_inbox.down.sql => 000296_notifications_inbox.down.sql} (100%) rename coderd/database/migrations/{000295_notifications_inbox.up.sql => 000296_notifications_inbox.up.sql} (100%) diff --git a/coderd/database/migrations/000295_notifications_inbox.down.sql b/coderd/database/migrations/000296_notifications_inbox.down.sql similarity index 100% rename from coderd/database/migrations/000295_notifications_inbox.down.sql rename to coderd/database/migrations/000296_notifications_inbox.down.sql diff --git a/coderd/database/migrations/000295_notifications_inbox.up.sql b/coderd/database/migrations/000296_notifications_inbox.up.sql similarity index 100% rename from coderd/database/migrations/000295_notifications_inbox.up.sql rename to coderd/database/migrations/000296_notifications_inbox.up.sql From 1f18ecad62e6964e01985f06801b52ef711fa7f6 Mon Sep 17 00:00:00 2001 From: defelmnq Date: Thu, 20 Feb 2025 11:12:56 +0000 Subject: [PATCH 08/37] make gen --- coderd/database/dbgen/dbgen.go | 1 + 1 file changed, 1 insertion(+) diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go index 0eba2fa207ed2..40ef050021408 100644 --- a/coderd/database/dbgen/dbgen.go +++ b/coderd/database/dbgen/dbgen.go @@ -459,6 +459,7 @@ func NotificationInbox(t testing.TB, db database.Store, orig database.InsertInbo Title: takeFirst(orig.Title, testutil.GetRandomName(t)), Content: takeFirst(orig.Content, testutil.GetRandomName(t)), Icon: takeFirst(orig.Icon, ""), + Actions: orig.Actions, CreatedAt: takeFirst(orig.CreatedAt, dbtime.Now()), }) require.NoError(t, err, "insert notification") From 3bb9c5778efc3b2389dc93937acd1e2cffc07780 Mon Sep 17 00:00:00 2001 From: defelmnq Date: Thu, 20 Feb 2025 18:14:39 +0000 Subject: [PATCH 09/37] rename inbox notifications --- coderd/apidoc/docs.go | 4 +- coderd/apidoc/swagger.json | 4 +- coderd/database/dbauthz/dbauthz.go | 20 ++--- coderd/database/dbauthz/dbauthz_test.go | 34 ++++----- coderd/database/dbgen/dbgen.go | 2 +- coderd/database/dbmem/dbmem.go | 74 ++++++++++++------- coderd/database/dbmetrics/querymetrics.go | 27 ++++--- coderd/database/dbmock/dbmock.go | 44 +++++------ coderd/database/dump.sql | 38 +++++----- .../000296_notifications_inbox.up.sql | 6 +- coderd/database/modelmethods.go | 4 +- coderd/database/models.go | 26 +++---- coderd/database/querier.go | 12 +-- coderd/database/queries.sql.go | 70 +++++++++--------- .../database/queries/notificationsinbox.sql | 18 ++--- coderd/database/unique_constraint.go | 2 +- coderd/rbac/object_gen.go | 20 ++--- coderd/rbac/policy/policy.go | 8 +- codersdk/rbacresources_gen.go | 4 +- docs/reference/api/members.md | 10 +-- docs/reference/api/schemas.md | 2 +- site/src/api/rbacresourcesGenerated.ts | 10 +-- site/src/api/typesGenerated.ts | 4 +- 23 files changed, 236 insertions(+), 207 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 767ec1339e7da..3d05a57f4b782 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -13691,8 +13691,8 @@ const docTemplate = `{ "group", "group_member", "idpsync_settings", + "inbox_notification", "license", - "notification_inbox", "notification_message", "notification_preference", "notification_template", @@ -13728,8 +13728,8 @@ const docTemplate = `{ "ResourceGroup", "ResourceGroupMember", "ResourceIdpsyncSettings", + "ResourceInboxNotification", "ResourceLicense", - "ResourceNotificationInbox", "ResourceNotificationMessage", "ResourceNotificationPreference", "ResourceNotificationTemplate", diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index ce91007cc3c5d..ffdf8e1484014 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -12384,8 +12384,8 @@ "group", "group_member", "idpsync_settings", + "inbox_notification", "license", - "notification_inbox", "notification_message", "notification_preference", "notification_template", @@ -12421,8 +12421,8 @@ "ResourceGroup", "ResourceGroupMember", "ResourceIdpsyncSettings", + "ResourceInboxNotification", "ResourceLicense", - "ResourceNotificationInbox", "ResourceNotificationMessage", "ResourceNotificationPreference", "ResourceNotificationTemplate", diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 1271e06f527c8..3727bc06410f5 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -1417,12 +1417,12 @@ func (q *querier) FavoriteWorkspace(ctx context.Context, id uuid.UUID) error { return update(q.log, q.auth, fetch, q.db.FavoriteWorkspace)(ctx, id) } -func (q *querier) FetchInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.NotificationsInbox, error) { +func (q *querier) FetchInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.FetchInboxNotificationsByUserID)(ctx, userID) } -func (q *querier) FetchInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx context.Context, arg database.FetchInboxNotificationsByUserIDAndTemplateIDAndTargetsParams) ([]database.NotificationsInbox, error) { - return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.FetchInboxNotificationsByUserIDAndTemplateIDAndTargets)(ctx, arg) +func (q *querier) FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { + return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets)(ctx, arg) } func (q *querier) FetchMemoryResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) (database.WorkspaceAgentMemoryResourceMonitor, error) { @@ -1446,12 +1446,12 @@ func (q *querier) FetchNewMessageMetadata(ctx context.Context, arg database.Fetc return q.db.FetchNewMessageMetadata(ctx, arg) } -func (q *querier) FetchUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.NotificationsInbox, error) { +func (q *querier) FetchUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.FetchUnreadInboxNotificationsByUserID)(ctx, userID) } -func (q *querier) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx context.Context, arg database.FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetsParams) ([]database.NotificationsInbox, error) { - return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets)(ctx, arg) +func (q *querier) FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { + return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets)(ctx, arg) } func (q *querier) FetchVolumesResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) ([]database.WorkspaceAgentVolumeResourceMonitor, error) { @@ -1766,7 +1766,7 @@ func (q *querier) GetHungProvisionerJobs(ctx context.Context, hungSince time.Tim return q.db.GetHungProvisionerJobs(ctx, hungSince) } -func (q *querier) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (database.NotificationsInbox, error) { +func (q *querier) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (database.InboxNotification, error) { return fetchWithAction(q.log, q.auth, policy.ActionRead, q.db.GetInboxNotificationByID)(ctx, id) } @@ -3097,8 +3097,8 @@ func (q *querier) InsertGroupMember(ctx context.Context, arg database.InsertGrou return update(q.log, q.auth, fetch, q.db.InsertGroupMember)(ctx, arg) } -func (q *querier) InsertInboxNotification(ctx context.Context, arg database.InsertInboxNotificationParams) (database.NotificationsInbox, error) { - return insert(q.log, q.auth, rbac.ResourceNotificationInbox.WithOwner(arg.UserID.String()), q.db.InsertInboxNotification)(ctx, arg) +func (q *querier) InsertInboxNotification(ctx context.Context, arg database.InsertInboxNotificationParams) (database.InboxNotification, error) { + return insert(q.log, q.auth, rbac.ResourceInboxNotification.WithOwner(arg.UserID.String()), q.db.InsertInboxNotification)(ctx, arg) } func (q *querier) InsertLicense(ctx context.Context, arg database.InsertLicenseParams) (database.License, error) { @@ -3589,7 +3589,7 @@ func (q *querier) RevokeDBCryptKey(ctx context.Context, activeKeyDigest string) } func (q *querier) SetInboxNotificationAsRead(ctx context.Context, args database.SetInboxNotificationAsReadParams) error { - fetchFunc := func(ctx context.Context, args database.SetInboxNotificationAsReadParams) (database.NotificationsInbox, error) { + fetchFunc := func(ctx context.Context, args database.SetInboxNotificationAsReadParams) (database.InboxNotification, error) { return q.db.GetInboxNotificationByID(ctx, args.ID) } diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 4d5d4f3851939..fbba2a3f824a5 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -4497,7 +4497,7 @@ func (s *MethodTestSuite) TestNotifications() { Icon: "test icon", }) - check.Args(u.ID).Asserts(rbac.ResourceNotificationInbox.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.NotificationsInbox{notif}) + check.Args(u.ID).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif}) })) s.Run("FetchInboxNotificationsByUserID", s.Subtest(func(db database.Store, check *expects) { @@ -4519,10 +4519,10 @@ func (s *MethodTestSuite) TestNotifications() { Icon: "test icon", }) - check.Args(u.ID).Asserts(rbac.ResourceNotificationInbox.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.NotificationsInbox{notif}) + check.Args(u.ID).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif}) })) - s.Run("FetchInboxNotificationsByUserIDAndTemplateIDAndTargets", s.Subtest(func(db database.Store, check *expects) { + s.Run("FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) o := dbgen.Organization(s.T(), db, database.Organization{}) tpl := dbgen.Template(s.T(), db, database.Template{ @@ -4544,14 +4544,14 @@ func (s *MethodTestSuite) TestNotifications() { Icon: "test icon", }) - check.Args(database.FetchInboxNotificationsByUserIDAndTemplateIDAndTargetsParams{ - UserID: u.ID, - TemplateID: tpl.ID, - Targets: []uuid.UUID{u.ID}, - }).Asserts(rbac.ResourceNotificationInbox.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.NotificationsInbox{notif}) + check.Args(database.FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams{ + UserID: u.ID, + Templates: []uuid.UUID{tpl.ID}, + Targets: []uuid.UUID{u.ID}, + }).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif}) })) - s.Run("FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets", s.Subtest(func(db database.Store, check *expects) { + s.Run("FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) o := dbgen.Organization(s.T(), db, database.Organization{}) tpl := dbgen.Template(s.T(), db, database.Template{ @@ -4573,11 +4573,11 @@ func (s *MethodTestSuite) TestNotifications() { Icon: "test icon", }) - check.Args(database.FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetsParams{ - UserID: u.ID, - TemplateID: tpl.ID, - Targets: []uuid.UUID{u.ID}, - }).Asserts(rbac.ResourceNotificationInbox.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.NotificationsInbox{notif}) + check.Args(database.FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams{ + UserID: u.ID, + Templates: []uuid.UUID{tpl.ID}, + Targets: []uuid.UUID{u.ID}, + }).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif}) })) s.Run("GetInboxNotificationByID", s.Subtest(func(db database.Store, check *expects) { @@ -4602,12 +4602,12 @@ func (s *MethodTestSuite) TestNotifications() { Icon: "test icon", }) - check.Args(notifID).Asserts(rbac.ResourceNotificationInbox.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns(notif) + check.Args(notifID).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns(notif) })) s.Run("InsertInboxNotification", s.Subtest(func(_ database.Store, check *expects) { owner := uuid.UUID{} - check.Args(database.InsertInboxNotificationParams{}).Asserts(rbac.ResourceNotificationInbox.WithOwner(owner.String()), policy.ActionCreate) + check.Args(database.InsertInboxNotificationParams{}).Asserts(rbac.ResourceInboxNotification.WithOwner(owner.String()), policy.ActionCreate) })) s.Run("SetInboxNotificationAsRead", s.Subtest(func(db database.Store, check *expects) { @@ -4638,7 +4638,7 @@ func (s *MethodTestSuite) TestNotifications() { check.Args(database.SetInboxNotificationAsReadParams{ ID: notifID, ReadAt: sql.NullTime{Time: readAt, Valid: true}, - }).Asserts(rbac.ResourceNotificationInbox.WithID(notifID).WithOwner(u.ID.String()), policy.ActionUpdate) + }).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionUpdate) })) } diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go index 40ef050021408..3810fcb5052cf 100644 --- a/coderd/database/dbgen/dbgen.go +++ b/coderd/database/dbgen/dbgen.go @@ -450,7 +450,7 @@ func OrganizationMember(t testing.TB, db database.Store, orig database.Organizat return mem } -func NotificationInbox(t testing.TB, db database.Store, orig database.InsertInboxNotificationParams) database.NotificationsInbox { +func NotificationInbox(t testing.TB, db database.Store, orig database.InsertInboxNotificationParams) database.InboxNotification { notification, err := db.InsertInboxNotification(genCtx, database.InsertInboxNotificationParams{ ID: takeFirst(orig.ID, uuid.New()), UserID: takeFirst(orig.UserID, uuid.New()), diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 70ae3ce30d5f0..9dfd9974b72d5 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -67,7 +67,7 @@ func New() database.Store { gitSSHKey: make([]database.GitSSHKey, 0), notificationMessages: make([]database.NotificationMessage, 0), notificationPreferences: make([]database.NotificationPreference, 0), - notificationsInbox: make([]database.NotificationsInbox, 0), + InboxNotification: make([]database.InboxNotification, 0), parameterSchemas: make([]database.ParameterSchema, 0), provisionerDaemons: make([]database.ProvisionerDaemon, 0), provisionerKeys: make([]database.ProvisionerKey, 0), @@ -207,7 +207,7 @@ type data struct { notificationMessages []database.NotificationMessage notificationPreferences []database.NotificationPreference notificationReportGeneratorLogs []database.NotificationReportGeneratorLog - notificationsInbox []database.NotificationsInbox + InboxNotification []database.InboxNotification oauth2ProviderApps []database.OAuth2ProviderApp oauth2ProviderAppSecrets []database.OAuth2ProviderAppSecret oauth2ProviderAppCodes []database.OAuth2ProviderAppCode @@ -2365,12 +2365,12 @@ func (q *FakeQuerier) FavoriteWorkspace(_ context.Context, arg uuid.UUID) error return nil } -func (q *FakeQuerier) FetchInboxNotificationsByUserID(_ context.Context, userID uuid.UUID) ([]database.NotificationsInbox, error) { +func (q *FakeQuerier) FetchInboxNotificationsByUserID(_ context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { q.mutex.RLock() defer q.mutex.RUnlock() - notifications := make([]database.NotificationsInbox, 0) - for _, notification := range q.notificationsInbox { + notifications := make([]database.InboxNotification, 0) + for _, notification := range q.InboxNotification { if notification.UserID == userID { notifications = append(notifications, notification) } @@ -2379,13 +2379,24 @@ func (q *FakeQuerier) FetchInboxNotificationsByUserID(_ context.Context, userID return notifications, nil } -func (q *FakeQuerier) FetchInboxNotificationsByUserIDAndTemplateIDAndTargets(_ context.Context, arg database.FetchInboxNotificationsByUserIDAndTemplateIDAndTargetsParams) ([]database.NotificationsInbox, error) { +func (q *FakeQuerier) FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets(_ context.Context, arg database.FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { q.mutex.RLock() defer q.mutex.RUnlock() - notifications := make([]database.NotificationsInbox, 0) - for _, notification := range q.notificationsInbox { - if notification.UserID == arg.UserID && notification.TemplateID == arg.TemplateID { + notifications := make([]database.InboxNotification, 0) + for _, notification := range q.InboxNotification { + if notification.UserID == arg.UserID { + for _, template := range arg.Templates { + templateFound := false + if notification.TemplateID == template { + templateFound = true + } + + if !templateFound { + continue + } + } + for _, target := range arg.Targets { isFound := false for _, insertedTarget := range notification.Targets { @@ -2449,12 +2460,12 @@ func (q *FakeQuerier) FetchNewMessageMetadata(_ context.Context, arg database.Fe }, nil } -func (q *FakeQuerier) FetchUnreadInboxNotificationsByUserID(_ context.Context, userID uuid.UUID) ([]database.NotificationsInbox, error) { +func (q *FakeQuerier) FetchUnreadInboxNotificationsByUserID(_ context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { q.mutex.RLock() defer q.mutex.RUnlock() - notifications := make([]database.NotificationsInbox, 0) - for _, notification := range q.notificationsInbox { + notifications := make([]database.InboxNotification, 0) + for _, notification := range q.InboxNotification { if notification.UserID == userID && !notification.ReadAt.Valid { notifications = append(notifications, notification) } @@ -2463,13 +2474,24 @@ func (q *FakeQuerier) FetchUnreadInboxNotificationsByUserID(_ context.Context, u return notifications, nil } -func (q *FakeQuerier) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets(_ context.Context, arg database.FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetsParams) ([]database.NotificationsInbox, error) { +func (q *FakeQuerier) FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(_ context.Context, arg database.FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { q.mutex.RLock() defer q.mutex.RUnlock() - notifications := make([]database.NotificationsInbox, 0) - for _, notification := range q.notificationsInbox { - if notification.UserID == arg.UserID && notification.TemplateID == arg.TemplateID && !notification.ReadAt.Valid { + notifications := make([]database.InboxNotification, 0) + for _, notification := range q.InboxNotification { + if notification.UserID == arg.UserID && !notification.ReadAt.Valid { + for _, template := range arg.Templates { + templateFound := false + if notification.TemplateID == template { + templateFound = true + } + + if !templateFound { + continue + } + } + for _, target := range arg.Targets { isFound := false for _, insertedTarget := range notification.Targets { @@ -3418,17 +3440,17 @@ func (q *FakeQuerier) GetHungProvisionerJobs(_ context.Context, hungSince time.T return hungJobs, nil } -func (q *FakeQuerier) GetInboxNotificationByID(_ context.Context, id uuid.UUID) (database.NotificationsInbox, error) { +func (q *FakeQuerier) GetInboxNotificationByID(_ context.Context, id uuid.UUID) (database.InboxNotification, error) { q.mutex.RLock() defer q.mutex.RUnlock() - for _, notification := range q.notificationsInbox { + for _, notification := range q.InboxNotification { if notification.ID == id { return notification, nil } } - return database.NotificationsInbox{}, sql.ErrNoRows + return database.InboxNotification{}, sql.ErrNoRows } func (q *FakeQuerier) GetJFrogXrayScanByWorkspaceAndAgentID(_ context.Context, arg database.GetJFrogXrayScanByWorkspaceAndAgentIDParams) (database.JfrogXrayScan, error) { @@ -8058,15 +8080,15 @@ func (q *FakeQuerier) InsertGroupMember(_ context.Context, arg database.InsertGr return nil } -func (q *FakeQuerier) InsertInboxNotification(_ context.Context, arg database.InsertInboxNotificationParams) (database.NotificationsInbox, error) { +func (q *FakeQuerier) InsertInboxNotification(_ context.Context, arg database.InsertInboxNotificationParams) (database.InboxNotification, error) { if err := validateDatabaseType(arg); err != nil { - return database.NotificationsInbox{}, err + return database.InboxNotification{}, err } q.mutex.Lock() defer q.mutex.Unlock() - notification := database.NotificationsInbox{ + notification := database.InboxNotification{ ID: arg.ID, UserID: arg.UserID, TemplateID: arg.TemplateID, @@ -8078,7 +8100,7 @@ func (q *FakeQuerier) InsertInboxNotification(_ context.Context, arg database.In CreatedAt: time.Now(), } - q.notificationsInbox = append(q.notificationsInbox, notification) + q.InboxNotification = append(q.InboxNotification, notification) return notification, nil } @@ -9575,9 +9597,9 @@ func (q *FakeQuerier) SetInboxNotificationAsRead(_ context.Context, arg database q.mutex.Lock() defer q.mutex.Unlock() - for i := range q.notificationsInbox { - if q.notificationsInbox[i].ID == arg.ID { - q.notificationsInbox[i].ReadAt = arg.ReadAt + for i := range q.InboxNotification { + if q.InboxNotification[i].ID == arg.ID { + q.InboxNotification[i].ReadAt = arg.ReadAt } } diff --git a/coderd/database/dbmetrics/querymetrics.go b/coderd/database/dbmetrics/querymetrics.go index 09af01efcb3e1..c18232ac54f0d 100644 --- a/coderd/database/dbmetrics/querymetrics.go +++ b/coderd/database/dbmetrics/querymetrics.go @@ -77,6 +77,13 @@ func (m queryMetricsStore) InTx(f func(database.Store) error, options *database. return m.dbMetrics.InTx(f, options) } +func (m queryMetricsStore) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx context.Context, arg database.FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { + start := time.Now() + r0, r1 := m.s.FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx, arg) + m.queryLatencies.WithLabelValues("FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m queryMetricsStore) AcquireLock(ctx context.Context, pgAdvisoryXactLock int64) error { start := time.Now() err := m.s.AcquireLock(ctx, pgAdvisoryXactLock) @@ -434,17 +441,17 @@ func (m queryMetricsStore) FavoriteWorkspace(ctx context.Context, arg uuid.UUID) return r0 } -func (m queryMetricsStore) FetchInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.NotificationsInbox, error) { +func (m queryMetricsStore) FetchInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { start := time.Now() r0, r1 := m.s.FetchInboxNotificationsByUserID(ctx, userID) m.queryLatencies.WithLabelValues("FetchInboxNotificationsByUserID").Observe(time.Since(start).Seconds()) return r0, r1 } -func (m queryMetricsStore) FetchInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx context.Context, arg database.FetchInboxNotificationsByUserIDAndTemplateIDAndTargetsParams) ([]database.NotificationsInbox, error) { +func (m queryMetricsStore) FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { start := time.Now() - r0, r1 := m.s.FetchInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx, arg) - m.queryLatencies.WithLabelValues("FetchInboxNotificationsByUserIDAndTemplateIDAndTargets").Observe(time.Since(start).Seconds()) + r0, r1 := m.s.FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx, arg) + m.queryLatencies.WithLabelValues("FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets").Observe(time.Since(start).Seconds()) return r0, r1 } @@ -462,17 +469,17 @@ func (m queryMetricsStore) FetchNewMessageMetadata(ctx context.Context, arg data return r0, r1 } -func (m queryMetricsStore) FetchUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.NotificationsInbox, error) { +func (m queryMetricsStore) FetchUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { start := time.Now() r0, r1 := m.s.FetchUnreadInboxNotificationsByUserID(ctx, userID) m.queryLatencies.WithLabelValues("FetchUnreadInboxNotificationsByUserID").Observe(time.Since(start).Seconds()) return r0, r1 } -func (m queryMetricsStore) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx context.Context, arg database.FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetsParams) ([]database.NotificationsInbox, error) { +func (m queryMetricsStore) FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { start := time.Now() - r0, r1 := m.s.FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx, arg) - m.queryLatencies.WithLabelValues("FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets").Observe(time.Since(start).Seconds()) + r0, r1 := m.s.FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx, arg) + m.queryLatencies.WithLabelValues("FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets").Observe(time.Since(start).Seconds()) return r0, r1 } @@ -798,7 +805,7 @@ func (m queryMetricsStore) GetHungProvisionerJobs(ctx context.Context, hungSince return jobs, err } -func (m queryMetricsStore) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (database.NotificationsInbox, error) { +func (m queryMetricsStore) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (database.InboxNotification, error) { start := time.Now() r0, r1 := m.s.GetInboxNotificationByID(ctx, id) m.queryLatencies.WithLabelValues("GetInboxNotificationByID").Observe(time.Since(start).Seconds()) @@ -1904,7 +1911,7 @@ func (m queryMetricsStore) InsertGroupMember(ctx context.Context, arg database.I return err } -func (m queryMetricsStore) InsertInboxNotification(ctx context.Context, arg database.InsertInboxNotificationParams) (database.NotificationsInbox, error) { +func (m queryMetricsStore) InsertInboxNotification(ctx context.Context, arg database.InsertInboxNotificationParams) (database.InboxNotification, error) { start := time.Now() r0, r1 := m.s.InsertInboxNotification(ctx, arg) m.queryLatencies.WithLabelValues("InsertInboxNotification").Observe(time.Since(start).Seconds()) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index ebb98f25756d4..3a360771c44db 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -772,10 +772,10 @@ func (mr *MockStoreMockRecorder) FavoriteWorkspace(ctx, id any) *gomock.Call { } // FetchInboxNotificationsByUserID mocks base method. -func (m *MockStore) FetchInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.NotificationsInbox, error) { +func (m *MockStore) FetchInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "FetchInboxNotificationsByUserID", ctx, userID) - ret0, _ := ret[0].([]database.NotificationsInbox) + ret0, _ := ret[0].([]database.InboxNotification) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -786,19 +786,19 @@ func (mr *MockStoreMockRecorder) FetchInboxNotificationsByUserID(ctx, userID any return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchInboxNotificationsByUserID", reflect.TypeOf((*MockStore)(nil).FetchInboxNotificationsByUserID), ctx, userID) } -// FetchInboxNotificationsByUserIDAndTemplateIDAndTargets mocks base method. -func (m *MockStore) FetchInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx context.Context, arg database.FetchInboxNotificationsByUserIDAndTemplateIDAndTargetsParams) ([]database.NotificationsInbox, error) { +// FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets mocks base method. +func (m *MockStore) FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "FetchInboxNotificationsByUserIDAndTemplateIDAndTargets", ctx, arg) - ret0, _ := ret[0].([]database.NotificationsInbox) + ret := m.ctrl.Call(m, "FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets", ctx, arg) + ret0, _ := ret[0].([]database.InboxNotification) ret1, _ := ret[1].(error) return ret0, ret1 } -// FetchInboxNotificationsByUserIDAndTemplateIDAndTargets indicates an expected call of FetchInboxNotificationsByUserIDAndTemplateIDAndTargets. -func (mr *MockStoreMockRecorder) FetchInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx, arg any) *gomock.Call { +// FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets indicates an expected call of FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets. +func (mr *MockStoreMockRecorder) FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx, arg any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchInboxNotificationsByUserIDAndTemplateIDAndTargets", reflect.TypeOf((*MockStore)(nil).FetchInboxNotificationsByUserIDAndTemplateIDAndTargets), ctx, arg) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets", reflect.TypeOf((*MockStore)(nil).FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets), ctx, arg) } // FetchMemoryResourceMonitorsByAgentID mocks base method. @@ -832,10 +832,10 @@ func (mr *MockStoreMockRecorder) FetchNewMessageMetadata(ctx, arg any) *gomock.C } // FetchUnreadInboxNotificationsByUserID mocks base method. -func (m *MockStore) FetchUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.NotificationsInbox, error) { +func (m *MockStore) FetchUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "FetchUnreadInboxNotificationsByUserID", ctx, userID) - ret0, _ := ret[0].([]database.NotificationsInbox) + ret0, _ := ret[0].([]database.InboxNotification) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -846,19 +846,19 @@ func (mr *MockStoreMockRecorder) FetchUnreadInboxNotificationsByUserID(ctx, user return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchUnreadInboxNotificationsByUserID", reflect.TypeOf((*MockStore)(nil).FetchUnreadInboxNotificationsByUserID), ctx, userID) } -// FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets mocks base method. -func (m *MockStore) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx context.Context, arg database.FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetsParams) ([]database.NotificationsInbox, error) { +// FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets mocks base method. +func (m *MockStore) FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets", ctx, arg) - ret0, _ := ret[0].([]database.NotificationsInbox) + ret := m.ctrl.Call(m, "FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets", ctx, arg) + ret0, _ := ret[0].([]database.InboxNotification) ret1, _ := ret[1].(error) return ret0, ret1 } -// FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets indicates an expected call of FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets. -func (mr *MockStoreMockRecorder) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx, arg any) *gomock.Call { +// FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets indicates an expected call of FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets. +func (mr *MockStoreMockRecorder) FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx, arg any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets", reflect.TypeOf((*MockStore)(nil).FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets), ctx, arg) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets", reflect.TypeOf((*MockStore)(nil).FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets), ctx, arg) } // FetchVolumesResourceMonitorsByAgentID mocks base method. @@ -1627,10 +1627,10 @@ func (mr *MockStoreMockRecorder) GetHungProvisionerJobs(ctx, updatedAt any) *gom } // GetInboxNotificationByID mocks base method. -func (m *MockStore) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (database.NotificationsInbox, error) { +func (m *MockStore) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (database.InboxNotification, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetInboxNotificationByID", ctx, id) - ret0, _ := ret[0].(database.NotificationsInbox) + ret0, _ := ret[0].(database.InboxNotification) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -4037,10 +4037,10 @@ func (mr *MockStoreMockRecorder) InsertGroupMember(ctx, arg any) *gomock.Call { } // InsertInboxNotification mocks base method. -func (m *MockStore) InsertInboxNotification(ctx context.Context, arg database.InsertInboxNotificationParams) (database.NotificationsInbox, error) { +func (m *MockStore) InsertInboxNotification(ctx context.Context, arg database.InsertInboxNotificationParams) (database.InboxNotification, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "InsertInboxNotification", ctx, arg) - ret0, _ := ret[0].(database.NotificationsInbox) + ret0, _ := ret[0].(database.InboxNotification) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 6c6feb67235f4..5d41098f77afd 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -831,6 +831,19 @@ CREATE VIEW group_members_expanded AS COMMENT ON VIEW group_members_expanded IS 'Joins group members with user information, organization ID, group name. Includes both regular group members and organization members (as part of the "Everyone" group).'; +CREATE TABLE inbox_notifications ( + id uuid NOT NULL, + user_id uuid NOT NULL, + template_id uuid NOT NULL, + targets uuid[], + title text NOT NULL, + content text NOT NULL, + icon text NOT NULL, + actions jsonb NOT NULL, + read_at timestamp with time zone, + created_at timestamp with time zone DEFAULT now() NOT NULL +); + CREATE TABLE jfrog_xray_scans ( agent_id uuid NOT NULL, workspace_id uuid NOT NULL, @@ -912,19 +925,6 @@ COMMENT ON TABLE notification_templates IS 'Templates from which to create notif COMMENT ON COLUMN notification_templates.method IS 'NULL defers to the deployment-level method'; -CREATE TABLE notifications_inbox ( - id uuid NOT NULL, - user_id uuid NOT NULL, - template_id uuid NOT NULL, - targets uuid[], - title text NOT NULL, - content text NOT NULL, - icon text NOT NULL, - actions jsonb NOT NULL, - read_at timestamp with time zone, - created_at timestamp with time zone DEFAULT now() NOT NULL -); - CREATE TABLE oauth2_provider_app_codes ( id uuid NOT NULL, created_at timestamp with time zone NOT NULL, @@ -1992,6 +1992,9 @@ ALTER TABLE ONLY groups ALTER TABLE ONLY groups ADD CONSTRAINT groups_pkey PRIMARY KEY (id); +ALTER TABLE ONLY inbox_notifications + ADD CONSTRAINT inbox_notifications_pkey PRIMARY KEY (id); + ALTER TABLE ONLY jfrog_xray_scans ADD CONSTRAINT jfrog_xray_scans_pkey PRIMARY KEY (agent_id, workspace_id); @@ -2016,9 +2019,6 @@ ALTER TABLE ONLY notification_templates ALTER TABLE ONLY notification_templates ADD CONSTRAINT notification_templates_pkey PRIMARY KEY (id); -ALTER TABLE ONLY notifications_inbox - ADD CONSTRAINT notifications_inbox_pkey PRIMARY KEY (id); - ALTER TABLE ONLY oauth2_provider_app_codes ADD CONSTRAINT oauth2_provider_app_codes_pkey PRIMARY KEY (id); @@ -2228,11 +2228,11 @@ CREATE INDEX idx_custom_roles_id ON custom_roles USING btree (id); CREATE UNIQUE INDEX idx_custom_roles_name_lower ON custom_roles USING btree (lower(name)); -CREATE INDEX idx_notification_messages_status ON notification_messages USING btree (status); +CREATE INDEX idx_inbox_notifications_user_id_read_at ON inbox_notifications USING btree (user_id, read_at); -CREATE INDEX idx_notifications_inbox_user_id_read_at ON notifications_inbox USING btree (user_id, read_at); +CREATE INDEX idx_inbox_notifications_user_id_template_id_targets ON inbox_notifications USING btree (user_id, template_id, targets); -CREATE INDEX idx_notifications_inbox_user_id_template_id_targets ON notifications_inbox USING btree (user_id, template_id, targets); +CREATE INDEX idx_notification_messages_status ON notification_messages USING btree (status); CREATE INDEX idx_organization_member_organization_id_uuid ON organization_members USING btree (organization_id); diff --git a/coderd/database/migrations/000296_notifications_inbox.up.sql b/coderd/database/migrations/000296_notifications_inbox.up.sql index 637f34c0a273b..a8034a9a8b93e 100644 --- a/coderd/database/migrations/000296_notifications_inbox.up.sql +++ b/coderd/database/migrations/000296_notifications_inbox.up.sql @@ -1,4 +1,4 @@ -CREATE TABLE notifications_inbox ( +CREATE TABLE inbox_notifications ( id UUID PRIMARY KEY, user_id UUID NOT NULL, template_id UUID NOT NULL, @@ -11,5 +11,5 @@ CREATE TABLE notifications_inbox ( created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() ); -CREATE INDEX idx_notifications_inbox_user_id_read_at ON notifications_inbox(user_id, read_at); -CREATE INDEX idx_notifications_inbox_user_id_template_id_targets ON notifications_inbox(user_id, template_id, targets); +CREATE INDEX idx_inbox_notifications_user_id_read_at ON inbox_notifications(user_id, read_at); +CREATE INDEX idx_inbox_notifications_user_id_template_id_targets ON inbox_notifications(user_id, template_id, targets); diff --git a/coderd/database/modelmethods.go b/coderd/database/modelmethods.go index ad018b9a0214e..4f8d058aeaf0d 100644 --- a/coderd/database/modelmethods.go +++ b/coderd/database/modelmethods.go @@ -168,8 +168,8 @@ func (TemplateVersion) RBACObject(template Template) rbac.Object { return template.RBACObject() } -func (i NotificationsInbox) RBACObject() rbac.Object { - return rbac.ResourceNotificationInbox. +func (i InboxNotification) RBACObject() rbac.Object { + return rbac.ResourceInboxNotification. WithID(i.ID). WithOwner(i.UserID.String()) } diff --git a/coderd/database/models.go b/coderd/database/models.go index ca1dc70ee9ee0..325c9339b3d90 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -2557,6 +2557,19 @@ type GroupMemberTable struct { GroupID uuid.UUID `db:"group_id" json:"group_id"` } +type InboxNotification struct { + ID uuid.UUID `db:"id" json:"id"` + UserID uuid.UUID `db:"user_id" json:"user_id"` + TemplateID uuid.UUID `db:"template_id" json:"template_id"` + Targets []uuid.UUID `db:"targets" json:"targets"` + Title string `db:"title" json:"title"` + Content string `db:"content" json:"content"` + Icon string `db:"icon" json:"icon"` + Actions json.RawMessage `db:"actions" json:"actions"` + ReadAt sql.NullTime `db:"read_at" json:"read_at"` + CreatedAt time.Time `db:"created_at" json:"created_at"` +} + type JfrogXrayScan struct { AgentID uuid.UUID `db:"agent_id" json:"agent_id"` WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"` @@ -2623,19 +2636,6 @@ type NotificationTemplate struct { EnabledByDefault bool `db:"enabled_by_default" json:"enabled_by_default"` } -type NotificationsInbox struct { - ID uuid.UUID `db:"id" json:"id"` - UserID uuid.UUID `db:"user_id" json:"user_id"` - TemplateID uuid.UUID `db:"template_id" json:"template_id"` - Targets []uuid.UUID `db:"targets" json:"targets"` - Title string `db:"title" json:"title"` - Content string `db:"content" json:"content"` - Icon string `db:"icon" json:"icon"` - Actions json.RawMessage `db:"actions" json:"actions"` - ReadAt sql.NullTime `db:"read_at" json:"read_at"` - CreatedAt time.Time `db:"created_at" json:"created_at"` -} - // A table used to configure apps that can use Coder as an OAuth2 provider, the reverse of what we are calling external authentication. type OAuth2ProviderApp struct { ID uuid.UUID `db:"id" json:"id"` diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 3099dfc297492..7236e75141e92 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -112,13 +112,13 @@ type sqlcQuerier interface { DisableForeignKeysAndTriggers(ctx context.Context) error EnqueueNotificationMessage(ctx context.Context, arg EnqueueNotificationMessageParams) error FavoriteWorkspace(ctx context.Context, id uuid.UUID) error - FetchInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]NotificationsInbox, error) - FetchInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx context.Context, arg FetchInboxNotificationsByUserIDAndTemplateIDAndTargetsParams) ([]NotificationsInbox, error) + FetchInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]InboxNotification, error) + FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]InboxNotification, error) FetchMemoryResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) (WorkspaceAgentMemoryResourceMonitor, error) // This is used to build up the notification_message's JSON payload. FetchNewMessageMetadata(ctx context.Context, arg FetchNewMessageMetadataParams) (FetchNewMessageMetadataRow, error) - FetchUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]NotificationsInbox, error) - FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx context.Context, arg FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetsParams) ([]NotificationsInbox, error) + FetchUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]InboxNotification, error) + FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]InboxNotification, error) FetchVolumesResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) ([]WorkspaceAgentVolumeResourceMonitor, error) GetAPIKeyByID(ctx context.Context, id string) (APIKey, error) // there is no unique constraint on empty token names @@ -175,7 +175,7 @@ type sqlcQuerier interface { GetGroups(ctx context.Context, arg GetGroupsParams) ([]GetGroupsRow, error) GetHealthSettings(ctx context.Context) (string, error) GetHungProvisionerJobs(ctx context.Context, updatedAt time.Time) ([]ProvisionerJob, error) - GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (NotificationsInbox, error) + GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (InboxNotification, error) GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg GetJFrogXrayScanByWorkspaceAndAgentIDParams) (JfrogXrayScan, error) GetLastUpdateCheck(ctx context.Context) (string, error) GetLatestCryptoKeyByFeature(ctx context.Context, feature CryptoKeyFeature) (CryptoKey, error) @@ -401,7 +401,7 @@ type sqlcQuerier interface { InsertGitSSHKey(ctx context.Context, arg InsertGitSSHKeyParams) (GitSSHKey, error) InsertGroup(ctx context.Context, arg InsertGroupParams) (Group, error) InsertGroupMember(ctx context.Context, arg InsertGroupMemberParams) error - InsertInboxNotification(ctx context.Context, arg InsertInboxNotificationParams) (NotificationsInbox, error) + InsertInboxNotification(ctx context.Context, arg InsertInboxNotificationParams) (InboxNotification, error) InsertLicense(ctx context.Context, arg InsertLicenseParams) (License, error) InsertMemoryResourceMonitor(ctx context.Context, arg InsertMemoryResourceMonitorParams) (WorkspaceAgentMemoryResourceMonitor, error) // Inserts any group by name that does not exist. All new groups are given diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 56fa40e34572e..da7fa43aa3737 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -4299,18 +4299,18 @@ func (q *sqlQuerier) UpsertNotificationReportGeneratorLog(ctx context.Context, a } const fetchInboxNotificationsByUserID = `-- name: FetchInboxNotificationsByUserID :many -SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM notifications_inbox WHERE user_id = $1 ORDER BY created_at DESC +SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 ORDER BY created_at DESC ` -func (q *sqlQuerier) FetchInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]NotificationsInbox, error) { +func (q *sqlQuerier) FetchInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]InboxNotification, error) { rows, err := q.db.QueryContext(ctx, fetchInboxNotificationsByUserID, userID) if err != nil { return nil, err } defer rows.Close() - var items []NotificationsInbox + var items []InboxNotification for rows.Next() { - var i NotificationsInbox + var i InboxNotification if err := rows.Scan( &i.ID, &i.UserID, @@ -4336,25 +4336,25 @@ func (q *sqlQuerier) FetchInboxNotificationsByUserID(ctx context.Context, userID return items, nil } -const fetchInboxNotificationsByUserIDAndTemplateIDAndTargets = `-- name: FetchInboxNotificationsByUserIDAndTemplateIDAndTargets :many -SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM notifications_inbox WHERE user_id = $1 AND template_id = $2 AND targets @> COALESCE($3, ARRAY[]::UUID[]) ORDER BY created_at DESC +const fetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets = `-- name: FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many +SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 AND template_id = ANY($2::UUID[]) AND targets @> COALESCE($3, ARRAY[]::UUID[]) ORDER BY created_at DESC ` -type FetchInboxNotificationsByUserIDAndTemplateIDAndTargetsParams struct { - UserID uuid.UUID `db:"user_id" json:"user_id"` - TemplateID uuid.UUID `db:"template_id" json:"template_id"` - Targets []uuid.UUID `db:"targets" json:"targets"` +type FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams struct { + UserID uuid.UUID `db:"user_id" json:"user_id"` + Templates []uuid.UUID `db:"templates" json:"templates"` + Targets []uuid.UUID `db:"targets" json:"targets"` } -func (q *sqlQuerier) FetchInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx context.Context, arg FetchInboxNotificationsByUserIDAndTemplateIDAndTargetsParams) ([]NotificationsInbox, error) { - rows, err := q.db.QueryContext(ctx, fetchInboxNotificationsByUserIDAndTemplateIDAndTargets, arg.UserID, arg.TemplateID, pq.Array(arg.Targets)) +func (q *sqlQuerier) FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]InboxNotification, error) { + rows, err := q.db.QueryContext(ctx, fetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets, arg.UserID, pq.Array(arg.Templates), pq.Array(arg.Targets)) if err != nil { return nil, err } defer rows.Close() - var items []NotificationsInbox + var items []InboxNotification for rows.Next() { - var i NotificationsInbox + var i InboxNotification if err := rows.Scan( &i.ID, &i.UserID, @@ -4381,18 +4381,18 @@ func (q *sqlQuerier) FetchInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx } const fetchUnreadInboxNotificationsByUserID = `-- name: FetchUnreadInboxNotificationsByUserID :many -SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM notifications_inbox WHERE user_id = $1 AND read_at IS NULL ORDER BY created_at DESC +SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 AND read_at IS NULL ORDER BY created_at DESC ` -func (q *sqlQuerier) FetchUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]NotificationsInbox, error) { +func (q *sqlQuerier) FetchUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]InboxNotification, error) { rows, err := q.db.QueryContext(ctx, fetchUnreadInboxNotificationsByUserID, userID) if err != nil { return nil, err } defer rows.Close() - var items []NotificationsInbox + var items []InboxNotification for rows.Next() { - var i NotificationsInbox + var i InboxNotification if err := rows.Scan( &i.ID, &i.UserID, @@ -4418,25 +4418,25 @@ func (q *sqlQuerier) FetchUnreadInboxNotificationsByUserID(ctx context.Context, return items, nil } -const fetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets = `-- name: FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets :many -SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM notifications_inbox WHERE user_id = $1 AND template_id = $2 AND targets @> COALESCE($3, ARRAY[]::UUID[]) AND read_at IS NULL ORDER BY created_at DESC +const fetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets = `-- name: FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many +SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 AND template_id = ANY($2::UUID[]) AND targets @> COALESCE($3, ARRAY[]::UUID[]) AND read_at IS NULL ORDER BY created_at DESC ` -type FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetsParams struct { - UserID uuid.UUID `db:"user_id" json:"user_id"` - TemplateID uuid.UUID `db:"template_id" json:"template_id"` - Targets []uuid.UUID `db:"targets" json:"targets"` +type FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams struct { + UserID uuid.UUID `db:"user_id" json:"user_id"` + Templates []uuid.UUID `db:"templates" json:"templates"` + Targets []uuid.UUID `db:"targets" json:"targets"` } -func (q *sqlQuerier) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx context.Context, arg FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargetsParams) ([]NotificationsInbox, error) { - rows, err := q.db.QueryContext(ctx, fetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets, arg.UserID, arg.TemplateID, pq.Array(arg.Targets)) +func (q *sqlQuerier) FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]InboxNotification, error) { + rows, err := q.db.QueryContext(ctx, fetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets, arg.UserID, pq.Array(arg.Templates), pq.Array(arg.Targets)) if err != nil { return nil, err } defer rows.Close() - var items []NotificationsInbox + var items []InboxNotification for rows.Next() { - var i NotificationsInbox + var i InboxNotification if err := rows.Scan( &i.ID, &i.UserID, @@ -4463,12 +4463,12 @@ func (q *sqlQuerier) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTarget } const getInboxNotificationByID = `-- name: GetInboxNotificationByID :one -SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM notifications_inbox WHERE id = $1 +SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE id = $1 ` -func (q *sqlQuerier) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (NotificationsInbox, error) { +func (q *sqlQuerier) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (InboxNotification, error) { row := q.db.QueryRowContext(ctx, getInboxNotificationByID, id) - var i NotificationsInbox + var i InboxNotification err := row.Scan( &i.ID, &i.UserID, @@ -4486,7 +4486,7 @@ func (q *sqlQuerier) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) const insertInboxNotification = `-- name: InsertInboxNotification :one INSERT INTO - notifications_inbox ( + inbox_notifications ( id, user_id, template_id, @@ -4513,7 +4513,7 @@ type InsertInboxNotificationParams struct { CreatedAt time.Time `db:"created_at" json:"created_at"` } -func (q *sqlQuerier) InsertInboxNotification(ctx context.Context, arg InsertInboxNotificationParams) (NotificationsInbox, error) { +func (q *sqlQuerier) InsertInboxNotification(ctx context.Context, arg InsertInboxNotificationParams) (InboxNotification, error) { row := q.db.QueryRowContext(ctx, insertInboxNotification, arg.ID, arg.UserID, @@ -4525,7 +4525,7 @@ func (q *sqlQuerier) InsertInboxNotification(ctx context.Context, arg InsertInbo arg.Actions, arg.CreatedAt, ) - var i NotificationsInbox + var i InboxNotification err := row.Scan( &i.ID, &i.UserID, @@ -4543,7 +4543,7 @@ func (q *sqlQuerier) InsertInboxNotification(ctx context.Context, arg InsertInbo const setInboxNotificationAsRead = `-- name: SetInboxNotificationAsRead :exec UPDATE - notifications_inbox + inbox_notifications SET read_at = $1 WHERE diff --git a/coderd/database/queries/notificationsinbox.sql b/coderd/database/queries/notificationsinbox.sql index 8e7ae2c20d1d1..089d6c4b78a61 100644 --- a/coderd/database/queries/notificationsinbox.sql +++ b/coderd/database/queries/notificationsinbox.sql @@ -1,21 +1,21 @@ -- name: FetchUnreadInboxNotificationsByUserID :many -SELECT * FROM notifications_inbox WHERE user_id = $1 AND read_at IS NULL ORDER BY created_at DESC; +SELECT * FROM inbox_notifications WHERE user_id = $1 AND read_at IS NULL ORDER BY created_at DESC; -- name: FetchInboxNotificationsByUserID :many -SELECT * FROM notifications_inbox WHERE user_id = $1 ORDER BY created_at DESC; +SELECT * FROM inbox_notifications WHERE user_id = $1 ORDER BY created_at DESC; --- name: FetchInboxNotificationsByUserIDAndTemplateIDAndTargets :many -SELECT * FROM notifications_inbox WHERE user_id = $1 AND template_id = $2 AND targets @> COALESCE($3, ARRAY[]::UUID[]) ORDER BY created_at DESC; +-- name: FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many +SELECT * FROM inbox_notifications WHERE user_id = @user_id AND template_id = ANY(@templates::UUID[]) AND targets @> COALESCE(@targets, ARRAY[]::UUID[]) ORDER BY created_at DESC; --- name: FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets :many -SELECT * FROM notifications_inbox WHERE user_id = $1 AND template_id = $2 AND targets @> COALESCE($3, ARRAY[]::UUID[]) AND read_at IS NULL ORDER BY created_at DESC; +-- name: FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many +SELECT * FROM inbox_notifications WHERE user_id = @user_id AND template_id = ANY(@templates::UUID[]) AND targets @> COALESCE(@targets, ARRAY[]::UUID[]) AND read_at IS NULL ORDER BY created_at DESC; -- name: GetInboxNotificationByID :one -SELECT * FROM notifications_inbox WHERE id = $1; +SELECT * FROM inbox_notifications WHERE id = $1; -- name: InsertInboxNotification :one INSERT INTO - notifications_inbox ( + inbox_notifications ( id, user_id, template_id, @@ -31,7 +31,7 @@ VALUES -- name: SetInboxNotificationAsRead :exec UPDATE - notifications_inbox + inbox_notifications SET read_at = $1 WHERE diff --git a/coderd/database/unique_constraint.go b/coderd/database/unique_constraint.go index a4ad011a2150d..9f1d184493c70 100644 --- a/coderd/database/unique_constraint.go +++ b/coderd/database/unique_constraint.go @@ -21,6 +21,7 @@ const ( UniqueGroupMembersUserIDGroupIDKey UniqueConstraint = "group_members_user_id_group_id_key" // ALTER TABLE ONLY group_members ADD CONSTRAINT group_members_user_id_group_id_key UNIQUE (user_id, group_id); UniqueGroupsNameOrganizationIDKey UniqueConstraint = "groups_name_organization_id_key" // ALTER TABLE ONLY groups ADD CONSTRAINT groups_name_organization_id_key UNIQUE (name, organization_id); UniqueGroupsPkey UniqueConstraint = "groups_pkey" // ALTER TABLE ONLY groups ADD CONSTRAINT groups_pkey PRIMARY KEY (id); + UniqueInboxNotificationsPkey UniqueConstraint = "inbox_notifications_pkey" // ALTER TABLE ONLY inbox_notifications ADD CONSTRAINT inbox_notifications_pkey PRIMARY KEY (id); UniqueJfrogXrayScansPkey UniqueConstraint = "jfrog_xray_scans_pkey" // ALTER TABLE ONLY jfrog_xray_scans ADD CONSTRAINT jfrog_xray_scans_pkey PRIMARY KEY (agent_id, workspace_id); UniqueLicensesJWTKey UniqueConstraint = "licenses_jwt_key" // ALTER TABLE ONLY licenses ADD CONSTRAINT licenses_jwt_key UNIQUE (jwt); UniqueLicensesPkey UniqueConstraint = "licenses_pkey" // ALTER TABLE ONLY licenses ADD CONSTRAINT licenses_pkey PRIMARY KEY (id); @@ -29,7 +30,6 @@ const ( UniqueNotificationReportGeneratorLogsPkey UniqueConstraint = "notification_report_generator_logs_pkey" // ALTER TABLE ONLY notification_report_generator_logs ADD CONSTRAINT notification_report_generator_logs_pkey PRIMARY KEY (notification_template_id); UniqueNotificationTemplatesNameKey UniqueConstraint = "notification_templates_name_key" // ALTER TABLE ONLY notification_templates ADD CONSTRAINT notification_templates_name_key UNIQUE (name); UniqueNotificationTemplatesPkey UniqueConstraint = "notification_templates_pkey" // ALTER TABLE ONLY notification_templates ADD CONSTRAINT notification_templates_pkey PRIMARY KEY (id); - UniqueNotificationsInboxPkey UniqueConstraint = "notifications_inbox_pkey" // ALTER TABLE ONLY notifications_inbox ADD CONSTRAINT notifications_inbox_pkey PRIMARY KEY (id); UniqueOauth2ProviderAppCodesPkey UniqueConstraint = "oauth2_provider_app_codes_pkey" // ALTER TABLE ONLY oauth2_provider_app_codes ADD CONSTRAINT oauth2_provider_app_codes_pkey PRIMARY KEY (id); UniqueOauth2ProviderAppCodesSecretPrefixKey UniqueConstraint = "oauth2_provider_app_codes_secret_prefix_key" // ALTER TABLE ONLY oauth2_provider_app_codes ADD CONSTRAINT oauth2_provider_app_codes_secret_prefix_key UNIQUE (secret_prefix); UniqueOauth2ProviderAppSecretsPkey UniqueConstraint = "oauth2_provider_app_secrets_pkey" // ALTER TABLE ONLY oauth2_provider_app_secrets ADD CONSTRAINT oauth2_provider_app_secrets_pkey PRIMARY KEY (id); diff --git a/coderd/rbac/object_gen.go b/coderd/rbac/object_gen.go index c947829562d1a..a9ab9431994ef 100644 --- a/coderd/rbac/object_gen.go +++ b/coderd/rbac/object_gen.go @@ -120,6 +120,15 @@ var ( Type: "idpsync_settings", } + // ResourceInboxNotification + // Valid Actions + // - "ActionCreate" :: create inbox notifications + // - "ActionRead" :: read inbox notifications + // - "ActionUpdate" :: update inbox notifications + ResourceInboxNotification = Object{ + Type: "inbox_notification", + } + // ResourceLicense // Valid Actions // - "ActionCreate" :: create a license @@ -129,15 +138,6 @@ var ( Type: "license", } - // ResourceNotificationInbox - // Valid Actions - // - "ActionCreate" :: create notifications inbox - // - "ActionRead" :: read notifications inbox - // - "ActionUpdate" :: update notifications inbox - ResourceNotificationInbox = Object{ - Type: "notification_inbox", - } - // ResourceNotificationMessage // Valid Actions // - "ActionCreate" :: create notification messages @@ -353,8 +353,8 @@ func AllResources() []Objecter { ResourceGroup, ResourceGroupMember, ResourceIdpsyncSettings, + ResourceInboxNotification, ResourceLicense, - ResourceNotificationInbox, ResourceNotificationMessage, ResourceNotificationPreference, ResourceNotificationTemplate, diff --git a/coderd/rbac/policy/policy.go b/coderd/rbac/policy/policy.go index ee3995c5087ab..a3b9763bcdc24 100644 --- a/coderd/rbac/policy/policy.go +++ b/coderd/rbac/policy/policy.go @@ -287,11 +287,11 @@ var RBACPermissions = map[string]PermissionDefinition{ ActionUpdate: actDef("update notification preferences"), }, }, - "notification_inbox": { + "inbox_notification": { Actions: map[Action]ActionDefinition{ - ActionCreate: actDef("create notifications inbox"), - ActionRead: actDef("read notifications inbox"), - ActionUpdate: actDef("update notifications inbox"), + ActionCreate: actDef("create inbox notifications"), + ActionRead: actDef("read inbox notifications"), + ActionUpdate: actDef("update inbox notifications"), }, }, "crypto_key": { diff --git a/codersdk/rbacresources_gen.go b/codersdk/rbacresources_gen.go index 65a848caa6c27..db5b7fdf66089 100644 --- a/codersdk/rbacresources_gen.go +++ b/codersdk/rbacresources_gen.go @@ -17,8 +17,8 @@ const ( ResourceGroup RBACResource = "group" ResourceGroupMember RBACResource = "group_member" ResourceIdpsyncSettings RBACResource = "idpsync_settings" + ResourceInboxNotification RBACResource = "inbox_notification" ResourceLicense RBACResource = "license" - ResourceNotificationInbox RBACResource = "notification_inbox" ResourceNotificationMessage RBACResource = "notification_message" ResourceNotificationPreference RBACResource = "notification_preference" ResourceNotificationTemplate RBACResource = "notification_template" @@ -75,8 +75,8 @@ var RBACResourceActions = map[RBACResource][]RBACAction{ ResourceGroup: {ActionCreate, ActionDelete, ActionRead, ActionUpdate}, ResourceGroupMember: {ActionRead}, ResourceIdpsyncSettings: {ActionRead, ActionUpdate}, + ResourceInboxNotification: {ActionCreate, ActionRead, ActionUpdate}, ResourceLicense: {ActionCreate, ActionDelete, ActionRead}, - ResourceNotificationInbox: {ActionCreate, ActionRead, ActionUpdate}, ResourceNotificationMessage: {ActionCreate, ActionDelete, ActionRead, ActionUpdate}, ResourceNotificationPreference: {ActionRead, ActionUpdate}, ResourceNotificationTemplate: {ActionRead, ActionUpdate}, diff --git a/docs/reference/api/members.md b/docs/reference/api/members.md index 8251514e78dc2..bf266c6252039 100644 --- a/docs/reference/api/members.md +++ b/docs/reference/api/members.md @@ -192,8 +192,8 @@ Status Code **200** | `resource_type` | `group` | | `resource_type` | `group_member` | | `resource_type` | `idpsync_settings` | +| `resource_type` | `inbox_notification` | | `resource_type` | `license` | -| `resource_type` | `notification_inbox` | | `resource_type` | `notification_message` | | `resource_type` | `notification_preference` | | `resource_type` | `notification_template` | @@ -356,8 +356,8 @@ Status Code **200** | `resource_type` | `group` | | `resource_type` | `group_member` | | `resource_type` | `idpsync_settings` | +| `resource_type` | `inbox_notification` | | `resource_type` | `license` | -| `resource_type` | `notification_inbox` | | `resource_type` | `notification_message` | | `resource_type` | `notification_preference` | | `resource_type` | `notification_template` | @@ -520,8 +520,8 @@ Status Code **200** | `resource_type` | `group` | | `resource_type` | `group_member` | | `resource_type` | `idpsync_settings` | +| `resource_type` | `inbox_notification` | | `resource_type` | `license` | -| `resource_type` | `notification_inbox` | | `resource_type` | `notification_message` | | `resource_type` | `notification_preference` | | `resource_type` | `notification_template` | @@ -653,8 +653,8 @@ Status Code **200** | `resource_type` | `group` | | `resource_type` | `group_member` | | `resource_type` | `idpsync_settings` | +| `resource_type` | `inbox_notification` | | `resource_type` | `license` | -| `resource_type` | `notification_inbox` | | `resource_type` | `notification_message` | | `resource_type` | `notification_preference` | | `resource_type` | `notification_template` | @@ -918,8 +918,8 @@ Status Code **200** | `resource_type` | `group` | | `resource_type` | `group_member` | | `resource_type` | `idpsync_settings` | +| `resource_type` | `inbox_notification` | | `resource_type` | `license` | -| `resource_type` | `notification_inbox` | | `resource_type` | `notification_message` | | `resource_type` | `notification_preference` | | `resource_type` | `notification_template` | diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index a68e8dc059104..8b0cdd0c7815c 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -5105,8 +5105,8 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith | `group` | | `group_member` | | `idpsync_settings` | +| `inbox_notification` | | `license` | -| `notification_inbox` | | `notification_message` | | `notification_preference` | | `notification_template` | diff --git a/site/src/api/rbacresourcesGenerated.ts b/site/src/api/rbacresourcesGenerated.ts index 71ad1700111fe..f219807d5d813 100644 --- a/site/src/api/rbacresourcesGenerated.ts +++ b/site/src/api/rbacresourcesGenerated.ts @@ -65,16 +65,16 @@ export const RBACResourceActions: Partial< read: "read IdP sync settings", update: "update IdP sync settings", }, + inbox_notification: { + create: "create inbox notifications", + read: "read inbox notifications", + update: "update inbox notifications", + }, license: { create: "create a license", delete: "delete license", read: "read licenses", }, - notification_inbox: { - create: "create notifications inbox", - read: "read notifications inbox", - update: "update notifications inbox", - }, notification_message: { create: "create notification messages", delete: "delete notification messages", diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 9b8c1a2924fda..3673299a4567c 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1875,8 +1875,8 @@ export type RBACResource = | "group" | "group_member" | "idpsync_settings" + | "inbox_notification" | "license" - | "notification_inbox" | "notification_message" | "notification_preference" | "notification_template" @@ -1912,8 +1912,8 @@ export const RBACResources: RBACResource[] = [ "group", "group_member", "idpsync_settings", + "inbox_notification", "license", - "notification_inbox", "notification_message", "notification_preference", "notification_template", From 27c159281997b82ec09ed92225e49bfc0775c42e Mon Sep 17 00:00:00 2001 From: defelmnq Date: Thu, 20 Feb 2025 18:24:52 +0000 Subject: [PATCH 10/37] rename inbox notifications fix rbac roles --- coderd/database/dbauthz/dbauthz_test.go | 31 +++++++++++++++++-- .../000296_notifications_inbox.down.sql | 2 +- .../000296_notifications_inbox.up.sql | 25 +++++++++++++++ coderd/rbac/roles_test.go | 11 +++++++ 4 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 coderd/database/migrations/testdata/fixtures/000296_notifications_inbox.up.sql diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index fbba2a3f824a5..cdeeea02cef3c 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -4495,6 +4495,7 @@ func (s *MethodTestSuite) TestNotifications() { Title: "test title", Content: "test content notification", Icon: "test icon", + Actions: json.RawMessage("{}"), }) check.Args(u.ID).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif}) @@ -4517,6 +4518,7 @@ func (s *MethodTestSuite) TestNotifications() { Title: "test title", Content: "test content notification", Icon: "test icon", + Actions: json.RawMessage("{}"), }) check.Args(u.ID).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif}) @@ -4542,6 +4544,7 @@ func (s *MethodTestSuite) TestNotifications() { Title: "test title", Content: "test content notification", Icon: "test icon", + Actions: json.RawMessage("{}"), }) check.Args(database.FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams{ @@ -4571,6 +4574,7 @@ func (s *MethodTestSuite) TestNotifications() { Title: "test title", Content: "test content notification", Icon: "test icon", + Actions: json.RawMessage("{}"), }) check.Args(database.FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams{ @@ -4600,14 +4604,34 @@ func (s *MethodTestSuite) TestNotifications() { Title: "test title", Content: "test content notification", Icon: "test icon", + Actions: json.RawMessage("{}"), }) check.Args(notifID).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns(notif) })) - s.Run("InsertInboxNotification", s.Subtest(func(_ database.Store, check *expects) { - owner := uuid.UUID{} - check.Args(database.InsertInboxNotificationParams{}).Asserts(rbac.ResourceInboxNotification.WithOwner(owner.String()), policy.ActionCreate) + s.Run("InsertInboxNotification", s.Subtest(func(db database.Store, check *expects) { + u := dbgen.User(s.T(), db, database.User{}) + o := dbgen.Organization(s.T(), db, database.Organization{}) + tpl := dbgen.Template(s.T(), db, database.Template{ + OrganizationID: o.ID, + CreatedBy: u.ID, + }) + + notifID := uuid.New() + + targets := []uuid.UUID{u.ID, tpl.ID} + + check.Args(database.InsertInboxNotificationParams{ + ID: notifID, + UserID: u.ID, + TemplateID: tpl.ID, + Targets: targets, + Title: "test title", + Content: "test content notification", + Icon: "test icon", + Actions: json.RawMessage("{}"), + }).Asserts(rbac.ResourceInboxNotification.WithOwner(u.ID.String()), policy.ActionCreate) })) s.Run("SetInboxNotificationAsRead", s.Subtest(func(db database.Store, check *expects) { @@ -4631,6 +4655,7 @@ func (s *MethodTestSuite) TestNotifications() { Title: "test title", Content: "test content notification", Icon: "test icon", + Actions: json.RawMessage("{}"), }) notif.ReadAt = sql.NullTime{Time: readAt, Valid: true} diff --git a/coderd/database/migrations/000296_notifications_inbox.down.sql b/coderd/database/migrations/000296_notifications_inbox.down.sql index 21a6935b2bb8d..803d67939a5b4 100644 --- a/coderd/database/migrations/000296_notifications_inbox.down.sql +++ b/coderd/database/migrations/000296_notifications_inbox.down.sql @@ -1 +1 @@ -DROP TABLE IF EXISTS notifications_inbox; +DROP TABLE IF EXISTS inbox_notifications; diff --git a/coderd/database/migrations/testdata/fixtures/000296_notifications_inbox.up.sql b/coderd/database/migrations/testdata/fixtures/000296_notifications_inbox.up.sql new file mode 100644 index 0000000000000..a4532221dd603 --- /dev/null +++ b/coderd/database/migrations/testdata/fixtures/000296_notifications_inbox.up.sql @@ -0,0 +1,25 @@ +INSERT INTO + inbox_notifications ( + id, + user_id, + template_id, + targets, + title, + content, + icon, + actions, + read_at, + created_at + ) + VALUES ( + '68b396aa-7f53-4bf1-b8d8-4cbf5fa244e5', -- uuid + '45e89705-e09d-4850-bcec-f9a937f5d78d', -- uuid + '193590e9-918f-4ef9-be47-04625f49c4c3', -- uuid + ARRAY[]::UUID[], -- uuid[] + 'Test Notification', + 'This is a test notification', + 'https://test.coder.com/favicon.ico', + '{}', + '2024-01-01 00:00:00', + '2024-01-01 00:00:00' + ); diff --git a/coderd/rbac/roles_test.go b/coderd/rbac/roles_test.go index cb43b1b1751d6..342002e0c448f 100644 --- a/coderd/rbac/roles_test.go +++ b/coderd/rbac/roles_test.go @@ -354,6 +354,17 @@ func TestRolePermissions(t *testing.T) { false: {setOtherOrg, setOrgNotMe, templateAdmin, userAdmin}, }, }, + { + Name: "InboxNotification", + Actions: []policy.Action{ + policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, + }, + Resource: rbac.ResourceInboxNotification.WithID(uuid.New()).InOrg(orgID).WithOwner(currentUser.String()), + AuthorizeMap: map[bool][]hasAuthSubjects{ + true: {owner, orgMemberMe, orgAdmin}, + false: {setOtherOrg, orgUserAdmin, orgTemplateAdmin, orgAuditor, templateAdmin, userAdmin, memberMe}, + }, + }, { Name: "UserData", Actions: []policy.Action{policy.ActionReadPersonal, policy.ActionUpdatePersonal}, From 08c55c3fa835e6e7ba11da15ac13406941c4d11e Mon Sep 17 00:00:00 2001 From: defelmnq Date: Fri, 21 Feb 2025 06:31:22 +0000 Subject: [PATCH 11/37] Improve authz --- coderd/database/dbauthz/dbauthz.go | 1 + .../testdata/fixtures/000296_notifications_inbox.up.sql | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 3727bc06410f5..45ff59e41bfc2 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -281,6 +281,7 @@ var ( DisplayName: "Notifier", Site: rbac.Permissions(map[string][]policy.Action{ rbac.ResourceNotificationMessage.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete}, + rbac.ResourceInboxNotification.Type: {policy.ActionCreate}, }), Org: map[string][]rbac.Permission{}, User: []rbac.Permission{}, diff --git a/coderd/database/migrations/testdata/fixtures/000296_notifications_inbox.up.sql b/coderd/database/migrations/testdata/fixtures/000296_notifications_inbox.up.sql index a4532221dd603..2533e7ee38b80 100644 --- a/coderd/database/migrations/testdata/fixtures/000296_notifications_inbox.up.sql +++ b/coderd/database/migrations/testdata/fixtures/000296_notifications_inbox.up.sql @@ -20,6 +20,6 @@ INSERT INTO 'This is a test notification', 'https://test.coder.com/favicon.ico', '{}', - '2024-01-01 00:00:00', - '2024-01-01 00:00:00' + '2025-01-01 00:00:00', + '2025-01-01 00:00:00' ); From b44144825780c35cec1a1add22465b936bd3c3ac Mon Sep 17 00:00:00 2001 From: defelmnq Date: Fri, 21 Feb 2025 06:52:53 +0000 Subject: [PATCH 12/37] add references in db --- coderd/database/migrations/000296_notifications_inbox.up.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coderd/database/migrations/000296_notifications_inbox.up.sql b/coderd/database/migrations/000296_notifications_inbox.up.sql index a8034a9a8b93e..d1369cced76b7 100644 --- a/coderd/database/migrations/000296_notifications_inbox.up.sql +++ b/coderd/database/migrations/000296_notifications_inbox.up.sql @@ -1,7 +1,7 @@ CREATE TABLE inbox_notifications ( id UUID PRIMARY KEY, - user_id UUID NOT NULL, - template_id UUID NOT NULL, + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + template_id UUID NOT NULL REFERENCES notification_templates(id) ON DELETE CASCADE, targets UUID[], title TEXT NOT NULL, content TEXT NOT NULL, From 1814c58e080b61917a3c4abd2f264187a780c1ae Mon Sep 17 00:00:00 2001 From: defelmnq Date: Fri, 21 Feb 2025 06:58:08 +0000 Subject: [PATCH 13/37] fix test icon --- coderd/database/dbauthz/dbauthz_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index cdeeea02cef3c..8429bad3663d7 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -4494,7 +4494,7 @@ func (s *MethodTestSuite) TestNotifications() { TemplateID: tpl.ID, Title: "test title", Content: "test content notification", - Icon: "test icon", + Icon: "https://coder.com/favicon.ico", Actions: json.RawMessage("{}"), }) @@ -4517,7 +4517,7 @@ func (s *MethodTestSuite) TestNotifications() { TemplateID: tpl.ID, Title: "test title", Content: "test content notification", - Icon: "test icon", + Icon: "https://coder.com/favicon.ico", Actions: json.RawMessage("{}"), }) @@ -4543,7 +4543,7 @@ func (s *MethodTestSuite) TestNotifications() { Targets: targets, Title: "test title", Content: "test content notification", - Icon: "test icon", + Icon: "https://coder.com/favicon.ico", Actions: json.RawMessage("{}"), }) @@ -4573,7 +4573,7 @@ func (s *MethodTestSuite) TestNotifications() { Targets: targets, Title: "test title", Content: "test content notification", - Icon: "test icon", + Icon: "https://coder.com/favicon.ico", Actions: json.RawMessage("{}"), }) @@ -4603,7 +4603,7 @@ func (s *MethodTestSuite) TestNotifications() { Targets: targets, Title: "test title", Content: "test content notification", - Icon: "test icon", + Icon: "https://coder.com/favicon.ico", Actions: json.RawMessage("{}"), }) @@ -4629,7 +4629,7 @@ func (s *MethodTestSuite) TestNotifications() { Targets: targets, Title: "test title", Content: "test content notification", - Icon: "test icon", + Icon: "https://coder.com/favicon.ico", Actions: json.RawMessage("{}"), }).Asserts(rbac.ResourceInboxNotification.WithOwner(u.ID.String()), policy.ActionCreate) })) @@ -4654,7 +4654,7 @@ func (s *MethodTestSuite) TestNotifications() { Targets: targets, Title: "test title", Content: "test content notification", - Icon: "test icon", + Icon: "https://coder.com/favicon.ico", Actions: json.RawMessage("{}"), }) From 9f46f51d92af419cf57d2203ad6b54d2fe5095da Mon Sep 17 00:00:00 2001 From: defelmnq Date: Fri, 21 Feb 2025 07:00:12 +0000 Subject: [PATCH 14/37] improve foreign keys --- coderd/database/dump.sql | 6 + coderd/database/foreign_key_constraint.go | 2 + coderd/database/querier.go | 581 ---------------------- 3 files changed, 8 insertions(+), 581 deletions(-) delete mode 100644 coderd/database/querier.go diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 5d41098f77afd..07b3ec35f4de0 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -2428,6 +2428,12 @@ ALTER TABLE ONLY group_members ALTER TABLE ONLY groups ADD CONSTRAINT groups_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; +ALTER TABLE ONLY inbox_notifications + ADD CONSTRAINT inbox_notifications_template_id_fkey FOREIGN KEY (template_id) REFERENCES notification_templates(id) ON DELETE CASCADE; + +ALTER TABLE ONLY inbox_notifications + ADD CONSTRAINT inbox_notifications_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; + ALTER TABLE ONLY jfrog_xray_scans ADD CONSTRAINT jfrog_xray_scans_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE; diff --git a/coderd/database/foreign_key_constraint.go b/coderd/database/foreign_key_constraint.go index 66c379a749e01..525d240f25267 100644 --- a/coderd/database/foreign_key_constraint.go +++ b/coderd/database/foreign_key_constraint.go @@ -14,6 +14,8 @@ const ( ForeignKeyGroupMembersGroupID ForeignKeyConstraint = "group_members_group_id_fkey" // ALTER TABLE ONLY group_members ADD CONSTRAINT group_members_group_id_fkey FOREIGN KEY (group_id) REFERENCES groups(id) ON DELETE CASCADE; ForeignKeyGroupMembersUserID ForeignKeyConstraint = "group_members_user_id_fkey" // ALTER TABLE ONLY group_members ADD CONSTRAINT group_members_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; ForeignKeyGroupsOrganizationID ForeignKeyConstraint = "groups_organization_id_fkey" // ALTER TABLE ONLY groups ADD CONSTRAINT groups_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; + ForeignKeyInboxNotificationsTemplateID ForeignKeyConstraint = "inbox_notifications_template_id_fkey" // ALTER TABLE ONLY inbox_notifications ADD CONSTRAINT inbox_notifications_template_id_fkey FOREIGN KEY (template_id) REFERENCES notification_templates(id) ON DELETE CASCADE; + ForeignKeyInboxNotificationsUserID ForeignKeyConstraint = "inbox_notifications_user_id_fkey" // ALTER TABLE ONLY inbox_notifications ADD CONSTRAINT inbox_notifications_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; ForeignKeyJfrogXrayScansAgentID ForeignKeyConstraint = "jfrog_xray_scans_agent_id_fkey" // ALTER TABLE ONLY jfrog_xray_scans ADD CONSTRAINT jfrog_xray_scans_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE; ForeignKeyJfrogXrayScansWorkspaceID ForeignKeyConstraint = "jfrog_xray_scans_workspace_id_fkey" // ALTER TABLE ONLY jfrog_xray_scans ADD CONSTRAINT jfrog_xray_scans_workspace_id_fkey FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE; ForeignKeyNotificationMessagesNotificationTemplateID ForeignKeyConstraint = "notification_messages_notification_template_id_fkey" // ALTER TABLE ONLY notification_messages ADD CONSTRAINT notification_messages_notification_template_id_fkey FOREIGN KEY (notification_template_id) REFERENCES notification_templates(id) ON DELETE CASCADE; diff --git a/coderd/database/querier.go b/coderd/database/querier.go deleted file mode 100644 index 7236e75141e92..0000000000000 --- a/coderd/database/querier.go +++ /dev/null @@ -1,581 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.27.0 - -package database - -import ( - "context" - "time" - - "github.com/google/uuid" -) - -type sqlcQuerier interface { - // Blocks until the lock is acquired. - // - // This must be called from within a transaction. The lock will be automatically - // released when the transaction ends. - AcquireLock(ctx context.Context, pgAdvisoryXactLock int64) error - // Acquires the lease for a given count of notification messages, to enable concurrent dequeuing and subsequent sending. - // Only rows that aren't already leased (or ones which are leased but have exceeded their lease period) are returned. - // - // A "lease" here refers to a notifier taking ownership of a notification_messages row. A lease survives for the duration - // of CODER_NOTIFICATIONS_LEASE_PERIOD. Once a message is delivered, its status is updated and the lease expires (set to NULL). - // If a message exceeds its lease, that implies the notifier did not shutdown cleanly, or the table update failed somehow, - // and the row will then be eligible to be dequeued by another notifier. - // - // SKIP LOCKED is used to jump over locked rows. This prevents multiple notifiers from acquiring the same messages. - // See: https://www.postgresql.org/docs/9.5/sql-select.html#SQL-FOR-UPDATE-SHARE - // - AcquireNotificationMessages(ctx context.Context, arg AcquireNotificationMessagesParams) ([]AcquireNotificationMessagesRow, error) - // Acquires the lock for a single job that isn't started, completed, - // canceled, and that matches an array of provisioner types. - // - // SKIP LOCKED is used to jump over locked rows. This prevents - // multiple provisioners from acquiring the same jobs. See: - // https://www.postgresql.org/docs/9.5/sql-select.html#SQL-FOR-UPDATE-SHARE - AcquireProvisionerJob(ctx context.Context, arg AcquireProvisionerJobParams) (ProvisionerJob, error) - // Bumps the workspace deadline by the template's configured "activity_bump" - // duration (default 1h). If the workspace bump will cross an autostart - // threshold, then the bump is autostart + TTL. This is the deadline behavior if - // the workspace was to autostart from a stopped state. - // - // Max deadline is respected, and the deadline will never be bumped past it. - // The deadline will never decrease. - // We only bump if the template has an activity bump duration set. - // We only bump if the raw interval is positive and non-zero. - // We only bump if workspace shutdown is manual. - // We only bump when 5% of the deadline has elapsed. - ActivityBumpWorkspace(ctx context.Context, arg ActivityBumpWorkspaceParams) error - // AllUserIDs returns all UserIDs regardless of user status or deletion. - AllUserIDs(ctx context.Context) ([]uuid.UUID, error) - // Archiving templates is a soft delete action, so is reversible. - // Archiving prevents the version from being used and discovered - // by listing. - // Only unused template versions will be archived, which are any versions not - // referenced by the latest build of a workspace. - ArchiveUnusedTemplateVersions(ctx context.Context, arg ArchiveUnusedTemplateVersionsParams) ([]uuid.UUID, error) - BatchUpdateWorkspaceLastUsedAt(ctx context.Context, arg BatchUpdateWorkspaceLastUsedAtParams) error - BatchUpdateWorkspaceNextStartAt(ctx context.Context, arg BatchUpdateWorkspaceNextStartAtParams) error - BulkMarkNotificationMessagesFailed(ctx context.Context, arg BulkMarkNotificationMessagesFailedParams) (int64, error) - BulkMarkNotificationMessagesSent(ctx context.Context, arg BulkMarkNotificationMessagesSentParams) (int64, error) - CleanTailnetCoordinators(ctx context.Context) error - CleanTailnetLostPeers(ctx context.Context) error - CleanTailnetTunnels(ctx context.Context) error - CustomRoles(ctx context.Context, arg CustomRolesParams) ([]CustomRole, error) - DeleteAPIKeyByID(ctx context.Context, id string) error - DeleteAPIKeysByUserID(ctx context.Context, userID uuid.UUID) error - DeleteAllTailnetClientSubscriptions(ctx context.Context, arg DeleteAllTailnetClientSubscriptionsParams) error - DeleteAllTailnetTunnels(ctx context.Context, arg DeleteAllTailnetTunnelsParams) 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) - DeleteCustomRole(ctx context.Context, arg DeleteCustomRoleParams) error - DeleteExternalAuthLink(ctx context.Context, arg DeleteExternalAuthLinkParams) error - DeleteGitSSHKey(ctx context.Context, userID uuid.UUID) error - DeleteGroupByID(ctx context.Context, id uuid.UUID) error - DeleteGroupMemberFromGroup(ctx context.Context, arg DeleteGroupMemberFromGroupParams) error - DeleteLicense(ctx context.Context, id int32) (int32, error) - DeleteOAuth2ProviderAppByID(ctx context.Context, id uuid.UUID) error - DeleteOAuth2ProviderAppCodeByID(ctx context.Context, id uuid.UUID) error - DeleteOAuth2ProviderAppCodesByAppAndUserID(ctx context.Context, arg DeleteOAuth2ProviderAppCodesByAppAndUserIDParams) error - DeleteOAuth2ProviderAppSecretByID(ctx context.Context, id uuid.UUID) error - DeleteOAuth2ProviderAppTokensByAppAndUserID(ctx context.Context, arg DeleteOAuth2ProviderAppTokensByAppAndUserIDParams) error - // Delete all notification messages which have not been updated for over a week. - DeleteOldNotificationMessages(ctx context.Context) error - // Delete provisioner daemons that have been created at least a week ago - // and have not connected to coderd since a week. - // A provisioner daemon with "zeroed" last_seen_at column indicates possible - // connectivity issues (no provisioner daemon activity since registration). - DeleteOldProvisionerDaemons(ctx context.Context) error - // If an agent hasn't connected in the last 7 days, we purge it's logs. - // Exception: if the logs are related to the latest build, we keep those around. - // Logs can take up a lot of space, so it's important we clean up frequently. - DeleteOldWorkspaceAgentLogs(ctx context.Context, threshold time.Time) error - DeleteOldWorkspaceAgentStats(ctx context.Context) error - DeleteOrganization(ctx context.Context, id uuid.UUID) error - DeleteOrganizationMember(ctx context.Context, arg DeleteOrganizationMemberParams) error - DeleteProvisionerKey(ctx context.Context, id uuid.UUID) error - DeleteReplicasUpdatedBefore(ctx context.Context, updatedAt time.Time) error - DeleteRuntimeConfig(ctx context.Context, key string) error - DeleteTailnetAgent(ctx context.Context, arg DeleteTailnetAgentParams) (DeleteTailnetAgentRow, error) - DeleteTailnetClient(ctx context.Context, arg DeleteTailnetClientParams) (DeleteTailnetClientRow, error) - DeleteTailnetClientSubscription(ctx context.Context, arg DeleteTailnetClientSubscriptionParams) error - DeleteTailnetPeer(ctx context.Context, arg DeleteTailnetPeerParams) (DeleteTailnetPeerRow, error) - DeleteTailnetTunnel(ctx context.Context, arg DeleteTailnetTunnelParams) (DeleteTailnetTunnelRow, error) - DeleteWorkspaceAgentPortShare(ctx context.Context, arg DeleteWorkspaceAgentPortShareParams) error - DeleteWorkspaceAgentPortSharesByTemplate(ctx context.Context, templateID uuid.UUID) error - // Disable foreign keys and triggers for all tables. - // Deprecated: disable foreign keys was created to aid in migrating off - // of the test-only in-memory database. Do not use this in new code. - DisableForeignKeysAndTriggers(ctx context.Context) error - EnqueueNotificationMessage(ctx context.Context, arg EnqueueNotificationMessageParams) error - FavoriteWorkspace(ctx context.Context, id uuid.UUID) error - FetchInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]InboxNotification, error) - FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]InboxNotification, error) - FetchMemoryResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) (WorkspaceAgentMemoryResourceMonitor, error) - // This is used to build up the notification_message's JSON payload. - FetchNewMessageMetadata(ctx context.Context, arg FetchNewMessageMetadataParams) (FetchNewMessageMetadataRow, error) - FetchUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]InboxNotification, error) - FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]InboxNotification, error) - FetchVolumesResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) ([]WorkspaceAgentVolumeResourceMonitor, error) - GetAPIKeyByID(ctx context.Context, id string) (APIKey, error) - // there is no unique constraint on empty token names - GetAPIKeyByName(ctx context.Context, arg GetAPIKeyByNameParams) (APIKey, error) - GetAPIKeysByLoginType(ctx context.Context, loginType LoginType) ([]APIKey, error) - GetAPIKeysByUserID(ctx context.Context, arg GetAPIKeysByUserIDParams) ([]APIKey, error) - GetAPIKeysLastUsedAfter(ctx context.Context, lastUsed time.Time) ([]APIKey, error) - GetActiveUserCount(ctx context.Context) (int64, error) - GetActiveWorkspaceBuildsByTemplateID(ctx context.Context, templateID uuid.UUID) ([]WorkspaceBuild, error) - GetAllTailnetAgents(ctx context.Context) ([]TailnetAgent, error) - // For PG Coordinator HTMLDebug - GetAllTailnetCoordinators(ctx context.Context) ([]TailnetCoordinator, error) - GetAllTailnetPeers(ctx context.Context) ([]TailnetPeer, error) - GetAllTailnetTunnels(ctx context.Context) ([]TailnetTunnel, error) - GetAnnouncementBanners(ctx context.Context) (string, error) - GetAppSecurityKey(ctx context.Context) (string, error) - GetApplicationName(ctx context.Context) (string, error) - // GetAuditLogsBefore retrieves `row_limit` number of audit logs before the provided - // ID. - GetAuditLogsOffset(ctx context.Context, arg GetAuditLogsOffsetParams) ([]GetAuditLogsOffsetRow, error) - // This function returns roles for authorization purposes. Implied member roles - // are included. - GetAuthorizationUserRoles(ctx context.Context, userID uuid.UUID) (GetAuthorizationUserRolesRow, error) - GetCoordinatorResumeTokenSigningKey(ctx context.Context) (string, error) - GetCryptoKeyByFeatureAndSequence(ctx context.Context, arg GetCryptoKeyByFeatureAndSequenceParams) (CryptoKey, error) - GetCryptoKeys(ctx context.Context) ([]CryptoKey, error) - GetCryptoKeysByFeature(ctx context.Context, feature CryptoKeyFeature) ([]CryptoKey, error) - GetDBCryptKeys(ctx context.Context) ([]DBCryptKey, error) - GetDERPMeshKey(ctx context.Context) (string, error) - GetDefaultOrganization(ctx context.Context) (Organization, error) - GetDefaultProxyConfig(ctx context.Context) (GetDefaultProxyConfigRow, error) - GetDeploymentDAUs(ctx context.Context, tzOffset int32) ([]GetDeploymentDAUsRow, error) - GetDeploymentID(ctx context.Context) (string, error) - GetDeploymentWorkspaceAgentStats(ctx context.Context, createdAt time.Time) (GetDeploymentWorkspaceAgentStatsRow, error) - GetDeploymentWorkspaceAgentUsageStats(ctx context.Context, createdAt time.Time) (GetDeploymentWorkspaceAgentUsageStatsRow, error) - GetDeploymentWorkspaceStats(ctx context.Context) (GetDeploymentWorkspaceStatsRow, error) - GetEligibleProvisionerDaemonsByProvisionerJobIDs(ctx context.Context, provisionerJobIds []uuid.UUID) ([]GetEligibleProvisionerDaemonsByProvisionerJobIDsRow, error) - GetExternalAuthLink(ctx context.Context, arg GetExternalAuthLinkParams) (ExternalAuthLink, error) - GetExternalAuthLinksByUserID(ctx context.Context, userID uuid.UUID) ([]ExternalAuthLink, error) - GetFailedWorkspaceBuildsByTemplateID(ctx context.Context, arg GetFailedWorkspaceBuildsByTemplateIDParams) ([]GetFailedWorkspaceBuildsByTemplateIDRow, error) - GetFileByHashAndCreator(ctx context.Context, arg GetFileByHashAndCreatorParams) (File, error) - GetFileByID(ctx context.Context, id uuid.UUID) (File, error) - // Get all templates that use a file. - GetFileTemplates(ctx context.Context, fileID uuid.UUID) ([]GetFileTemplatesRow, error) - GetGitSSHKey(ctx context.Context, userID uuid.UUID) (GitSSHKey, error) - GetGroupByID(ctx context.Context, id uuid.UUID) (Group, error) - GetGroupByOrgAndName(ctx context.Context, arg GetGroupByOrgAndNameParams) (Group, error) - GetGroupMembers(ctx context.Context) ([]GroupMember, error) - GetGroupMembersByGroupID(ctx context.Context, groupID uuid.UUID) ([]GroupMember, error) - // Returns the total count of members in a group. Shows the total - // count even if the caller does not have read access to ResourceGroupMember. - // They only need ResourceGroup read access. - GetGroupMembersCountByGroupID(ctx context.Context, groupID uuid.UUID) (int64, error) - GetGroups(ctx context.Context, arg GetGroupsParams) ([]GetGroupsRow, error) - GetHealthSettings(ctx context.Context) (string, error) - GetHungProvisionerJobs(ctx context.Context, updatedAt time.Time) ([]ProvisionerJob, error) - GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (InboxNotification, error) - GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg GetJFrogXrayScanByWorkspaceAndAgentIDParams) (JfrogXrayScan, error) - GetLastUpdateCheck(ctx context.Context) (string, error) - GetLatestCryptoKeyByFeature(ctx context.Context, feature CryptoKeyFeature) (CryptoKey, error) - GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (WorkspaceBuild, error) - GetLatestWorkspaceBuilds(ctx context.Context) ([]WorkspaceBuild, error) - GetLatestWorkspaceBuildsByWorkspaceIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceBuild, error) - GetLicenseByID(ctx context.Context, id int32) (License, error) - GetLicenses(ctx context.Context) ([]License, error) - GetLogoURL(ctx context.Context) (string, error) - GetNotificationMessagesByStatus(ctx context.Context, arg GetNotificationMessagesByStatusParams) ([]NotificationMessage, error) - // Fetch the notification report generator log indicating recent activity. - GetNotificationReportGeneratorLogByTemplate(ctx context.Context, templateID uuid.UUID) (NotificationReportGeneratorLog, error) - GetNotificationTemplateByID(ctx context.Context, id uuid.UUID) (NotificationTemplate, error) - GetNotificationTemplatesByKind(ctx context.Context, kind NotificationTemplateKind) ([]NotificationTemplate, error) - GetNotificationsSettings(ctx context.Context) (string, error) - GetOAuth2ProviderAppByID(ctx context.Context, id uuid.UUID) (OAuth2ProviderApp, error) - GetOAuth2ProviderAppCodeByID(ctx context.Context, id uuid.UUID) (OAuth2ProviderAppCode, error) - GetOAuth2ProviderAppCodeByPrefix(ctx context.Context, secretPrefix []byte) (OAuth2ProviderAppCode, error) - GetOAuth2ProviderAppSecretByID(ctx context.Context, id uuid.UUID) (OAuth2ProviderAppSecret, error) - GetOAuth2ProviderAppSecretByPrefix(ctx context.Context, secretPrefix []byte) (OAuth2ProviderAppSecret, error) - GetOAuth2ProviderAppSecretsByAppID(ctx context.Context, appID uuid.UUID) ([]OAuth2ProviderAppSecret, error) - GetOAuth2ProviderAppTokenByPrefix(ctx context.Context, hashPrefix []byte) (OAuth2ProviderAppToken, error) - GetOAuth2ProviderApps(ctx context.Context) ([]OAuth2ProviderApp, error) - GetOAuth2ProviderAppsByUserID(ctx context.Context, userID uuid.UUID) ([]GetOAuth2ProviderAppsByUserIDRow, error) - GetOAuthSigningKey(ctx context.Context) (string, error) - GetOrganizationByID(ctx context.Context, id uuid.UUID) (Organization, error) - GetOrganizationByName(ctx context.Context, name string) (Organization, error) - GetOrganizationIDsByMemberIDs(ctx context.Context, ids []uuid.UUID) ([]GetOrganizationIDsByMemberIDsRow, error) - GetOrganizations(ctx context.Context, arg GetOrganizationsParams) ([]Organization, error) - GetOrganizationsByUserID(ctx context.Context, userID uuid.UUID) ([]Organization, error) - GetParameterSchemasByJobID(ctx context.Context, jobID uuid.UUID) ([]ParameterSchema, error) - GetPresetByWorkspaceBuildID(ctx context.Context, workspaceBuildID uuid.UUID) (TemplateVersionPreset, error) - GetPresetParametersByTemplateVersionID(ctx context.Context, templateVersionID uuid.UUID) ([]TemplateVersionPresetParameter, error) - GetPresetsByTemplateVersionID(ctx context.Context, templateVersionID uuid.UUID) ([]TemplateVersionPreset, error) - GetPreviousTemplateVersion(ctx context.Context, arg GetPreviousTemplateVersionParams) (TemplateVersion, error) - GetProvisionerDaemons(ctx context.Context) ([]ProvisionerDaemon, error) - GetProvisionerDaemonsByOrganization(ctx context.Context, arg GetProvisionerDaemonsByOrganizationParams) ([]ProvisionerDaemon, error) - // Current job information. - // Previous job information. - GetProvisionerDaemonsWithStatusByOrganization(ctx context.Context, arg GetProvisionerDaemonsWithStatusByOrganizationParams) ([]GetProvisionerDaemonsWithStatusByOrganizationRow, error) - GetProvisionerJobByID(ctx context.Context, id uuid.UUID) (ProvisionerJob, error) - GetProvisionerJobTimingsByJobID(ctx context.Context, jobID uuid.UUID) ([]ProvisionerJobTiming, error) - GetProvisionerJobsByIDs(ctx context.Context, ids []uuid.UUID) ([]ProvisionerJob, error) - GetProvisionerJobsByIDsWithQueuePosition(ctx context.Context, ids []uuid.UUID) ([]GetProvisionerJobsByIDsWithQueuePositionRow, error) - GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisioner(ctx context.Context, arg GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisionerParams) ([]GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisionerRow, error) - GetProvisionerJobsCreatedAfter(ctx context.Context, createdAt time.Time) ([]ProvisionerJob, error) - GetProvisionerKeyByHashedSecret(ctx context.Context, hashedSecret []byte) (ProvisionerKey, error) - GetProvisionerKeyByID(ctx context.Context, id uuid.UUID) (ProvisionerKey, error) - GetProvisionerKeyByName(ctx context.Context, arg GetProvisionerKeyByNameParams) (ProvisionerKey, error) - GetProvisionerLogsAfterID(ctx context.Context, arg GetProvisionerLogsAfterIDParams) ([]ProvisionerJobLog, error) - GetQuotaAllowanceForUser(ctx context.Context, arg GetQuotaAllowanceForUserParams) (int64, error) - GetQuotaConsumedForUser(ctx context.Context, arg GetQuotaConsumedForUserParams) (int64, error) - GetReplicaByID(ctx context.Context, id uuid.UUID) (Replica, error) - GetReplicasUpdatedAfter(ctx context.Context, updatedAt time.Time) ([]Replica, error) - GetRuntimeConfig(ctx context.Context, key string) (string, error) - GetTailnetAgents(ctx context.Context, id uuid.UUID) ([]TailnetAgent, error) - GetTailnetClientsForAgent(ctx context.Context, agentID uuid.UUID) ([]TailnetClient, error) - GetTailnetPeers(ctx context.Context, id uuid.UUID) ([]TailnetPeer, error) - GetTailnetTunnelPeerBindings(ctx context.Context, srcID uuid.UUID) ([]GetTailnetTunnelPeerBindingsRow, error) - GetTailnetTunnelPeerIDs(ctx context.Context, srcID uuid.UUID) ([]GetTailnetTunnelPeerIDsRow, error) - GetTelemetryItem(ctx context.Context, key string) (TelemetryItem, error) - GetTelemetryItems(ctx context.Context) ([]TelemetryItem, error) - // GetTemplateAppInsights returns the aggregate usage of each app in a given - // timeframe. The result can be filtered on template_ids, meaning only user data - // from workspaces based on those templates will be included. - GetTemplateAppInsights(ctx context.Context, arg GetTemplateAppInsightsParams) ([]GetTemplateAppInsightsRow, error) - // GetTemplateAppInsightsByTemplate is used for Prometheus metrics. Keep - // in sync with GetTemplateAppInsights and UpsertTemplateUsageStats. - GetTemplateAppInsightsByTemplate(ctx context.Context, arg GetTemplateAppInsightsByTemplateParams) ([]GetTemplateAppInsightsByTemplateRow, error) - GetTemplateAverageBuildTime(ctx context.Context, arg GetTemplateAverageBuildTimeParams) (GetTemplateAverageBuildTimeRow, error) - GetTemplateByID(ctx context.Context, id uuid.UUID) (Template, error) - GetTemplateByOrganizationAndName(ctx context.Context, arg GetTemplateByOrganizationAndNameParams) (Template, error) - GetTemplateDAUs(ctx context.Context, arg GetTemplateDAUsParams) ([]GetTemplateDAUsRow, error) - // GetTemplateInsights returns the aggregate user-produced usage of all - // workspaces in a given timeframe. The template IDs, active users, and - // usage_seconds all reflect any usage in the template, including apps. - // - // When combining data from multiple templates, we must make a guess at - // how the user behaved for the 30 minute interval. In this case we make - // the assumption that if the user used two workspaces for 15 minutes, - // they did so sequentially, thus we sum the usage up to a maximum of - // 30 minutes with LEAST(SUM(n), 30). - GetTemplateInsights(ctx context.Context, arg GetTemplateInsightsParams) (GetTemplateInsightsRow, error) - // GetTemplateInsightsByInterval returns all intervals between start and end - // time, if end time is a partial interval, it will be included in the results and - // that interval will be shorter than a full one. If there is no data for a selected - // interval/template, it will be included in the results with 0 active users. - GetTemplateInsightsByInterval(ctx context.Context, arg GetTemplateInsightsByIntervalParams) ([]GetTemplateInsightsByIntervalRow, error) - // GetTemplateInsightsByTemplate is used for Prometheus metrics. Keep - // in sync with GetTemplateInsights and UpsertTemplateUsageStats. - GetTemplateInsightsByTemplate(ctx context.Context, arg GetTemplateInsightsByTemplateParams) ([]GetTemplateInsightsByTemplateRow, error) - // GetTemplateParameterInsights does for each template in a given timeframe, - // look for the latest workspace build (for every workspace) that has been - // created in the timeframe and return the aggregate usage counts of parameter - // values. - GetTemplateParameterInsights(ctx context.Context, arg GetTemplateParameterInsightsParams) ([]GetTemplateParameterInsightsRow, error) - GetTemplateUsageStats(ctx context.Context, arg GetTemplateUsageStatsParams) ([]TemplateUsageStat, error) - GetTemplateVersionByID(ctx context.Context, id uuid.UUID) (TemplateVersion, error) - GetTemplateVersionByJobID(ctx context.Context, jobID uuid.UUID) (TemplateVersion, error) - GetTemplateVersionByTemplateIDAndName(ctx context.Context, arg GetTemplateVersionByTemplateIDAndNameParams) (TemplateVersion, error) - GetTemplateVersionParameters(ctx context.Context, templateVersionID uuid.UUID) ([]TemplateVersionParameter, error) - GetTemplateVersionVariables(ctx context.Context, templateVersionID uuid.UUID) ([]TemplateVersionVariable, error) - GetTemplateVersionWorkspaceTags(ctx context.Context, templateVersionID uuid.UUID) ([]TemplateVersionWorkspaceTag, error) - GetTemplateVersionsByIDs(ctx context.Context, ids []uuid.UUID) ([]TemplateVersion, error) - GetTemplateVersionsByTemplateID(ctx context.Context, arg GetTemplateVersionsByTemplateIDParams) ([]TemplateVersion, error) - GetTemplateVersionsCreatedAfter(ctx context.Context, createdAt time.Time) ([]TemplateVersion, error) - GetTemplates(ctx context.Context) ([]Template, error) - GetTemplatesWithFilter(ctx context.Context, arg GetTemplatesWithFilterParams) ([]Template, error) - GetUnexpiredLicenses(ctx context.Context) ([]License, error) - // GetUserActivityInsights returns the ranking with top active users. - // The result can be filtered on template_ids, meaning only user data - // from workspaces based on those templates will be included. - // Note: The usage_seconds and usage_seconds_cumulative differ only when - // requesting deployment-wide (or multiple template) data. Cumulative - // produces a bloated value if a user has used multiple templates - // simultaneously. - GetUserActivityInsights(ctx context.Context, arg GetUserActivityInsightsParams) ([]GetUserActivityInsightsRow, error) - GetUserByEmailOrUsername(ctx context.Context, arg GetUserByEmailOrUsernameParams) (User, error) - GetUserByID(ctx context.Context, id uuid.UUID) (User, error) - GetUserCount(ctx context.Context) (int64, error) - // GetUserLatencyInsights returns the median and 95th percentile connection - // latency that users have experienced. The result can be filtered on - // template_ids, meaning only user data from workspaces based on those templates - // will be included. - GetUserLatencyInsights(ctx context.Context, arg GetUserLatencyInsightsParams) ([]GetUserLatencyInsightsRow, error) - GetUserLinkByLinkedID(ctx context.Context, linkedID string) (UserLink, error) - GetUserLinkByUserIDLoginType(ctx context.Context, arg GetUserLinkByUserIDLoginTypeParams) (UserLink, error) - GetUserLinksByUserID(ctx context.Context, userID uuid.UUID) ([]UserLink, error) - GetUserNotificationPreferences(ctx context.Context, userID uuid.UUID) ([]NotificationPreference, error) - // GetUserStatusCounts returns the count of users in each status over time. - // The time range is inclusively defined by the start_time and end_time parameters. - // - // Bucketing: - // Between the start_time and end_time, we include each timestamp where a user's status changed or they were deleted. - // We do not bucket these results by day or some other time unit. This is because such bucketing would hide potentially - // important patterns. If a user was active for 23 hours and 59 minutes, and then suspended, a daily bucket would hide this. - // A daily bucket would also have required us to carefully manage the timezone of the bucket based on the timezone of the user. - // - // Accumulation: - // We do not start counting from 0 at the start_time. We check the last status change before the start_time for each user. As such, - // the result shows the total number of users in each status on any particular day. - GetUserStatusCounts(ctx context.Context, arg GetUserStatusCountsParams) ([]GetUserStatusCountsRow, error) - GetUserWorkspaceBuildParameters(ctx context.Context, arg GetUserWorkspaceBuildParametersParams) ([]GetUserWorkspaceBuildParametersRow, error) - // This will never return deleted users. - GetUsers(ctx context.Context, arg GetUsersParams) ([]GetUsersRow, error) - // This shouldn't check for deleted, because it's frequently used - // 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) - 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) - GetWorkspaceAgentLifecycleStateByID(ctx context.Context, id uuid.UUID) (GetWorkspaceAgentLifecycleStateByIDRow, error) - GetWorkspaceAgentLogSourcesByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceAgentLogSource, error) - GetWorkspaceAgentLogsAfter(ctx context.Context, arg GetWorkspaceAgentLogsAfterParams) ([]WorkspaceAgentLog, error) - GetWorkspaceAgentMetadata(ctx context.Context, arg GetWorkspaceAgentMetadataParams) ([]WorkspaceAgentMetadatum, error) - GetWorkspaceAgentPortShare(ctx context.Context, arg GetWorkspaceAgentPortShareParams) (WorkspaceAgentPortShare, error) - GetWorkspaceAgentScriptTimingsByBuildID(ctx context.Context, id uuid.UUID) ([]GetWorkspaceAgentScriptTimingsByBuildIDRow, error) - GetWorkspaceAgentScriptsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceAgentScript, error) - GetWorkspaceAgentStats(ctx context.Context, createdAt time.Time) ([]GetWorkspaceAgentStatsRow, error) - GetWorkspaceAgentStatsAndLabels(ctx context.Context, createdAt time.Time) ([]GetWorkspaceAgentStatsAndLabelsRow, error) - // `minute_buckets` could return 0 rows if there are no usage stats since `created_at`. - GetWorkspaceAgentUsageStats(ctx context.Context, createdAt time.Time) ([]GetWorkspaceAgentUsageStatsRow, error) - GetWorkspaceAgentUsageStatsAndLabels(ctx context.Context, createdAt time.Time) ([]GetWorkspaceAgentUsageStatsAndLabelsRow, error) - GetWorkspaceAgentsByResourceIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceAgent, error) - GetWorkspaceAgentsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceAgent, error) - GetWorkspaceAgentsInLatestBuildByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) ([]WorkspaceAgent, error) - GetWorkspaceAppByAgentIDAndSlug(ctx context.Context, arg GetWorkspaceAppByAgentIDAndSlugParams) (WorkspaceApp, error) - GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid.UUID) ([]WorkspaceApp, error) - GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceApp, error) - GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceApp, error) - GetWorkspaceBuildByID(ctx context.Context, id uuid.UUID) (WorkspaceBuild, error) - GetWorkspaceBuildByJobID(ctx context.Context, jobID uuid.UUID) (WorkspaceBuild, error) - GetWorkspaceBuildByWorkspaceIDAndBuildNumber(ctx context.Context, arg GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams) (WorkspaceBuild, error) - GetWorkspaceBuildParameters(ctx context.Context, workspaceBuildID uuid.UUID) ([]WorkspaceBuildParameter, error) - GetWorkspaceBuildStatsByTemplates(ctx context.Context, since time.Time) ([]GetWorkspaceBuildStatsByTemplatesRow, error) - GetWorkspaceBuildsByWorkspaceID(ctx context.Context, arg GetWorkspaceBuildsByWorkspaceIDParams) ([]WorkspaceBuild, error) - GetWorkspaceBuildsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceBuild, error) - GetWorkspaceByAgentID(ctx context.Context, agentID uuid.UUID) (Workspace, error) - GetWorkspaceByID(ctx context.Context, id uuid.UUID) (Workspace, error) - GetWorkspaceByOwnerIDAndName(ctx context.Context, arg GetWorkspaceByOwnerIDAndNameParams) (Workspace, error) - GetWorkspaceByWorkspaceAppID(ctx context.Context, workspaceAppID uuid.UUID) (Workspace, error) - GetWorkspaceModulesByJobID(ctx context.Context, jobID uuid.UUID) ([]WorkspaceModule, error) - GetWorkspaceModulesCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceModule, error) - GetWorkspaceProxies(ctx context.Context) ([]WorkspaceProxy, error) - // Finds a workspace proxy that has an access URL or app hostname that matches - // the provided hostname. This is to check if a hostname matches any workspace - // proxy. - // - // The hostname must be sanitized to only contain [a-zA-Z0-9.-] before calling - // this query. The scheme, port and path should be stripped. - // - GetWorkspaceProxyByHostname(ctx context.Context, arg GetWorkspaceProxyByHostnameParams) (WorkspaceProxy, error) - GetWorkspaceProxyByID(ctx context.Context, id uuid.UUID) (WorkspaceProxy, error) - GetWorkspaceProxyByName(ctx context.Context, name string) (WorkspaceProxy, error) - GetWorkspaceResourceByID(ctx context.Context, id uuid.UUID) (WorkspaceResource, error) - GetWorkspaceResourceMetadataByResourceIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceResourceMetadatum, error) - GetWorkspaceResourceMetadataCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceResourceMetadatum, error) - GetWorkspaceResourcesByJobID(ctx context.Context, jobID uuid.UUID) ([]WorkspaceResource, error) - GetWorkspaceResourcesByJobIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceResource, error) - GetWorkspaceResourcesCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceResource, error) - GetWorkspaceUniqueOwnerCountByTemplateIDs(ctx context.Context, templateIds []uuid.UUID) ([]GetWorkspaceUniqueOwnerCountByTemplateIDsRow, error) - // build_params is used to filter by build parameters if present. - // It has to be a CTE because the set returning function 'unnest' cannot - // be used in a WHERE clause. - GetWorkspaces(ctx context.Context, arg GetWorkspacesParams) ([]GetWorkspacesRow, error) - GetWorkspacesAndAgentsByOwnerID(ctx context.Context, ownerID uuid.UUID) ([]GetWorkspacesAndAgentsByOwnerIDRow, error) - GetWorkspacesByTemplateID(ctx context.Context, templateID uuid.UUID) ([]WorkspaceTable, error) - GetWorkspacesEligibleForTransition(ctx context.Context, now time.Time) ([]GetWorkspacesEligibleForTransitionRow, error) - InsertAPIKey(ctx context.Context, arg InsertAPIKeyParams) (APIKey, error) - // We use the organization_id as the id - // for simplicity since all users is - // every member of the org. - InsertAllUsersGroup(ctx context.Context, organizationID uuid.UUID) (Group, error) - InsertAuditLog(ctx context.Context, arg InsertAuditLogParams) (AuditLog, error) - InsertCryptoKey(ctx context.Context, arg InsertCryptoKeyParams) (CryptoKey, error) - InsertCustomRole(ctx context.Context, arg InsertCustomRoleParams) (CustomRole, error) - InsertDBCryptKey(ctx context.Context, arg InsertDBCryptKeyParams) error - InsertDERPMeshKey(ctx context.Context, value string) error - InsertDeploymentID(ctx context.Context, value string) error - InsertExternalAuthLink(ctx context.Context, arg InsertExternalAuthLinkParams) (ExternalAuthLink, error) - InsertFile(ctx context.Context, arg InsertFileParams) (File, error) - InsertGitSSHKey(ctx context.Context, arg InsertGitSSHKeyParams) (GitSSHKey, error) - InsertGroup(ctx context.Context, arg InsertGroupParams) (Group, error) - InsertGroupMember(ctx context.Context, arg InsertGroupMemberParams) error - InsertInboxNotification(ctx context.Context, arg InsertInboxNotificationParams) (InboxNotification, error) - InsertLicense(ctx context.Context, arg InsertLicenseParams) (License, error) - InsertMemoryResourceMonitor(ctx context.Context, arg InsertMemoryResourceMonitorParams) (WorkspaceAgentMemoryResourceMonitor, error) - // Inserts any group by name that does not exist. All new groups are given - // a random uuid, are inserted into the same organization. They have the default - // values for avatar, display name, and quota allowance (all zero values). - // If the name conflicts, do nothing. - InsertMissingGroups(ctx context.Context, arg InsertMissingGroupsParams) ([]Group, error) - InsertOAuth2ProviderApp(ctx context.Context, arg InsertOAuth2ProviderAppParams) (OAuth2ProviderApp, error) - InsertOAuth2ProviderAppCode(ctx context.Context, arg InsertOAuth2ProviderAppCodeParams) (OAuth2ProviderAppCode, error) - InsertOAuth2ProviderAppSecret(ctx context.Context, arg InsertOAuth2ProviderAppSecretParams) (OAuth2ProviderAppSecret, error) - InsertOAuth2ProviderAppToken(ctx context.Context, arg InsertOAuth2ProviderAppTokenParams) (OAuth2ProviderAppToken, error) - InsertOrganization(ctx context.Context, arg InsertOrganizationParams) (Organization, error) - InsertOrganizationMember(ctx context.Context, arg InsertOrganizationMemberParams) (OrganizationMember, error) - InsertPreset(ctx context.Context, arg InsertPresetParams) (TemplateVersionPreset, error) - InsertPresetParameters(ctx context.Context, arg InsertPresetParametersParams) ([]TemplateVersionPresetParameter, error) - InsertProvisionerJob(ctx context.Context, arg InsertProvisionerJobParams) (ProvisionerJob, error) - InsertProvisionerJobLogs(ctx context.Context, arg InsertProvisionerJobLogsParams) ([]ProvisionerJobLog, error) - InsertProvisionerJobTimings(ctx context.Context, arg InsertProvisionerJobTimingsParams) ([]ProvisionerJobTiming, error) - InsertProvisionerKey(ctx context.Context, arg InsertProvisionerKeyParams) (ProvisionerKey, error) - InsertReplica(ctx context.Context, arg InsertReplicaParams) (Replica, error) - InsertTelemetryItemIfNotExists(ctx context.Context, arg InsertTelemetryItemIfNotExistsParams) error - InsertTemplate(ctx context.Context, arg InsertTemplateParams) error - InsertTemplateVersion(ctx context.Context, arg InsertTemplateVersionParams) error - InsertTemplateVersionParameter(ctx context.Context, arg InsertTemplateVersionParameterParams) (TemplateVersionParameter, error) - InsertTemplateVersionVariable(ctx context.Context, arg InsertTemplateVersionVariableParams) (TemplateVersionVariable, error) - InsertTemplateVersionWorkspaceTag(ctx context.Context, arg InsertTemplateVersionWorkspaceTagParams) (TemplateVersionWorkspaceTag, error) - InsertUser(ctx context.Context, arg InsertUserParams) (User, error) - // InsertUserGroupsByID adds a user to all provided groups, if they exist. - // If there is a conflict, the user is already a member - InsertUserGroupsByID(ctx context.Context, arg InsertUserGroupsByIDParams) ([]uuid.UUID, error) - // InsertUserGroupsByName adds a user to all provided groups, if they exist. - InsertUserGroupsByName(ctx context.Context, arg InsertUserGroupsByNameParams) error - InsertUserLink(ctx context.Context, arg InsertUserLinkParams) (UserLink, error) - InsertVolumeResourceMonitor(ctx context.Context, arg InsertVolumeResourceMonitorParams) (WorkspaceAgentVolumeResourceMonitor, error) - InsertWorkspace(ctx context.Context, arg InsertWorkspaceParams) (WorkspaceTable, error) - InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspaceAgentParams) (WorkspaceAgent, error) - InsertWorkspaceAgentLogSources(ctx context.Context, arg InsertWorkspaceAgentLogSourcesParams) ([]WorkspaceAgentLogSource, error) - InsertWorkspaceAgentLogs(ctx context.Context, arg InsertWorkspaceAgentLogsParams) ([]WorkspaceAgentLog, error) - InsertWorkspaceAgentMetadata(ctx context.Context, arg InsertWorkspaceAgentMetadataParams) error - InsertWorkspaceAgentScriptTimings(ctx context.Context, arg InsertWorkspaceAgentScriptTimingsParams) (WorkspaceAgentScriptTiming, error) - InsertWorkspaceAgentScripts(ctx context.Context, arg InsertWorkspaceAgentScriptsParams) ([]WorkspaceAgentScript, error) - InsertWorkspaceAgentStats(ctx context.Context, arg InsertWorkspaceAgentStatsParams) error - InsertWorkspaceApp(ctx context.Context, arg InsertWorkspaceAppParams) (WorkspaceApp, error) - InsertWorkspaceAppStats(ctx context.Context, arg InsertWorkspaceAppStatsParams) error - InsertWorkspaceBuild(ctx context.Context, arg InsertWorkspaceBuildParams) error - InsertWorkspaceBuildParameters(ctx context.Context, arg InsertWorkspaceBuildParametersParams) error - InsertWorkspaceModule(ctx context.Context, arg InsertWorkspaceModuleParams) (WorkspaceModule, error) - InsertWorkspaceProxy(ctx context.Context, arg InsertWorkspaceProxyParams) (WorkspaceProxy, error) - InsertWorkspaceResource(ctx context.Context, arg InsertWorkspaceResourceParams) (WorkspaceResource, error) - InsertWorkspaceResourceMetadata(ctx context.Context, arg InsertWorkspaceResourceMetadataParams) ([]WorkspaceResourceMetadatum, error) - ListProvisionerKeysByOrganization(ctx context.Context, organizationID uuid.UUID) ([]ProvisionerKey, error) - ListProvisionerKeysByOrganizationExcludeReserved(ctx context.Context, organizationID uuid.UUID) ([]ProvisionerKey, error) - ListWorkspaceAgentPortShares(ctx context.Context, workspaceID uuid.UUID) ([]WorkspaceAgentPortShare, error) - OIDCClaimFieldValues(ctx context.Context, arg OIDCClaimFieldValuesParams) ([]string, error) - // OIDCClaimFields returns a list of distinct keys in the the merged_claims fields. - // This query is used to generate the list of available sync fields for idp sync settings. - OIDCClaimFields(ctx context.Context, organizationID uuid.UUID) ([]string, error) - // Arguments are optional with uuid.Nil to ignore. - // - Use just 'organization_id' to get all members of an org - // - Use just 'user_id' to get all orgs a user is a member of - // - Use both to get a specific org member row - OrganizationMembers(ctx context.Context, arg OrganizationMembersParams) ([]OrganizationMembersRow, error) - ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(ctx context.Context, templateID uuid.UUID) error - RegisterWorkspaceProxy(ctx context.Context, arg RegisterWorkspaceProxyParams) (WorkspaceProxy, error) - RemoveUserFromAllGroups(ctx context.Context, userID uuid.UUID) error - RemoveUserFromGroups(ctx context.Context, arg RemoveUserFromGroupsParams) ([]uuid.UUID, error) - RevokeDBCryptKey(ctx context.Context, activeKeyDigest string) error - SetInboxNotificationAsRead(ctx context.Context, arg SetInboxNotificationAsReadParams) error - // Non blocking lock. Returns true if the lock was acquired, false otherwise. - // - // This must be called from within a transaction. The lock will be automatically - // released when the transaction ends. - TryAcquireLock(ctx context.Context, pgTryAdvisoryXactLock int64) (bool, error) - // This will always work regardless of the current state of the template version. - UnarchiveTemplateVersion(ctx context.Context, arg UnarchiveTemplateVersionParams) error - UnfavoriteWorkspace(ctx context.Context, id uuid.UUID) error - UpdateAPIKeyByID(ctx context.Context, arg UpdateAPIKeyByIDParams) error - UpdateCryptoKeyDeletesAt(ctx context.Context, arg UpdateCryptoKeyDeletesAtParams) (CryptoKey, error) - UpdateCustomRole(ctx context.Context, arg UpdateCustomRoleParams) (CustomRole, error) - UpdateExternalAuthLink(ctx context.Context, arg UpdateExternalAuthLinkParams) (ExternalAuthLink, error) - UpdateExternalAuthLinkRefreshToken(ctx context.Context, arg UpdateExternalAuthLinkRefreshTokenParams) error - UpdateGitSSHKey(ctx context.Context, arg UpdateGitSSHKeyParams) (GitSSHKey, error) - UpdateGroupByID(ctx context.Context, arg UpdateGroupByIDParams) (Group, error) - UpdateInactiveUsersToDormant(ctx context.Context, arg UpdateInactiveUsersToDormantParams) ([]UpdateInactiveUsersToDormantRow, error) - UpdateMemberRoles(ctx context.Context, arg UpdateMemberRolesParams) (OrganizationMember, error) - UpdateMemoryResourceMonitor(ctx context.Context, arg UpdateMemoryResourceMonitorParams) error - UpdateNotificationTemplateMethodByID(ctx context.Context, arg UpdateNotificationTemplateMethodByIDParams) (NotificationTemplate, error) - UpdateOAuth2ProviderAppByID(ctx context.Context, arg UpdateOAuth2ProviderAppByIDParams) (OAuth2ProviderApp, error) - UpdateOAuth2ProviderAppSecretByID(ctx context.Context, arg UpdateOAuth2ProviderAppSecretByIDParams) (OAuth2ProviderAppSecret, error) - UpdateOrganization(ctx context.Context, arg UpdateOrganizationParams) (Organization, error) - UpdateProvisionerDaemonLastSeenAt(ctx context.Context, arg UpdateProvisionerDaemonLastSeenAtParams) error - UpdateProvisionerJobByID(ctx context.Context, arg UpdateProvisionerJobByIDParams) error - UpdateProvisionerJobWithCancelByID(ctx context.Context, arg UpdateProvisionerJobWithCancelByIDParams) error - UpdateProvisionerJobWithCompleteByID(ctx context.Context, arg UpdateProvisionerJobWithCompleteByIDParams) error - UpdateReplica(ctx context.Context, arg UpdateReplicaParams) (Replica, error) - UpdateTailnetPeerStatusByCoordinator(ctx context.Context, arg UpdateTailnetPeerStatusByCoordinatorParams) error - UpdateTemplateACLByID(ctx context.Context, arg UpdateTemplateACLByIDParams) error - UpdateTemplateAccessControlByID(ctx context.Context, arg UpdateTemplateAccessControlByIDParams) error - UpdateTemplateActiveVersionByID(ctx context.Context, arg UpdateTemplateActiveVersionByIDParams) error - UpdateTemplateDeletedByID(ctx context.Context, arg UpdateTemplateDeletedByIDParams) error - UpdateTemplateMetaByID(ctx context.Context, arg UpdateTemplateMetaByIDParams) error - UpdateTemplateScheduleByID(ctx context.Context, arg UpdateTemplateScheduleByIDParams) error - UpdateTemplateVersionByID(ctx context.Context, arg UpdateTemplateVersionByIDParams) error - UpdateTemplateVersionDescriptionByJobID(ctx context.Context, arg UpdateTemplateVersionDescriptionByJobIDParams) error - UpdateTemplateVersionExternalAuthProvidersByJobID(ctx context.Context, arg UpdateTemplateVersionExternalAuthProvidersByJobIDParams) error - UpdateTemplateWorkspacesLastUsedAt(ctx context.Context, arg UpdateTemplateWorkspacesLastUsedAtParams) error - UpdateUserAppearanceSettings(ctx context.Context, arg UpdateUserAppearanceSettingsParams) (User, error) - UpdateUserDeletedByID(ctx context.Context, id uuid.UUID) error - UpdateUserGithubComUserID(ctx context.Context, arg UpdateUserGithubComUserIDParams) error - UpdateUserHashedOneTimePasscode(ctx context.Context, arg UpdateUserHashedOneTimePasscodeParams) error - UpdateUserHashedPassword(ctx context.Context, arg UpdateUserHashedPasswordParams) error - UpdateUserLastSeenAt(ctx context.Context, arg UpdateUserLastSeenAtParams) (User, error) - UpdateUserLink(ctx context.Context, arg UpdateUserLinkParams) (UserLink, error) - UpdateUserLinkedID(ctx context.Context, arg UpdateUserLinkedIDParams) (UserLink, error) - UpdateUserLoginType(ctx context.Context, arg UpdateUserLoginTypeParams) (User, error) - UpdateUserNotificationPreferences(ctx context.Context, arg UpdateUserNotificationPreferencesParams) (int64, error) - UpdateUserProfile(ctx context.Context, arg UpdateUserProfileParams) (User, error) - UpdateUserQuietHoursSchedule(ctx context.Context, arg UpdateUserQuietHoursScheduleParams) (User, error) - UpdateUserRoles(ctx context.Context, arg UpdateUserRolesParams) (User, error) - UpdateUserStatus(ctx context.Context, arg UpdateUserStatusParams) (User, error) - UpdateVolumeResourceMonitor(ctx context.Context, arg UpdateVolumeResourceMonitorParams) error - UpdateWorkspace(ctx context.Context, arg UpdateWorkspaceParams) (WorkspaceTable, error) - UpdateWorkspaceAgentConnectionByID(ctx context.Context, arg UpdateWorkspaceAgentConnectionByIDParams) error - UpdateWorkspaceAgentLifecycleStateByID(ctx context.Context, arg UpdateWorkspaceAgentLifecycleStateByIDParams) error - UpdateWorkspaceAgentLogOverflowByID(ctx context.Context, arg UpdateWorkspaceAgentLogOverflowByIDParams) error - UpdateWorkspaceAgentMetadata(ctx context.Context, arg UpdateWorkspaceAgentMetadataParams) error - UpdateWorkspaceAgentStartupByID(ctx context.Context, arg UpdateWorkspaceAgentStartupByIDParams) error - UpdateWorkspaceAppHealthByID(ctx context.Context, arg UpdateWorkspaceAppHealthByIDParams) error - UpdateWorkspaceAutomaticUpdates(ctx context.Context, arg UpdateWorkspaceAutomaticUpdatesParams) error - UpdateWorkspaceAutostart(ctx context.Context, arg UpdateWorkspaceAutostartParams) error - UpdateWorkspaceBuildCostByID(ctx context.Context, arg UpdateWorkspaceBuildCostByIDParams) error - UpdateWorkspaceBuildDeadlineByID(ctx context.Context, arg UpdateWorkspaceBuildDeadlineByIDParams) error - UpdateWorkspaceBuildProvisionerStateByID(ctx context.Context, arg UpdateWorkspaceBuildProvisionerStateByIDParams) error - UpdateWorkspaceDeletedByID(ctx context.Context, arg UpdateWorkspaceDeletedByIDParams) error - UpdateWorkspaceDormantDeletingAt(ctx context.Context, arg UpdateWorkspaceDormantDeletingAtParams) (WorkspaceTable, error) - UpdateWorkspaceLastUsedAt(ctx context.Context, arg UpdateWorkspaceLastUsedAtParams) error - UpdateWorkspaceNextStartAt(ctx context.Context, arg UpdateWorkspaceNextStartAtParams) error - // This allows editing the properties of a workspace proxy. - UpdateWorkspaceProxy(ctx context.Context, arg UpdateWorkspaceProxyParams) (WorkspaceProxy, error) - UpdateWorkspaceProxyDeleted(ctx context.Context, arg UpdateWorkspaceProxyDeletedParams) error - UpdateWorkspaceTTL(ctx context.Context, arg UpdateWorkspaceTTLParams) error - UpdateWorkspacesDormantDeletingAtByTemplateID(ctx context.Context, arg UpdateWorkspacesDormantDeletingAtByTemplateIDParams) ([]WorkspaceTable, error) - UpdateWorkspacesTTLByTemplateID(ctx context.Context, arg UpdateWorkspacesTTLByTemplateIDParams) error - UpsertAnnouncementBanners(ctx context.Context, value string) error - UpsertAppSecurityKey(ctx context.Context, value string) error - UpsertApplicationName(ctx context.Context, value string) error - UpsertCoordinatorResumeTokenSigningKey(ctx context.Context, value string) error - // The default proxy is implied and not actually stored in the database. - // So we need to store it's configuration here for display purposes. - // The functional values are immutable and controlled implicitly. - UpsertDefaultProxy(ctx context.Context, arg UpsertDefaultProxyParams) error - UpsertHealthSettings(ctx context.Context, value string) error - UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg UpsertJFrogXrayScanByWorkspaceAndAgentIDParams) error - UpsertLastUpdateCheck(ctx context.Context, value string) error - UpsertLogoURL(ctx context.Context, value string) error - // Insert or update notification report generator logs with recent activity. - UpsertNotificationReportGeneratorLog(ctx context.Context, arg UpsertNotificationReportGeneratorLogParams) error - UpsertNotificationsSettings(ctx context.Context, value string) error - UpsertOAuthSigningKey(ctx context.Context, value string) error - UpsertProvisionerDaemon(ctx context.Context, arg UpsertProvisionerDaemonParams) (ProvisionerDaemon, error) - UpsertRuntimeConfig(ctx context.Context, arg UpsertRuntimeConfigParams) error - UpsertTailnetAgent(ctx context.Context, arg UpsertTailnetAgentParams) (TailnetAgent, error) - UpsertTailnetClient(ctx context.Context, arg UpsertTailnetClientParams) (TailnetClient, error) - UpsertTailnetClientSubscription(ctx context.Context, arg UpsertTailnetClientSubscriptionParams) error - UpsertTailnetCoordinator(ctx context.Context, id uuid.UUID) (TailnetCoordinator, error) - UpsertTailnetPeer(ctx context.Context, arg UpsertTailnetPeerParams) (TailnetPeer, error) - UpsertTailnetTunnel(ctx context.Context, arg UpsertTailnetTunnelParams) (TailnetTunnel, error) - UpsertTelemetryItem(ctx context.Context, arg UpsertTelemetryItemParams) error - // This query aggregates the workspace_agent_stats and workspace_app_stats data - // into a single table for efficient storage and querying. Half-hour buckets are - // 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 - UpsertWorkspaceAgentPortShare(ctx context.Context, arg UpsertWorkspaceAgentPortShareParams) (WorkspaceAgentPortShare, error) -} - -var _ sqlcQuerier = (*sqlQuerier)(nil) From ddc65e6faf4fa11a7b54aef417702178bf337bf2 Mon Sep 17 00:00:00 2001 From: defelmnq Date: Fri, 21 Feb 2025 07:10:08 +0000 Subject: [PATCH 15/37] add back querier file --- coderd/database/querier.go | 581 +++++++++++++++++++++++++++++++++++++ 1 file changed, 581 insertions(+) create mode 100644 coderd/database/querier.go diff --git a/coderd/database/querier.go b/coderd/database/querier.go new file mode 100644 index 0000000000000..7236e75141e92 --- /dev/null +++ b/coderd/database/querier.go @@ -0,0 +1,581 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 + +package database + +import ( + "context" + "time" + + "github.com/google/uuid" +) + +type sqlcQuerier interface { + // Blocks until the lock is acquired. + // + // This must be called from within a transaction. The lock will be automatically + // released when the transaction ends. + AcquireLock(ctx context.Context, pgAdvisoryXactLock int64) error + // Acquires the lease for a given count of notification messages, to enable concurrent dequeuing and subsequent sending. + // Only rows that aren't already leased (or ones which are leased but have exceeded their lease period) are returned. + // + // A "lease" here refers to a notifier taking ownership of a notification_messages row. A lease survives for the duration + // of CODER_NOTIFICATIONS_LEASE_PERIOD. Once a message is delivered, its status is updated and the lease expires (set to NULL). + // If a message exceeds its lease, that implies the notifier did not shutdown cleanly, or the table update failed somehow, + // and the row will then be eligible to be dequeued by another notifier. + // + // SKIP LOCKED is used to jump over locked rows. This prevents multiple notifiers from acquiring the same messages. + // See: https://www.postgresql.org/docs/9.5/sql-select.html#SQL-FOR-UPDATE-SHARE + // + AcquireNotificationMessages(ctx context.Context, arg AcquireNotificationMessagesParams) ([]AcquireNotificationMessagesRow, error) + // Acquires the lock for a single job that isn't started, completed, + // canceled, and that matches an array of provisioner types. + // + // SKIP LOCKED is used to jump over locked rows. This prevents + // multiple provisioners from acquiring the same jobs. See: + // https://www.postgresql.org/docs/9.5/sql-select.html#SQL-FOR-UPDATE-SHARE + AcquireProvisionerJob(ctx context.Context, arg AcquireProvisionerJobParams) (ProvisionerJob, error) + // Bumps the workspace deadline by the template's configured "activity_bump" + // duration (default 1h). If the workspace bump will cross an autostart + // threshold, then the bump is autostart + TTL. This is the deadline behavior if + // the workspace was to autostart from a stopped state. + // + // Max deadline is respected, and the deadline will never be bumped past it. + // The deadline will never decrease. + // We only bump if the template has an activity bump duration set. + // We only bump if the raw interval is positive and non-zero. + // We only bump if workspace shutdown is manual. + // We only bump when 5% of the deadline has elapsed. + ActivityBumpWorkspace(ctx context.Context, arg ActivityBumpWorkspaceParams) error + // AllUserIDs returns all UserIDs regardless of user status or deletion. + AllUserIDs(ctx context.Context) ([]uuid.UUID, error) + // Archiving templates is a soft delete action, so is reversible. + // Archiving prevents the version from being used and discovered + // by listing. + // Only unused template versions will be archived, which are any versions not + // referenced by the latest build of a workspace. + ArchiveUnusedTemplateVersions(ctx context.Context, arg ArchiveUnusedTemplateVersionsParams) ([]uuid.UUID, error) + BatchUpdateWorkspaceLastUsedAt(ctx context.Context, arg BatchUpdateWorkspaceLastUsedAtParams) error + BatchUpdateWorkspaceNextStartAt(ctx context.Context, arg BatchUpdateWorkspaceNextStartAtParams) error + BulkMarkNotificationMessagesFailed(ctx context.Context, arg BulkMarkNotificationMessagesFailedParams) (int64, error) + BulkMarkNotificationMessagesSent(ctx context.Context, arg BulkMarkNotificationMessagesSentParams) (int64, error) + CleanTailnetCoordinators(ctx context.Context) error + CleanTailnetLostPeers(ctx context.Context) error + CleanTailnetTunnels(ctx context.Context) error + CustomRoles(ctx context.Context, arg CustomRolesParams) ([]CustomRole, error) + DeleteAPIKeyByID(ctx context.Context, id string) error + DeleteAPIKeysByUserID(ctx context.Context, userID uuid.UUID) error + DeleteAllTailnetClientSubscriptions(ctx context.Context, arg DeleteAllTailnetClientSubscriptionsParams) error + DeleteAllTailnetTunnels(ctx context.Context, arg DeleteAllTailnetTunnelsParams) 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) + DeleteCustomRole(ctx context.Context, arg DeleteCustomRoleParams) error + DeleteExternalAuthLink(ctx context.Context, arg DeleteExternalAuthLinkParams) error + DeleteGitSSHKey(ctx context.Context, userID uuid.UUID) error + DeleteGroupByID(ctx context.Context, id uuid.UUID) error + DeleteGroupMemberFromGroup(ctx context.Context, arg DeleteGroupMemberFromGroupParams) error + DeleteLicense(ctx context.Context, id int32) (int32, error) + DeleteOAuth2ProviderAppByID(ctx context.Context, id uuid.UUID) error + DeleteOAuth2ProviderAppCodeByID(ctx context.Context, id uuid.UUID) error + DeleteOAuth2ProviderAppCodesByAppAndUserID(ctx context.Context, arg DeleteOAuth2ProviderAppCodesByAppAndUserIDParams) error + DeleteOAuth2ProviderAppSecretByID(ctx context.Context, id uuid.UUID) error + DeleteOAuth2ProviderAppTokensByAppAndUserID(ctx context.Context, arg DeleteOAuth2ProviderAppTokensByAppAndUserIDParams) error + // Delete all notification messages which have not been updated for over a week. + DeleteOldNotificationMessages(ctx context.Context) error + // Delete provisioner daemons that have been created at least a week ago + // and have not connected to coderd since a week. + // A provisioner daemon with "zeroed" last_seen_at column indicates possible + // connectivity issues (no provisioner daemon activity since registration). + DeleteOldProvisionerDaemons(ctx context.Context) error + // If an agent hasn't connected in the last 7 days, we purge it's logs. + // Exception: if the logs are related to the latest build, we keep those around. + // Logs can take up a lot of space, so it's important we clean up frequently. + DeleteOldWorkspaceAgentLogs(ctx context.Context, threshold time.Time) error + DeleteOldWorkspaceAgentStats(ctx context.Context) error + DeleteOrganization(ctx context.Context, id uuid.UUID) error + DeleteOrganizationMember(ctx context.Context, arg DeleteOrganizationMemberParams) error + DeleteProvisionerKey(ctx context.Context, id uuid.UUID) error + DeleteReplicasUpdatedBefore(ctx context.Context, updatedAt time.Time) error + DeleteRuntimeConfig(ctx context.Context, key string) error + DeleteTailnetAgent(ctx context.Context, arg DeleteTailnetAgentParams) (DeleteTailnetAgentRow, error) + DeleteTailnetClient(ctx context.Context, arg DeleteTailnetClientParams) (DeleteTailnetClientRow, error) + DeleteTailnetClientSubscription(ctx context.Context, arg DeleteTailnetClientSubscriptionParams) error + DeleteTailnetPeer(ctx context.Context, arg DeleteTailnetPeerParams) (DeleteTailnetPeerRow, error) + DeleteTailnetTunnel(ctx context.Context, arg DeleteTailnetTunnelParams) (DeleteTailnetTunnelRow, error) + DeleteWorkspaceAgentPortShare(ctx context.Context, arg DeleteWorkspaceAgentPortShareParams) error + DeleteWorkspaceAgentPortSharesByTemplate(ctx context.Context, templateID uuid.UUID) error + // Disable foreign keys and triggers for all tables. + // Deprecated: disable foreign keys was created to aid in migrating off + // of the test-only in-memory database. Do not use this in new code. + DisableForeignKeysAndTriggers(ctx context.Context) error + EnqueueNotificationMessage(ctx context.Context, arg EnqueueNotificationMessageParams) error + FavoriteWorkspace(ctx context.Context, id uuid.UUID) error + FetchInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]InboxNotification, error) + FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]InboxNotification, error) + FetchMemoryResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) (WorkspaceAgentMemoryResourceMonitor, error) + // This is used to build up the notification_message's JSON payload. + FetchNewMessageMetadata(ctx context.Context, arg FetchNewMessageMetadataParams) (FetchNewMessageMetadataRow, error) + FetchUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]InboxNotification, error) + FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]InboxNotification, error) + FetchVolumesResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) ([]WorkspaceAgentVolumeResourceMonitor, error) + GetAPIKeyByID(ctx context.Context, id string) (APIKey, error) + // there is no unique constraint on empty token names + GetAPIKeyByName(ctx context.Context, arg GetAPIKeyByNameParams) (APIKey, error) + GetAPIKeysByLoginType(ctx context.Context, loginType LoginType) ([]APIKey, error) + GetAPIKeysByUserID(ctx context.Context, arg GetAPIKeysByUserIDParams) ([]APIKey, error) + GetAPIKeysLastUsedAfter(ctx context.Context, lastUsed time.Time) ([]APIKey, error) + GetActiveUserCount(ctx context.Context) (int64, error) + GetActiveWorkspaceBuildsByTemplateID(ctx context.Context, templateID uuid.UUID) ([]WorkspaceBuild, error) + GetAllTailnetAgents(ctx context.Context) ([]TailnetAgent, error) + // For PG Coordinator HTMLDebug + GetAllTailnetCoordinators(ctx context.Context) ([]TailnetCoordinator, error) + GetAllTailnetPeers(ctx context.Context) ([]TailnetPeer, error) + GetAllTailnetTunnels(ctx context.Context) ([]TailnetTunnel, error) + GetAnnouncementBanners(ctx context.Context) (string, error) + GetAppSecurityKey(ctx context.Context) (string, error) + GetApplicationName(ctx context.Context) (string, error) + // GetAuditLogsBefore retrieves `row_limit` number of audit logs before the provided + // ID. + GetAuditLogsOffset(ctx context.Context, arg GetAuditLogsOffsetParams) ([]GetAuditLogsOffsetRow, error) + // This function returns roles for authorization purposes. Implied member roles + // are included. + GetAuthorizationUserRoles(ctx context.Context, userID uuid.UUID) (GetAuthorizationUserRolesRow, error) + GetCoordinatorResumeTokenSigningKey(ctx context.Context) (string, error) + GetCryptoKeyByFeatureAndSequence(ctx context.Context, arg GetCryptoKeyByFeatureAndSequenceParams) (CryptoKey, error) + GetCryptoKeys(ctx context.Context) ([]CryptoKey, error) + GetCryptoKeysByFeature(ctx context.Context, feature CryptoKeyFeature) ([]CryptoKey, error) + GetDBCryptKeys(ctx context.Context) ([]DBCryptKey, error) + GetDERPMeshKey(ctx context.Context) (string, error) + GetDefaultOrganization(ctx context.Context) (Organization, error) + GetDefaultProxyConfig(ctx context.Context) (GetDefaultProxyConfigRow, error) + GetDeploymentDAUs(ctx context.Context, tzOffset int32) ([]GetDeploymentDAUsRow, error) + GetDeploymentID(ctx context.Context) (string, error) + GetDeploymentWorkspaceAgentStats(ctx context.Context, createdAt time.Time) (GetDeploymentWorkspaceAgentStatsRow, error) + GetDeploymentWorkspaceAgentUsageStats(ctx context.Context, createdAt time.Time) (GetDeploymentWorkspaceAgentUsageStatsRow, error) + GetDeploymentWorkspaceStats(ctx context.Context) (GetDeploymentWorkspaceStatsRow, error) + GetEligibleProvisionerDaemonsByProvisionerJobIDs(ctx context.Context, provisionerJobIds []uuid.UUID) ([]GetEligibleProvisionerDaemonsByProvisionerJobIDsRow, error) + GetExternalAuthLink(ctx context.Context, arg GetExternalAuthLinkParams) (ExternalAuthLink, error) + GetExternalAuthLinksByUserID(ctx context.Context, userID uuid.UUID) ([]ExternalAuthLink, error) + GetFailedWorkspaceBuildsByTemplateID(ctx context.Context, arg GetFailedWorkspaceBuildsByTemplateIDParams) ([]GetFailedWorkspaceBuildsByTemplateIDRow, error) + GetFileByHashAndCreator(ctx context.Context, arg GetFileByHashAndCreatorParams) (File, error) + GetFileByID(ctx context.Context, id uuid.UUID) (File, error) + // Get all templates that use a file. + GetFileTemplates(ctx context.Context, fileID uuid.UUID) ([]GetFileTemplatesRow, error) + GetGitSSHKey(ctx context.Context, userID uuid.UUID) (GitSSHKey, error) + GetGroupByID(ctx context.Context, id uuid.UUID) (Group, error) + GetGroupByOrgAndName(ctx context.Context, arg GetGroupByOrgAndNameParams) (Group, error) + GetGroupMembers(ctx context.Context) ([]GroupMember, error) + GetGroupMembersByGroupID(ctx context.Context, groupID uuid.UUID) ([]GroupMember, error) + // Returns the total count of members in a group. Shows the total + // count even if the caller does not have read access to ResourceGroupMember. + // They only need ResourceGroup read access. + GetGroupMembersCountByGroupID(ctx context.Context, groupID uuid.UUID) (int64, error) + GetGroups(ctx context.Context, arg GetGroupsParams) ([]GetGroupsRow, error) + GetHealthSettings(ctx context.Context) (string, error) + GetHungProvisionerJobs(ctx context.Context, updatedAt time.Time) ([]ProvisionerJob, error) + GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (InboxNotification, error) + GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg GetJFrogXrayScanByWorkspaceAndAgentIDParams) (JfrogXrayScan, error) + GetLastUpdateCheck(ctx context.Context) (string, error) + GetLatestCryptoKeyByFeature(ctx context.Context, feature CryptoKeyFeature) (CryptoKey, error) + GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (WorkspaceBuild, error) + GetLatestWorkspaceBuilds(ctx context.Context) ([]WorkspaceBuild, error) + GetLatestWorkspaceBuildsByWorkspaceIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceBuild, error) + GetLicenseByID(ctx context.Context, id int32) (License, error) + GetLicenses(ctx context.Context) ([]License, error) + GetLogoURL(ctx context.Context) (string, error) + GetNotificationMessagesByStatus(ctx context.Context, arg GetNotificationMessagesByStatusParams) ([]NotificationMessage, error) + // Fetch the notification report generator log indicating recent activity. + GetNotificationReportGeneratorLogByTemplate(ctx context.Context, templateID uuid.UUID) (NotificationReportGeneratorLog, error) + GetNotificationTemplateByID(ctx context.Context, id uuid.UUID) (NotificationTemplate, error) + GetNotificationTemplatesByKind(ctx context.Context, kind NotificationTemplateKind) ([]NotificationTemplate, error) + GetNotificationsSettings(ctx context.Context) (string, error) + GetOAuth2ProviderAppByID(ctx context.Context, id uuid.UUID) (OAuth2ProviderApp, error) + GetOAuth2ProviderAppCodeByID(ctx context.Context, id uuid.UUID) (OAuth2ProviderAppCode, error) + GetOAuth2ProviderAppCodeByPrefix(ctx context.Context, secretPrefix []byte) (OAuth2ProviderAppCode, error) + GetOAuth2ProviderAppSecretByID(ctx context.Context, id uuid.UUID) (OAuth2ProviderAppSecret, error) + GetOAuth2ProviderAppSecretByPrefix(ctx context.Context, secretPrefix []byte) (OAuth2ProviderAppSecret, error) + GetOAuth2ProviderAppSecretsByAppID(ctx context.Context, appID uuid.UUID) ([]OAuth2ProviderAppSecret, error) + GetOAuth2ProviderAppTokenByPrefix(ctx context.Context, hashPrefix []byte) (OAuth2ProviderAppToken, error) + GetOAuth2ProviderApps(ctx context.Context) ([]OAuth2ProviderApp, error) + GetOAuth2ProviderAppsByUserID(ctx context.Context, userID uuid.UUID) ([]GetOAuth2ProviderAppsByUserIDRow, error) + GetOAuthSigningKey(ctx context.Context) (string, error) + GetOrganizationByID(ctx context.Context, id uuid.UUID) (Organization, error) + GetOrganizationByName(ctx context.Context, name string) (Organization, error) + GetOrganizationIDsByMemberIDs(ctx context.Context, ids []uuid.UUID) ([]GetOrganizationIDsByMemberIDsRow, error) + GetOrganizations(ctx context.Context, arg GetOrganizationsParams) ([]Organization, error) + GetOrganizationsByUserID(ctx context.Context, userID uuid.UUID) ([]Organization, error) + GetParameterSchemasByJobID(ctx context.Context, jobID uuid.UUID) ([]ParameterSchema, error) + GetPresetByWorkspaceBuildID(ctx context.Context, workspaceBuildID uuid.UUID) (TemplateVersionPreset, error) + GetPresetParametersByTemplateVersionID(ctx context.Context, templateVersionID uuid.UUID) ([]TemplateVersionPresetParameter, error) + GetPresetsByTemplateVersionID(ctx context.Context, templateVersionID uuid.UUID) ([]TemplateVersionPreset, error) + GetPreviousTemplateVersion(ctx context.Context, arg GetPreviousTemplateVersionParams) (TemplateVersion, error) + GetProvisionerDaemons(ctx context.Context) ([]ProvisionerDaemon, error) + GetProvisionerDaemonsByOrganization(ctx context.Context, arg GetProvisionerDaemonsByOrganizationParams) ([]ProvisionerDaemon, error) + // Current job information. + // Previous job information. + GetProvisionerDaemonsWithStatusByOrganization(ctx context.Context, arg GetProvisionerDaemonsWithStatusByOrganizationParams) ([]GetProvisionerDaemonsWithStatusByOrganizationRow, error) + GetProvisionerJobByID(ctx context.Context, id uuid.UUID) (ProvisionerJob, error) + GetProvisionerJobTimingsByJobID(ctx context.Context, jobID uuid.UUID) ([]ProvisionerJobTiming, error) + GetProvisionerJobsByIDs(ctx context.Context, ids []uuid.UUID) ([]ProvisionerJob, error) + GetProvisionerJobsByIDsWithQueuePosition(ctx context.Context, ids []uuid.UUID) ([]GetProvisionerJobsByIDsWithQueuePositionRow, error) + GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisioner(ctx context.Context, arg GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisionerParams) ([]GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisionerRow, error) + GetProvisionerJobsCreatedAfter(ctx context.Context, createdAt time.Time) ([]ProvisionerJob, error) + GetProvisionerKeyByHashedSecret(ctx context.Context, hashedSecret []byte) (ProvisionerKey, error) + GetProvisionerKeyByID(ctx context.Context, id uuid.UUID) (ProvisionerKey, error) + GetProvisionerKeyByName(ctx context.Context, arg GetProvisionerKeyByNameParams) (ProvisionerKey, error) + GetProvisionerLogsAfterID(ctx context.Context, arg GetProvisionerLogsAfterIDParams) ([]ProvisionerJobLog, error) + GetQuotaAllowanceForUser(ctx context.Context, arg GetQuotaAllowanceForUserParams) (int64, error) + GetQuotaConsumedForUser(ctx context.Context, arg GetQuotaConsumedForUserParams) (int64, error) + GetReplicaByID(ctx context.Context, id uuid.UUID) (Replica, error) + GetReplicasUpdatedAfter(ctx context.Context, updatedAt time.Time) ([]Replica, error) + GetRuntimeConfig(ctx context.Context, key string) (string, error) + GetTailnetAgents(ctx context.Context, id uuid.UUID) ([]TailnetAgent, error) + GetTailnetClientsForAgent(ctx context.Context, agentID uuid.UUID) ([]TailnetClient, error) + GetTailnetPeers(ctx context.Context, id uuid.UUID) ([]TailnetPeer, error) + GetTailnetTunnelPeerBindings(ctx context.Context, srcID uuid.UUID) ([]GetTailnetTunnelPeerBindingsRow, error) + GetTailnetTunnelPeerIDs(ctx context.Context, srcID uuid.UUID) ([]GetTailnetTunnelPeerIDsRow, error) + GetTelemetryItem(ctx context.Context, key string) (TelemetryItem, error) + GetTelemetryItems(ctx context.Context) ([]TelemetryItem, error) + // GetTemplateAppInsights returns the aggregate usage of each app in a given + // timeframe. The result can be filtered on template_ids, meaning only user data + // from workspaces based on those templates will be included. + GetTemplateAppInsights(ctx context.Context, arg GetTemplateAppInsightsParams) ([]GetTemplateAppInsightsRow, error) + // GetTemplateAppInsightsByTemplate is used for Prometheus metrics. Keep + // in sync with GetTemplateAppInsights and UpsertTemplateUsageStats. + GetTemplateAppInsightsByTemplate(ctx context.Context, arg GetTemplateAppInsightsByTemplateParams) ([]GetTemplateAppInsightsByTemplateRow, error) + GetTemplateAverageBuildTime(ctx context.Context, arg GetTemplateAverageBuildTimeParams) (GetTemplateAverageBuildTimeRow, error) + GetTemplateByID(ctx context.Context, id uuid.UUID) (Template, error) + GetTemplateByOrganizationAndName(ctx context.Context, arg GetTemplateByOrganizationAndNameParams) (Template, error) + GetTemplateDAUs(ctx context.Context, arg GetTemplateDAUsParams) ([]GetTemplateDAUsRow, error) + // GetTemplateInsights returns the aggregate user-produced usage of all + // workspaces in a given timeframe. The template IDs, active users, and + // usage_seconds all reflect any usage in the template, including apps. + // + // When combining data from multiple templates, we must make a guess at + // how the user behaved for the 30 minute interval. In this case we make + // the assumption that if the user used two workspaces for 15 minutes, + // they did so sequentially, thus we sum the usage up to a maximum of + // 30 minutes with LEAST(SUM(n), 30). + GetTemplateInsights(ctx context.Context, arg GetTemplateInsightsParams) (GetTemplateInsightsRow, error) + // GetTemplateInsightsByInterval returns all intervals between start and end + // time, if end time is a partial interval, it will be included in the results and + // that interval will be shorter than a full one. If there is no data for a selected + // interval/template, it will be included in the results with 0 active users. + GetTemplateInsightsByInterval(ctx context.Context, arg GetTemplateInsightsByIntervalParams) ([]GetTemplateInsightsByIntervalRow, error) + // GetTemplateInsightsByTemplate is used for Prometheus metrics. Keep + // in sync with GetTemplateInsights and UpsertTemplateUsageStats. + GetTemplateInsightsByTemplate(ctx context.Context, arg GetTemplateInsightsByTemplateParams) ([]GetTemplateInsightsByTemplateRow, error) + // GetTemplateParameterInsights does for each template in a given timeframe, + // look for the latest workspace build (for every workspace) that has been + // created in the timeframe and return the aggregate usage counts of parameter + // values. + GetTemplateParameterInsights(ctx context.Context, arg GetTemplateParameterInsightsParams) ([]GetTemplateParameterInsightsRow, error) + GetTemplateUsageStats(ctx context.Context, arg GetTemplateUsageStatsParams) ([]TemplateUsageStat, error) + GetTemplateVersionByID(ctx context.Context, id uuid.UUID) (TemplateVersion, error) + GetTemplateVersionByJobID(ctx context.Context, jobID uuid.UUID) (TemplateVersion, error) + GetTemplateVersionByTemplateIDAndName(ctx context.Context, arg GetTemplateVersionByTemplateIDAndNameParams) (TemplateVersion, error) + GetTemplateVersionParameters(ctx context.Context, templateVersionID uuid.UUID) ([]TemplateVersionParameter, error) + GetTemplateVersionVariables(ctx context.Context, templateVersionID uuid.UUID) ([]TemplateVersionVariable, error) + GetTemplateVersionWorkspaceTags(ctx context.Context, templateVersionID uuid.UUID) ([]TemplateVersionWorkspaceTag, error) + GetTemplateVersionsByIDs(ctx context.Context, ids []uuid.UUID) ([]TemplateVersion, error) + GetTemplateVersionsByTemplateID(ctx context.Context, arg GetTemplateVersionsByTemplateIDParams) ([]TemplateVersion, error) + GetTemplateVersionsCreatedAfter(ctx context.Context, createdAt time.Time) ([]TemplateVersion, error) + GetTemplates(ctx context.Context) ([]Template, error) + GetTemplatesWithFilter(ctx context.Context, arg GetTemplatesWithFilterParams) ([]Template, error) + GetUnexpiredLicenses(ctx context.Context) ([]License, error) + // GetUserActivityInsights returns the ranking with top active users. + // The result can be filtered on template_ids, meaning only user data + // from workspaces based on those templates will be included. + // Note: The usage_seconds and usage_seconds_cumulative differ only when + // requesting deployment-wide (or multiple template) data. Cumulative + // produces a bloated value if a user has used multiple templates + // simultaneously. + GetUserActivityInsights(ctx context.Context, arg GetUserActivityInsightsParams) ([]GetUserActivityInsightsRow, error) + GetUserByEmailOrUsername(ctx context.Context, arg GetUserByEmailOrUsernameParams) (User, error) + GetUserByID(ctx context.Context, id uuid.UUID) (User, error) + GetUserCount(ctx context.Context) (int64, error) + // GetUserLatencyInsights returns the median and 95th percentile connection + // latency that users have experienced. The result can be filtered on + // template_ids, meaning only user data from workspaces based on those templates + // will be included. + GetUserLatencyInsights(ctx context.Context, arg GetUserLatencyInsightsParams) ([]GetUserLatencyInsightsRow, error) + GetUserLinkByLinkedID(ctx context.Context, linkedID string) (UserLink, error) + GetUserLinkByUserIDLoginType(ctx context.Context, arg GetUserLinkByUserIDLoginTypeParams) (UserLink, error) + GetUserLinksByUserID(ctx context.Context, userID uuid.UUID) ([]UserLink, error) + GetUserNotificationPreferences(ctx context.Context, userID uuid.UUID) ([]NotificationPreference, error) + // GetUserStatusCounts returns the count of users in each status over time. + // The time range is inclusively defined by the start_time and end_time parameters. + // + // Bucketing: + // Between the start_time and end_time, we include each timestamp where a user's status changed or they were deleted. + // We do not bucket these results by day or some other time unit. This is because such bucketing would hide potentially + // important patterns. If a user was active for 23 hours and 59 minutes, and then suspended, a daily bucket would hide this. + // A daily bucket would also have required us to carefully manage the timezone of the bucket based on the timezone of the user. + // + // Accumulation: + // We do not start counting from 0 at the start_time. We check the last status change before the start_time for each user. As such, + // the result shows the total number of users in each status on any particular day. + GetUserStatusCounts(ctx context.Context, arg GetUserStatusCountsParams) ([]GetUserStatusCountsRow, error) + GetUserWorkspaceBuildParameters(ctx context.Context, arg GetUserWorkspaceBuildParametersParams) ([]GetUserWorkspaceBuildParametersRow, error) + // This will never return deleted users. + GetUsers(ctx context.Context, arg GetUsersParams) ([]GetUsersRow, error) + // This shouldn't check for deleted, because it's frequently used + // 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) + 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) + GetWorkspaceAgentLifecycleStateByID(ctx context.Context, id uuid.UUID) (GetWorkspaceAgentLifecycleStateByIDRow, error) + GetWorkspaceAgentLogSourcesByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceAgentLogSource, error) + GetWorkspaceAgentLogsAfter(ctx context.Context, arg GetWorkspaceAgentLogsAfterParams) ([]WorkspaceAgentLog, error) + GetWorkspaceAgentMetadata(ctx context.Context, arg GetWorkspaceAgentMetadataParams) ([]WorkspaceAgentMetadatum, error) + GetWorkspaceAgentPortShare(ctx context.Context, arg GetWorkspaceAgentPortShareParams) (WorkspaceAgentPortShare, error) + GetWorkspaceAgentScriptTimingsByBuildID(ctx context.Context, id uuid.UUID) ([]GetWorkspaceAgentScriptTimingsByBuildIDRow, error) + GetWorkspaceAgentScriptsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceAgentScript, error) + GetWorkspaceAgentStats(ctx context.Context, createdAt time.Time) ([]GetWorkspaceAgentStatsRow, error) + GetWorkspaceAgentStatsAndLabels(ctx context.Context, createdAt time.Time) ([]GetWorkspaceAgentStatsAndLabelsRow, error) + // `minute_buckets` could return 0 rows if there are no usage stats since `created_at`. + GetWorkspaceAgentUsageStats(ctx context.Context, createdAt time.Time) ([]GetWorkspaceAgentUsageStatsRow, error) + GetWorkspaceAgentUsageStatsAndLabels(ctx context.Context, createdAt time.Time) ([]GetWorkspaceAgentUsageStatsAndLabelsRow, error) + GetWorkspaceAgentsByResourceIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceAgent, error) + GetWorkspaceAgentsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceAgent, error) + GetWorkspaceAgentsInLatestBuildByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) ([]WorkspaceAgent, error) + GetWorkspaceAppByAgentIDAndSlug(ctx context.Context, arg GetWorkspaceAppByAgentIDAndSlugParams) (WorkspaceApp, error) + GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid.UUID) ([]WorkspaceApp, error) + GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceApp, error) + GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceApp, error) + GetWorkspaceBuildByID(ctx context.Context, id uuid.UUID) (WorkspaceBuild, error) + GetWorkspaceBuildByJobID(ctx context.Context, jobID uuid.UUID) (WorkspaceBuild, error) + GetWorkspaceBuildByWorkspaceIDAndBuildNumber(ctx context.Context, arg GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams) (WorkspaceBuild, error) + GetWorkspaceBuildParameters(ctx context.Context, workspaceBuildID uuid.UUID) ([]WorkspaceBuildParameter, error) + GetWorkspaceBuildStatsByTemplates(ctx context.Context, since time.Time) ([]GetWorkspaceBuildStatsByTemplatesRow, error) + GetWorkspaceBuildsByWorkspaceID(ctx context.Context, arg GetWorkspaceBuildsByWorkspaceIDParams) ([]WorkspaceBuild, error) + GetWorkspaceBuildsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceBuild, error) + GetWorkspaceByAgentID(ctx context.Context, agentID uuid.UUID) (Workspace, error) + GetWorkspaceByID(ctx context.Context, id uuid.UUID) (Workspace, error) + GetWorkspaceByOwnerIDAndName(ctx context.Context, arg GetWorkspaceByOwnerIDAndNameParams) (Workspace, error) + GetWorkspaceByWorkspaceAppID(ctx context.Context, workspaceAppID uuid.UUID) (Workspace, error) + GetWorkspaceModulesByJobID(ctx context.Context, jobID uuid.UUID) ([]WorkspaceModule, error) + GetWorkspaceModulesCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceModule, error) + GetWorkspaceProxies(ctx context.Context) ([]WorkspaceProxy, error) + // Finds a workspace proxy that has an access URL or app hostname that matches + // the provided hostname. This is to check if a hostname matches any workspace + // proxy. + // + // The hostname must be sanitized to only contain [a-zA-Z0-9.-] before calling + // this query. The scheme, port and path should be stripped. + // + GetWorkspaceProxyByHostname(ctx context.Context, arg GetWorkspaceProxyByHostnameParams) (WorkspaceProxy, error) + GetWorkspaceProxyByID(ctx context.Context, id uuid.UUID) (WorkspaceProxy, error) + GetWorkspaceProxyByName(ctx context.Context, name string) (WorkspaceProxy, error) + GetWorkspaceResourceByID(ctx context.Context, id uuid.UUID) (WorkspaceResource, error) + GetWorkspaceResourceMetadataByResourceIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceResourceMetadatum, error) + GetWorkspaceResourceMetadataCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceResourceMetadatum, error) + GetWorkspaceResourcesByJobID(ctx context.Context, jobID uuid.UUID) ([]WorkspaceResource, error) + GetWorkspaceResourcesByJobIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceResource, error) + GetWorkspaceResourcesCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceResource, error) + GetWorkspaceUniqueOwnerCountByTemplateIDs(ctx context.Context, templateIds []uuid.UUID) ([]GetWorkspaceUniqueOwnerCountByTemplateIDsRow, error) + // build_params is used to filter by build parameters if present. + // It has to be a CTE because the set returning function 'unnest' cannot + // be used in a WHERE clause. + GetWorkspaces(ctx context.Context, arg GetWorkspacesParams) ([]GetWorkspacesRow, error) + GetWorkspacesAndAgentsByOwnerID(ctx context.Context, ownerID uuid.UUID) ([]GetWorkspacesAndAgentsByOwnerIDRow, error) + GetWorkspacesByTemplateID(ctx context.Context, templateID uuid.UUID) ([]WorkspaceTable, error) + GetWorkspacesEligibleForTransition(ctx context.Context, now time.Time) ([]GetWorkspacesEligibleForTransitionRow, error) + InsertAPIKey(ctx context.Context, arg InsertAPIKeyParams) (APIKey, error) + // We use the organization_id as the id + // for simplicity since all users is + // every member of the org. + InsertAllUsersGroup(ctx context.Context, organizationID uuid.UUID) (Group, error) + InsertAuditLog(ctx context.Context, arg InsertAuditLogParams) (AuditLog, error) + InsertCryptoKey(ctx context.Context, arg InsertCryptoKeyParams) (CryptoKey, error) + InsertCustomRole(ctx context.Context, arg InsertCustomRoleParams) (CustomRole, error) + InsertDBCryptKey(ctx context.Context, arg InsertDBCryptKeyParams) error + InsertDERPMeshKey(ctx context.Context, value string) error + InsertDeploymentID(ctx context.Context, value string) error + InsertExternalAuthLink(ctx context.Context, arg InsertExternalAuthLinkParams) (ExternalAuthLink, error) + InsertFile(ctx context.Context, arg InsertFileParams) (File, error) + InsertGitSSHKey(ctx context.Context, arg InsertGitSSHKeyParams) (GitSSHKey, error) + InsertGroup(ctx context.Context, arg InsertGroupParams) (Group, error) + InsertGroupMember(ctx context.Context, arg InsertGroupMemberParams) error + InsertInboxNotification(ctx context.Context, arg InsertInboxNotificationParams) (InboxNotification, error) + InsertLicense(ctx context.Context, arg InsertLicenseParams) (License, error) + InsertMemoryResourceMonitor(ctx context.Context, arg InsertMemoryResourceMonitorParams) (WorkspaceAgentMemoryResourceMonitor, error) + // Inserts any group by name that does not exist. All new groups are given + // a random uuid, are inserted into the same organization. They have the default + // values for avatar, display name, and quota allowance (all zero values). + // If the name conflicts, do nothing. + InsertMissingGroups(ctx context.Context, arg InsertMissingGroupsParams) ([]Group, error) + InsertOAuth2ProviderApp(ctx context.Context, arg InsertOAuth2ProviderAppParams) (OAuth2ProviderApp, error) + InsertOAuth2ProviderAppCode(ctx context.Context, arg InsertOAuth2ProviderAppCodeParams) (OAuth2ProviderAppCode, error) + InsertOAuth2ProviderAppSecret(ctx context.Context, arg InsertOAuth2ProviderAppSecretParams) (OAuth2ProviderAppSecret, error) + InsertOAuth2ProviderAppToken(ctx context.Context, arg InsertOAuth2ProviderAppTokenParams) (OAuth2ProviderAppToken, error) + InsertOrganization(ctx context.Context, arg InsertOrganizationParams) (Organization, error) + InsertOrganizationMember(ctx context.Context, arg InsertOrganizationMemberParams) (OrganizationMember, error) + InsertPreset(ctx context.Context, arg InsertPresetParams) (TemplateVersionPreset, error) + InsertPresetParameters(ctx context.Context, arg InsertPresetParametersParams) ([]TemplateVersionPresetParameter, error) + InsertProvisionerJob(ctx context.Context, arg InsertProvisionerJobParams) (ProvisionerJob, error) + InsertProvisionerJobLogs(ctx context.Context, arg InsertProvisionerJobLogsParams) ([]ProvisionerJobLog, error) + InsertProvisionerJobTimings(ctx context.Context, arg InsertProvisionerJobTimingsParams) ([]ProvisionerJobTiming, error) + InsertProvisionerKey(ctx context.Context, arg InsertProvisionerKeyParams) (ProvisionerKey, error) + InsertReplica(ctx context.Context, arg InsertReplicaParams) (Replica, error) + InsertTelemetryItemIfNotExists(ctx context.Context, arg InsertTelemetryItemIfNotExistsParams) error + InsertTemplate(ctx context.Context, arg InsertTemplateParams) error + InsertTemplateVersion(ctx context.Context, arg InsertTemplateVersionParams) error + InsertTemplateVersionParameter(ctx context.Context, arg InsertTemplateVersionParameterParams) (TemplateVersionParameter, error) + InsertTemplateVersionVariable(ctx context.Context, arg InsertTemplateVersionVariableParams) (TemplateVersionVariable, error) + InsertTemplateVersionWorkspaceTag(ctx context.Context, arg InsertTemplateVersionWorkspaceTagParams) (TemplateVersionWorkspaceTag, error) + InsertUser(ctx context.Context, arg InsertUserParams) (User, error) + // InsertUserGroupsByID adds a user to all provided groups, if they exist. + // If there is a conflict, the user is already a member + InsertUserGroupsByID(ctx context.Context, arg InsertUserGroupsByIDParams) ([]uuid.UUID, error) + // InsertUserGroupsByName adds a user to all provided groups, if they exist. + InsertUserGroupsByName(ctx context.Context, arg InsertUserGroupsByNameParams) error + InsertUserLink(ctx context.Context, arg InsertUserLinkParams) (UserLink, error) + InsertVolumeResourceMonitor(ctx context.Context, arg InsertVolumeResourceMonitorParams) (WorkspaceAgentVolumeResourceMonitor, error) + InsertWorkspace(ctx context.Context, arg InsertWorkspaceParams) (WorkspaceTable, error) + InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspaceAgentParams) (WorkspaceAgent, error) + InsertWorkspaceAgentLogSources(ctx context.Context, arg InsertWorkspaceAgentLogSourcesParams) ([]WorkspaceAgentLogSource, error) + InsertWorkspaceAgentLogs(ctx context.Context, arg InsertWorkspaceAgentLogsParams) ([]WorkspaceAgentLog, error) + InsertWorkspaceAgentMetadata(ctx context.Context, arg InsertWorkspaceAgentMetadataParams) error + InsertWorkspaceAgentScriptTimings(ctx context.Context, arg InsertWorkspaceAgentScriptTimingsParams) (WorkspaceAgentScriptTiming, error) + InsertWorkspaceAgentScripts(ctx context.Context, arg InsertWorkspaceAgentScriptsParams) ([]WorkspaceAgentScript, error) + InsertWorkspaceAgentStats(ctx context.Context, arg InsertWorkspaceAgentStatsParams) error + InsertWorkspaceApp(ctx context.Context, arg InsertWorkspaceAppParams) (WorkspaceApp, error) + InsertWorkspaceAppStats(ctx context.Context, arg InsertWorkspaceAppStatsParams) error + InsertWorkspaceBuild(ctx context.Context, arg InsertWorkspaceBuildParams) error + InsertWorkspaceBuildParameters(ctx context.Context, arg InsertWorkspaceBuildParametersParams) error + InsertWorkspaceModule(ctx context.Context, arg InsertWorkspaceModuleParams) (WorkspaceModule, error) + InsertWorkspaceProxy(ctx context.Context, arg InsertWorkspaceProxyParams) (WorkspaceProxy, error) + InsertWorkspaceResource(ctx context.Context, arg InsertWorkspaceResourceParams) (WorkspaceResource, error) + InsertWorkspaceResourceMetadata(ctx context.Context, arg InsertWorkspaceResourceMetadataParams) ([]WorkspaceResourceMetadatum, error) + ListProvisionerKeysByOrganization(ctx context.Context, organizationID uuid.UUID) ([]ProvisionerKey, error) + ListProvisionerKeysByOrganizationExcludeReserved(ctx context.Context, organizationID uuid.UUID) ([]ProvisionerKey, error) + ListWorkspaceAgentPortShares(ctx context.Context, workspaceID uuid.UUID) ([]WorkspaceAgentPortShare, error) + OIDCClaimFieldValues(ctx context.Context, arg OIDCClaimFieldValuesParams) ([]string, error) + // OIDCClaimFields returns a list of distinct keys in the the merged_claims fields. + // This query is used to generate the list of available sync fields for idp sync settings. + OIDCClaimFields(ctx context.Context, organizationID uuid.UUID) ([]string, error) + // Arguments are optional with uuid.Nil to ignore. + // - Use just 'organization_id' to get all members of an org + // - Use just 'user_id' to get all orgs a user is a member of + // - Use both to get a specific org member row + OrganizationMembers(ctx context.Context, arg OrganizationMembersParams) ([]OrganizationMembersRow, error) + ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(ctx context.Context, templateID uuid.UUID) error + RegisterWorkspaceProxy(ctx context.Context, arg RegisterWorkspaceProxyParams) (WorkspaceProxy, error) + RemoveUserFromAllGroups(ctx context.Context, userID uuid.UUID) error + RemoveUserFromGroups(ctx context.Context, arg RemoveUserFromGroupsParams) ([]uuid.UUID, error) + RevokeDBCryptKey(ctx context.Context, activeKeyDigest string) error + SetInboxNotificationAsRead(ctx context.Context, arg SetInboxNotificationAsReadParams) error + // Non blocking lock. Returns true if the lock was acquired, false otherwise. + // + // This must be called from within a transaction. The lock will be automatically + // released when the transaction ends. + TryAcquireLock(ctx context.Context, pgTryAdvisoryXactLock int64) (bool, error) + // This will always work regardless of the current state of the template version. + UnarchiveTemplateVersion(ctx context.Context, arg UnarchiveTemplateVersionParams) error + UnfavoriteWorkspace(ctx context.Context, id uuid.UUID) error + UpdateAPIKeyByID(ctx context.Context, arg UpdateAPIKeyByIDParams) error + UpdateCryptoKeyDeletesAt(ctx context.Context, arg UpdateCryptoKeyDeletesAtParams) (CryptoKey, error) + UpdateCustomRole(ctx context.Context, arg UpdateCustomRoleParams) (CustomRole, error) + UpdateExternalAuthLink(ctx context.Context, arg UpdateExternalAuthLinkParams) (ExternalAuthLink, error) + UpdateExternalAuthLinkRefreshToken(ctx context.Context, arg UpdateExternalAuthLinkRefreshTokenParams) error + UpdateGitSSHKey(ctx context.Context, arg UpdateGitSSHKeyParams) (GitSSHKey, error) + UpdateGroupByID(ctx context.Context, arg UpdateGroupByIDParams) (Group, error) + UpdateInactiveUsersToDormant(ctx context.Context, arg UpdateInactiveUsersToDormantParams) ([]UpdateInactiveUsersToDormantRow, error) + UpdateMemberRoles(ctx context.Context, arg UpdateMemberRolesParams) (OrganizationMember, error) + UpdateMemoryResourceMonitor(ctx context.Context, arg UpdateMemoryResourceMonitorParams) error + UpdateNotificationTemplateMethodByID(ctx context.Context, arg UpdateNotificationTemplateMethodByIDParams) (NotificationTemplate, error) + UpdateOAuth2ProviderAppByID(ctx context.Context, arg UpdateOAuth2ProviderAppByIDParams) (OAuth2ProviderApp, error) + UpdateOAuth2ProviderAppSecretByID(ctx context.Context, arg UpdateOAuth2ProviderAppSecretByIDParams) (OAuth2ProviderAppSecret, error) + UpdateOrganization(ctx context.Context, arg UpdateOrganizationParams) (Organization, error) + UpdateProvisionerDaemonLastSeenAt(ctx context.Context, arg UpdateProvisionerDaemonLastSeenAtParams) error + UpdateProvisionerJobByID(ctx context.Context, arg UpdateProvisionerJobByIDParams) error + UpdateProvisionerJobWithCancelByID(ctx context.Context, arg UpdateProvisionerJobWithCancelByIDParams) error + UpdateProvisionerJobWithCompleteByID(ctx context.Context, arg UpdateProvisionerJobWithCompleteByIDParams) error + UpdateReplica(ctx context.Context, arg UpdateReplicaParams) (Replica, error) + UpdateTailnetPeerStatusByCoordinator(ctx context.Context, arg UpdateTailnetPeerStatusByCoordinatorParams) error + UpdateTemplateACLByID(ctx context.Context, arg UpdateTemplateACLByIDParams) error + UpdateTemplateAccessControlByID(ctx context.Context, arg UpdateTemplateAccessControlByIDParams) error + UpdateTemplateActiveVersionByID(ctx context.Context, arg UpdateTemplateActiveVersionByIDParams) error + UpdateTemplateDeletedByID(ctx context.Context, arg UpdateTemplateDeletedByIDParams) error + UpdateTemplateMetaByID(ctx context.Context, arg UpdateTemplateMetaByIDParams) error + UpdateTemplateScheduleByID(ctx context.Context, arg UpdateTemplateScheduleByIDParams) error + UpdateTemplateVersionByID(ctx context.Context, arg UpdateTemplateVersionByIDParams) error + UpdateTemplateVersionDescriptionByJobID(ctx context.Context, arg UpdateTemplateVersionDescriptionByJobIDParams) error + UpdateTemplateVersionExternalAuthProvidersByJobID(ctx context.Context, arg UpdateTemplateVersionExternalAuthProvidersByJobIDParams) error + UpdateTemplateWorkspacesLastUsedAt(ctx context.Context, arg UpdateTemplateWorkspacesLastUsedAtParams) error + UpdateUserAppearanceSettings(ctx context.Context, arg UpdateUserAppearanceSettingsParams) (User, error) + UpdateUserDeletedByID(ctx context.Context, id uuid.UUID) error + UpdateUserGithubComUserID(ctx context.Context, arg UpdateUserGithubComUserIDParams) error + UpdateUserHashedOneTimePasscode(ctx context.Context, arg UpdateUserHashedOneTimePasscodeParams) error + UpdateUserHashedPassword(ctx context.Context, arg UpdateUserHashedPasswordParams) error + UpdateUserLastSeenAt(ctx context.Context, arg UpdateUserLastSeenAtParams) (User, error) + UpdateUserLink(ctx context.Context, arg UpdateUserLinkParams) (UserLink, error) + UpdateUserLinkedID(ctx context.Context, arg UpdateUserLinkedIDParams) (UserLink, error) + UpdateUserLoginType(ctx context.Context, arg UpdateUserLoginTypeParams) (User, error) + UpdateUserNotificationPreferences(ctx context.Context, arg UpdateUserNotificationPreferencesParams) (int64, error) + UpdateUserProfile(ctx context.Context, arg UpdateUserProfileParams) (User, error) + UpdateUserQuietHoursSchedule(ctx context.Context, arg UpdateUserQuietHoursScheduleParams) (User, error) + UpdateUserRoles(ctx context.Context, arg UpdateUserRolesParams) (User, error) + UpdateUserStatus(ctx context.Context, arg UpdateUserStatusParams) (User, error) + UpdateVolumeResourceMonitor(ctx context.Context, arg UpdateVolumeResourceMonitorParams) error + UpdateWorkspace(ctx context.Context, arg UpdateWorkspaceParams) (WorkspaceTable, error) + UpdateWorkspaceAgentConnectionByID(ctx context.Context, arg UpdateWorkspaceAgentConnectionByIDParams) error + UpdateWorkspaceAgentLifecycleStateByID(ctx context.Context, arg UpdateWorkspaceAgentLifecycleStateByIDParams) error + UpdateWorkspaceAgentLogOverflowByID(ctx context.Context, arg UpdateWorkspaceAgentLogOverflowByIDParams) error + UpdateWorkspaceAgentMetadata(ctx context.Context, arg UpdateWorkspaceAgentMetadataParams) error + UpdateWorkspaceAgentStartupByID(ctx context.Context, arg UpdateWorkspaceAgentStartupByIDParams) error + UpdateWorkspaceAppHealthByID(ctx context.Context, arg UpdateWorkspaceAppHealthByIDParams) error + UpdateWorkspaceAutomaticUpdates(ctx context.Context, arg UpdateWorkspaceAutomaticUpdatesParams) error + UpdateWorkspaceAutostart(ctx context.Context, arg UpdateWorkspaceAutostartParams) error + UpdateWorkspaceBuildCostByID(ctx context.Context, arg UpdateWorkspaceBuildCostByIDParams) error + UpdateWorkspaceBuildDeadlineByID(ctx context.Context, arg UpdateWorkspaceBuildDeadlineByIDParams) error + UpdateWorkspaceBuildProvisionerStateByID(ctx context.Context, arg UpdateWorkspaceBuildProvisionerStateByIDParams) error + UpdateWorkspaceDeletedByID(ctx context.Context, arg UpdateWorkspaceDeletedByIDParams) error + UpdateWorkspaceDormantDeletingAt(ctx context.Context, arg UpdateWorkspaceDormantDeletingAtParams) (WorkspaceTable, error) + UpdateWorkspaceLastUsedAt(ctx context.Context, arg UpdateWorkspaceLastUsedAtParams) error + UpdateWorkspaceNextStartAt(ctx context.Context, arg UpdateWorkspaceNextStartAtParams) error + // This allows editing the properties of a workspace proxy. + UpdateWorkspaceProxy(ctx context.Context, arg UpdateWorkspaceProxyParams) (WorkspaceProxy, error) + UpdateWorkspaceProxyDeleted(ctx context.Context, arg UpdateWorkspaceProxyDeletedParams) error + UpdateWorkspaceTTL(ctx context.Context, arg UpdateWorkspaceTTLParams) error + UpdateWorkspacesDormantDeletingAtByTemplateID(ctx context.Context, arg UpdateWorkspacesDormantDeletingAtByTemplateIDParams) ([]WorkspaceTable, error) + UpdateWorkspacesTTLByTemplateID(ctx context.Context, arg UpdateWorkspacesTTLByTemplateIDParams) error + UpsertAnnouncementBanners(ctx context.Context, value string) error + UpsertAppSecurityKey(ctx context.Context, value string) error + UpsertApplicationName(ctx context.Context, value string) error + UpsertCoordinatorResumeTokenSigningKey(ctx context.Context, value string) error + // The default proxy is implied and not actually stored in the database. + // So we need to store it's configuration here for display purposes. + // The functional values are immutable and controlled implicitly. + UpsertDefaultProxy(ctx context.Context, arg UpsertDefaultProxyParams) error + UpsertHealthSettings(ctx context.Context, value string) error + UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg UpsertJFrogXrayScanByWorkspaceAndAgentIDParams) error + UpsertLastUpdateCheck(ctx context.Context, value string) error + UpsertLogoURL(ctx context.Context, value string) error + // Insert or update notification report generator logs with recent activity. + UpsertNotificationReportGeneratorLog(ctx context.Context, arg UpsertNotificationReportGeneratorLogParams) error + UpsertNotificationsSettings(ctx context.Context, value string) error + UpsertOAuthSigningKey(ctx context.Context, value string) error + UpsertProvisionerDaemon(ctx context.Context, arg UpsertProvisionerDaemonParams) (ProvisionerDaemon, error) + UpsertRuntimeConfig(ctx context.Context, arg UpsertRuntimeConfigParams) error + UpsertTailnetAgent(ctx context.Context, arg UpsertTailnetAgentParams) (TailnetAgent, error) + UpsertTailnetClient(ctx context.Context, arg UpsertTailnetClientParams) (TailnetClient, error) + UpsertTailnetClientSubscription(ctx context.Context, arg UpsertTailnetClientSubscriptionParams) error + UpsertTailnetCoordinator(ctx context.Context, id uuid.UUID) (TailnetCoordinator, error) + UpsertTailnetPeer(ctx context.Context, arg UpsertTailnetPeerParams) (TailnetPeer, error) + UpsertTailnetTunnel(ctx context.Context, arg UpsertTailnetTunnelParams) (TailnetTunnel, error) + UpsertTelemetryItem(ctx context.Context, arg UpsertTelemetryItemParams) error + // This query aggregates the workspace_agent_stats and workspace_app_stats data + // into a single table for efficient storage and querying. Half-hour buckets are + // 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 + UpsertWorkspaceAgentPortShare(ctx context.Context, arg UpsertWorkspaceAgentPortShareParams) (WorkspaceAgentPortShare, error) +} + +var _ sqlcQuerier = (*sqlQuerier)(nil) From d1ce11eb549c4b0af6376e4969a184e82705d817 Mon Sep 17 00:00:00 2001 From: defelmnq Date: Fri, 21 Feb 2025 07:30:34 +0000 Subject: [PATCH 16/37] improve uuid in seeding --- .../testdata/fixtures/000296_notifications_inbox.up.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coderd/database/migrations/testdata/fixtures/000296_notifications_inbox.up.sql b/coderd/database/migrations/testdata/fixtures/000296_notifications_inbox.up.sql index 2533e7ee38b80..fb4cecf096eae 100644 --- a/coderd/database/migrations/testdata/fixtures/000296_notifications_inbox.up.sql +++ b/coderd/database/migrations/testdata/fixtures/000296_notifications_inbox.up.sql @@ -13,8 +13,8 @@ INSERT INTO ) VALUES ( '68b396aa-7f53-4bf1-b8d8-4cbf5fa244e5', -- uuid - '45e89705-e09d-4850-bcec-f9a937f5d78d', -- uuid - '193590e9-918f-4ef9-be47-04625f49c4c3', -- uuid + '5755e622-fadd-44ca-98da-5df070491844', -- uuid + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', -- uuid ARRAY[]::UUID[], -- uuid[] 'Test Notification', 'This is a test notification', From 1f2fa2493579020061bf82e97a678b6e899d9de7 Mon Sep 17 00:00:00 2001 From: defelmnq Date: Fri, 21 Feb 2025 07:48:49 +0000 Subject: [PATCH 17/37] improve dbauthz testing --- coderd/database/dbauthz/dbauthz_test.go | 63 ++++++------------------- 1 file changed, 14 insertions(+), 49 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 8429bad3663d7..03df91ef7d9b9 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -4480,18 +4480,13 @@ func (s *MethodTestSuite) TestNotifications() { s.Run("FetchUnreadInboxNotificationsByUserID", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) - o := dbgen.Organization(s.T(), db, database.Organization{}) - tpl := dbgen.Template(s.T(), db, database.Template{ - OrganizationID: o.ID, - CreatedBy: u.ID, - }) notifID := uuid.New() notif := dbgen.NotificationInbox(s.T(), db, database.InsertInboxNotificationParams{ ID: notifID, UserID: u.ID, - TemplateID: tpl.ID, + TemplateID: notifications.TemplateWorkspaceAutoUpdated, Title: "test title", Content: "test content notification", Icon: "https://coder.com/favicon.ico", @@ -4503,18 +4498,13 @@ func (s *MethodTestSuite) TestNotifications() { s.Run("FetchInboxNotificationsByUserID", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) - o := dbgen.Organization(s.T(), db, database.Organization{}) - tpl := dbgen.Template(s.T(), db, database.Template{ - OrganizationID: o.ID, - CreatedBy: u.ID, - }) notifID := uuid.New() notif := dbgen.NotificationInbox(s.T(), db, database.InsertInboxNotificationParams{ ID: notifID, UserID: u.ID, - TemplateID: tpl.ID, + TemplateID: notifications.TemplateWorkspaceAutoUpdated, Title: "test title", Content: "test content notification", Icon: "https://coder.com/favicon.ico", @@ -4526,20 +4516,15 @@ func (s *MethodTestSuite) TestNotifications() { s.Run("FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) - o := dbgen.Organization(s.T(), db, database.Organization{}) - tpl := dbgen.Template(s.T(), db, database.Template{ - OrganizationID: o.ID, - CreatedBy: u.ID, - }) notifID := uuid.New() - targets := []uuid.UUID{u.ID, tpl.ID} + targets := []uuid.UUID{u.ID, notifications.TemplateWorkspaceAutoUpdated} notif := dbgen.NotificationInbox(s.T(), db, database.InsertInboxNotificationParams{ ID: notifID, UserID: u.ID, - TemplateID: tpl.ID, + TemplateID: notifications.TemplateWorkspaceAutoUpdated, Targets: targets, Title: "test title", Content: "test content notification", @@ -4549,27 +4534,22 @@ func (s *MethodTestSuite) TestNotifications() { check.Args(database.FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams{ UserID: u.ID, - Templates: []uuid.UUID{tpl.ID}, + Templates: []uuid.UUID{notifications.TemplateWorkspaceAutoUpdated}, Targets: []uuid.UUID{u.ID}, }).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif}) })) s.Run("FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) - o := dbgen.Organization(s.T(), db, database.Organization{}) - tpl := dbgen.Template(s.T(), db, database.Template{ - OrganizationID: o.ID, - CreatedBy: u.ID, - }) notifID := uuid.New() - targets := []uuid.UUID{u.ID, tpl.ID} + targets := []uuid.UUID{u.ID, notifications.TemplateWorkspaceAutoUpdated} notif := dbgen.NotificationInbox(s.T(), db, database.InsertInboxNotificationParams{ ID: notifID, UserID: u.ID, - TemplateID: tpl.ID, + TemplateID: notifications.TemplateWorkspaceAutoUpdated, Targets: targets, Title: "test title", Content: "test content notification", @@ -4579,27 +4559,22 @@ func (s *MethodTestSuite) TestNotifications() { check.Args(database.FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams{ UserID: u.ID, - Templates: []uuid.UUID{tpl.ID}, + Templates: []uuid.UUID{notifications.TemplateWorkspaceAutoUpdated}, Targets: []uuid.UUID{u.ID}, }).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif}) })) s.Run("GetInboxNotificationByID", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) - o := dbgen.Organization(s.T(), db, database.Organization{}) - tpl := dbgen.Template(s.T(), db, database.Template{ - OrganizationID: o.ID, - CreatedBy: u.ID, - }) notifID := uuid.New() - targets := []uuid.UUID{u.ID, tpl.ID} + targets := []uuid.UUID{u.ID, notifications.TemplateWorkspaceAutoUpdated} notif := dbgen.NotificationInbox(s.T(), db, database.InsertInboxNotificationParams{ ID: notifID, UserID: u.ID, - TemplateID: tpl.ID, + TemplateID: notifications.TemplateWorkspaceAutoUpdated, Targets: targets, Title: "test title", Content: "test content notification", @@ -4612,20 +4587,15 @@ func (s *MethodTestSuite) TestNotifications() { s.Run("InsertInboxNotification", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) - o := dbgen.Organization(s.T(), db, database.Organization{}) - tpl := dbgen.Template(s.T(), db, database.Template{ - OrganizationID: o.ID, - CreatedBy: u.ID, - }) notifID := uuid.New() - targets := []uuid.UUID{u.ID, tpl.ID} + targets := []uuid.UUID{u.ID, notifications.TemplateWorkspaceAutoUpdated} check.Args(database.InsertInboxNotificationParams{ ID: notifID, UserID: u.ID, - TemplateID: tpl.ID, + TemplateID: notifications.TemplateWorkspaceAutoUpdated, Targets: targets, Title: "test title", Content: "test content notification", @@ -4636,21 +4606,16 @@ func (s *MethodTestSuite) TestNotifications() { s.Run("SetInboxNotificationAsRead", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) - o := dbgen.Organization(s.T(), db, database.Organization{}) - tpl := dbgen.Template(s.T(), db, database.Template{ - OrganizationID: o.ID, - CreatedBy: u.ID, - }) notifID := uuid.New() - targets := []uuid.UUID{u.ID, tpl.ID} + targets := []uuid.UUID{u.ID, notifications.TemplateWorkspaceAutoUpdated} readAt := dbtestutil.NowInDefaultTimezone() notif := dbgen.NotificationInbox(s.T(), db, database.InsertInboxNotificationParams{ ID: notifID, UserID: u.ID, - TemplateID: tpl.ID, + TemplateID: notifications.TemplateWorkspaceAutoUpdated, Targets: targets, Title: "test title", Content: "test content notification", From 791ed767c68b695ade5e7f57cb5d0b8a809fcac2 Mon Sep 17 00:00:00 2001 From: defelmnq Date: Fri, 21 Feb 2025 11:33:04 +0000 Subject: [PATCH 18/37] format migration --- .../000296_notifications_inbox.up.sql | 20 +++++++++---------- .../database/queries/notificationsinbox.sql | 7 +++++++ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/coderd/database/migrations/000296_notifications_inbox.up.sql b/coderd/database/migrations/000296_notifications_inbox.up.sql index d1369cced76b7..f89090e029cbe 100644 --- a/coderd/database/migrations/000296_notifications_inbox.up.sql +++ b/coderd/database/migrations/000296_notifications_inbox.up.sql @@ -1,14 +1,14 @@ CREATE TABLE inbox_notifications ( - id UUID PRIMARY KEY, - user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, - template_id UUID NOT NULL REFERENCES notification_templates(id) ON DELETE CASCADE, - targets UUID[], - title TEXT NOT NULL, - content TEXT NOT NULL, - icon TEXT NOT NULL, - actions JSONB NOT NULL, - read_at TIMESTAMP WITH TIME ZONE, - created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() + id UUID PRIMARY KEY, + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + template_id UUID NOT NULL REFERENCES notification_templates(id) ON DELETE CASCADE, + targets UUID[], + title TEXT NOT NULL, + content TEXT NOT NULL, + icon TEXT NOT NULL, + actions JSONB NOT NULL, + read_at TIMESTAMP WITH TIME ZONE, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() ); CREATE INDEX idx_inbox_notifications_user_id_read_at ON inbox_notifications(user_id, read_at); diff --git a/coderd/database/queries/notificationsinbox.sql b/coderd/database/queries/notificationsinbox.sql index 089d6c4b78a61..7682b95b66055 100644 --- a/coderd/database/queries/notificationsinbox.sql +++ b/coderd/database/queries/notificationsinbox.sql @@ -5,9 +5,16 @@ SELECT * FROM inbox_notifications WHERE user_id = $1 AND read_at IS NULL ORDER B SELECT * FROM inbox_notifications WHERE user_id = $1 ORDER BY created_at DESC; -- name: FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many +-- Fetches inbox notifications for a user filtered by templates and targets +-- @param user_id: The user ID +-- @param templates: The template IDs to filter by - the template_id = ANY(@templates::UUID[]) condition checks if the template_id is in the @templates array +-- @param targets: The target IDs to filter by - the targets @> COALESCE(@targets, ARRAY[]::UUID[]) condition checks if the targets array (from the DB) contains all the elements in the @targets array SELECT * FROM inbox_notifications WHERE user_id = @user_id AND template_id = ANY(@templates::UUID[]) AND targets @> COALESCE(@targets, ARRAY[]::UUID[]) ORDER BY created_at DESC; -- name: FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many +-- @param user_id: The user ID +-- @param templates: The template IDs to filter by - the template_id = ANY(@templates::UUID[]) condition checks if the template_id is in the @templates array +-- @param targets: The target IDs to filter by - the targets @> COALESCE(@targets, ARRAY[]::UUID[]) condition checks if the targets array (from the DB) contains all the elements in the @targets array SELECT * FROM inbox_notifications WHERE user_id = @user_id AND template_id = ANY(@templates::UUID[]) AND targets @> COALESCE(@targets, ARRAY[]::UUID[]) AND read_at IS NULL ORDER BY created_at DESC; -- name: GetInboxNotificationByID :one From e14135460b0671117eee4b1a861539bdd4b8bd8a Mon Sep 17 00:00:00 2001 From: defelmnq Date: Fri, 21 Feb 2025 11:36:29 +0000 Subject: [PATCH 19/37] format migration --- coderd/database/migrations/000296_notifications_inbox.up.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/database/migrations/000296_notifications_inbox.up.sql b/coderd/database/migrations/000296_notifications_inbox.up.sql index f89090e029cbe..7c6b81afa89d9 100644 --- a/coderd/database/migrations/000296_notifications_inbox.up.sql +++ b/coderd/database/migrations/000296_notifications_inbox.up.sql @@ -8,7 +8,7 @@ CREATE TABLE inbox_notifications ( icon TEXT NOT NULL, actions JSONB NOT NULL, read_at TIMESTAMP WITH TIME ZONE, - created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() ); CREATE INDEX idx_inbox_notifications_user_id_read_at ON inbox_notifications(user_id, read_at); From a14723b280507fdd6346e9e6574823028ec15b01 Mon Sep 17 00:00:00 2001 From: defelmnq Date: Fri, 21 Feb 2025 12:39:57 +0000 Subject: [PATCH 20/37] format migration --- coderd/database/queries/notificationsinbox.sql | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/coderd/database/queries/notificationsinbox.sql b/coderd/database/queries/notificationsinbox.sql index 7682b95b66055..9903f64248855 100644 --- a/coderd/database/queries/notificationsinbox.sql +++ b/coderd/database/queries/notificationsinbox.sql @@ -1,4 +1,5 @@ -- name: FetchUnreadInboxNotificationsByUserID :many +-- SELECT * FROM inbox_notifications WHERE user_id = $1 AND read_at IS NULL ORDER BY created_at DESC; -- name: FetchInboxNotificationsByUserID :many @@ -6,15 +7,15 @@ SELECT * FROM inbox_notifications WHERE user_id = $1 ORDER BY created_at DESC; -- name: FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many -- Fetches inbox notifications for a user filtered by templates and targets --- @param user_id: The user ID --- @param templates: The template IDs to filter by - the template_id = ANY(@templates::UUID[]) condition checks if the template_id is in the @templates array --- @param targets: The target IDs to filter by - the targets @> COALESCE(@targets, ARRAY[]::UUID[]) condition checks if the targets array (from the DB) contains all the elements in the @targets array +-- param user_id: The user ID +-- param templates: The template IDs to filter by - the template_id = ANY(@templates::UUID[]) condition checks if the template_id is in the @templates array +-- param targets: The target IDs to filter by - the targets @> COALESCE(@targets, ARRAY[]::UUID[]) condition checks if the targets array (from the DB) contains all the elements in the @targets array SELECT * FROM inbox_notifications WHERE user_id = @user_id AND template_id = ANY(@templates::UUID[]) AND targets @> COALESCE(@targets, ARRAY[]::UUID[]) ORDER BY created_at DESC; -- name: FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many --- @param user_id: The user ID --- @param templates: The template IDs to filter by - the template_id = ANY(@templates::UUID[]) condition checks if the template_id is in the @templates array --- @param targets: The target IDs to filter by - the targets @> COALESCE(@targets, ARRAY[]::UUID[]) condition checks if the targets array (from the DB) contains all the elements in the @targets array +-- param user_id: The user ID +-- param templates: The template IDs to filter by - the template_id = ANY(@templates::UUID[]) condition checks if the template_id is in the @templates array +-- param targets: The target IDs to filter by - the targets @> COALESCE(@targets, ARRAY[]::UUID[]) condition checks if the targets array (from the DB) contains all the elements in the @targets array SELECT * FROM inbox_notifications WHERE user_id = @user_id AND template_id = ANY(@templates::UUID[]) AND targets @> COALESCE(@targets, ARRAY[]::UUID[]) AND read_at IS NULL ORDER BY created_at DESC; -- name: GetInboxNotificationByID :one @@ -36,7 +37,7 @@ INSERT INTO VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *; --- name: SetInboxNotificationAsRead :exec +-- name: UpdateInboxNotificationReadStatus :exec UPDATE inbox_notifications SET From e413ab97b3fc53497cf13c96833b3b944981c414 Mon Sep 17 00:00:00 2001 From: defelmnq Date: Fri, 21 Feb 2025 14:41:18 +0000 Subject: [PATCH 21/37] rename functions --- coderd/database/dbauthz/dbauthz.go | 22 +-- coderd/database/dbauthz/dbauthz_test.go | 16 +- coderd/database/dbmem/dbmem.go | 10 +- coderd/database/dbmetrics/querymetrics.go | 36 ++-- coderd/database/dbmock/dbmock.go | 60 +++---- coderd/database/querier.go | 10 +- coderd/database/queries.sql.go | 50 +++--- .../database/queries/notificationsinbox.sql | 8 +- docs/reference/cli/index.md | 169 ------------------ 9 files changed, 106 insertions(+), 275 deletions(-) delete mode 100644 docs/reference/cli/index.md diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 45ff59e41bfc2..a03840d927d95 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -1418,12 +1418,12 @@ func (q *querier) FavoriteWorkspace(ctx context.Context, id uuid.UUID) error { return update(q.log, q.auth, fetch, q.db.FavoriteWorkspace)(ctx, id) } -func (q *querier) FetchInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { - return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.FetchInboxNotificationsByUserID)(ctx, userID) +func (q *querier) GetInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { + return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetInboxNotificationsByUserID)(ctx, userID) } -func (q *querier) FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { - return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets)(ctx, arg) +func (q *querier) GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { + return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets)(ctx, arg) } func (q *querier) FetchMemoryResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) (database.WorkspaceAgentMemoryResourceMonitor, error) { @@ -1447,12 +1447,12 @@ func (q *querier) FetchNewMessageMetadata(ctx context.Context, arg database.Fetc return q.db.FetchNewMessageMetadata(ctx, arg) } -func (q *querier) FetchUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { - return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.FetchUnreadInboxNotificationsByUserID)(ctx, userID) +func (q *querier) GetUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { + return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetUnreadInboxNotificationsByUserID)(ctx, userID) } -func (q *querier) FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { - return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets)(ctx, arg) +func (q *querier) GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { + return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets)(ctx, arg) } func (q *querier) FetchVolumesResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) ([]database.WorkspaceAgentVolumeResourceMonitor, error) { @@ -3589,12 +3589,12 @@ func (q *querier) RevokeDBCryptKey(ctx context.Context, activeKeyDigest string) return q.db.RevokeDBCryptKey(ctx, activeKeyDigest) } -func (q *querier) SetInboxNotificationAsRead(ctx context.Context, args database.SetInboxNotificationAsReadParams) error { - fetchFunc := func(ctx context.Context, args database.SetInboxNotificationAsReadParams) (database.InboxNotification, error) { +func (q *querier) UpdateInboxNotificationAsRead(ctx context.Context, args database.UpdateInboxNotificationAsReadParams) error { + fetchFunc := func(ctx context.Context, args database.UpdateInboxNotificationAsReadParams) (database.InboxNotification, error) { return q.db.GetInboxNotificationByID(ctx, args.ID) } - return update(q.log, q.auth, fetchFunc, q.db.SetInboxNotificationAsRead)(ctx, args) + return update(q.log, q.auth, fetchFunc, q.db.UpdateInboxNotificationAsRead)(ctx, args) } func (q *querier) TryAcquireLock(ctx context.Context, id int64) (bool, error) { diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 03df91ef7d9b9..d59fb5469eeba 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -4478,7 +4478,7 @@ func (s *MethodTestSuite) TestNotifications() { }).Asserts(rbac.ResourceNotificationPreference.WithOwner(user.ID.String()), policy.ActionUpdate) })) - s.Run("FetchUnreadInboxNotificationsByUserID", s.Subtest(func(db database.Store, check *expects) { + s.Run("GetUnreadInboxNotificationsByUserID", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) notifID := uuid.New() @@ -4496,7 +4496,7 @@ func (s *MethodTestSuite) TestNotifications() { check.Args(u.ID).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif}) })) - s.Run("FetchInboxNotificationsByUserID", s.Subtest(func(db database.Store, check *expects) { + s.Run("GetInboxNotificationsByUserID", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) notifID := uuid.New() @@ -4514,7 +4514,7 @@ func (s *MethodTestSuite) TestNotifications() { check.Args(u.ID).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif}) })) - s.Run("FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets", s.Subtest(func(db database.Store, check *expects) { + s.Run("GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) notifID := uuid.New() @@ -4532,14 +4532,14 @@ func (s *MethodTestSuite) TestNotifications() { Actions: json.RawMessage("{}"), }) - check.Args(database.FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams{ + check.Args(database.GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams{ UserID: u.ID, Templates: []uuid.UUID{notifications.TemplateWorkspaceAutoUpdated}, Targets: []uuid.UUID{u.ID}, }).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif}) })) - s.Run("FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets", s.Subtest(func(db database.Store, check *expects) { + s.Run("GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) notifID := uuid.New() @@ -4557,7 +4557,7 @@ func (s *MethodTestSuite) TestNotifications() { Actions: json.RawMessage("{}"), }) - check.Args(database.FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams{ + check.Args(database.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams{ UserID: u.ID, Templates: []uuid.UUID{notifications.TemplateWorkspaceAutoUpdated}, Targets: []uuid.UUID{u.ID}, @@ -4604,7 +4604,7 @@ func (s *MethodTestSuite) TestNotifications() { }).Asserts(rbac.ResourceInboxNotification.WithOwner(u.ID.String()), policy.ActionCreate) })) - s.Run("SetInboxNotificationAsRead", s.Subtest(func(db database.Store, check *expects) { + s.Run("UpdateInboxNotificationAsRead", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) notifID := uuid.New() @@ -4625,7 +4625,7 @@ func (s *MethodTestSuite) TestNotifications() { notif.ReadAt = sql.NullTime{Time: readAt, Valid: true} - check.Args(database.SetInboxNotificationAsReadParams{ + check.Args(database.UpdateInboxNotificationAsReadParams{ ID: notifID, ReadAt: sql.NullTime{Time: readAt, Valid: true}, }).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionUpdate) diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 9dfd9974b72d5..70ebdea74615b 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -2365,7 +2365,7 @@ func (q *FakeQuerier) FavoriteWorkspace(_ context.Context, arg uuid.UUID) error return nil } -func (q *FakeQuerier) FetchInboxNotificationsByUserID(_ context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { +func (q *FakeQuerier) GetInboxNotificationsByUserID(_ context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { q.mutex.RLock() defer q.mutex.RUnlock() @@ -2379,7 +2379,7 @@ func (q *FakeQuerier) FetchInboxNotificationsByUserID(_ context.Context, userID return notifications, nil } -func (q *FakeQuerier) FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets(_ context.Context, arg database.FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { +func (q *FakeQuerier) GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(_ context.Context, arg database.GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { q.mutex.RLock() defer q.mutex.RUnlock() @@ -2460,7 +2460,7 @@ func (q *FakeQuerier) FetchNewMessageMetadata(_ context.Context, arg database.Fe }, nil } -func (q *FakeQuerier) FetchUnreadInboxNotificationsByUserID(_ context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { +func (q *FakeQuerier) GetUnreadInboxNotificationsByUserID(_ context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { q.mutex.RLock() defer q.mutex.RUnlock() @@ -2474,7 +2474,7 @@ func (q *FakeQuerier) FetchUnreadInboxNotificationsByUserID(_ context.Context, u return notifications, nil } -func (q *FakeQuerier) FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(_ context.Context, arg database.FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { +func (q *FakeQuerier) GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(_ context.Context, arg database.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { q.mutex.RLock() defer q.mutex.RUnlock() @@ -9588,7 +9588,7 @@ func (q *FakeQuerier) RevokeDBCryptKey(_ context.Context, activeKeyDigest string return sql.ErrNoRows } -func (q *FakeQuerier) SetInboxNotificationAsRead(_ context.Context, arg database.SetInboxNotificationAsReadParams) error { +func (q *FakeQuerier) UpdateInboxNotificationAsRead(_ context.Context, arg database.UpdateInboxNotificationAsReadParams) error { err := validateDatabaseType(arg) if err != nil { return err diff --git a/coderd/database/dbmetrics/querymetrics.go b/coderd/database/dbmetrics/querymetrics.go index c18232ac54f0d..9869fdef01780 100644 --- a/coderd/database/dbmetrics/querymetrics.go +++ b/coderd/database/dbmetrics/querymetrics.go @@ -77,10 +77,10 @@ func (m queryMetricsStore) InTx(f func(database.Store) error, options *database. return m.dbMetrics.InTx(f, options) } -func (m queryMetricsStore) FetchUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx context.Context, arg database.FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { +func (m queryMetricsStore) GetUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx context.Context, arg database.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { start := time.Now() - r0, r1 := m.s.FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx, arg) - m.queryLatencies.WithLabelValues("FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets").Observe(time.Since(start).Seconds()) + r0, r1 := m.s.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx, arg) + m.queryLatencies.WithLabelValues("GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets").Observe(time.Since(start).Seconds()) return r0, r1 } @@ -441,17 +441,17 @@ func (m queryMetricsStore) FavoriteWorkspace(ctx context.Context, arg uuid.UUID) return r0 } -func (m queryMetricsStore) FetchInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { +func (m queryMetricsStore) GetInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { start := time.Now() - r0, r1 := m.s.FetchInboxNotificationsByUserID(ctx, userID) - m.queryLatencies.WithLabelValues("FetchInboxNotificationsByUserID").Observe(time.Since(start).Seconds()) + r0, r1 := m.s.GetInboxNotificationsByUserID(ctx, userID) + m.queryLatencies.WithLabelValues("GetInboxNotificationsByUserID").Observe(time.Since(start).Seconds()) return r0, r1 } -func (m queryMetricsStore) FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { +func (m queryMetricsStore) GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { start := time.Now() - r0, r1 := m.s.FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx, arg) - m.queryLatencies.WithLabelValues("FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets").Observe(time.Since(start).Seconds()) + r0, r1 := m.s.GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx, arg) + m.queryLatencies.WithLabelValues("GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets").Observe(time.Since(start).Seconds()) return r0, r1 } @@ -469,17 +469,17 @@ func (m queryMetricsStore) FetchNewMessageMetadata(ctx context.Context, arg data return r0, r1 } -func (m queryMetricsStore) FetchUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { +func (m queryMetricsStore) GetUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { start := time.Now() - r0, r1 := m.s.FetchUnreadInboxNotificationsByUserID(ctx, userID) - m.queryLatencies.WithLabelValues("FetchUnreadInboxNotificationsByUserID").Observe(time.Since(start).Seconds()) + r0, r1 := m.s.GetUnreadInboxNotificationsByUserID(ctx, userID) + m.queryLatencies.WithLabelValues("GetUnreadInboxNotificationsByUserID").Observe(time.Since(start).Seconds()) return r0, r1 } -func (m queryMetricsStore) FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { +func (m queryMetricsStore) GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { start := time.Now() - r0, r1 := m.s.FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx, arg) - m.queryLatencies.WithLabelValues("FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets").Observe(time.Since(start).Seconds()) + r0, r1 := m.s.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx, arg) + m.queryLatencies.WithLabelValues("GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets").Observe(time.Since(start).Seconds()) return r0, r1 } @@ -2296,10 +2296,10 @@ func (m queryMetricsStore) RevokeDBCryptKey(ctx context.Context, activeKeyDigest return r0 } -func (m queryMetricsStore) SetInboxNotificationAsRead(ctx context.Context, arg database.SetInboxNotificationAsReadParams) error { +func (m queryMetricsStore) UpdateInboxNotificationAsRead(ctx context.Context, arg database.UpdateInboxNotificationAsReadParams) error { start := time.Now() - r0 := m.s.SetInboxNotificationAsRead(ctx, arg) - m.queryLatencies.WithLabelValues("SetInboxNotificationAsRead").Observe(time.Since(start).Seconds()) + r0 := m.s.UpdateInboxNotificationAsRead(ctx, arg) + m.queryLatencies.WithLabelValues("UpdateInboxNotificationAsRead").Observe(time.Since(start).Seconds()) return r0 } diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index 3a360771c44db..21f6e634859a9 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -771,34 +771,34 @@ func (mr *MockStoreMockRecorder) FavoriteWorkspace(ctx, id any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FavoriteWorkspace", reflect.TypeOf((*MockStore)(nil).FavoriteWorkspace), ctx, id) } -// FetchInboxNotificationsByUserID mocks base method. -func (m *MockStore) FetchInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { +// GetInboxNotificationsByUserID mocks base method. +func (m *MockStore) GetInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "FetchInboxNotificationsByUserID", ctx, userID) + ret := m.ctrl.Call(m, "GetInboxNotificationsByUserID", ctx, userID) ret0, _ := ret[0].([]database.InboxNotification) ret1, _ := ret[1].(error) return ret0, ret1 } -// FetchInboxNotificationsByUserID indicates an expected call of FetchInboxNotificationsByUserID. -func (mr *MockStoreMockRecorder) FetchInboxNotificationsByUserID(ctx, userID any) *gomock.Call { +// GetInboxNotificationsByUserID indicates an expected call of GetInboxNotificationsByUserID. +func (mr *MockStoreMockRecorder) GetInboxNotificationsByUserID(ctx, userID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchInboxNotificationsByUserID", reflect.TypeOf((*MockStore)(nil).FetchInboxNotificationsByUserID), ctx, userID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInboxNotificationsByUserID", reflect.TypeOf((*MockStore)(nil).GetInboxNotificationsByUserID), ctx, userID) } -// FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets mocks base method. -func (m *MockStore) FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { +// GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets mocks base method. +func (m *MockStore) GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets", ctx, arg) + ret := m.ctrl.Call(m, "GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets", ctx, arg) ret0, _ := ret[0].([]database.InboxNotification) ret1, _ := ret[1].(error) return ret0, ret1 } -// FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets indicates an expected call of FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets. -func (mr *MockStoreMockRecorder) FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx, arg any) *gomock.Call { +// GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets indicates an expected call of GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets. +func (mr *MockStoreMockRecorder) GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx, arg any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets", reflect.TypeOf((*MockStore)(nil).FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets), ctx, arg) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets", reflect.TypeOf((*MockStore)(nil).GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets), ctx, arg) } // FetchMemoryResourceMonitorsByAgentID mocks base method. @@ -831,34 +831,34 @@ func (mr *MockStoreMockRecorder) FetchNewMessageMetadata(ctx, arg any) *gomock.C return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchNewMessageMetadata", reflect.TypeOf((*MockStore)(nil).FetchNewMessageMetadata), ctx, arg) } -// FetchUnreadInboxNotificationsByUserID mocks base method. -func (m *MockStore) FetchUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { +// GetUnreadInboxNotificationsByUserID mocks base method. +func (m *MockStore) GetUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "FetchUnreadInboxNotificationsByUserID", ctx, userID) + ret := m.ctrl.Call(m, "GetUnreadInboxNotificationsByUserID", ctx, userID) ret0, _ := ret[0].([]database.InboxNotification) ret1, _ := ret[1].(error) return ret0, ret1 } -// FetchUnreadInboxNotificationsByUserID indicates an expected call of FetchUnreadInboxNotificationsByUserID. -func (mr *MockStoreMockRecorder) FetchUnreadInboxNotificationsByUserID(ctx, userID any) *gomock.Call { +// GetUnreadInboxNotificationsByUserID indicates an expected call of GetUnreadInboxNotificationsByUserID. +func (mr *MockStoreMockRecorder) GetUnreadInboxNotificationsByUserID(ctx, userID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchUnreadInboxNotificationsByUserID", reflect.TypeOf((*MockStore)(nil).FetchUnreadInboxNotificationsByUserID), ctx, userID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUnreadInboxNotificationsByUserID", reflect.TypeOf((*MockStore)(nil).GetUnreadInboxNotificationsByUserID), ctx, userID) } -// FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets mocks base method. -func (m *MockStore) FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { +// GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets mocks base method. +func (m *MockStore) GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets", ctx, arg) + ret := m.ctrl.Call(m, "GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets", ctx, arg) ret0, _ := ret[0].([]database.InboxNotification) ret1, _ := ret[1].(error) return ret0, ret1 } -// FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets indicates an expected call of FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets. -func (mr *MockStoreMockRecorder) FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx, arg any) *gomock.Call { +// GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets indicates an expected call of GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets. +func (mr *MockStoreMockRecorder) GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx, arg any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets", reflect.TypeOf((*MockStore)(nil).FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets), ctx, arg) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets", reflect.TypeOf((*MockStore)(nil).GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets), ctx, arg) } // FetchVolumesResourceMonitorsByAgentID mocks base method. @@ -4879,18 +4879,18 @@ func (mr *MockStoreMockRecorder) RevokeDBCryptKey(ctx, activeKeyDigest any) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RevokeDBCryptKey", reflect.TypeOf((*MockStore)(nil).RevokeDBCryptKey), ctx, activeKeyDigest) } -// SetInboxNotificationAsRead mocks base method. -func (m *MockStore) SetInboxNotificationAsRead(ctx context.Context, arg database.SetInboxNotificationAsReadParams) error { +// UpdateInboxNotificationAsRead mocks base method. +func (m *MockStore) UpdateInboxNotificationAsRead(ctx context.Context, arg database.UpdateInboxNotificationAsReadParams) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SetInboxNotificationAsRead", ctx, arg) + ret := m.ctrl.Call(m, "UpdateInboxNotificationAsRead", ctx, arg) ret0, _ := ret[0].(error) return ret0 } -// SetInboxNotificationAsRead indicates an expected call of SetInboxNotificationAsRead. -func (mr *MockStoreMockRecorder) SetInboxNotificationAsRead(ctx, arg any) *gomock.Call { +// UpdateInboxNotificationAsRead indicates an expected call of UpdateInboxNotificationAsRead. +func (mr *MockStoreMockRecorder) UpdateInboxNotificationAsRead(ctx, arg any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetInboxNotificationAsRead", reflect.TypeOf((*MockStore)(nil).SetInboxNotificationAsRead), ctx, arg) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateInboxNotificationAsRead", reflect.TypeOf((*MockStore)(nil).UpdateInboxNotificationAsRead), ctx, arg) } // TryAcquireLock mocks base method. diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 7236e75141e92..b6de3756d3c62 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -112,13 +112,13 @@ type sqlcQuerier interface { DisableForeignKeysAndTriggers(ctx context.Context) error EnqueueNotificationMessage(ctx context.Context, arg EnqueueNotificationMessageParams) error FavoriteWorkspace(ctx context.Context, id uuid.UUID) error - FetchInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]InboxNotification, error) - FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]InboxNotification, error) + GetInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]InboxNotification, error) + GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]InboxNotification, error) FetchMemoryResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) (WorkspaceAgentMemoryResourceMonitor, error) // This is used to build up the notification_message's JSON payload. FetchNewMessageMetadata(ctx context.Context, arg FetchNewMessageMetadataParams) (FetchNewMessageMetadataRow, error) - FetchUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]InboxNotification, error) - FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]InboxNotification, error) + GetUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]InboxNotification, error) + GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]InboxNotification, error) FetchVolumesResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) ([]WorkspaceAgentVolumeResourceMonitor, error) GetAPIKeyByID(ctx context.Context, id string) (APIKey, error) // there is no unique constraint on empty token names @@ -469,7 +469,7 @@ type sqlcQuerier interface { RemoveUserFromAllGroups(ctx context.Context, userID uuid.UUID) error RemoveUserFromGroups(ctx context.Context, arg RemoveUserFromGroupsParams) ([]uuid.UUID, error) RevokeDBCryptKey(ctx context.Context, activeKeyDigest string) error - SetInboxNotificationAsRead(ctx context.Context, arg SetInboxNotificationAsReadParams) error + UpdateInboxNotificationAsRead(ctx context.Context, arg UpdateInboxNotificationAsReadParams) error // Non blocking lock. Returns true if the lock was acquired, false otherwise. // // This must be called from within a transaction. The lock will be automatically diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index da7fa43aa3737..ab1563e2f0bf4 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -3551,7 +3551,7 @@ func (q *sqlQuerier) GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, } const upsertJFrogXrayScanByWorkspaceAndAgentID = `-- name: UpsertJFrogXrayScanByWorkspaceAndAgentID :exec -INSERT INTO +INSERT INTO jfrog_xray_scans ( agent_id, workspace_id, @@ -3560,7 +3560,7 @@ INSERT INTO medium, results_url ) -VALUES +VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (agent_id, workspace_id) DO UPDATE SET critical = $3, high = $4, medium = $5, results_url = $6 @@ -4298,11 +4298,11 @@ func (q *sqlQuerier) UpsertNotificationReportGeneratorLog(ctx context.Context, a return err } -const fetchInboxNotificationsByUserID = `-- name: FetchInboxNotificationsByUserID :many +const fetchInboxNotificationsByUserID = `-- name: GetInboxNotificationsByUserID :many SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 ORDER BY created_at DESC ` -func (q *sqlQuerier) FetchInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]InboxNotification, error) { +func (q *sqlQuerier) GetInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]InboxNotification, error) { rows, err := q.db.QueryContext(ctx, fetchInboxNotificationsByUserID, userID) if err != nil { return nil, err @@ -4336,17 +4336,17 @@ func (q *sqlQuerier) FetchInboxNotificationsByUserID(ctx context.Context, userID return items, nil } -const fetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets = `-- name: FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many +const fetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets = `-- name: GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 AND template_id = ANY($2::UUID[]) AND targets @> COALESCE($3, ARRAY[]::UUID[]) ORDER BY created_at DESC ` -type FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams struct { +type GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams struct { UserID uuid.UUID `db:"user_id" json:"user_id"` Templates []uuid.UUID `db:"templates" json:"templates"` Targets []uuid.UUID `db:"targets" json:"targets"` } -func (q *sqlQuerier) FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]InboxNotification, error) { +func (q *sqlQuerier) GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]InboxNotification, error) { rows, err := q.db.QueryContext(ctx, fetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets, arg.UserID, pq.Array(arg.Templates), pq.Array(arg.Targets)) if err != nil { return nil, err @@ -4380,11 +4380,11 @@ func (q *sqlQuerier) FetchInboxNotificationsByUserIDFilteredByTemplatesAndTarget return items, nil } -const fetchUnreadInboxNotificationsByUserID = `-- name: FetchUnreadInboxNotificationsByUserID :many +const fetchUnreadInboxNotificationsByUserID = `-- name: GetUnreadInboxNotificationsByUserID :many SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 AND read_at IS NULL ORDER BY created_at DESC ` -func (q *sqlQuerier) FetchUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]InboxNotification, error) { +func (q *sqlQuerier) GetUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]InboxNotification, error) { rows, err := q.db.QueryContext(ctx, fetchUnreadInboxNotificationsByUserID, userID) if err != nil { return nil, err @@ -4418,17 +4418,17 @@ func (q *sqlQuerier) FetchUnreadInboxNotificationsByUserID(ctx context.Context, return items, nil } -const fetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets = `-- name: FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many +const fetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets = `-- name: GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 AND template_id = ANY($2::UUID[]) AND targets @> COALESCE($3, ARRAY[]::UUID[]) AND read_at IS NULL ORDER BY created_at DESC ` -type FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams struct { +type GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams struct { UserID uuid.UUID `db:"user_id" json:"user_id"` Templates []uuid.UUID `db:"templates" json:"templates"` Targets []uuid.UUID `db:"targets" json:"targets"` } -func (q *sqlQuerier) FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]InboxNotification, error) { +func (q *sqlQuerier) GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]InboxNotification, error) { rows, err := q.db.QueryContext(ctx, fetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets, arg.UserID, pq.Array(arg.Templates), pq.Array(arg.Targets)) if err != nil { return nil, err @@ -4541,7 +4541,7 @@ func (q *sqlQuerier) InsertInboxNotification(ctx context.Context, arg InsertInbo return i, err } -const setInboxNotificationAsRead = `-- name: SetInboxNotificationAsRead :exec +const setInboxNotificationAsRead = `-- name: UpdateInboxNotificationAsRead :exec UPDATE inbox_notifications SET @@ -4550,12 +4550,12 @@ WHERE id = $2 ` -type SetInboxNotificationAsReadParams struct { +type UpdateInboxNotificationAsReadParams struct { ReadAt sql.NullTime `db:"read_at" json:"read_at"` ID uuid.UUID `db:"id" json:"id"` } -func (q *sqlQuerier) SetInboxNotificationAsRead(ctx context.Context, arg SetInboxNotificationAsReadParams) error { +func (q *sqlQuerier) UpdateInboxNotificationAsRead(ctx context.Context, arg UpdateInboxNotificationAsReadParams) error { _, err := q.db.ExecContext(ctx, setInboxNotificationAsRead, arg.ReadAt, arg.ID) return err } @@ -7210,7 +7210,7 @@ FROM provisioner_keys WHERE organization_id = $1 -AND +AND lower(name) = lower($2) ` @@ -7326,10 +7326,10 @@ WHERE AND -- exclude reserved built-in key id != '00000000-0000-0000-0000-000000000001'::uuid -AND +AND -- exclude reserved user-auth key id != '00000000-0000-0000-0000-000000000002'::uuid -AND +AND -- exclude reserved psk key id != '00000000-0000-0000-0000-000000000003'::uuid ` @@ -9015,7 +9015,7 @@ func (q *sqlQuerier) GetTailnetTunnelPeerIDs(ctx context.Context, srcID uuid.UUI } const updateTailnetPeerStatusByCoordinator = `-- name: UpdateTailnetPeerStatusByCoordinator :exec -UPDATE +UPDATE tailnet_peers SET status = $2 @@ -10817,14 +10817,14 @@ DO $$ DECLARE table_record record; BEGIN - FOR table_record IN - SELECT table_schema, table_name - FROM information_schema.tables + FOR table_record IN + SELECT table_schema, table_name + FROM information_schema.tables WHERE table_schema NOT IN ('pg_catalog', 'information_schema') AND table_type = 'BASE TABLE' LOOP - EXECUTE format('ALTER TABLE %I.%I DISABLE TRIGGER ALL', - table_record.table_schema, + EXECUTE format('ALTER TABLE %I.%I DISABLE TRIGGER ALL', + table_record.table_schema, table_record.table_name); END LOOP; END; @@ -13931,7 +13931,7 @@ WITH agent_stats AS ( coalesce((PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY connection_median_latency_ms)), -1)::FLOAT AS workspace_connection_latency_95 FROM workspace_agent_stats -- The greater than 0 is to support legacy agents that don't report connection_median_latency_ms. - WHERE workspace_agent_stats.created_at > $1 AND connection_median_latency_ms > 0 + WHERE workspace_agent_stats.created_at > $1 AND connection_median_latency_ms > 0 GROUP BY user_id, agent_id, workspace_id, template_id ), latest_agent_stats AS ( SELECT diff --git a/coderd/database/queries/notificationsinbox.sql b/coderd/database/queries/notificationsinbox.sql index 9903f64248855..c27dad0ddd9cb 100644 --- a/coderd/database/queries/notificationsinbox.sql +++ b/coderd/database/queries/notificationsinbox.sql @@ -1,18 +1,18 @@ --- name: FetchUnreadInboxNotificationsByUserID :many +-- name: GetUnreadInboxNotificationsByUserID :many -- SELECT * FROM inbox_notifications WHERE user_id = $1 AND read_at IS NULL ORDER BY created_at DESC; --- name: FetchInboxNotificationsByUserID :many +-- name: GetInboxNotificationsByUserID :many SELECT * FROM inbox_notifications WHERE user_id = $1 ORDER BY created_at DESC; --- name: FetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many +-- name: GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many -- Fetches inbox notifications for a user filtered by templates and targets -- param user_id: The user ID -- param templates: The template IDs to filter by - the template_id = ANY(@templates::UUID[]) condition checks if the template_id is in the @templates array -- param targets: The target IDs to filter by - the targets @> COALESCE(@targets, ARRAY[]::UUID[]) condition checks if the targets array (from the DB) contains all the elements in the @targets array SELECT * FROM inbox_notifications WHERE user_id = @user_id AND template_id = ANY(@templates::UUID[]) AND targets @> COALESCE(@targets, ARRAY[]::UUID[]) ORDER BY created_at DESC; --- name: FetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many +-- name: GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many -- param user_id: The user ID -- param templates: The template IDs to filter by - the template_id = ANY(@templates::UUID[]) condition checks if the template_id is in the @templates array -- param targets: The target IDs to filter by - the targets @> COALESCE(@targets, ARRAY[]::UUID[]) condition checks if the targets array (from the DB) contains all the elements in the @targets array diff --git a/docs/reference/cli/index.md b/docs/reference/cli/index.md deleted file mode 100644 index 9ad8f5590e727..0000000000000 --- a/docs/reference/cli/index.md +++ /dev/null @@ -1,169 +0,0 @@ - -# coder - -## Usage - -```console -coder [global-flags] -``` - -## Description - -```console -Coder — A tool for provisioning self-hosted development environments with Terraform. - - Start a Coder server: - - $ coder server - - - Get started by creating a template from an example: - - $ coder templates init -``` - -## Subcommands - -| Name | Purpose | -|----------------------------------------------------|-------------------------------------------------------------------------------------------------------| -| [completion](./completion.md) | Install or update shell completion scripts for the detected or chosen shell. | -| [dotfiles](./dotfiles.md) | Personalize your workspace by applying a canonical dotfiles repository | -| [external-auth](./external-auth.md) | Manage external authentication | -| [login](./login.md) | Authenticate with Coder deployment | -| [logout](./logout.md) | Unauthenticate your local session | -| [netcheck](./netcheck.md) | Print network debug information for DERP and STUN | -| [notifications](./notifications.md) | Manage Coder notifications | -| [organizations](./organizations.md) | Organization related commands | -| [port-forward](./port-forward.md) | Forward ports from a workspace to the local machine. For reverse port forwarding, use "coder ssh -R". | -| [publickey](./publickey.md) | Output your Coder public key used for Git operations | -| [reset-password](./reset-password.md) | Directly connect to the database to reset a user's password | -| [state](./state.md) | Manually manage Terraform state to fix broken workspaces | -| [templates](./templates.md) | Manage templates | -| [tokens](./tokens.md) | Manage personal access tokens | -| [users](./users.md) | Manage users | -| [version](./version.md) | Show coder version | -| [autoupdate](./autoupdate.md) | Toggle auto-update policy for a workspace | -| [config-ssh](./config-ssh.md) | Add an SSH Host entry for your workspaces "ssh coder.workspace" | -| [create](./create.md) | Create a workspace | -| [delete](./delete.md) | Delete a workspace | -| [favorite](./favorite.md) | Add a workspace to your favorites | -| [list](./list.md) | List workspaces | -| [open](./open.md) | Open a workspace | -| [ping](./ping.md) | Ping a workspace | -| [rename](./rename.md) | Rename a workspace | -| [restart](./restart.md) | Restart a workspace | -| [schedule](./schedule.md) | Schedule automated start and stop times for workspaces | -| [show](./show.md) | Display details of a workspace's resources and agents | -| [speedtest](./speedtest.md) | Run upload and download tests from your machine to a workspace | -| [ssh](./ssh.md) | Start a shell into a workspace | -| [start](./start.md) | Start a workspace | -| [stat](./stat.md) | Show resource usage for the current workspace. | -| [stop](./stop.md) | Stop a workspace | -| [unfavorite](./unfavorite.md) | Remove a workspace from your favorites | -| [update](./update.md) | Will update and start a given workspace if it is out of date | -| [whoami](./whoami.md) | Fetch authenticated user info for Coder deployment | -| [support](./support.md) | Commands for troubleshooting issues with a Coder deployment. | -| [server](./server.md) | Start a Coder server | -| [features](./features.md) | List Enterprise features | -| [licenses](./licenses.md) | Add, delete, and list licenses | -| [groups](./groups.md) | Manage groups | -| [provisioner](./provisioner.md) | View and manage provisioner daemons and jobs | - -## Options - -### --url - -| | | -|-------------|-------------------------| -| Type | url | -| Environment | $CODER_URL | - -URL to a deployment. - -### --debug-options - -| | | -|------|-------------------| -| Type | bool | - -Print all options, how they're set, then exit. - -### --token - -| | | -|-------------|-----------------------------------| -| Type | string | -| Environment | $CODER_SESSION_TOKEN | - -Specify an authentication token. For security reasons setting CODER_SESSION_TOKEN is preferred. - -### --no-version-warning - -| | | -|-------------|----------------------------------------| -| Type | bool | -| Environment | $CODER_NO_VERSION_WARNING | - -Suppress warning when client and server versions do not match. - -### --no-feature-warning - -| | | -|-------------|----------------------------------------| -| Type | bool | -| Environment | $CODER_NO_FEATURE_WARNING | - -Suppress warnings about unlicensed features. - -### --header - -| | | -|-------------|----------------------------| -| Type | string-array | -| Environment | $CODER_HEADER | - -Additional HTTP headers added to all requests. Provide as key=value. Can be specified multiple times. - -### --header-command - -| | | -|-------------|------------------------------------| -| Type | string | -| Environment | $CODER_HEADER_COMMAND | - -An external command that outputs additional HTTP headers added to all requests. The command must output each header as `key=value` on its own line. - -### -v, --verbose - -| | | -|-------------|-----------------------------| -| Type | bool | -| Environment | $CODER_VERBOSE | - -Enable verbose output. - -### --disable-direct-connections - -| | | -|-------------|------------------------------------------------| -| Type | bool | -| Environment | $CODER_DISABLE_DIRECT_CONNECTIONS | - -Disable direct (P2P) connections to workspaces. - -### --disable-network-telemetry - -| | | -|-------------|-----------------------------------------------| -| Type | bool | -| Environment | $CODER_DISABLE_NETWORK_TELEMETRY | - -Disable network telemetry. Network telemetry is collected when connecting to workspaces using the CLI, and is forwarded to the server. If telemetry is also enabled on the server, it may be sent to Coder. Network telemetry is used to measure network quality and detect regressions. - -### --global-config - -| | | -|-------------|--------------------------------| -| Type | string | -| Environment | $CODER_CONFIG_DIR | -| Default | ~/.config/coderv2 | - -Path to the global `coder` config directory. From 452583e809f7dac978888ebb5c6150ea7b84cb06 Mon Sep 17 00:00:00 2001 From: defelmnq Date: Fri, 21 Feb 2025 14:44:57 +0000 Subject: [PATCH 22/37] add back index --- docs/reference/cli/index.md | 169 ++++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 docs/reference/cli/index.md diff --git a/docs/reference/cli/index.md b/docs/reference/cli/index.md new file mode 100644 index 0000000000000..9ad8f5590e727 --- /dev/null +++ b/docs/reference/cli/index.md @@ -0,0 +1,169 @@ + +# coder + +## Usage + +```console +coder [global-flags] +``` + +## Description + +```console +Coder — A tool for provisioning self-hosted development environments with Terraform. + - Start a Coder server: + + $ coder server + + - Get started by creating a template from an example: + + $ coder templates init +``` + +## Subcommands + +| Name | Purpose | +|----------------------------------------------------|-------------------------------------------------------------------------------------------------------| +| [completion](./completion.md) | Install or update shell completion scripts for the detected or chosen shell. | +| [dotfiles](./dotfiles.md) | Personalize your workspace by applying a canonical dotfiles repository | +| [external-auth](./external-auth.md) | Manage external authentication | +| [login](./login.md) | Authenticate with Coder deployment | +| [logout](./logout.md) | Unauthenticate your local session | +| [netcheck](./netcheck.md) | Print network debug information for DERP and STUN | +| [notifications](./notifications.md) | Manage Coder notifications | +| [organizations](./organizations.md) | Organization related commands | +| [port-forward](./port-forward.md) | Forward ports from a workspace to the local machine. For reverse port forwarding, use "coder ssh -R". | +| [publickey](./publickey.md) | Output your Coder public key used for Git operations | +| [reset-password](./reset-password.md) | Directly connect to the database to reset a user's password | +| [state](./state.md) | Manually manage Terraform state to fix broken workspaces | +| [templates](./templates.md) | Manage templates | +| [tokens](./tokens.md) | Manage personal access tokens | +| [users](./users.md) | Manage users | +| [version](./version.md) | Show coder version | +| [autoupdate](./autoupdate.md) | Toggle auto-update policy for a workspace | +| [config-ssh](./config-ssh.md) | Add an SSH Host entry for your workspaces "ssh coder.workspace" | +| [create](./create.md) | Create a workspace | +| [delete](./delete.md) | Delete a workspace | +| [favorite](./favorite.md) | Add a workspace to your favorites | +| [list](./list.md) | List workspaces | +| [open](./open.md) | Open a workspace | +| [ping](./ping.md) | Ping a workspace | +| [rename](./rename.md) | Rename a workspace | +| [restart](./restart.md) | Restart a workspace | +| [schedule](./schedule.md) | Schedule automated start and stop times for workspaces | +| [show](./show.md) | Display details of a workspace's resources and agents | +| [speedtest](./speedtest.md) | Run upload and download tests from your machine to a workspace | +| [ssh](./ssh.md) | Start a shell into a workspace | +| [start](./start.md) | Start a workspace | +| [stat](./stat.md) | Show resource usage for the current workspace. | +| [stop](./stop.md) | Stop a workspace | +| [unfavorite](./unfavorite.md) | Remove a workspace from your favorites | +| [update](./update.md) | Will update and start a given workspace if it is out of date | +| [whoami](./whoami.md) | Fetch authenticated user info for Coder deployment | +| [support](./support.md) | Commands for troubleshooting issues with a Coder deployment. | +| [server](./server.md) | Start a Coder server | +| [features](./features.md) | List Enterprise features | +| [licenses](./licenses.md) | Add, delete, and list licenses | +| [groups](./groups.md) | Manage groups | +| [provisioner](./provisioner.md) | View and manage provisioner daemons and jobs | + +## Options + +### --url + +| | | +|-------------|-------------------------| +| Type | url | +| Environment | $CODER_URL | + +URL to a deployment. + +### --debug-options + +| | | +|------|-------------------| +| Type | bool | + +Print all options, how they're set, then exit. + +### --token + +| | | +|-------------|-----------------------------------| +| Type | string | +| Environment | $CODER_SESSION_TOKEN | + +Specify an authentication token. For security reasons setting CODER_SESSION_TOKEN is preferred. + +### --no-version-warning + +| | | +|-------------|----------------------------------------| +| Type | bool | +| Environment | $CODER_NO_VERSION_WARNING | + +Suppress warning when client and server versions do not match. + +### --no-feature-warning + +| | | +|-------------|----------------------------------------| +| Type | bool | +| Environment | $CODER_NO_FEATURE_WARNING | + +Suppress warnings about unlicensed features. + +### --header + +| | | +|-------------|----------------------------| +| Type | string-array | +| Environment | $CODER_HEADER | + +Additional HTTP headers added to all requests. Provide as key=value. Can be specified multiple times. + +### --header-command + +| | | +|-------------|------------------------------------| +| Type | string | +| Environment | $CODER_HEADER_COMMAND | + +An external command that outputs additional HTTP headers added to all requests. The command must output each header as `key=value` on its own line. + +### -v, --verbose + +| | | +|-------------|-----------------------------| +| Type | bool | +| Environment | $CODER_VERBOSE | + +Enable verbose output. + +### --disable-direct-connections + +| | | +|-------------|------------------------------------------------| +| Type | bool | +| Environment | $CODER_DISABLE_DIRECT_CONNECTIONS | + +Disable direct (P2P) connections to workspaces. + +### --disable-network-telemetry + +| | | +|-------------|-----------------------------------------------| +| Type | bool | +| Environment | $CODER_DISABLE_NETWORK_TELEMETRY | + +Disable network telemetry. Network telemetry is collected when connecting to workspaces using the CLI, and is forwarded to the server. If telemetry is also enabled on the server, it may be sent to Coder. Network telemetry is used to measure network quality and detect regressions. + +### --global-config + +| | | +|-------------|--------------------------------| +| Type | string | +| Environment | $CODER_CONFIG_DIR | +| Default | ~/.config/coderv2 | + +Path to the global `coder` config directory. From 5aa54e12de7b8cbe40d11a8eb42557a4083dd1a0 Mon Sep 17 00:00:00 2001 From: defelmnq Date: Fri, 21 Feb 2025 15:28:14 +0000 Subject: [PATCH 23/37] fix query name missing --- coderd/database/dbauthz/dbauthz.go | 48 ++--- coderd/database/dbauthz/dbauthz_test.go | 4 +- coderd/database/dbmem/dbmem.go | 248 +++++++++++----------- coderd/database/dbmetrics/querymetrics.go | 70 +++--- coderd/database/dbmock/dbmock.go | 148 ++++++------- coderd/database/querier.go | 18 +- coderd/database/queries.sql.go | 99 +++++---- 7 files changed, 325 insertions(+), 310 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index a03840d927d95..92d883e192547 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -1029,6 +1029,14 @@ func (q *querier) customRoleCheck(ctx context.Context, role database.CustomRole) return nil } +func (q *querier) UpdateInboxNotificationReadStatus(ctx context.Context, args database.UpdateInboxNotificationReadStatusParams) error { + fetchFunc := func(ctx context.Context, args database.UpdateInboxNotificationReadStatusParams) (database.InboxNotification, error) { + return q.db.GetInboxNotificationByID(ctx, args.ID) + } + + return update(q.log, q.auth, fetchFunc, q.db.UpdateInboxNotificationReadStatus)(ctx, args) +} + func (q *querier) AcquireLock(ctx context.Context, id int64) error { return q.db.AcquireLock(ctx, id) } @@ -1418,14 +1426,6 @@ func (q *querier) FavoriteWorkspace(ctx context.Context, id uuid.UUID) error { return update(q.log, q.auth, fetch, q.db.FavoriteWorkspace)(ctx, id) } -func (q *querier) GetInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { - return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetInboxNotificationsByUserID)(ctx, userID) -} - -func (q *querier) GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { - return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets)(ctx, arg) -} - func (q *querier) FetchMemoryResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) (database.WorkspaceAgentMemoryResourceMonitor, error) { workspace, err := q.db.GetWorkspaceByAgentID(ctx, agentID) if err != nil { @@ -1447,14 +1447,6 @@ func (q *querier) FetchNewMessageMetadata(ctx context.Context, arg database.Fetc return q.db.FetchNewMessageMetadata(ctx, arg) } -func (q *querier) GetUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { - return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetUnreadInboxNotificationsByUserID)(ctx, userID) -} - -func (q *querier) GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { - return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets)(ctx, arg) -} - func (q *querier) FetchVolumesResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) ([]database.WorkspaceAgentVolumeResourceMonitor, error) { workspace, err := q.db.GetWorkspaceByAgentID(ctx, agentID) if err != nil { @@ -1771,6 +1763,14 @@ func (q *querier) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (d return fetchWithAction(q.log, q.auth, policy.ActionRead, q.db.GetInboxNotificationByID)(ctx, id) } +func (q *querier) GetInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { + return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetInboxNotificationsByUserID)(ctx, userID) +} + +func (q *querier) GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { + return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets)(ctx, arg) +} + func (q *querier) GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.GetJFrogXrayScanByWorkspaceAndAgentIDParams) (database.JfrogXrayScan, error) { if _, err := fetch(q.log, q.auth, q.db.GetWorkspaceByID)(ctx, arg.WorkspaceID); err != nil { return database.JfrogXrayScan{}, err @@ -2461,6 +2461,14 @@ func (q *querier) GetUnexpiredLicenses(ctx context.Context) ([]database.License, return q.db.GetUnexpiredLicenses(ctx) } +func (q *querier) GetUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { + return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetUnreadInboxNotificationsByUserID)(ctx, userID) +} + +func (q *querier) GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { + return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets)(ctx, arg) +} + func (q *querier) GetUserActivityInsights(ctx context.Context, arg database.GetUserActivityInsightsParams) ([]database.GetUserActivityInsightsRow, error) { // Used by insights endpoints. Need to check both for auditors and for regular users with template acl perms. if err := q.authorizeContext(ctx, policy.ActionViewInsights, rbac.ResourceTemplate); err != nil { @@ -3589,14 +3597,6 @@ func (q *querier) RevokeDBCryptKey(ctx context.Context, activeKeyDigest string) return q.db.RevokeDBCryptKey(ctx, activeKeyDigest) } -func (q *querier) UpdateInboxNotificationAsRead(ctx context.Context, args database.UpdateInboxNotificationAsReadParams) error { - fetchFunc := func(ctx context.Context, args database.UpdateInboxNotificationAsReadParams) (database.InboxNotification, error) { - return q.db.GetInboxNotificationByID(ctx, args.ID) - } - - return update(q.log, q.auth, fetchFunc, q.db.UpdateInboxNotificationAsRead)(ctx, args) -} - func (q *querier) TryAcquireLock(ctx context.Context, id int64) (bool, error) { return q.db.TryAcquireLock(ctx, id) } diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index d59fb5469eeba..7e2edac36a9a9 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -4604,7 +4604,7 @@ func (s *MethodTestSuite) TestNotifications() { }).Asserts(rbac.ResourceInboxNotification.WithOwner(u.ID.String()), policy.ActionCreate) })) - s.Run("UpdateInboxNotificationAsRead", s.Subtest(func(db database.Store, check *expects) { + s.Run("UpdateInboxNotificationReadStatus", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) notifID := uuid.New() @@ -4625,7 +4625,7 @@ func (s *MethodTestSuite) TestNotifications() { notif.ReadAt = sql.NullTime{Time: readAt, Valid: true} - check.Args(database.UpdateInboxNotificationAsReadParams{ + check.Args(database.UpdateInboxNotificationReadStatusParams{ ID: notifID, ReadAt: sql.NullTime{Time: readAt, Valid: true}, }).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionUpdate) diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 70ebdea74615b..859c43225bfc0 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -1230,6 +1230,24 @@ func (q *FakeQuerier) getProvisionerJobsByIDsWithQueuePositionLocked(_ context.C return jobs, nil } +func (q *FakeQuerier) UpdateInboxNotificationReadStatus(_ context.Context, arg database.UpdateInboxNotificationReadStatusParams) error { + err := validateDatabaseType(arg) + if err != nil { + return err + } + + q.mutex.Lock() + defer q.mutex.Unlock() + + for i := range q.InboxNotification { + if q.InboxNotification[i].ID == arg.ID { + q.InboxNotification[i].ReadAt = arg.ReadAt + } + } + + return nil +} + func (*FakeQuerier) AcquireLock(_ context.Context, _ int64) error { return xerrors.New("AcquireLock must only be called within a transaction") } @@ -2365,59 +2383,6 @@ func (q *FakeQuerier) FavoriteWorkspace(_ context.Context, arg uuid.UUID) error return nil } -func (q *FakeQuerier) GetInboxNotificationsByUserID(_ context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - notifications := make([]database.InboxNotification, 0) - for _, notification := range q.InboxNotification { - if notification.UserID == userID { - notifications = append(notifications, notification) - } - } - - return notifications, nil -} - -func (q *FakeQuerier) GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(_ context.Context, arg database.GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - notifications := make([]database.InboxNotification, 0) - for _, notification := range q.InboxNotification { - if notification.UserID == arg.UserID { - for _, template := range arg.Templates { - templateFound := false - if notification.TemplateID == template { - templateFound = true - } - - if !templateFound { - continue - } - } - - for _, target := range arg.Targets { - isFound := false - for _, insertedTarget := range notification.Targets { - if insertedTarget == target { - isFound = true - break - } - } - - if !isFound { - continue - } - - notifications = append(notifications, notification) - } - } - } - - return notifications, nil -} - func (q *FakeQuerier) FetchMemoryResourceMonitorsByAgentID(_ context.Context, agentID uuid.UUID) (database.WorkspaceAgentMemoryResourceMonitor, error) { for _, monitor := range q.workspaceAgentMemoryResourceMonitors { if monitor.AgentID == agentID { @@ -2460,59 +2425,6 @@ func (q *FakeQuerier) FetchNewMessageMetadata(_ context.Context, arg database.Fe }, nil } -func (q *FakeQuerier) GetUnreadInboxNotificationsByUserID(_ context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - notifications := make([]database.InboxNotification, 0) - for _, notification := range q.InboxNotification { - if notification.UserID == userID && !notification.ReadAt.Valid { - notifications = append(notifications, notification) - } - } - - return notifications, nil -} - -func (q *FakeQuerier) GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(_ context.Context, arg database.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - notifications := make([]database.InboxNotification, 0) - for _, notification := range q.InboxNotification { - if notification.UserID == arg.UserID && !notification.ReadAt.Valid { - for _, template := range arg.Templates { - templateFound := false - if notification.TemplateID == template { - templateFound = true - } - - if !templateFound { - continue - } - } - - for _, target := range arg.Targets { - isFound := false - for _, insertedTarget := range notification.Targets { - if insertedTarget == target { - isFound = true - break - } - } - - if !isFound { - continue - } - - notifications = append(notifications, notification) - } - } - } - - return notifications, nil -} - func (q *FakeQuerier) FetchVolumesResourceMonitorsByAgentID(_ context.Context, agentID uuid.UUID) ([]database.WorkspaceAgentVolumeResourceMonitor, error) { monitors := []database.WorkspaceAgentVolumeResourceMonitor{} @@ -3453,6 +3365,59 @@ func (q *FakeQuerier) GetInboxNotificationByID(_ context.Context, id uuid.UUID) return database.InboxNotification{}, sql.ErrNoRows } +func (q *FakeQuerier) GetInboxNotificationsByUserID(_ context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { + q.mutex.RLock() + defer q.mutex.RUnlock() + + notifications := make([]database.InboxNotification, 0) + for _, notification := range q.InboxNotification { + if notification.UserID == userID { + notifications = append(notifications, notification) + } + } + + return notifications, nil +} + +func (q *FakeQuerier) GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(_ context.Context, arg database.GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { + q.mutex.RLock() + defer q.mutex.RUnlock() + + notifications := make([]database.InboxNotification, 0) + for _, notification := range q.InboxNotification { + if notification.UserID == arg.UserID { + for _, template := range arg.Templates { + templateFound := false + if notification.TemplateID == template { + templateFound = true + } + + if !templateFound { + continue + } + } + + for _, target := range arg.Targets { + isFound := false + for _, insertedTarget := range notification.Targets { + if insertedTarget == target { + isFound = true + break + } + } + + if !isFound { + continue + } + + notifications = append(notifications, notification) + } + } + } + + return notifications, nil +} + func (q *FakeQuerier) GetJFrogXrayScanByWorkspaceAndAgentID(_ context.Context, arg database.GetJFrogXrayScanByWorkspaceAndAgentIDParams) (database.JfrogXrayScan, error) { err := validateDatabaseType(arg) if err != nil { @@ -5905,6 +5870,59 @@ func (q *FakeQuerier) GetUnexpiredLicenses(_ context.Context) ([]database.Licens return results, nil } +func (q *FakeQuerier) GetUnreadInboxNotificationsByUserID(_ context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { + q.mutex.RLock() + defer q.mutex.RUnlock() + + notifications := make([]database.InboxNotification, 0) + for _, notification := range q.InboxNotification { + if notification.UserID == userID && !notification.ReadAt.Valid { + notifications = append(notifications, notification) + } + } + + return notifications, nil +} + +func (q *FakeQuerier) GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(_ context.Context, arg database.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { + q.mutex.RLock() + defer q.mutex.RUnlock() + + notifications := make([]database.InboxNotification, 0) + for _, notification := range q.InboxNotification { + if notification.UserID == arg.UserID && !notification.ReadAt.Valid { + for _, template := range arg.Templates { + templateFound := false + if notification.TemplateID == template { + templateFound = true + } + + if !templateFound { + continue + } + } + + for _, target := range arg.Targets { + isFound := false + for _, insertedTarget := range notification.Targets { + if insertedTarget == target { + isFound = true + break + } + } + + if !isFound { + continue + } + + notifications = append(notifications, notification) + } + } + } + + return notifications, nil +} + func (q *FakeQuerier) GetUserActivityInsights(_ context.Context, arg database.GetUserActivityInsightsParams) ([]database.GetUserActivityInsightsRow, error) { err := validateDatabaseType(arg) if err != nil { @@ -9588,24 +9606,6 @@ func (q *FakeQuerier) RevokeDBCryptKey(_ context.Context, activeKeyDigest string return sql.ErrNoRows } -func (q *FakeQuerier) UpdateInboxNotificationAsRead(_ context.Context, arg database.UpdateInboxNotificationAsReadParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i := range q.InboxNotification { - if q.InboxNotification[i].ID == arg.ID { - q.InboxNotification[i].ReadAt = arg.ReadAt - } - } - - return nil -} - func (*FakeQuerier) TryAcquireLock(_ context.Context, _ int64) (bool, error) { return false, xerrors.New("TryAcquireLock must only be called within a transaction") } diff --git a/coderd/database/dbmetrics/querymetrics.go b/coderd/database/dbmetrics/querymetrics.go index 9869fdef01780..4e22778f510ab 100644 --- a/coderd/database/dbmetrics/querymetrics.go +++ b/coderd/database/dbmetrics/querymetrics.go @@ -84,6 +84,13 @@ func (m queryMetricsStore) GetUnreadInboxNotificationsByUserIDAndTemplateIDAndTa return r0, r1 } +func (m queryMetricsStore) UpdateInboxNotificationReadStatus(ctx context.Context, arg database.UpdateInboxNotificationReadStatusParams) error { + start := time.Now() + r0 := m.s.UpdateInboxNotificationReadStatus(ctx, arg) + m.queryLatencies.WithLabelValues("UpdateInboxNotificationReadStatus").Observe(time.Since(start).Seconds()) + return r0 +} + func (m queryMetricsStore) AcquireLock(ctx context.Context, pgAdvisoryXactLock int64) error { start := time.Now() err := m.s.AcquireLock(ctx, pgAdvisoryXactLock) @@ -441,20 +448,6 @@ func (m queryMetricsStore) FavoriteWorkspace(ctx context.Context, arg uuid.UUID) return r0 } -func (m queryMetricsStore) GetInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { - start := time.Now() - r0, r1 := m.s.GetInboxNotificationsByUserID(ctx, userID) - m.queryLatencies.WithLabelValues("GetInboxNotificationsByUserID").Observe(time.Since(start).Seconds()) - return r0, r1 -} - -func (m queryMetricsStore) GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { - start := time.Now() - r0, r1 := m.s.GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx, arg) - m.queryLatencies.WithLabelValues("GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets").Observe(time.Since(start).Seconds()) - return r0, r1 -} - func (m queryMetricsStore) FetchMemoryResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) (database.WorkspaceAgentMemoryResourceMonitor, error) { start := time.Now() r0, r1 := m.s.FetchMemoryResourceMonitorsByAgentID(ctx, agentID) @@ -469,20 +462,6 @@ func (m queryMetricsStore) FetchNewMessageMetadata(ctx context.Context, arg data return r0, r1 } -func (m queryMetricsStore) GetUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { - start := time.Now() - r0, r1 := m.s.GetUnreadInboxNotificationsByUserID(ctx, userID) - m.queryLatencies.WithLabelValues("GetUnreadInboxNotificationsByUserID").Observe(time.Since(start).Seconds()) - return r0, r1 -} - -func (m queryMetricsStore) GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { - start := time.Now() - r0, r1 := m.s.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx, arg) - m.queryLatencies.WithLabelValues("GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets").Observe(time.Since(start).Seconds()) - return r0, r1 -} - func (m queryMetricsStore) FetchVolumesResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) ([]database.WorkspaceAgentVolumeResourceMonitor, error) { start := time.Now() r0, r1 := m.s.FetchVolumesResourceMonitorsByAgentID(ctx, agentID) @@ -812,6 +791,20 @@ func (m queryMetricsStore) GetInboxNotificationByID(ctx context.Context, id uuid return r0, r1 } +func (m queryMetricsStore) GetInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { + start := time.Now() + r0, r1 := m.s.GetInboxNotificationsByUserID(ctx, userID) + m.queryLatencies.WithLabelValues("GetInboxNotificationsByUserID").Observe(time.Since(start).Seconds()) + return r0, r1 +} + +func (m queryMetricsStore) GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { + start := time.Now() + r0, r1 := m.s.GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx, arg) + m.queryLatencies.WithLabelValues("GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m queryMetricsStore) GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.GetJFrogXrayScanByWorkspaceAndAgentIDParams) (database.JfrogXrayScan, error) { start := time.Now() r0, r1 := m.s.GetJFrogXrayScanByWorkspaceAndAgentID(ctx, arg) @@ -1386,6 +1379,20 @@ func (m queryMetricsStore) GetUnexpiredLicenses(ctx context.Context) ([]database return licenses, err } +func (m queryMetricsStore) GetUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { + start := time.Now() + r0, r1 := m.s.GetUnreadInboxNotificationsByUserID(ctx, userID) + m.queryLatencies.WithLabelValues("GetUnreadInboxNotificationsByUserID").Observe(time.Since(start).Seconds()) + return r0, r1 +} + +func (m queryMetricsStore) GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { + start := time.Now() + r0, r1 := m.s.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx, arg) + m.queryLatencies.WithLabelValues("GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m queryMetricsStore) GetUserActivityInsights(ctx context.Context, arg database.GetUserActivityInsightsParams) ([]database.GetUserActivityInsightsRow, error) { start := time.Now() r0, r1 := m.s.GetUserActivityInsights(ctx, arg) @@ -2296,13 +2303,6 @@ func (m queryMetricsStore) RevokeDBCryptKey(ctx context.Context, activeKeyDigest return r0 } -func (m queryMetricsStore) UpdateInboxNotificationAsRead(ctx context.Context, arg database.UpdateInboxNotificationAsReadParams) error { - start := time.Now() - r0 := m.s.UpdateInboxNotificationAsRead(ctx, arg) - m.queryLatencies.WithLabelValues("UpdateInboxNotificationAsRead").Observe(time.Since(start).Seconds()) - return r0 -} - func (m queryMetricsStore) TryAcquireLock(ctx context.Context, pgTryAdvisoryXactLock int64) (bool, error) { start := time.Now() ok, err := m.s.TryAcquireLock(ctx, pgTryAdvisoryXactLock) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index 21f6e634859a9..ac2630ddce456 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -771,36 +771,6 @@ func (mr *MockStoreMockRecorder) FavoriteWorkspace(ctx, id any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FavoriteWorkspace", reflect.TypeOf((*MockStore)(nil).FavoriteWorkspace), ctx, id) } -// GetInboxNotificationsByUserID mocks base method. -func (m *MockStore) GetInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetInboxNotificationsByUserID", ctx, userID) - ret0, _ := ret[0].([]database.InboxNotification) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetInboxNotificationsByUserID indicates an expected call of GetInboxNotificationsByUserID. -func (mr *MockStoreMockRecorder) GetInboxNotificationsByUserID(ctx, userID any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInboxNotificationsByUserID", reflect.TypeOf((*MockStore)(nil).GetInboxNotificationsByUserID), ctx, userID) -} - -// GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets mocks base method. -func (m *MockStore) GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets", ctx, arg) - ret0, _ := ret[0].([]database.InboxNotification) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets indicates an expected call of GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets. -func (mr *MockStoreMockRecorder) GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx, arg any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets", reflect.TypeOf((*MockStore)(nil).GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets), ctx, arg) -} - // FetchMemoryResourceMonitorsByAgentID mocks base method. func (m *MockStore) FetchMemoryResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) (database.WorkspaceAgentMemoryResourceMonitor, error) { m.ctrl.T.Helper() @@ -831,36 +801,6 @@ func (mr *MockStoreMockRecorder) FetchNewMessageMetadata(ctx, arg any) *gomock.C return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchNewMessageMetadata", reflect.TypeOf((*MockStore)(nil).FetchNewMessageMetadata), ctx, arg) } -// GetUnreadInboxNotificationsByUserID mocks base method. -func (m *MockStore) GetUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetUnreadInboxNotificationsByUserID", ctx, userID) - ret0, _ := ret[0].([]database.InboxNotification) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetUnreadInboxNotificationsByUserID indicates an expected call of GetUnreadInboxNotificationsByUserID. -func (mr *MockStoreMockRecorder) GetUnreadInboxNotificationsByUserID(ctx, userID any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUnreadInboxNotificationsByUserID", reflect.TypeOf((*MockStore)(nil).GetUnreadInboxNotificationsByUserID), ctx, userID) -} - -// GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets mocks base method. -func (m *MockStore) GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets", ctx, arg) - ret0, _ := ret[0].([]database.InboxNotification) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets indicates an expected call of GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets. -func (mr *MockStoreMockRecorder) GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx, arg any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets", reflect.TypeOf((*MockStore)(nil).GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets), ctx, arg) -} - // FetchVolumesResourceMonitorsByAgentID mocks base method. func (m *MockStore) FetchVolumesResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) ([]database.WorkspaceAgentVolumeResourceMonitor, error) { m.ctrl.T.Helper() @@ -1641,6 +1581,36 @@ func (mr *MockStoreMockRecorder) GetInboxNotificationByID(ctx, id any) *gomock.C return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInboxNotificationByID", reflect.TypeOf((*MockStore)(nil).GetInboxNotificationByID), ctx, id) } +// GetInboxNotificationsByUserID mocks base method. +func (m *MockStore) GetInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetInboxNotificationsByUserID", ctx, userID) + ret0, _ := ret[0].([]database.InboxNotification) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetInboxNotificationsByUserID indicates an expected call of GetInboxNotificationsByUserID. +func (mr *MockStoreMockRecorder) GetInboxNotificationsByUserID(ctx, userID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInboxNotificationsByUserID", reflect.TypeOf((*MockStore)(nil).GetInboxNotificationsByUserID), ctx, userID) +} + +// GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets mocks base method. +func (m *MockStore) GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets", ctx, arg) + ret0, _ := ret[0].([]database.InboxNotification) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets indicates an expected call of GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets. +func (mr *MockStoreMockRecorder) GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx, arg any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets", reflect.TypeOf((*MockStore)(nil).GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets), ctx, arg) +} + // GetJFrogXrayScanByWorkspaceAndAgentID mocks base method. func (m *MockStore) GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.GetJFrogXrayScanByWorkspaceAndAgentIDParams) (database.JfrogXrayScan, error) { m.ctrl.T.Helper() @@ -2901,6 +2871,36 @@ func (mr *MockStoreMockRecorder) GetUnexpiredLicenses(ctx any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUnexpiredLicenses", reflect.TypeOf((*MockStore)(nil).GetUnexpiredLicenses), ctx) } +// GetUnreadInboxNotificationsByUserID mocks base method. +func (m *MockStore) GetUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUnreadInboxNotificationsByUserID", ctx, userID) + ret0, _ := ret[0].([]database.InboxNotification) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUnreadInboxNotificationsByUserID indicates an expected call of GetUnreadInboxNotificationsByUserID. +func (mr *MockStoreMockRecorder) GetUnreadInboxNotificationsByUserID(ctx, userID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUnreadInboxNotificationsByUserID", reflect.TypeOf((*MockStore)(nil).GetUnreadInboxNotificationsByUserID), ctx, userID) +} + +// GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets mocks base method. +func (m *MockStore) GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets", ctx, arg) + ret0, _ := ret[0].([]database.InboxNotification) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets indicates an expected call of GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets. +func (mr *MockStoreMockRecorder) GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx, arg any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets", reflect.TypeOf((*MockStore)(nil).GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets), ctx, arg) +} + // GetUserActivityInsights mocks base method. func (m *MockStore) GetUserActivityInsights(ctx context.Context, arg database.GetUserActivityInsightsParams) ([]database.GetUserActivityInsightsRow, error) { m.ctrl.T.Helper() @@ -4879,20 +4879,6 @@ func (mr *MockStoreMockRecorder) RevokeDBCryptKey(ctx, activeKeyDigest any) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RevokeDBCryptKey", reflect.TypeOf((*MockStore)(nil).RevokeDBCryptKey), ctx, activeKeyDigest) } -// UpdateInboxNotificationAsRead mocks base method. -func (m *MockStore) UpdateInboxNotificationAsRead(ctx context.Context, arg database.UpdateInboxNotificationAsReadParams) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateInboxNotificationAsRead", ctx, arg) - ret0, _ := ret[0].(error) - return ret0 -} - -// UpdateInboxNotificationAsRead indicates an expected call of UpdateInboxNotificationAsRead. -func (mr *MockStoreMockRecorder) UpdateInboxNotificationAsRead(ctx, arg any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateInboxNotificationAsRead", reflect.TypeOf((*MockStore)(nil).UpdateInboxNotificationAsRead), ctx, arg) -} - // TryAcquireLock mocks base method. func (m *MockStore) TryAcquireLock(ctx context.Context, pgTryAdvisoryXactLock int64) (bool, error) { m.ctrl.T.Helper() @@ -5054,6 +5040,20 @@ func (mr *MockStoreMockRecorder) UpdateInactiveUsersToDormant(ctx, arg any) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateInactiveUsersToDormant", reflect.TypeOf((*MockStore)(nil).UpdateInactiveUsersToDormant), ctx, arg) } +// UpdateInboxNotificationReadStatus mocks base method. +func (m *MockStore) UpdateInboxNotificationReadStatus(ctx context.Context, arg database.UpdateInboxNotificationReadStatusParams) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateInboxNotificationReadStatus", ctx, arg) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateInboxNotificationReadStatus indicates an expected call of UpdateInboxNotificationReadStatus. +func (mr *MockStoreMockRecorder) UpdateInboxNotificationReadStatus(ctx, arg any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateInboxNotificationReadStatus", reflect.TypeOf((*MockStore)(nil).UpdateInboxNotificationReadStatus), ctx, arg) +} + // UpdateMemberRoles mocks base method. func (m *MockStore) UpdateMemberRoles(ctx context.Context, arg database.UpdateMemberRolesParams) (database.OrganizationMember, error) { m.ctrl.T.Helper() diff --git a/coderd/database/querier.go b/coderd/database/querier.go index b6de3756d3c62..bd8896d5d9102 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -112,13 +112,9 @@ type sqlcQuerier interface { DisableForeignKeysAndTriggers(ctx context.Context) error EnqueueNotificationMessage(ctx context.Context, arg EnqueueNotificationMessageParams) error FavoriteWorkspace(ctx context.Context, id uuid.UUID) error - GetInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]InboxNotification, error) - GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]InboxNotification, error) FetchMemoryResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) (WorkspaceAgentMemoryResourceMonitor, error) // This is used to build up the notification_message's JSON payload. FetchNewMessageMetadata(ctx context.Context, arg FetchNewMessageMetadataParams) (FetchNewMessageMetadataRow, error) - GetUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]InboxNotification, error) - GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]InboxNotification, error) FetchVolumesResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) ([]WorkspaceAgentVolumeResourceMonitor, error) GetAPIKeyByID(ctx context.Context, id string) (APIKey, error) // there is no unique constraint on empty token names @@ -176,6 +172,12 @@ type sqlcQuerier interface { GetHealthSettings(ctx context.Context) (string, error) GetHungProvisionerJobs(ctx context.Context, updatedAt time.Time) ([]ProvisionerJob, error) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (InboxNotification, error) + GetInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]InboxNotification, error) + // Fetches inbox notifications for a user filtered by templates and targets + // param user_id: The user ID + // param templates: The template IDs to filter by - the template_id = ANY(@templates::UUID[]) condition checks if the template_id is in the @templates array + // param targets: The target IDs to filter by - the targets @> COALESCE(@targets, ARRAY[]::UUID[]) condition checks if the targets array (from the DB) contains all the elements in the @targets array + GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]InboxNotification, error) GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg GetJFrogXrayScanByWorkspaceAndAgentIDParams) (JfrogXrayScan, error) GetLastUpdateCheck(ctx context.Context) (string, error) GetLatestCryptoKeyByFeature(ctx context.Context, feature CryptoKeyFeature) (CryptoKey, error) @@ -285,6 +287,12 @@ type sqlcQuerier interface { GetTemplates(ctx context.Context) ([]Template, error) GetTemplatesWithFilter(ctx context.Context, arg GetTemplatesWithFilterParams) ([]Template, error) GetUnexpiredLicenses(ctx context.Context) ([]License, error) + // + GetUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]InboxNotification, error) + // param user_id: The user ID + // param templates: The template IDs to filter by - the template_id = ANY(@templates::UUID[]) condition checks if the template_id is in the @templates array + // param targets: The target IDs to filter by - the targets @> COALESCE(@targets, ARRAY[]::UUID[]) condition checks if the targets array (from the DB) contains all the elements in the @targets array + GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]InboxNotification, error) // GetUserActivityInsights returns the ranking with top active users. // The result can be filtered on template_ids, meaning only user data // from workspaces based on those templates will be included. @@ -469,7 +477,6 @@ type sqlcQuerier interface { RemoveUserFromAllGroups(ctx context.Context, userID uuid.UUID) error RemoveUserFromGroups(ctx context.Context, arg RemoveUserFromGroupsParams) ([]uuid.UUID, error) RevokeDBCryptKey(ctx context.Context, activeKeyDigest string) error - UpdateInboxNotificationAsRead(ctx context.Context, arg UpdateInboxNotificationAsReadParams) error // Non blocking lock. Returns true if the lock was acquired, false otherwise. // // This must be called from within a transaction. The lock will be automatically @@ -486,6 +493,7 @@ type sqlcQuerier interface { UpdateGitSSHKey(ctx context.Context, arg UpdateGitSSHKeyParams) (GitSSHKey, error) UpdateGroupByID(ctx context.Context, arg UpdateGroupByIDParams) (Group, error) UpdateInactiveUsersToDormant(ctx context.Context, arg UpdateInactiveUsersToDormantParams) ([]UpdateInactiveUsersToDormantRow, error) + UpdateInboxNotificationReadStatus(ctx context.Context, arg UpdateInboxNotificationReadStatusParams) error UpdateMemberRoles(ctx context.Context, arg UpdateMemberRolesParams) (OrganizationMember, error) UpdateMemoryResourceMonitor(ctx context.Context, arg UpdateMemoryResourceMonitorParams) error UpdateNotificationTemplateMethodByID(ctx context.Context, arg UpdateNotificationTemplateMethodByIDParams) (NotificationTemplate, error) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index ab1563e2f0bf4..08aafc0b69c84 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -3551,7 +3551,7 @@ func (q *sqlQuerier) GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, } const upsertJFrogXrayScanByWorkspaceAndAgentID = `-- name: UpsertJFrogXrayScanByWorkspaceAndAgentID :exec -INSERT INTO +INSERT INTO jfrog_xray_scans ( agent_id, workspace_id, @@ -3560,7 +3560,7 @@ INSERT INTO medium, results_url ) -VALUES +VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (agent_id, workspace_id) DO UPDATE SET critical = $3, high = $4, medium = $5, results_url = $6 @@ -4298,12 +4298,34 @@ func (q *sqlQuerier) UpsertNotificationReportGeneratorLog(ctx context.Context, a return err } -const fetchInboxNotificationsByUserID = `-- name: GetInboxNotificationsByUserID :many +const getInboxNotificationByID = `-- name: GetInboxNotificationByID :one +SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE id = $1 +` + +func (q *sqlQuerier) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (InboxNotification, error) { + row := q.db.QueryRowContext(ctx, getInboxNotificationByID, id) + var i InboxNotification + err := row.Scan( + &i.ID, + &i.UserID, + &i.TemplateID, + pq.Array(&i.Targets), + &i.Title, + &i.Content, + &i.Icon, + &i.Actions, + &i.ReadAt, + &i.CreatedAt, + ) + return i, err +} + +const getInboxNotificationsByUserID = `-- name: GetInboxNotificationsByUserID :many SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 ORDER BY created_at DESC ` func (q *sqlQuerier) GetInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]InboxNotification, error) { - rows, err := q.db.QueryContext(ctx, fetchInboxNotificationsByUserID, userID) + rows, err := q.db.QueryContext(ctx, getInboxNotificationsByUserID, userID) if err != nil { return nil, err } @@ -4336,7 +4358,7 @@ func (q *sqlQuerier) GetInboxNotificationsByUserID(ctx context.Context, userID u return items, nil } -const fetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets = `-- name: GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many +const getInboxNotificationsByUserIDFilteredByTemplatesAndTargets = `-- name: GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 AND template_id = ANY($2::UUID[]) AND targets @> COALESCE($3, ARRAY[]::UUID[]) ORDER BY created_at DESC ` @@ -4346,8 +4368,12 @@ type GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams struct { Targets []uuid.UUID `db:"targets" json:"targets"` } +// Fetches inbox notifications for a user filtered by templates and targets +// param user_id: The user ID +// param templates: The template IDs to filter by - the template_id = ANY(@templates::UUID[]) condition checks if the template_id is in the @templates array +// param targets: The target IDs to filter by - the targets @> COALESCE(@targets, ARRAY[]::UUID[]) condition checks if the targets array (from the DB) contains all the elements in the @targets array func (q *sqlQuerier) GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]InboxNotification, error) { - rows, err := q.db.QueryContext(ctx, fetchInboxNotificationsByUserIDFilteredByTemplatesAndTargets, arg.UserID, pq.Array(arg.Templates), pq.Array(arg.Targets)) + rows, err := q.db.QueryContext(ctx, getInboxNotificationsByUserIDFilteredByTemplatesAndTargets, arg.UserID, pq.Array(arg.Templates), pq.Array(arg.Targets)) if err != nil { return nil, err } @@ -4380,12 +4406,12 @@ func (q *sqlQuerier) GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets( return items, nil } -const fetchUnreadInboxNotificationsByUserID = `-- name: GetUnreadInboxNotificationsByUserID :many +const getUnreadInboxNotificationsByUserID = `-- name: GetUnreadInboxNotificationsByUserID :many SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 AND read_at IS NULL ORDER BY created_at DESC ` func (q *sqlQuerier) GetUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]InboxNotification, error) { - rows, err := q.db.QueryContext(ctx, fetchUnreadInboxNotificationsByUserID, userID) + rows, err := q.db.QueryContext(ctx, getUnreadInboxNotificationsByUserID, userID) if err != nil { return nil, err } @@ -4418,7 +4444,7 @@ func (q *sqlQuerier) GetUnreadInboxNotificationsByUserID(ctx context.Context, us return items, nil } -const fetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets = `-- name: GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many +const getUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets = `-- name: GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 AND template_id = ANY($2::UUID[]) AND targets @> COALESCE($3, ARRAY[]::UUID[]) AND read_at IS NULL ORDER BY created_at DESC ` @@ -4428,8 +4454,11 @@ type GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams stru Targets []uuid.UUID `db:"targets" json:"targets"` } +// param user_id: The user ID +// param templates: The template IDs to filter by - the template_id = ANY(@templates::UUID[]) condition checks if the template_id is in the @templates array +// param targets: The target IDs to filter by - the targets @> COALESCE(@targets, ARRAY[]::UUID[]) condition checks if the targets array (from the DB) contains all the elements in the @targets array func (q *sqlQuerier) GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]InboxNotification, error) { - rows, err := q.db.QueryContext(ctx, fetchUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets, arg.UserID, pq.Array(arg.Templates), pq.Array(arg.Targets)) + rows, err := q.db.QueryContext(ctx, getUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets, arg.UserID, pq.Array(arg.Templates), pq.Array(arg.Targets)) if err != nil { return nil, err } @@ -4462,28 +4491,6 @@ func (q *sqlQuerier) GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTa return items, nil } -const getInboxNotificationByID = `-- name: GetInboxNotificationByID :one -SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE id = $1 -` - -func (q *sqlQuerier) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (InboxNotification, error) { - row := q.db.QueryRowContext(ctx, getInboxNotificationByID, id) - var i InboxNotification - err := row.Scan( - &i.ID, - &i.UserID, - &i.TemplateID, - pq.Array(&i.Targets), - &i.Title, - &i.Content, - &i.Icon, - &i.Actions, - &i.ReadAt, - &i.CreatedAt, - ) - return i, err -} - const insertInboxNotification = `-- name: InsertInboxNotification :one INSERT INTO inbox_notifications ( @@ -4541,7 +4548,7 @@ func (q *sqlQuerier) InsertInboxNotification(ctx context.Context, arg InsertInbo return i, err } -const setInboxNotificationAsRead = `-- name: UpdateInboxNotificationAsRead :exec +const updateInboxNotificationReadStatus = `-- name: UpdateInboxNotificationReadStatus :exec UPDATE inbox_notifications SET @@ -4550,13 +4557,13 @@ WHERE id = $2 ` -type UpdateInboxNotificationAsReadParams struct { +type UpdateInboxNotificationReadStatusParams struct { ReadAt sql.NullTime `db:"read_at" json:"read_at"` ID uuid.UUID `db:"id" json:"id"` } -func (q *sqlQuerier) UpdateInboxNotificationAsRead(ctx context.Context, arg UpdateInboxNotificationAsReadParams) error { - _, err := q.db.ExecContext(ctx, setInboxNotificationAsRead, arg.ReadAt, arg.ID) +func (q *sqlQuerier) UpdateInboxNotificationReadStatus(ctx context.Context, arg UpdateInboxNotificationReadStatusParams) error { + _, err := q.db.ExecContext(ctx, updateInboxNotificationReadStatus, arg.ReadAt, arg.ID) return err } @@ -7210,7 +7217,7 @@ FROM provisioner_keys WHERE organization_id = $1 -AND +AND lower(name) = lower($2) ` @@ -7326,10 +7333,10 @@ WHERE AND -- exclude reserved built-in key id != '00000000-0000-0000-0000-000000000001'::uuid -AND +AND -- exclude reserved user-auth key id != '00000000-0000-0000-0000-000000000002'::uuid -AND +AND -- exclude reserved psk key id != '00000000-0000-0000-0000-000000000003'::uuid ` @@ -9015,7 +9022,7 @@ func (q *sqlQuerier) GetTailnetTunnelPeerIDs(ctx context.Context, srcID uuid.UUI } const updateTailnetPeerStatusByCoordinator = `-- name: UpdateTailnetPeerStatusByCoordinator :exec -UPDATE +UPDATE tailnet_peers SET status = $2 @@ -10817,14 +10824,14 @@ DO $$ DECLARE table_record record; BEGIN - FOR table_record IN - SELECT table_schema, table_name - FROM information_schema.tables + FOR table_record IN + SELECT table_schema, table_name + FROM information_schema.tables WHERE table_schema NOT IN ('pg_catalog', 'information_schema') AND table_type = 'BASE TABLE' LOOP - EXECUTE format('ALTER TABLE %I.%I DISABLE TRIGGER ALL', - table_record.table_schema, + EXECUTE format('ALTER TABLE %I.%I DISABLE TRIGGER ALL', + table_record.table_schema, table_record.table_name); END LOOP; END; @@ -13931,7 +13938,7 @@ WITH agent_stats AS ( coalesce((PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY connection_median_latency_ms)), -1)::FLOAT AS workspace_connection_latency_95 FROM workspace_agent_stats -- The greater than 0 is to support legacy agents that don't report connection_median_latency_ms. - WHERE workspace_agent_stats.created_at > $1 AND connection_median_latency_ms > 0 + WHERE workspace_agent_stats.created_at > $1 AND connection_median_latency_ms > 0 GROUP BY user_id, agent_id, workspace_id, template_id ), latest_agent_stats AS ( SELECT From da56e8f40a1d8f049f8e7c7b9568a7506dd3e241 Mon Sep 17 00:00:00 2001 From: defelmnq Date: Fri, 21 Feb 2025 15:41:32 +0000 Subject: [PATCH 24/37] regen file --- coderd/database/dbauthz/dbauthz.go | 16 +++++----- coderd/database/dbmem/dbmem.go | 36 +++++++++++------------ coderd/database/dbmetrics/querymetrics.go | 14 ++++----- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 92d883e192547..1e37c25ad6d18 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -1029,14 +1029,6 @@ func (q *querier) customRoleCheck(ctx context.Context, role database.CustomRole) return nil } -func (q *querier) UpdateInboxNotificationReadStatus(ctx context.Context, args database.UpdateInboxNotificationReadStatusParams) error { - fetchFunc := func(ctx context.Context, args database.UpdateInboxNotificationReadStatusParams) (database.InboxNotification, error) { - return q.db.GetInboxNotificationByID(ctx, args.ID) - } - - return update(q.log, q.auth, fetchFunc, q.db.UpdateInboxNotificationReadStatus)(ctx, args) -} - func (q *querier) AcquireLock(ctx context.Context, id int64) error { return q.db.AcquireLock(ctx, id) } @@ -3700,6 +3692,14 @@ func (q *querier) UpdateInactiveUsersToDormant(ctx context.Context, lastSeenAfte return q.db.UpdateInactiveUsersToDormant(ctx, lastSeenAfter) } +func (q *querier) UpdateInboxNotificationReadStatus(ctx context.Context, args database.UpdateInboxNotificationReadStatusParams) error { + fetchFunc := func(ctx context.Context, args database.UpdateInboxNotificationReadStatusParams) (database.InboxNotification, error) { + return q.db.GetInboxNotificationByID(ctx, args.ID) + } + + return update(q.log, q.auth, fetchFunc, q.db.UpdateInboxNotificationReadStatus)(ctx, args) +} + func (q *querier) UpdateMemberRoles(ctx context.Context, arg database.UpdateMemberRolesParams) (database.OrganizationMember, error) { // Authorized fetch will check that the actor has read access to the org member since the org member is returned. member, err := database.ExpectOne(q.OrganizationMembers(ctx, database.OrganizationMembersParams{ diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 859c43225bfc0..69e05e344e38b 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -1230,24 +1230,6 @@ func (q *FakeQuerier) getProvisionerJobsByIDsWithQueuePositionLocked(_ context.C return jobs, nil } -func (q *FakeQuerier) UpdateInboxNotificationReadStatus(_ context.Context, arg database.UpdateInboxNotificationReadStatusParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i := range q.InboxNotification { - if q.InboxNotification[i].ID == arg.ID { - q.InboxNotification[i].ReadAt = arg.ReadAt - } - } - - return nil -} - func (*FakeQuerier) AcquireLock(_ context.Context, _ int64) error { return xerrors.New("AcquireLock must only be called within a transaction") } @@ -9836,6 +9818,24 @@ func (q *FakeQuerier) UpdateInactiveUsersToDormant(_ context.Context, params dat return updated, nil } +func (q *FakeQuerier) UpdateInboxNotificationReadStatus(_ context.Context, arg database.UpdateInboxNotificationReadStatusParams) error { + err := validateDatabaseType(arg) + if err != nil { + return err + } + + q.mutex.Lock() + defer q.mutex.Unlock() + + for i := range q.InboxNotification { + if q.InboxNotification[i].ID == arg.ID { + q.InboxNotification[i].ReadAt = arg.ReadAt + } + } + + return nil +} + func (q *FakeQuerier) UpdateMemberRoles(_ context.Context, arg database.UpdateMemberRolesParams) (database.OrganizationMember, error) { if err := validateDatabaseType(arg); err != nil { return database.OrganizationMember{}, err diff --git a/coderd/database/dbmetrics/querymetrics.go b/coderd/database/dbmetrics/querymetrics.go index 4e22778f510ab..d76dc08742c57 100644 --- a/coderd/database/dbmetrics/querymetrics.go +++ b/coderd/database/dbmetrics/querymetrics.go @@ -84,13 +84,6 @@ func (m queryMetricsStore) GetUnreadInboxNotificationsByUserIDAndTemplateIDAndTa return r0, r1 } -func (m queryMetricsStore) UpdateInboxNotificationReadStatus(ctx context.Context, arg database.UpdateInboxNotificationReadStatusParams) error { - start := time.Now() - r0 := m.s.UpdateInboxNotificationReadStatus(ctx, arg) - m.queryLatencies.WithLabelValues("UpdateInboxNotificationReadStatus").Observe(time.Since(start).Seconds()) - return r0 -} - func (m queryMetricsStore) AcquireLock(ctx context.Context, pgAdvisoryXactLock int64) error { start := time.Now() err := m.s.AcquireLock(ctx, pgAdvisoryXactLock) @@ -2380,6 +2373,13 @@ func (m queryMetricsStore) UpdateInactiveUsersToDormant(ctx context.Context, las return r0, r1 } +func (m queryMetricsStore) UpdateInboxNotificationReadStatus(ctx context.Context, arg database.UpdateInboxNotificationReadStatusParams) error { + start := time.Now() + r0 := m.s.UpdateInboxNotificationReadStatus(ctx, arg) + m.queryLatencies.WithLabelValues("UpdateInboxNotificationReadStatus").Observe(time.Since(start).Seconds()) + return r0 +} + func (m queryMetricsStore) UpdateMemberRoles(ctx context.Context, arg database.UpdateMemberRolesParams) (database.OrganizationMember, error) { start := time.Now() member, err := m.s.UpdateMemberRoles(ctx, arg) From 30b5ac4574db0f196fd5c9e9290751bcf3185b03 Mon Sep 17 00:00:00 2001 From: defelmnq Date: Sun, 23 Feb 2025 08:54:18 +0000 Subject: [PATCH 25/37] add count queries --- coderd/database/dbauthz/dbauthz.go | 11 +++- coderd/database/dbmem/dbmem.go | 28 ++++++++-- coderd/database/dbmetrics/querymetrics.go | 11 +++- coderd/database/dbmock/dbmock.go | 31 ++++++++--- coderd/database/querier.go | 5 +- coderd/database/queries.sql.go | 54 +++++++++++++++---- .../database/queries/notificationsinbox.sql | 11 ++-- 7 files changed, 119 insertions(+), 32 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 1e37c25ad6d18..096d79273dd21 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -1126,6 +1126,13 @@ func (q *querier) CleanTailnetTunnels(ctx context.Context) error { return q.db.CleanTailnetTunnels(ctx) } +func (q *querier) CountUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) (int64, error) { + if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceNotificationMessage.WithOwner(userID.String())); err != nil { + return 0, err + } + return q.db.CountUnreadInboxNotificationsByUserID(ctx, userID) +} + // TODO: Handle org scoped lookups func (q *querier) CustomRoles(ctx context.Context, arg database.CustomRolesParams) ([]database.CustomRole, error) { if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceAssignRole); err != nil { @@ -1755,7 +1762,7 @@ func (q *querier) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (d return fetchWithAction(q.log, q.auth, policy.ActionRead, q.db.GetInboxNotificationByID)(ctx, id) } -func (q *querier) GetInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { +func (q *querier) GetInboxNotificationsByUserID(ctx context.Context, userID database.GetInboxNotificationsByUserIDParams) ([]database.InboxNotification, error) { return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetInboxNotificationsByUserID)(ctx, userID) } @@ -2453,7 +2460,7 @@ func (q *querier) GetUnexpiredLicenses(ctx context.Context) ([]database.License, return q.db.GetUnexpiredLicenses(ctx) } -func (q *querier) GetUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { +func (q *querier) GetUnreadInboxNotificationsByUserID(ctx context.Context, userID database.GetUnreadInboxNotificationsByUserIDParams) ([]database.InboxNotification, error) { return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetUnreadInboxNotificationsByUserID)(ctx, userID) } diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 69e05e344e38b..53d434fb23350 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -1599,6 +1599,26 @@ func (*FakeQuerier) CleanTailnetTunnels(context.Context) error { return ErrUnimplemented } +func (q *FakeQuerier) CountUnreadInboxNotificationsByUserID(_ context.Context, userID uuid.UUID) (int64, error) { + q.mutex.RLock() + defer q.mutex.RUnlock() + + var count int64 + for _, notification := range q.InboxNotification { + if notification.UserID != userID { + continue + } + + if notification.ReadAt.Valid { + continue + } + + count++ + } + + return count, nil +} + func (q *FakeQuerier) CustomRoles(_ context.Context, arg database.CustomRolesParams) ([]database.CustomRole, error) { q.mutex.Lock() defer q.mutex.Unlock() @@ -3347,13 +3367,13 @@ func (q *FakeQuerier) GetInboxNotificationByID(_ context.Context, id uuid.UUID) return database.InboxNotification{}, sql.ErrNoRows } -func (q *FakeQuerier) GetInboxNotificationsByUserID(_ context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { +func (q *FakeQuerier) GetInboxNotificationsByUserID(_ context.Context, params database.GetInboxNotificationsByUserIDParams) ([]database.InboxNotification, error) { q.mutex.RLock() defer q.mutex.RUnlock() notifications := make([]database.InboxNotification, 0) for _, notification := range q.InboxNotification { - if notification.UserID == userID { + if notification.UserID == params.UserID { notifications = append(notifications, notification) } } @@ -5852,13 +5872,13 @@ func (q *FakeQuerier) GetUnexpiredLicenses(_ context.Context) ([]database.Licens return results, nil } -func (q *FakeQuerier) GetUnreadInboxNotificationsByUserID(_ context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { +func (q *FakeQuerier) GetUnreadInboxNotificationsByUserID(_ context.Context, params database.GetUnreadInboxNotificationsByUserIDParams) ([]database.InboxNotification, error) { q.mutex.RLock() defer q.mutex.RUnlock() notifications := make([]database.InboxNotification, 0) for _, notification := range q.InboxNotification { - if notification.UserID == userID && !notification.ReadAt.Valid { + if notification.UserID == params.UserID && !notification.ReadAt.Valid { notifications = append(notifications, notification) } } diff --git a/coderd/database/dbmetrics/querymetrics.go b/coderd/database/dbmetrics/querymetrics.go index d76dc08742c57..4cc3a49b28c57 100644 --- a/coderd/database/dbmetrics/querymetrics.go +++ b/coderd/database/dbmetrics/querymetrics.go @@ -175,6 +175,13 @@ func (m queryMetricsStore) CleanTailnetTunnels(ctx context.Context) error { return r0 } +func (m queryMetricsStore) CountUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) (int64, error) { + start := time.Now() + r0, r1 := m.s.CountUnreadInboxNotificationsByUserID(ctx, userID) + m.queryLatencies.WithLabelValues("CountUnreadInboxNotificationsByUserID").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m queryMetricsStore) CustomRoles(ctx context.Context, arg database.CustomRolesParams) ([]database.CustomRole, error) { start := time.Now() r0, r1 := m.s.CustomRoles(ctx, arg) @@ -784,7 +791,7 @@ func (m queryMetricsStore) GetInboxNotificationByID(ctx context.Context, id uuid return r0, r1 } -func (m queryMetricsStore) GetInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { +func (m queryMetricsStore) GetInboxNotificationsByUserID(ctx context.Context, userID database.GetInboxNotificationsByUserIDParams) ([]database.InboxNotification, error) { start := time.Now() r0, r1 := m.s.GetInboxNotificationsByUserID(ctx, userID) m.queryLatencies.WithLabelValues("GetInboxNotificationsByUserID").Observe(time.Since(start).Seconds()) @@ -1372,7 +1379,7 @@ func (m queryMetricsStore) GetUnexpiredLicenses(ctx context.Context) ([]database return licenses, err } -func (m queryMetricsStore) GetUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { +func (m queryMetricsStore) GetUnreadInboxNotificationsByUserID(ctx context.Context, userID database.GetUnreadInboxNotificationsByUserIDParams) ([]database.InboxNotification, error) { start := time.Now() r0, r1 := m.s.GetUnreadInboxNotificationsByUserID(ctx, userID) m.queryLatencies.WithLabelValues("GetUnreadInboxNotificationsByUserID").Observe(time.Since(start).Seconds()) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index ac2630ddce456..02b1b0945b2b4 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -232,6 +232,21 @@ func (mr *MockStoreMockRecorder) CleanTailnetTunnels(ctx any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CleanTailnetTunnels", reflect.TypeOf((*MockStore)(nil).CleanTailnetTunnels), ctx) } +// CountUnreadInboxNotificationsByUserID mocks base method. +func (m *MockStore) CountUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CountUnreadInboxNotificationsByUserID", ctx, userID) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CountUnreadInboxNotificationsByUserID indicates an expected call of CountUnreadInboxNotificationsByUserID. +func (mr *MockStoreMockRecorder) CountUnreadInboxNotificationsByUserID(ctx, userID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CountUnreadInboxNotificationsByUserID", reflect.TypeOf((*MockStore)(nil).CountUnreadInboxNotificationsByUserID), ctx, userID) +} + // CustomRoles mocks base method. func (m *MockStore) CustomRoles(ctx context.Context, arg database.CustomRolesParams) ([]database.CustomRole, error) { m.ctrl.T.Helper() @@ -1582,18 +1597,18 @@ func (mr *MockStoreMockRecorder) GetInboxNotificationByID(ctx, id any) *gomock.C } // GetInboxNotificationsByUserID mocks base method. -func (m *MockStore) GetInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { +func (m *MockStore) GetInboxNotificationsByUserID(ctx context.Context, arg database.GetInboxNotificationsByUserIDParams) ([]database.InboxNotification, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetInboxNotificationsByUserID", ctx, userID) + ret := m.ctrl.Call(m, "GetInboxNotificationsByUserID", ctx, arg) ret0, _ := ret[0].([]database.InboxNotification) ret1, _ := ret[1].(error) return ret0, ret1 } // GetInboxNotificationsByUserID indicates an expected call of GetInboxNotificationsByUserID. -func (mr *MockStoreMockRecorder) GetInboxNotificationsByUserID(ctx, userID any) *gomock.Call { +func (mr *MockStoreMockRecorder) GetInboxNotificationsByUserID(ctx, arg any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInboxNotificationsByUserID", reflect.TypeOf((*MockStore)(nil).GetInboxNotificationsByUserID), ctx, userID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInboxNotificationsByUserID", reflect.TypeOf((*MockStore)(nil).GetInboxNotificationsByUserID), ctx, arg) } // GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets mocks base method. @@ -2872,18 +2887,18 @@ func (mr *MockStoreMockRecorder) GetUnexpiredLicenses(ctx any) *gomock.Call { } // GetUnreadInboxNotificationsByUserID mocks base method. -func (m *MockStore) GetUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.InboxNotification, error) { +func (m *MockStore) GetUnreadInboxNotificationsByUserID(ctx context.Context, arg database.GetUnreadInboxNotificationsByUserIDParams) ([]database.InboxNotification, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetUnreadInboxNotificationsByUserID", ctx, userID) + ret := m.ctrl.Call(m, "GetUnreadInboxNotificationsByUserID", ctx, arg) ret0, _ := ret[0].([]database.InboxNotification) ret1, _ := ret[1].(error) return ret0, ret1 } // GetUnreadInboxNotificationsByUserID indicates an expected call of GetUnreadInboxNotificationsByUserID. -func (mr *MockStoreMockRecorder) GetUnreadInboxNotificationsByUserID(ctx, userID any) *gomock.Call { +func (mr *MockStoreMockRecorder) GetUnreadInboxNotificationsByUserID(ctx, arg any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUnreadInboxNotificationsByUserID", reflect.TypeOf((*MockStore)(nil).GetUnreadInboxNotificationsByUserID), ctx, userID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUnreadInboxNotificationsByUserID", reflect.TypeOf((*MockStore)(nil).GetUnreadInboxNotificationsByUserID), ctx, arg) } // GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets mocks base method. diff --git a/coderd/database/querier.go b/coderd/database/querier.go index bd8896d5d9102..72962f7e07076 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -63,6 +63,7 @@ type sqlcQuerier interface { CleanTailnetCoordinators(ctx context.Context) error CleanTailnetLostPeers(ctx context.Context) error CleanTailnetTunnels(ctx context.Context) error + CountUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) (int64, error) CustomRoles(ctx context.Context, arg CustomRolesParams) ([]CustomRole, error) DeleteAPIKeyByID(ctx context.Context, id string) error DeleteAPIKeysByUserID(ctx context.Context, userID uuid.UUID) error @@ -172,7 +173,7 @@ type sqlcQuerier interface { GetHealthSettings(ctx context.Context) (string, error) GetHungProvisionerJobs(ctx context.Context, updatedAt time.Time) ([]ProvisionerJob, error) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (InboxNotification, error) - GetInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]InboxNotification, error) + GetInboxNotificationsByUserID(ctx context.Context, arg GetInboxNotificationsByUserIDParams) ([]InboxNotification, error) // Fetches inbox notifications for a user filtered by templates and targets // param user_id: The user ID // param templates: The template IDs to filter by - the template_id = ANY(@templates::UUID[]) condition checks if the template_id is in the @templates array @@ -288,7 +289,7 @@ type sqlcQuerier interface { GetTemplatesWithFilter(ctx context.Context, arg GetTemplatesWithFilterParams) ([]Template, error) GetUnexpiredLicenses(ctx context.Context) ([]License, error) // - GetUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]InboxNotification, error) + GetUnreadInboxNotificationsByUserID(ctx context.Context, arg GetUnreadInboxNotificationsByUserIDParams) ([]InboxNotification, error) // param user_id: The user ID // param templates: The template IDs to filter by - the template_id = ANY(@templates::UUID[]) condition checks if the template_id is in the @templates array // param targets: The target IDs to filter by - the targets @> COALESCE(@targets, ARRAY[]::UUID[]) condition checks if the targets array (from the DB) contains all the elements in the @targets array diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 08aafc0b69c84..749a46ee842a3 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -4298,6 +4298,17 @@ func (q *sqlQuerier) UpsertNotificationReportGeneratorLog(ctx context.Context, a return err } +const countUnreadInboxNotificationsByUserID = `-- name: CountUnreadInboxNotificationsByUserID :one +SELECT COUNT(*) FROM inbox_notifications WHERE user_id = $1 AND read_at IS NULL +` + +func (q *sqlQuerier) CountUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) (int64, error) { + row := q.db.QueryRowContext(ctx, countUnreadInboxNotificationsByUserID, userID) + var count int64 + err := row.Scan(&count) + return count, err +} + const getInboxNotificationByID = `-- name: GetInboxNotificationByID :one SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE id = $1 ` @@ -4321,11 +4332,16 @@ func (q *sqlQuerier) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) } const getInboxNotificationsByUserID = `-- name: GetInboxNotificationsByUserID :many -SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 ORDER BY created_at DESC +SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 ORDER BY created_at DESC LIMIT (COALESCE(NULLIF($2 :: INT, 0), 25)) ` -func (q *sqlQuerier) GetInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]InboxNotification, error) { - rows, err := q.db.QueryContext(ctx, getInboxNotificationsByUserID, userID) +type GetInboxNotificationsByUserIDParams struct { + UserID uuid.UUID `db:"user_id" json:"user_id"` + LimitOpt int32 `db:"limit_opt" json:"limit_opt"` +} + +func (q *sqlQuerier) GetInboxNotificationsByUserID(ctx context.Context, arg GetInboxNotificationsByUserIDParams) ([]InboxNotification, error) { + rows, err := q.db.QueryContext(ctx, getInboxNotificationsByUserID, arg.UserID, arg.LimitOpt) if err != nil { return nil, err } @@ -4359,13 +4375,14 @@ func (q *sqlQuerier) GetInboxNotificationsByUserID(ctx context.Context, userID u } const getInboxNotificationsByUserIDFilteredByTemplatesAndTargets = `-- name: GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many -SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 AND template_id = ANY($2::UUID[]) AND targets @> COALESCE($3, ARRAY[]::UUID[]) ORDER BY created_at DESC +SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 AND template_id = ANY($2::UUID[]) AND targets @> COALESCE($3, ARRAY[]::UUID[]) ORDER BY created_at DESC LIMIT (COALESCE(NULLIF($4 :: INT, 0), 25)) ` type GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams struct { UserID uuid.UUID `db:"user_id" json:"user_id"` Templates []uuid.UUID `db:"templates" json:"templates"` Targets []uuid.UUID `db:"targets" json:"targets"` + LimitOpt int32 `db:"limit_opt" json:"limit_opt"` } // Fetches inbox notifications for a user filtered by templates and targets @@ -4373,7 +4390,12 @@ type GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams struct { // param templates: The template IDs to filter by - the template_id = ANY(@templates::UUID[]) condition checks if the template_id is in the @templates array // param targets: The target IDs to filter by - the targets @> COALESCE(@targets, ARRAY[]::UUID[]) condition checks if the targets array (from the DB) contains all the elements in the @targets array func (q *sqlQuerier) GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]InboxNotification, error) { - rows, err := q.db.QueryContext(ctx, getInboxNotificationsByUserIDFilteredByTemplatesAndTargets, arg.UserID, pq.Array(arg.Templates), pq.Array(arg.Targets)) + rows, err := q.db.QueryContext(ctx, getInboxNotificationsByUserIDFilteredByTemplatesAndTargets, + arg.UserID, + pq.Array(arg.Templates), + pq.Array(arg.Targets), + arg.LimitOpt, + ) if err != nil { return nil, err } @@ -4407,11 +4429,17 @@ func (q *sqlQuerier) GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets( } const getUnreadInboxNotificationsByUserID = `-- name: GetUnreadInboxNotificationsByUserID :many -SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 AND read_at IS NULL ORDER BY created_at DESC +SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 AND read_at IS NULL AND ($2 IS NULL OR id > $2) ORDER BY created_at DESC LIMIT (COALESCE(NULLIF($3 :: INT, 0), 25)) ` -func (q *sqlQuerier) GetUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) ([]InboxNotification, error) { - rows, err := q.db.QueryContext(ctx, getUnreadInboxNotificationsByUserID, userID) +type GetUnreadInboxNotificationsByUserIDParams struct { + UserID uuid.UUID `db:"user_id" json:"user_id"` + IDOpt interface{} `db:"id_opt" json:"id_opt"` + LimitOpt int32 `db:"limit_opt" json:"limit_opt"` +} + +func (q *sqlQuerier) GetUnreadInboxNotificationsByUserID(ctx context.Context, arg GetUnreadInboxNotificationsByUserIDParams) ([]InboxNotification, error) { + rows, err := q.db.QueryContext(ctx, getUnreadInboxNotificationsByUserID, arg.UserID, arg.IDOpt, arg.LimitOpt) if err != nil { return nil, err } @@ -4445,20 +4473,26 @@ func (q *sqlQuerier) GetUnreadInboxNotificationsByUserID(ctx context.Context, us } const getUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets = `-- name: GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many -SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 AND template_id = ANY($2::UUID[]) AND targets @> COALESCE($3, ARRAY[]::UUID[]) AND read_at IS NULL ORDER BY created_at DESC +SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 AND template_id = ANY($2::UUID[]) AND targets @> COALESCE($3, ARRAY[]::UUID[]) AND read_at IS NULL ORDER BY created_at DESC LIMIT (COALESCE(NULLIF($4 :: INT, 0), 25)) ` type GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams struct { UserID uuid.UUID `db:"user_id" json:"user_id"` Templates []uuid.UUID `db:"templates" json:"templates"` Targets []uuid.UUID `db:"targets" json:"targets"` + LimitOpt int32 `db:"limit_opt" json:"limit_opt"` } // param user_id: The user ID // param templates: The template IDs to filter by - the template_id = ANY(@templates::UUID[]) condition checks if the template_id is in the @templates array // param targets: The target IDs to filter by - the targets @> COALESCE(@targets, ARRAY[]::UUID[]) condition checks if the targets array (from the DB) contains all the elements in the @targets array func (q *sqlQuerier) GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]InboxNotification, error) { - rows, err := q.db.QueryContext(ctx, getUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets, arg.UserID, pq.Array(arg.Templates), pq.Array(arg.Targets)) + rows, err := q.db.QueryContext(ctx, getUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets, + arg.UserID, + pq.Array(arg.Templates), + pq.Array(arg.Targets), + arg.LimitOpt, + ) if err != nil { return nil, err } diff --git a/coderd/database/queries/notificationsinbox.sql b/coderd/database/queries/notificationsinbox.sql index c27dad0ddd9cb..9fbd5b4f0e0ab 100644 --- a/coderd/database/queries/notificationsinbox.sql +++ b/coderd/database/queries/notificationsinbox.sql @@ -1,26 +1,29 @@ -- name: GetUnreadInboxNotificationsByUserID :many -- -SELECT * FROM inbox_notifications WHERE user_id = $1 AND read_at IS NULL ORDER BY created_at DESC; +SELECT * FROM inbox_notifications WHERE user_id = @user_id AND read_at IS NULL AND (@id_opt IS NULL OR id > @id_opt) ORDER BY created_at DESC LIMIT (COALESCE(NULLIF(@limit_opt :: INT, 0), 25)); -- name: GetInboxNotificationsByUserID :many -SELECT * FROM inbox_notifications WHERE user_id = $1 ORDER BY created_at DESC; +SELECT * FROM inbox_notifications WHERE user_id = @user_id ORDER BY created_at DESC LIMIT (COALESCE(NULLIF(@limit_opt :: INT, 0), 25)); -- name: GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many -- Fetches inbox notifications for a user filtered by templates and targets -- param user_id: The user ID -- param templates: The template IDs to filter by - the template_id = ANY(@templates::UUID[]) condition checks if the template_id is in the @templates array -- param targets: The target IDs to filter by - the targets @> COALESCE(@targets, ARRAY[]::UUID[]) condition checks if the targets array (from the DB) contains all the elements in the @targets array -SELECT * FROM inbox_notifications WHERE user_id = @user_id AND template_id = ANY(@templates::UUID[]) AND targets @> COALESCE(@targets, ARRAY[]::UUID[]) ORDER BY created_at DESC; +SELECT * FROM inbox_notifications WHERE user_id = @user_id AND template_id = ANY(@templates::UUID[]) AND targets @> COALESCE(@targets, ARRAY[]::UUID[]) ORDER BY created_at DESC LIMIT (COALESCE(NULLIF(@limit_opt :: INT, 0), 25)); -- name: GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many -- param user_id: The user ID -- param templates: The template IDs to filter by - the template_id = ANY(@templates::UUID[]) condition checks if the template_id is in the @templates array -- param targets: The target IDs to filter by - the targets @> COALESCE(@targets, ARRAY[]::UUID[]) condition checks if the targets array (from the DB) contains all the elements in the @targets array -SELECT * FROM inbox_notifications WHERE user_id = @user_id AND template_id = ANY(@templates::UUID[]) AND targets @> COALESCE(@targets, ARRAY[]::UUID[]) AND read_at IS NULL ORDER BY created_at DESC; +SELECT * FROM inbox_notifications WHERE user_id = @user_id AND template_id = ANY(@templates::UUID[]) AND targets @> COALESCE(@targets, ARRAY[]::UUID[]) AND read_at IS NULL ORDER BY created_at DESC LIMIT (COALESCE(NULLIF(@limit_opt :: INT, 0), 25)); -- name: GetInboxNotificationByID :one SELECT * FROM inbox_notifications WHERE id = $1; +-- name: CountUnreadInboxNotificationsByUserID :one +SELECT COUNT(*) FROM inbox_notifications WHERE user_id = $1 AND read_at IS NULL; + -- name: InsertInboxNotification :one INSERT INTO inbox_notifications ( From 8230ef3e92ea058c6a9ce3d0ed69204dd10b1917 Mon Sep 17 00:00:00 2001 From: defelmnq Date: Sun, 23 Feb 2025 10:07:29 +0000 Subject: [PATCH 26/37] improve queries to add pagination --- coderd/database/queries.sql.go | 43 +++++++++++++++---- .../database/queries/notificationsinbox.sql | 30 +++++++++++-- 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 749a46ee842a3..20408fe2a56e0 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -4332,16 +4332,21 @@ func (q *sqlQuerier) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) } const getInboxNotificationsByUserID = `-- name: GetInboxNotificationsByUserID :many -SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 ORDER BY created_at DESC LIMIT (COALESCE(NULLIF($2 :: INT, 0), 25)) +SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE + user_id = $1 AND + ($2::UUID == '00000000-0000-0000-0000-000000000000'::UUID OR id > $2::UUID) + ORDER BY created_at DESC + LIMIT (COALESCE(NULLIF($3 :: INT, 0), 25)) ` type GetInboxNotificationsByUserIDParams struct { UserID uuid.UUID `db:"user_id" json:"user_id"` + IDOpt uuid.UUID `db:"id_opt" json:"id_opt"` LimitOpt int32 `db:"limit_opt" json:"limit_opt"` } func (q *sqlQuerier) GetInboxNotificationsByUserID(ctx context.Context, arg GetInboxNotificationsByUserIDParams) ([]InboxNotification, error) { - rows, err := q.db.QueryContext(ctx, getInboxNotificationsByUserID, arg.UserID, arg.LimitOpt) + rows, err := q.db.QueryContext(ctx, getInboxNotificationsByUserID, arg.UserID, arg.IDOpt, arg.LimitOpt) if err != nil { return nil, err } @@ -4375,13 +4380,20 @@ func (q *sqlQuerier) GetInboxNotificationsByUserID(ctx context.Context, arg GetI } const getInboxNotificationsByUserIDFilteredByTemplatesAndTargets = `-- name: GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many -SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 AND template_id = ANY($2::UUID[]) AND targets @> COALESCE($3, ARRAY[]::UUID[]) ORDER BY created_at DESC LIMIT (COALESCE(NULLIF($4 :: INT, 0), 25)) +SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE + user_id = $1 AND + template_id = ANY($2::UUID[]) AND + targets @> COALESCE($3, ARRAY[]::UUID[]) AND + ($4::UUID == '00000000-0000-0000-0000-000000000000'::UUID OR id > $4::UUID) + ORDER BY created_at DESC + LIMIT (COALESCE(NULLIF($5 :: INT, 0), 25)) ` type GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams struct { UserID uuid.UUID `db:"user_id" json:"user_id"` Templates []uuid.UUID `db:"templates" json:"templates"` Targets []uuid.UUID `db:"targets" json:"targets"` + IDOpt uuid.UUID `db:"id_opt" json:"id_opt"` LimitOpt int32 `db:"limit_opt" json:"limit_opt"` } @@ -4394,6 +4406,7 @@ func (q *sqlQuerier) GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets( arg.UserID, pq.Array(arg.Templates), pq.Array(arg.Targets), + arg.IDOpt, arg.LimitOpt, ) if err != nil { @@ -4429,13 +4442,18 @@ func (q *sqlQuerier) GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets( } const getUnreadInboxNotificationsByUserID = `-- name: GetUnreadInboxNotificationsByUserID :many -SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 AND read_at IS NULL AND ($2 IS NULL OR id > $2) ORDER BY created_at DESC LIMIT (COALESCE(NULLIF($3 :: INT, 0), 25)) +SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE + user_id = $1 AND + read_at IS NULL AND + ($2::UUID == '00000000-0000-0000-0000-000000000000'::UUID OR id > $2::UUID) + ORDER BY created_at DESC + LIMIT (COALESCE(NULLIF($3 :: INT, 0), 25)) ` type GetUnreadInboxNotificationsByUserIDParams struct { - UserID uuid.UUID `db:"user_id" json:"user_id"` - IDOpt interface{} `db:"id_opt" json:"id_opt"` - LimitOpt int32 `db:"limit_opt" json:"limit_opt"` + UserID uuid.UUID `db:"user_id" json:"user_id"` + IDOpt uuid.UUID `db:"id_opt" json:"id_opt"` + LimitOpt int32 `db:"limit_opt" json:"limit_opt"` } func (q *sqlQuerier) GetUnreadInboxNotificationsByUserID(ctx context.Context, arg GetUnreadInboxNotificationsByUserIDParams) ([]InboxNotification, error) { @@ -4473,13 +4491,21 @@ func (q *sqlQuerier) GetUnreadInboxNotificationsByUserID(ctx context.Context, ar } const getUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets = `-- name: GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many -SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 AND template_id = ANY($2::UUID[]) AND targets @> COALESCE($3, ARRAY[]::UUID[]) AND read_at IS NULL ORDER BY created_at DESC LIMIT (COALESCE(NULLIF($4 :: INT, 0), 25)) +SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE + user_id = $1 AND + template_id = ANY($2::UUID[]) AND + targets @> COALESCE($3, ARRAY[]::UUID[]) AND + read_at IS NULL AND + ($4::UUID == '00000000-0000-0000-0000-000000000000'::UUID OR id > $4::UUID) + ORDER BY created_at DESC + LIMIT (COALESCE(NULLIF($5 :: INT, 0), 25)) ` type GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams struct { UserID uuid.UUID `db:"user_id" json:"user_id"` Templates []uuid.UUID `db:"templates" json:"templates"` Targets []uuid.UUID `db:"targets" json:"targets"` + IDOpt uuid.UUID `db:"id_opt" json:"id_opt"` LimitOpt int32 `db:"limit_opt" json:"limit_opt"` } @@ -4491,6 +4517,7 @@ func (q *sqlQuerier) GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTa arg.UserID, pq.Array(arg.Templates), pq.Array(arg.Targets), + arg.IDOpt, arg.LimitOpt, ) if err != nil { diff --git a/coderd/database/queries/notificationsinbox.sql b/coderd/database/queries/notificationsinbox.sql index 9fbd5b4f0e0ab..f5a61c8e9e1c2 100644 --- a/coderd/database/queries/notificationsinbox.sql +++ b/coderd/database/queries/notificationsinbox.sql @@ -1,22 +1,44 @@ -- name: GetUnreadInboxNotificationsByUserID :many -- -SELECT * FROM inbox_notifications WHERE user_id = @user_id AND read_at IS NULL AND (@id_opt IS NULL OR id > @id_opt) ORDER BY created_at DESC LIMIT (COALESCE(NULLIF(@limit_opt :: INT, 0), 25)); +SELECT * FROM inbox_notifications WHERE + user_id = @user_id AND + read_at IS NULL AND + (@id_opt::UUID == '00000000-0000-0000-0000-000000000000'::UUID OR id > @id_opt::UUID) + ORDER BY created_at DESC + LIMIT (COALESCE(NULLIF(@limit_opt :: INT, 0), 25)); -- name: GetInboxNotificationsByUserID :many -SELECT * FROM inbox_notifications WHERE user_id = @user_id ORDER BY created_at DESC LIMIT (COALESCE(NULLIF(@limit_opt :: INT, 0), 25)); +SELECT * FROM inbox_notifications WHERE + user_id = @user_id AND + (@id_opt::UUID == '00000000-0000-0000-0000-000000000000'::UUID OR id > @id_opt::UUID) + ORDER BY created_at DESC + LIMIT (COALESCE(NULLIF(@limit_opt :: INT, 0), 25)); -- name: GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many -- Fetches inbox notifications for a user filtered by templates and targets -- param user_id: The user ID -- param templates: The template IDs to filter by - the template_id = ANY(@templates::UUID[]) condition checks if the template_id is in the @templates array -- param targets: The target IDs to filter by - the targets @> COALESCE(@targets, ARRAY[]::UUID[]) condition checks if the targets array (from the DB) contains all the elements in the @targets array -SELECT * FROM inbox_notifications WHERE user_id = @user_id AND template_id = ANY(@templates::UUID[]) AND targets @> COALESCE(@targets, ARRAY[]::UUID[]) ORDER BY created_at DESC LIMIT (COALESCE(NULLIF(@limit_opt :: INT, 0), 25)); +SELECT * FROM inbox_notifications WHERE + user_id = @user_id AND + template_id = ANY(@templates::UUID[]) AND + targets @> COALESCE(@targets, ARRAY[]::UUID[]) AND + (@id_opt::UUID == '00000000-0000-0000-0000-000000000000'::UUID OR id > @id_opt::UUID) + ORDER BY created_at DESC + LIMIT (COALESCE(NULLIF(@limit_opt :: INT, 0), 25)); -- name: GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many -- param user_id: The user ID -- param templates: The template IDs to filter by - the template_id = ANY(@templates::UUID[]) condition checks if the template_id is in the @templates array -- param targets: The target IDs to filter by - the targets @> COALESCE(@targets, ARRAY[]::UUID[]) condition checks if the targets array (from the DB) contains all the elements in the @targets array -SELECT * FROM inbox_notifications WHERE user_id = @user_id AND template_id = ANY(@templates::UUID[]) AND targets @> COALESCE(@targets, ARRAY[]::UUID[]) AND read_at IS NULL ORDER BY created_at DESC LIMIT (COALESCE(NULLIF(@limit_opt :: INT, 0), 25)); +SELECT * FROM inbox_notifications WHERE + user_id = @user_id AND + template_id = ANY(@templates::UUID[]) AND + targets @> COALESCE(@targets, ARRAY[]::UUID[]) AND + read_at IS NULL AND + (@id_opt::UUID == '00000000-0000-0000-0000-000000000000'::UUID OR id > @id_opt::UUID) + ORDER BY created_at DESC + LIMIT (COALESCE(NULLIF(@limit_opt :: INT, 0), 25)); -- name: GetInboxNotificationByID :one SELECT * FROM inbox_notifications WHERE id = $1; From 783bfe074f20117710bf936ab418e1401c879da1 Mon Sep 17 00:00:00 2001 From: defelmnq Date: Sun, 23 Feb 2025 11:46:50 +0000 Subject: [PATCH 27/37] fix dbauthz tests --- coderd/database/dbauthz/dbauthz_test.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 7e2edac36a9a9..cce4c50b14c78 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -4493,7 +4493,9 @@ func (s *MethodTestSuite) TestNotifications() { Actions: json.RawMessage("{}"), }) - check.Args(u.ID).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif}) + check.Args(database.GetUnreadInboxNotificationsByUserIDParams{ + UserID: u.ID, + }).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif}) })) s.Run("GetInboxNotificationsByUserID", s.Subtest(func(db database.Store, check *expects) { @@ -4511,7 +4513,9 @@ func (s *MethodTestSuite) TestNotifications() { Actions: json.RawMessage("{}"), }) - check.Args(u.ID).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif}) + check.Args(database.GetInboxNotificationsByUserIDParams{ + UserID: u.ID, + }).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif}) })) s.Run("GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets", s.Subtest(func(db database.Store, check *expects) { From c5aa917a714cd64a596b35fc042701d9e3600536 Mon Sep 17 00:00:00 2001 From: defelmnq Date: Sun, 23 Feb 2025 11:53:46 +0000 Subject: [PATCH 28/37] fix sqlc queries --- coderd/database/queries.sql.go | 8 ++++---- coderd/database/queries/notificationsinbox.sql | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 20408fe2a56e0..39fb0b47099c7 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -4334,7 +4334,7 @@ func (q *sqlQuerier) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) const getInboxNotificationsByUserID = `-- name: GetInboxNotificationsByUserID :many SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 AND - ($2::UUID == '00000000-0000-0000-0000-000000000000'::UUID OR id > $2::UUID) + ($2::UUID = '00000000-0000-0000-0000-000000000000'::UUID OR id > $2::UUID) ORDER BY created_at DESC LIMIT (COALESCE(NULLIF($3 :: INT, 0), 25)) ` @@ -4384,7 +4384,7 @@ SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at user_id = $1 AND template_id = ANY($2::UUID[]) AND targets @> COALESCE($3, ARRAY[]::UUID[]) AND - ($4::UUID == '00000000-0000-0000-0000-000000000000'::UUID OR id > $4::UUID) + ($4::UUID = '00000000-0000-0000-0000-000000000000'::UUID OR id > $4::UUID) ORDER BY created_at DESC LIMIT (COALESCE(NULLIF($5 :: INT, 0), 25)) ` @@ -4445,7 +4445,7 @@ const getUnreadInboxNotificationsByUserID = `-- name: GetUnreadInboxNotification SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 AND read_at IS NULL AND - ($2::UUID == '00000000-0000-0000-0000-000000000000'::UUID OR id > $2::UUID) + ($2::UUID = '00000000-0000-0000-0000-000000000000'::UUID OR id > $2::UUID) ORDER BY created_at DESC LIMIT (COALESCE(NULLIF($3 :: INT, 0), 25)) ` @@ -4496,7 +4496,7 @@ SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at template_id = ANY($2::UUID[]) AND targets @> COALESCE($3, ARRAY[]::UUID[]) AND read_at IS NULL AND - ($4::UUID == '00000000-0000-0000-0000-000000000000'::UUID OR id > $4::UUID) + ($4::UUID = '00000000-0000-0000-0000-000000000000'::UUID OR id > $4::UUID) ORDER BY created_at DESC LIMIT (COALESCE(NULLIF($5 :: INT, 0), 25)) ` diff --git a/coderd/database/queries/notificationsinbox.sql b/coderd/database/queries/notificationsinbox.sql index f5a61c8e9e1c2..72b17a61d262a 100644 --- a/coderd/database/queries/notificationsinbox.sql +++ b/coderd/database/queries/notificationsinbox.sql @@ -3,14 +3,14 @@ SELECT * FROM inbox_notifications WHERE user_id = @user_id AND read_at IS NULL AND - (@id_opt::UUID == '00000000-0000-0000-0000-000000000000'::UUID OR id > @id_opt::UUID) + (@id_opt::UUID = '00000000-0000-0000-0000-000000000000'::UUID OR id > @id_opt::UUID) ORDER BY created_at DESC LIMIT (COALESCE(NULLIF(@limit_opt :: INT, 0), 25)); -- name: GetInboxNotificationsByUserID :many SELECT * FROM inbox_notifications WHERE user_id = @user_id AND - (@id_opt::UUID == '00000000-0000-0000-0000-000000000000'::UUID OR id > @id_opt::UUID) + (@id_opt::UUID = '00000000-0000-0000-0000-000000000000'::UUID OR id > @id_opt::UUID) ORDER BY created_at DESC LIMIT (COALESCE(NULLIF(@limit_opt :: INT, 0), 25)); @@ -23,7 +23,7 @@ SELECT * FROM inbox_notifications WHERE user_id = @user_id AND template_id = ANY(@templates::UUID[]) AND targets @> COALESCE(@targets, ARRAY[]::UUID[]) AND - (@id_opt::UUID == '00000000-0000-0000-0000-000000000000'::UUID OR id > @id_opt::UUID) + (@id_opt::UUID = '00000000-0000-0000-0000-000000000000'::UUID OR id > @id_opt::UUID) ORDER BY created_at DESC LIMIT (COALESCE(NULLIF(@limit_opt :: INT, 0), 25)); @@ -36,7 +36,7 @@ SELECT * FROM inbox_notifications WHERE template_id = ANY(@templates::UUID[]) AND targets @> COALESCE(@targets, ARRAY[]::UUID[]) AND read_at IS NULL AND - (@id_opt::UUID == '00000000-0000-0000-0000-000000000000'::UUID OR id > @id_opt::UUID) + (@id_opt::UUID = '00000000-0000-0000-0000-000000000000'::UUID OR id > @id_opt::UUID) ORDER BY created_at DESC LIMIT (COALESCE(NULLIF(@limit_opt :: INT, 0), 25)); From da50947f3fe7d518a9ba74cb9ad460748833da7c Mon Sep 17 00:00:00 2001 From: defelmnq Date: Sun, 23 Feb 2025 12:34:08 +0000 Subject: [PATCH 29/37] improve dbauthz testing --- coderd/database/dbauthz/dbauthz.go | 2 +- coderd/database/dbauthz/dbauthz_test.go | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 096d79273dd21..5a665292def1d 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -1127,7 +1127,7 @@ func (q *querier) CleanTailnetTunnels(ctx context.Context) error { } func (q *querier) CountUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) (int64, error) { - if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceNotificationMessage.WithOwner(userID.String())); err != nil { + if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceInboxNotification.WithOwner(userID.String())); err != nil { return 0, err } return q.db.CountUnreadInboxNotificationsByUserID(ctx, userID) diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index cce4c50b14c78..ab9a430dc01ee 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -4589,6 +4589,27 @@ func (s *MethodTestSuite) TestNotifications() { check.Args(notifID).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns(notif) })) + s.Run("CountUnreadInboxNotificationsByUserID", s.Subtest(func(db database.Store, check *expects) { + u := dbgen.User(s.T(), db, database.User{}) + + notifID := uuid.New() + + targets := []uuid.UUID{u.ID, notifications.TemplateWorkspaceAutoUpdated} + + _ = dbgen.NotificationInbox(s.T(), db, database.InsertInboxNotificationParams{ + ID: notifID, + UserID: u.ID, + TemplateID: notifications.TemplateWorkspaceAutoUpdated, + Targets: targets, + Title: "test title", + Content: "test content notification", + Icon: "https://coder.com/favicon.ico", + Actions: json.RawMessage("{}"), + }) + + check.Args(u.ID).Asserts(rbac.ResourceInboxNotification.WithOwner(u.ID.String()), policy.ActionRead).Returns(int64(1)) + })) + s.Run("InsertInboxNotification", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) From c1da7f839011afdf1ac20c2558750c9274c53762 Mon Sep 17 00:00:00 2001 From: defelmnq Date: Mon, 24 Feb 2025 12:10:25 +0000 Subject: [PATCH 30/37] improve sql queries --- coderd/database/dbauthz/dbauthz.go | 8 - coderd/database/dbauthz/dbauthz_test.go | 45 ------ coderd/database/dbmem/dbmem.go | 53 ------- coderd/database/dbmetrics/querymetrics.go | 21 --- coderd/database/dbmock/dbmock.go | 30 ---- coderd/database/querier.go | 6 - coderd/database/queries.sql.go | 149 +++--------------- .../database/queries/notificationsinbox.sql | 28 +--- 8 files changed, 28 insertions(+), 312 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 5a665292def1d..e9e04a51314e4 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -2460,14 +2460,6 @@ func (q *querier) GetUnexpiredLicenses(ctx context.Context) ([]database.License, return q.db.GetUnexpiredLicenses(ctx) } -func (q *querier) GetUnreadInboxNotificationsByUserID(ctx context.Context, userID database.GetUnreadInboxNotificationsByUserIDParams) ([]database.InboxNotification, error) { - return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetUnreadInboxNotificationsByUserID)(ctx, userID) -} - -func (q *querier) GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { - return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets)(ctx, arg) -} - func (q *querier) GetUserActivityInsights(ctx context.Context, arg database.GetUserActivityInsightsParams) ([]database.GetUserActivityInsightsRow, error) { // Used by insights endpoints. Need to check both for auditors and for regular users with template acl perms. if err := q.authorizeContext(ctx, policy.ActionViewInsights, rbac.ResourceTemplate); err != nil { diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index ab9a430dc01ee..a5737f048e7da 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -4478,26 +4478,6 @@ func (s *MethodTestSuite) TestNotifications() { }).Asserts(rbac.ResourceNotificationPreference.WithOwner(user.ID.String()), policy.ActionUpdate) })) - s.Run("GetUnreadInboxNotificationsByUserID", s.Subtest(func(db database.Store, check *expects) { - u := dbgen.User(s.T(), db, database.User{}) - - notifID := uuid.New() - - notif := dbgen.NotificationInbox(s.T(), db, database.InsertInboxNotificationParams{ - ID: notifID, - UserID: u.ID, - TemplateID: notifications.TemplateWorkspaceAutoUpdated, - Title: "test title", - Content: "test content notification", - Icon: "https://coder.com/favicon.ico", - Actions: json.RawMessage("{}"), - }) - - check.Args(database.GetUnreadInboxNotificationsByUserIDParams{ - UserID: u.ID, - }).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif}) - })) - s.Run("GetInboxNotificationsByUserID", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) @@ -4543,31 +4523,6 @@ func (s *MethodTestSuite) TestNotifications() { }).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif}) })) - s.Run("GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets", s.Subtest(func(db database.Store, check *expects) { - u := dbgen.User(s.T(), db, database.User{}) - - notifID := uuid.New() - - targets := []uuid.UUID{u.ID, notifications.TemplateWorkspaceAutoUpdated} - - notif := dbgen.NotificationInbox(s.T(), db, database.InsertInboxNotificationParams{ - ID: notifID, - UserID: u.ID, - TemplateID: notifications.TemplateWorkspaceAutoUpdated, - Targets: targets, - Title: "test title", - Content: "test content notification", - Icon: "https://coder.com/favicon.ico", - Actions: json.RawMessage("{}"), - }) - - check.Args(database.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams{ - UserID: u.ID, - Templates: []uuid.UUID{notifications.TemplateWorkspaceAutoUpdated}, - Targets: []uuid.UUID{u.ID}, - }).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif}) - })) - s.Run("GetInboxNotificationByID", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 53d434fb23350..927dfc81fe0c2 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -5872,59 +5872,6 @@ func (q *FakeQuerier) GetUnexpiredLicenses(_ context.Context) ([]database.Licens return results, nil } -func (q *FakeQuerier) GetUnreadInboxNotificationsByUserID(_ context.Context, params database.GetUnreadInboxNotificationsByUserIDParams) ([]database.InboxNotification, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - notifications := make([]database.InboxNotification, 0) - for _, notification := range q.InboxNotification { - if notification.UserID == params.UserID && !notification.ReadAt.Valid { - notifications = append(notifications, notification) - } - } - - return notifications, nil -} - -func (q *FakeQuerier) GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(_ context.Context, arg database.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - notifications := make([]database.InboxNotification, 0) - for _, notification := range q.InboxNotification { - if notification.UserID == arg.UserID && !notification.ReadAt.Valid { - for _, template := range arg.Templates { - templateFound := false - if notification.TemplateID == template { - templateFound = true - } - - if !templateFound { - continue - } - } - - for _, target := range arg.Targets { - isFound := false - for _, insertedTarget := range notification.Targets { - if insertedTarget == target { - isFound = true - break - } - } - - if !isFound { - continue - } - - notifications = append(notifications, notification) - } - } - } - - return notifications, nil -} - func (q *FakeQuerier) GetUserActivityInsights(_ context.Context, arg database.GetUserActivityInsightsParams) ([]database.GetUserActivityInsightsRow, error) { err := validateDatabaseType(arg) if err != nil { diff --git a/coderd/database/dbmetrics/querymetrics.go b/coderd/database/dbmetrics/querymetrics.go index 4cc3a49b28c57..31ec4a4dad5c8 100644 --- a/coderd/database/dbmetrics/querymetrics.go +++ b/coderd/database/dbmetrics/querymetrics.go @@ -77,13 +77,6 @@ func (m queryMetricsStore) InTx(f func(database.Store) error, options *database. return m.dbMetrics.InTx(f, options) } -func (m queryMetricsStore) GetUnreadInboxNotificationsByUserIDAndTemplateIDAndTargets(ctx context.Context, arg database.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { - start := time.Now() - r0, r1 := m.s.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx, arg) - m.queryLatencies.WithLabelValues("GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets").Observe(time.Since(start).Seconds()) - return r0, r1 -} - func (m queryMetricsStore) AcquireLock(ctx context.Context, pgAdvisoryXactLock int64) error { start := time.Now() err := m.s.AcquireLock(ctx, pgAdvisoryXactLock) @@ -1379,20 +1372,6 @@ func (m queryMetricsStore) GetUnexpiredLicenses(ctx context.Context) ([]database return licenses, err } -func (m queryMetricsStore) GetUnreadInboxNotificationsByUserID(ctx context.Context, userID database.GetUnreadInboxNotificationsByUserIDParams) ([]database.InboxNotification, error) { - start := time.Now() - r0, r1 := m.s.GetUnreadInboxNotificationsByUserID(ctx, userID) - m.queryLatencies.WithLabelValues("GetUnreadInboxNotificationsByUserID").Observe(time.Since(start).Seconds()) - return r0, r1 -} - -func (m queryMetricsStore) GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { - start := time.Now() - r0, r1 := m.s.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx, arg) - m.queryLatencies.WithLabelValues("GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets").Observe(time.Since(start).Seconds()) - return r0, r1 -} - func (m queryMetricsStore) GetUserActivityInsights(ctx context.Context, arg database.GetUserActivityInsightsParams) ([]database.GetUserActivityInsightsRow, error) { start := time.Now() r0, r1 := m.s.GetUserActivityInsights(ctx, arg) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index 02b1b0945b2b4..58269709e8bf3 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -2886,36 +2886,6 @@ func (mr *MockStoreMockRecorder) GetUnexpiredLicenses(ctx any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUnexpiredLicenses", reflect.TypeOf((*MockStore)(nil).GetUnexpiredLicenses), ctx) } -// GetUnreadInboxNotificationsByUserID mocks base method. -func (m *MockStore) GetUnreadInboxNotificationsByUserID(ctx context.Context, arg database.GetUnreadInboxNotificationsByUserIDParams) ([]database.InboxNotification, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetUnreadInboxNotificationsByUserID", ctx, arg) - ret0, _ := ret[0].([]database.InboxNotification) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetUnreadInboxNotificationsByUserID indicates an expected call of GetUnreadInboxNotificationsByUserID. -func (mr *MockStoreMockRecorder) GetUnreadInboxNotificationsByUserID(ctx, arg any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUnreadInboxNotificationsByUserID", reflect.TypeOf((*MockStore)(nil).GetUnreadInboxNotificationsByUserID), ctx, arg) -} - -// GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets mocks base method. -func (m *MockStore) GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets", ctx, arg) - ret0, _ := ret[0].([]database.InboxNotification) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets indicates an expected call of GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets. -func (mr *MockStoreMockRecorder) GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx, arg any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets", reflect.TypeOf((*MockStore)(nil).GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets), ctx, arg) -} - // GetUserActivityInsights mocks base method. func (m *MockStore) GetUserActivityInsights(ctx context.Context, arg database.GetUserActivityInsightsParams) ([]database.GetUserActivityInsightsRow, error) { m.ctrl.T.Helper() diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 72962f7e07076..5c2145c412682 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -288,12 +288,6 @@ type sqlcQuerier interface { GetTemplates(ctx context.Context) ([]Template, error) GetTemplatesWithFilter(ctx context.Context, arg GetTemplatesWithFilterParams) ([]Template, error) GetUnexpiredLicenses(ctx context.Context) ([]License, error) - // - GetUnreadInboxNotificationsByUserID(ctx context.Context, arg GetUnreadInboxNotificationsByUserIDParams) ([]InboxNotification, error) - // param user_id: The user ID - // param templates: The template IDs to filter by - the template_id = ANY(@templates::UUID[]) condition checks if the template_id is in the @templates array - // param targets: The target IDs to filter by - the targets @> COALESCE(@targets, ARRAY[]::UUID[]) condition checks if the targets array (from the DB) contains all the elements in the @targets array - GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]InboxNotification, error) // GetUserActivityInsights returns the ranking with top active users. // The result can be filtered on template_ids, meaning only user data // from workspaces based on those templates will be included. diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 39fb0b47099c7..124876df5103a 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -4334,19 +4334,26 @@ func (q *sqlQuerier) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) const getInboxNotificationsByUserID = `-- name: GetInboxNotificationsByUserID :many SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 AND - ($2::UUID = '00000000-0000-0000-0000-000000000000'::UUID OR id > $2::UUID) + ($2 = 'ALL' OR ($2 = 'UNREAD' AND read_at IS NULL) OR ($2 = 'READ' AND read_at IS NOT NULL)) AND + ($3::TIMESTAMPTZ IS NULL OR created_at < $3::TIMESTAMPTZ) ORDER BY created_at DESC - LIMIT (COALESCE(NULLIF($3 :: INT, 0), 25)) + LIMIT (COALESCE(NULLIF($4 :: INT, 0), 25)) ` type GetInboxNotificationsByUserIDParams struct { - UserID uuid.UUID `db:"user_id" json:"user_id"` - IDOpt uuid.UUID `db:"id_opt" json:"id_opt"` - LimitOpt int32 `db:"limit_opt" json:"limit_opt"` + UserID uuid.UUID `db:"user_id" json:"user_id"` + ReadStatus interface{} `db:"read_status" json:"read_status"` + CreatedAtOpt time.Time `db:"created_at_opt" json:"created_at_opt"` + LimitOpt int32 `db:"limit_opt" json:"limit_opt"` } func (q *sqlQuerier) GetInboxNotificationsByUserID(ctx context.Context, arg GetInboxNotificationsByUserIDParams) ([]InboxNotification, error) { - rows, err := q.db.QueryContext(ctx, getInboxNotificationsByUserID, arg.UserID, arg.IDOpt, arg.LimitOpt) + rows, err := q.db.QueryContext(ctx, getInboxNotificationsByUserID, + arg.UserID, + arg.ReadStatus, + arg.CreatedAtOpt, + arg.LimitOpt, + ) if err != nil { return nil, err } @@ -4384,17 +4391,19 @@ SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at user_id = $1 AND template_id = ANY($2::UUID[]) AND targets @> COALESCE($3, ARRAY[]::UUID[]) AND - ($4::UUID = '00000000-0000-0000-0000-000000000000'::UUID OR id > $4::UUID) + ($4 = 'ALL' OR ($4 = 'UNREAD' AND read_at IS NULL) OR ($4 = 'READ' AND read_at IS NOT NULL)) AND + ($5::TIMESTAMPTZ IS NULL OR created_at < $5::TIMESTAMPTZ) ORDER BY created_at DESC - LIMIT (COALESCE(NULLIF($5 :: INT, 0), 25)) + LIMIT (COALESCE(NULLIF($6 :: INT, 0), 25)) ` type GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams struct { - UserID uuid.UUID `db:"user_id" json:"user_id"` - Templates []uuid.UUID `db:"templates" json:"templates"` - Targets []uuid.UUID `db:"targets" json:"targets"` - IDOpt uuid.UUID `db:"id_opt" json:"id_opt"` - LimitOpt int32 `db:"limit_opt" json:"limit_opt"` + UserID uuid.UUID `db:"user_id" json:"user_id"` + Templates []uuid.UUID `db:"templates" json:"templates"` + Targets []uuid.UUID `db:"targets" json:"targets"` + ReadStatus interface{} `db:"read_status" json:"read_status"` + CreatedAtOpt time.Time `db:"created_at_opt" json:"created_at_opt"` + LimitOpt int32 `db:"limit_opt" json:"limit_opt"` } // Fetches inbox notifications for a user filtered by templates and targets @@ -4406,118 +4415,8 @@ func (q *sqlQuerier) GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets( arg.UserID, pq.Array(arg.Templates), pq.Array(arg.Targets), - arg.IDOpt, - arg.LimitOpt, - ) - if err != nil { - return nil, err - } - defer rows.Close() - var items []InboxNotification - for rows.Next() { - var i InboxNotification - if err := rows.Scan( - &i.ID, - &i.UserID, - &i.TemplateID, - pq.Array(&i.Targets), - &i.Title, - &i.Content, - &i.Icon, - &i.Actions, - &i.ReadAt, - &i.CreatedAt, - ); 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 getUnreadInboxNotificationsByUserID = `-- name: GetUnreadInboxNotificationsByUserID :many -SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE - user_id = $1 AND - read_at IS NULL AND - ($2::UUID = '00000000-0000-0000-0000-000000000000'::UUID OR id > $2::UUID) - ORDER BY created_at DESC - LIMIT (COALESCE(NULLIF($3 :: INT, 0), 25)) -` - -type GetUnreadInboxNotificationsByUserIDParams struct { - UserID uuid.UUID `db:"user_id" json:"user_id"` - IDOpt uuid.UUID `db:"id_opt" json:"id_opt"` - LimitOpt int32 `db:"limit_opt" json:"limit_opt"` -} - -func (q *sqlQuerier) GetUnreadInboxNotificationsByUserID(ctx context.Context, arg GetUnreadInboxNotificationsByUserIDParams) ([]InboxNotification, error) { - rows, err := q.db.QueryContext(ctx, getUnreadInboxNotificationsByUserID, arg.UserID, arg.IDOpt, arg.LimitOpt) - if err != nil { - return nil, err - } - defer rows.Close() - var items []InboxNotification - for rows.Next() { - var i InboxNotification - if err := rows.Scan( - &i.ID, - &i.UserID, - &i.TemplateID, - pq.Array(&i.Targets), - &i.Title, - &i.Content, - &i.Icon, - &i.Actions, - &i.ReadAt, - &i.CreatedAt, - ); 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 getUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets = `-- name: GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many -SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE - user_id = $1 AND - template_id = ANY($2::UUID[]) AND - targets @> COALESCE($3, ARRAY[]::UUID[]) AND - read_at IS NULL AND - ($4::UUID = '00000000-0000-0000-0000-000000000000'::UUID OR id > $4::UUID) - ORDER BY created_at DESC - LIMIT (COALESCE(NULLIF($5 :: INT, 0), 25)) -` - -type GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams struct { - UserID uuid.UUID `db:"user_id" json:"user_id"` - Templates []uuid.UUID `db:"templates" json:"templates"` - Targets []uuid.UUID `db:"targets" json:"targets"` - IDOpt uuid.UUID `db:"id_opt" json:"id_opt"` - LimitOpt int32 `db:"limit_opt" json:"limit_opt"` -} - -// param user_id: The user ID -// param templates: The template IDs to filter by - the template_id = ANY(@templates::UUID[]) condition checks if the template_id is in the @templates array -// param targets: The target IDs to filter by - the targets @> COALESCE(@targets, ARRAY[]::UUID[]) condition checks if the targets array (from the DB) contains all the elements in the @targets array -func (q *sqlQuerier) GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]InboxNotification, error) { - rows, err := q.db.QueryContext(ctx, getUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets, - arg.UserID, - pq.Array(arg.Templates), - pq.Array(arg.Targets), - arg.IDOpt, + arg.ReadStatus, + arg.CreatedAtOpt, arg.LimitOpt, ) if err != nil { diff --git a/coderd/database/queries/notificationsinbox.sql b/coderd/database/queries/notificationsinbox.sql index 72b17a61d262a..9743a7c88d509 100644 --- a/coderd/database/queries/notificationsinbox.sql +++ b/coderd/database/queries/notificationsinbox.sql @@ -1,16 +1,8 @@ --- name: GetUnreadInboxNotificationsByUserID :many --- -SELECT * FROM inbox_notifications WHERE - user_id = @user_id AND - read_at IS NULL AND - (@id_opt::UUID = '00000000-0000-0000-0000-000000000000'::UUID OR id > @id_opt::UUID) - ORDER BY created_at DESC - LIMIT (COALESCE(NULLIF(@limit_opt :: INT, 0), 25)); - -- name: GetInboxNotificationsByUserID :many SELECT * FROM inbox_notifications WHERE user_id = @user_id AND - (@id_opt::UUID = '00000000-0000-0000-0000-000000000000'::UUID OR id > @id_opt::UUID) + (@read_status = 'ALL' OR (@read_status = 'UNREAD' AND read_at IS NULL) OR (@read_status = 'READ' AND read_at IS NOT NULL)) AND + (@created_at_opt::TIMESTAMPTZ IS NULL OR created_at < @created_at_opt::TIMESTAMPTZ) ORDER BY created_at DESC LIMIT (COALESCE(NULLIF(@limit_opt :: INT, 0), 25)); @@ -23,20 +15,8 @@ SELECT * FROM inbox_notifications WHERE user_id = @user_id AND template_id = ANY(@templates::UUID[]) AND targets @> COALESCE(@targets, ARRAY[]::UUID[]) AND - (@id_opt::UUID = '00000000-0000-0000-0000-000000000000'::UUID OR id > @id_opt::UUID) - ORDER BY created_at DESC - LIMIT (COALESCE(NULLIF(@limit_opt :: INT, 0), 25)); - --- name: GetUnreadInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many --- param user_id: The user ID --- param templates: The template IDs to filter by - the template_id = ANY(@templates::UUID[]) condition checks if the template_id is in the @templates array --- param targets: The target IDs to filter by - the targets @> COALESCE(@targets, ARRAY[]::UUID[]) condition checks if the targets array (from the DB) contains all the elements in the @targets array -SELECT * FROM inbox_notifications WHERE - user_id = @user_id AND - template_id = ANY(@templates::UUID[]) AND - targets @> COALESCE(@targets, ARRAY[]::UUID[]) AND - read_at IS NULL AND - (@id_opt::UUID = '00000000-0000-0000-0000-000000000000'::UUID OR id > @id_opt::UUID) + (@read_status = 'ALL' OR (@read_status = 'UNREAD' AND read_at IS NULL) OR (@read_status = 'READ' AND read_at IS NOT NULL)) AND + (@created_at_opt::TIMESTAMPTZ IS NULL OR created_at < @created_at_opt::TIMESTAMPTZ) ORDER BY created_at DESC LIMIT (COALESCE(NULLIF(@limit_opt :: INT, 0), 25)); From f03976cbcc9baba28b03b19caf6d841975795a21 Mon Sep 17 00:00:00 2001 From: defelmnq Date: Mon, 24 Feb 2025 12:57:56 +0000 Subject: [PATCH 31/37] add type for read status --- coderd/database/dbauthz/dbauthz_test.go | 10 +++--- coderd/database/queries.sql.go | 14 ++++---- .../database/queries/notificationsinbox.sql | 4 +-- coderd/database/types.go | 34 +++++++++++++++++++ 4 files changed, 49 insertions(+), 13 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index a5737f048e7da..ecc8e5b984f83 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -4494,7 +4494,8 @@ func (s *MethodTestSuite) TestNotifications() { }) check.Args(database.GetInboxNotificationsByUserIDParams{ - UserID: u.ID, + UserID: u.ID, + ReadStatus: database.InboxNotificationReadStatusAll.String(), }).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif}) })) @@ -4517,9 +4518,10 @@ func (s *MethodTestSuite) TestNotifications() { }) check.Args(database.GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams{ - UserID: u.ID, - Templates: []uuid.UUID{notifications.TemplateWorkspaceAutoUpdated}, - Targets: []uuid.UUID{u.ID}, + UserID: u.ID, + Templates: []uuid.UUID{notifications.TemplateWorkspaceAutoUpdated}, + Targets: []uuid.UUID{u.ID}, + ReadStatus: database.InboxNotificationReadStatusAll.String(), }).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif}) })) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 124876df5103a..dead03013f739 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -4334,17 +4334,17 @@ func (q *sqlQuerier) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) const getInboxNotificationsByUserID = `-- name: GetInboxNotificationsByUserID :many SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 AND - ($2 = 'ALL' OR ($2 = 'UNREAD' AND read_at IS NULL) OR ($2 = 'READ' AND read_at IS NOT NULL)) AND + ($2::text = 'ALL' OR ($2::text = 'UNREAD' AND read_at IS NULL) OR ($2::text = 'READ' AND read_at IS NOT NULL)) AND ($3::TIMESTAMPTZ IS NULL OR created_at < $3::TIMESTAMPTZ) ORDER BY created_at DESC LIMIT (COALESCE(NULLIF($4 :: INT, 0), 25)) ` type GetInboxNotificationsByUserIDParams struct { - UserID uuid.UUID `db:"user_id" json:"user_id"` - ReadStatus interface{} `db:"read_status" json:"read_status"` - CreatedAtOpt time.Time `db:"created_at_opt" json:"created_at_opt"` - LimitOpt int32 `db:"limit_opt" json:"limit_opt"` + UserID uuid.UUID `db:"user_id" json:"user_id"` + ReadStatus string `db:"read_status" json:"read_status"` + CreatedAtOpt time.Time `db:"created_at_opt" json:"created_at_opt"` + LimitOpt int32 `db:"limit_opt" json:"limit_opt"` } func (q *sqlQuerier) GetInboxNotificationsByUserID(ctx context.Context, arg GetInboxNotificationsByUserIDParams) ([]InboxNotification, error) { @@ -4391,7 +4391,7 @@ SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at user_id = $1 AND template_id = ANY($2::UUID[]) AND targets @> COALESCE($3, ARRAY[]::UUID[]) AND - ($4 = 'ALL' OR ($4 = 'UNREAD' AND read_at IS NULL) OR ($4 = 'READ' AND read_at IS NOT NULL)) AND + ($4::text = 'ALL' OR ($4::text = 'UNREAD' AND read_at IS NULL) OR ($4::text = 'READ' AND read_at IS NOT NULL)) AND ($5::TIMESTAMPTZ IS NULL OR created_at < $5::TIMESTAMPTZ) ORDER BY created_at DESC LIMIT (COALESCE(NULLIF($6 :: INT, 0), 25)) @@ -4401,7 +4401,7 @@ type GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams struct { UserID uuid.UUID `db:"user_id" json:"user_id"` Templates []uuid.UUID `db:"templates" json:"templates"` Targets []uuid.UUID `db:"targets" json:"targets"` - ReadStatus interface{} `db:"read_status" json:"read_status"` + ReadStatus string `db:"read_status" json:"read_status"` CreatedAtOpt time.Time `db:"created_at_opt" json:"created_at_opt"` LimitOpt int32 `db:"limit_opt" json:"limit_opt"` } diff --git a/coderd/database/queries/notificationsinbox.sql b/coderd/database/queries/notificationsinbox.sql index 9743a7c88d509..3968fd4bc99ed 100644 --- a/coderd/database/queries/notificationsinbox.sql +++ b/coderd/database/queries/notificationsinbox.sql @@ -1,7 +1,7 @@ -- name: GetInboxNotificationsByUserID :many SELECT * FROM inbox_notifications WHERE user_id = @user_id AND - (@read_status = 'ALL' OR (@read_status = 'UNREAD' AND read_at IS NULL) OR (@read_status = 'READ' AND read_at IS NOT NULL)) AND + (@read_status::text = 'ALL' OR (@read_status::text = 'UNREAD' AND read_at IS NULL) OR (@read_status::text = 'READ' AND read_at IS NOT NULL)) AND (@created_at_opt::TIMESTAMPTZ IS NULL OR created_at < @created_at_opt::TIMESTAMPTZ) ORDER BY created_at DESC LIMIT (COALESCE(NULLIF(@limit_opt :: INT, 0), 25)); @@ -15,7 +15,7 @@ SELECT * FROM inbox_notifications WHERE user_id = @user_id AND template_id = ANY(@templates::UUID[]) AND targets @> COALESCE(@targets, ARRAY[]::UUID[]) AND - (@read_status = 'ALL' OR (@read_status = 'UNREAD' AND read_at IS NULL) OR (@read_status = 'READ' AND read_at IS NOT NULL)) AND + (@read_status::text = 'ALL' OR (@read_status::text = 'UNREAD' AND read_at IS NULL) OR (@read_status::text = 'READ' AND read_at IS NOT NULL)) AND (@created_at_opt::TIMESTAMPTZ IS NULL OR created_at < @created_at_opt::TIMESTAMPTZ) ORDER BY created_at DESC LIMIT (COALESCE(NULLIF(@limit_opt :: INT, 0), 25)); diff --git a/coderd/database/types.go b/coderd/database/types.go index 2528a30aa3fe8..92fad78c6d2a1 100644 --- a/coderd/database/types.go +++ b/coderd/database/types.go @@ -35,6 +35,40 @@ type NotificationsSettings struct { NotifierPaused bool `db:"notifier_paused" json:"notifier_paused"` } +type InboxNotificationReadStatus string + +const ( + InboxNotificationReadStatusRead InboxNotificationReadStatus = "READ" + InboxNotificationReadStatusUnread InboxNotificationReadStatus = "UNREAD" + InboxNotificationReadStatusAll InboxNotificationReadStatus = "ALL" +) + +func ParseInboxNotificationReadStatus(s string) (InboxNotificationReadStatus, error) { + switch s { + case "READ": + return InboxNotificationReadStatusRead, nil + case "UNREAD": + return InboxNotificationReadStatusUnread, nil + case "ALL": + return InboxNotificationReadStatusAll, nil + default: + return "", xerrors.Errorf("invalid InboxNotificationReadStatus: %s", s) + } +} + +func (s InboxNotificationReadStatus) String() string { + switch s { + case InboxNotificationReadStatusRead: + return "READ" + case InboxNotificationReadStatusUnread: + return "UNREAD" + case InboxNotificationReadStatusAll: + return "ALL" + default: + return "" + } +} + type Actions []policy.Action func (a *Actions) Scan(src interface{}) error { From 94763e8aa4ab25cf050e48a5fc64167dde216931 Mon Sep 17 00:00:00 2001 From: defelmnq Date: Mon, 24 Feb 2025 13:35:46 +0000 Subject: [PATCH 32/37] iterate on testing dbauthz --- coderd/database/dbauthz/dbauthz_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index ecc8e5b984f83..480c8c7998a10 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -4494,8 +4494,9 @@ func (s *MethodTestSuite) TestNotifications() { }) check.Args(database.GetInboxNotificationsByUserIDParams{ - UserID: u.ID, - ReadStatus: database.InboxNotificationReadStatusAll.String(), + UserID: u.ID, + ReadStatus: database.InboxNotificationReadStatusAll.String(), + CreatedAtOpt: time.Now().Add(-72 * time.Hour), }).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif}) })) From 0c5d3229d3933310b995faedfd219ea5ff58bae2 Mon Sep 17 00:00:00 2001 From: defelmnq Date: Mon, 24 Feb 2025 14:44:45 +0000 Subject: [PATCH 33/37] change zero value of timestamp --- coderd/database/dbauthz/dbauthz_test.go | 13 ++++++------- coderd/database/queries.sql.go | 4 ++-- coderd/database/queries/notificationsinbox.sql | 4 ++-- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 480c8c7998a10..9ef03fa6311f4 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -4483,7 +4483,7 @@ func (s *MethodTestSuite) TestNotifications() { notifID := uuid.New() - notif := dbgen.NotificationInbox(s.T(), db, database.InsertInboxNotificationParams{ + _ = dbgen.NotificationInbox(s.T(), db, database.InsertInboxNotificationParams{ ID: notifID, UserID: u.ID, TemplateID: notifications.TemplateWorkspaceAutoUpdated, @@ -4494,10 +4494,9 @@ func (s *MethodTestSuite) TestNotifications() { }) check.Args(database.GetInboxNotificationsByUserIDParams{ - UserID: u.ID, - ReadStatus: database.InboxNotificationReadStatusAll.String(), - CreatedAtOpt: time.Now().Add(-72 * time.Hour), - }).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif}) + UserID: u.ID, + ReadStatus: database.InboxNotificationReadStatusAll.String(), + }).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead) })) s.Run("GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets", s.Subtest(func(db database.Store, check *expects) { @@ -4507,7 +4506,7 @@ func (s *MethodTestSuite) TestNotifications() { targets := []uuid.UUID{u.ID, notifications.TemplateWorkspaceAutoUpdated} - notif := dbgen.NotificationInbox(s.T(), db, database.InsertInboxNotificationParams{ + _ = dbgen.NotificationInbox(s.T(), db, database.InsertInboxNotificationParams{ ID: notifID, UserID: u.ID, TemplateID: notifications.TemplateWorkspaceAutoUpdated, @@ -4523,7 +4522,7 @@ func (s *MethodTestSuite) TestNotifications() { Templates: []uuid.UUID{notifications.TemplateWorkspaceAutoUpdated}, Targets: []uuid.UUID{u.ID}, ReadStatus: database.InboxNotificationReadStatusAll.String(), - }).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif}) + }).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead) })) s.Run("GetInboxNotificationByID", s.Subtest(func(db database.Store, check *expects) { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index dead03013f739..e1926db66b506 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -4335,7 +4335,7 @@ const getInboxNotificationsByUserID = `-- name: GetInboxNotificationsByUserID :m SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 AND ($2::text = 'ALL' OR ($2::text = 'UNREAD' AND read_at IS NULL) OR ($2::text = 'READ' AND read_at IS NOT NULL)) AND - ($3::TIMESTAMPTZ IS NULL OR created_at < $3::TIMESTAMPTZ) + ($3::TIMESTAMPTZ = '0001-01-01 00:00:00Z' OR created_at < $3::TIMESTAMPTZ) ORDER BY created_at DESC LIMIT (COALESCE(NULLIF($4 :: INT, 0), 25)) ` @@ -4392,7 +4392,7 @@ SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at template_id = ANY($2::UUID[]) AND targets @> COALESCE($3, ARRAY[]::UUID[]) AND ($4::text = 'ALL' OR ($4::text = 'UNREAD' AND read_at IS NULL) OR ($4::text = 'READ' AND read_at IS NOT NULL)) AND - ($5::TIMESTAMPTZ IS NULL OR created_at < $5::TIMESTAMPTZ) + ($5::TIMESTAMPTZ = '0001-01-01 00:00:00Z' OR created_at < $5::TIMESTAMPTZ) ORDER BY created_at DESC LIMIT (COALESCE(NULLIF($6 :: INT, 0), 25)) ` diff --git a/coderd/database/queries/notificationsinbox.sql b/coderd/database/queries/notificationsinbox.sql index 3968fd4bc99ed..451aec18f83f9 100644 --- a/coderd/database/queries/notificationsinbox.sql +++ b/coderd/database/queries/notificationsinbox.sql @@ -2,7 +2,7 @@ SELECT * FROM inbox_notifications WHERE user_id = @user_id AND (@read_status::text = 'ALL' OR (@read_status::text = 'UNREAD' AND read_at IS NULL) OR (@read_status::text = 'READ' AND read_at IS NOT NULL)) AND - (@created_at_opt::TIMESTAMPTZ IS NULL OR created_at < @created_at_opt::TIMESTAMPTZ) + (@created_at_opt::TIMESTAMPTZ = '0001-01-01 00:00:00Z' OR created_at < @created_at_opt::TIMESTAMPTZ) ORDER BY created_at DESC LIMIT (COALESCE(NULLIF(@limit_opt :: INT, 0), 25)); @@ -16,7 +16,7 @@ SELECT * FROM inbox_notifications WHERE template_id = ANY(@templates::UUID[]) AND targets @> COALESCE(@targets, ARRAY[]::UUID[]) AND (@read_status::text = 'ALL' OR (@read_status::text = 'UNREAD' AND read_at IS NULL) OR (@read_status::text = 'READ' AND read_at IS NOT NULL)) AND - (@created_at_opt::TIMESTAMPTZ IS NULL OR created_at < @created_at_opt::TIMESTAMPTZ) + (@created_at_opt::TIMESTAMPTZ = '0001-01-01 00:00:00Z' OR created_at < @created_at_opt::TIMESTAMPTZ) ORDER BY created_at DESC LIMIT (COALESCE(NULLIF(@limit_opt :: INT, 0), 25)); From 66d6fd6d2ab50ffada001765b3331ac4f3f4d108 Mon Sep 17 00:00:00 2001 From: defelmnq Date: Tue, 25 Feb 2025 06:41:26 +0000 Subject: [PATCH 34/37] add returns check in tests --- coderd/database/dbauthz/dbauthz_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 9ef03fa6311f4..ecc8e5b984f83 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -4483,7 +4483,7 @@ func (s *MethodTestSuite) TestNotifications() { notifID := uuid.New() - _ = dbgen.NotificationInbox(s.T(), db, database.InsertInboxNotificationParams{ + notif := dbgen.NotificationInbox(s.T(), db, database.InsertInboxNotificationParams{ ID: notifID, UserID: u.ID, TemplateID: notifications.TemplateWorkspaceAutoUpdated, @@ -4496,7 +4496,7 @@ func (s *MethodTestSuite) TestNotifications() { check.Args(database.GetInboxNotificationsByUserIDParams{ UserID: u.ID, ReadStatus: database.InboxNotificationReadStatusAll.String(), - }).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead) + }).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif}) })) s.Run("GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets", s.Subtest(func(db database.Store, check *expects) { @@ -4506,7 +4506,7 @@ func (s *MethodTestSuite) TestNotifications() { targets := []uuid.UUID{u.ID, notifications.TemplateWorkspaceAutoUpdated} - _ = dbgen.NotificationInbox(s.T(), db, database.InsertInboxNotificationParams{ + notif := dbgen.NotificationInbox(s.T(), db, database.InsertInboxNotificationParams{ ID: notifID, UserID: u.ID, TemplateID: notifications.TemplateWorkspaceAutoUpdated, @@ -4522,7 +4522,7 @@ func (s *MethodTestSuite) TestNotifications() { Templates: []uuid.UUID{notifications.TemplateWorkspaceAutoUpdated}, Targets: []uuid.UUID{u.ID}, ReadStatus: database.InboxNotificationReadStatusAll.String(), - }).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead) + }).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif}) })) s.Run("GetInboxNotificationByID", s.Subtest(func(db database.Store, check *expects) { From b54dbef488d3110c54e92748166a1a75e7888c33 Mon Sep 17 00:00:00 2001 From: defelmnq Date: Tue, 25 Feb 2025 06:49:51 +0000 Subject: [PATCH 35/37] rename migration --- ...cations_inbox.down.sql => 000297_notifications_inbox.down.sql} | 0 ...tifications_inbox.up.sql => 000297_notifications_inbox.up.sql} | 0 ...tifications_inbox.up.sql => 000297_notifications_inbox.up.sql} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename coderd/database/migrations/{000296_notifications_inbox.down.sql => 000297_notifications_inbox.down.sql} (100%) rename coderd/database/migrations/{000296_notifications_inbox.up.sql => 000297_notifications_inbox.up.sql} (100%) rename coderd/database/migrations/testdata/fixtures/{000296_notifications_inbox.up.sql => 000297_notifications_inbox.up.sql} (100%) diff --git a/coderd/database/migrations/000296_notifications_inbox.down.sql b/coderd/database/migrations/000297_notifications_inbox.down.sql similarity index 100% rename from coderd/database/migrations/000296_notifications_inbox.down.sql rename to coderd/database/migrations/000297_notifications_inbox.down.sql diff --git a/coderd/database/migrations/000296_notifications_inbox.up.sql b/coderd/database/migrations/000297_notifications_inbox.up.sql similarity index 100% rename from coderd/database/migrations/000296_notifications_inbox.up.sql rename to coderd/database/migrations/000297_notifications_inbox.up.sql diff --git a/coderd/database/migrations/testdata/fixtures/000296_notifications_inbox.up.sql b/coderd/database/migrations/testdata/fixtures/000297_notifications_inbox.up.sql similarity index 100% rename from coderd/database/migrations/testdata/fixtures/000296_notifications_inbox.up.sql rename to coderd/database/migrations/testdata/fixtures/000297_notifications_inbox.up.sql From e2e895b97ac91710b12937dc0703d4458b41228c Mon Sep 17 00:00:00 2001 From: defelmnq Date: Tue, 25 Feb 2025 07:10:44 +0000 Subject: [PATCH 36/37] improve queries comments --- coderd/database/queries/notificationsinbox.sql | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/coderd/database/queries/notificationsinbox.sql b/coderd/database/queries/notificationsinbox.sql index 451aec18f83f9..7aae7972274cd 100644 --- a/coderd/database/queries/notificationsinbox.sql +++ b/coderd/database/queries/notificationsinbox.sql @@ -1,4 +1,9 @@ -- name: GetInboxNotificationsByUserID :many +-- Fetches inbox notifications for a user filtered by templates and targets +-- param user_id: The user ID +-- param read_status: The read status to filter by - can be any of 'ALL', 'UNREAD', 'READ' +-- param created_at_opt: The created_at timestamp to filter by. This parameter is usd for pagination - it fetches notifications created before the specified timestamp if it is not the zero value +-- param limit_opt: The limit of notifications to fetch. If the limit is not specified, it defaults to 25 SELECT * FROM inbox_notifications WHERE user_id = @user_id AND (@read_status::text = 'ALL' OR (@read_status::text = 'UNREAD' AND read_at IS NULL) OR (@read_status::text = 'READ' AND read_at IS NOT NULL)) AND @@ -11,6 +16,9 @@ SELECT * FROM inbox_notifications WHERE -- param user_id: The user ID -- param templates: The template IDs to filter by - the template_id = ANY(@templates::UUID[]) condition checks if the template_id is in the @templates array -- param targets: The target IDs to filter by - the targets @> COALESCE(@targets, ARRAY[]::UUID[]) condition checks if the targets array (from the DB) contains all the elements in the @targets array +-- param read_status: The read status to filter by - can be any of 'ALL', 'UNREAD', 'READ' +-- param created_at_opt: The created_at timestamp to filter by. This parameter is usd for pagination - it fetches notifications created before the specified timestamp if it is not the zero value +-- param limit_opt: The limit of notifications to fetch. If the limit is not specified, it defaults to 25 SELECT * FROM inbox_notifications WHERE user_id = @user_id AND template_id = ANY(@templates::UUID[]) AND From e6abcbdb4c601124168b8d7f214129154d2b497d Mon Sep 17 00:00:00 2001 From: defelmnq Date: Tue, 25 Feb 2025 08:33:07 +0000 Subject: [PATCH 37/37] cange type for read status --- coderd/database/dbauthz/dbauthz.go | 8 +- coderd/database/dbauthz/dbauthz_test.go | 8 +- coderd/database/dbmem/dbmem.go | 78 ++++++------ coderd/database/dbmetrics/querymetrics.go | 14 +-- coderd/database/dbmock/dbmock.go | 30 ++--- coderd/database/dump.sql | 6 + .../000297_notifications_inbox.down.sql | 2 + .../000297_notifications_inbox.up.sql | 2 + coderd/database/models.go | 61 ++++++++++ coderd/database/querier.go | 16 ++- coderd/database/queries.sql.go | 112 ++++++++++-------- .../database/queries/notificationsinbox.sql | 6 +- coderd/database/types.go | 34 ------ 13 files changed, 215 insertions(+), 162 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 41b41133cc5ba..6ecb4c26e9b8e 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -1694,6 +1694,10 @@ func (q *querier) GetFileTemplates(ctx context.Context, fileID uuid.UUID) ([]dat return q.db.GetFileTemplates(ctx, fileID) } +func (q *querier) GetFilteredInboxNotificationsByUserID(ctx context.Context, arg database.GetFilteredInboxNotificationsByUserIDParams) ([]database.InboxNotification, error) { + return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetFilteredInboxNotificationsByUserID)(ctx, arg) +} + func (q *querier) GetGitSSHKey(ctx context.Context, userID uuid.UUID) (database.GitSSHKey, error) { return fetchWithAction(q.log, q.auth, policy.ActionReadPersonal, q.db.GetGitSSHKey)(ctx, userID) } @@ -1761,10 +1765,6 @@ func (q *querier) GetInboxNotificationsByUserID(ctx context.Context, userID data return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetInboxNotificationsByUserID)(ctx, userID) } -func (q *querier) GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { - return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets)(ctx, arg) -} - func (q *querier) GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.GetJFrogXrayScanByWorkspaceAndAgentIDParams) (database.JfrogXrayScan, error) { if _, err := fetch(q.log, q.auth, q.db.GetWorkspaceByID)(ctx, arg.WorkspaceID); err != nil { return database.JfrogXrayScan{}, err diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 552aae3a8d3e3..6c817d850fe44 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -4496,11 +4496,11 @@ func (s *MethodTestSuite) TestNotifications() { check.Args(database.GetInboxNotificationsByUserIDParams{ UserID: u.ID, - ReadStatus: database.InboxNotificationReadStatusAll.String(), + ReadStatus: database.InboxNotificationReadStatusAll, }).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif}) })) - s.Run("GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets", s.Subtest(func(db database.Store, check *expects) { + s.Run("GetFilteredInboxNotificationsByUserID", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) notifID := uuid.New() @@ -4518,11 +4518,11 @@ func (s *MethodTestSuite) TestNotifications() { Actions: json.RawMessage("{}"), }) - check.Args(database.GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams{ + check.Args(database.GetFilteredInboxNotificationsByUserIDParams{ UserID: u.ID, Templates: []uuid.UUID{notifications.TemplateWorkspaceAutoUpdated}, Targets: []uuid.UUID{u.ID}, - ReadStatus: database.InboxNotificationReadStatusAll.String(), + ReadStatus: database.InboxNotificationReadStatusAll, }).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif}) })) diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index a6e3ce9722247..489a3507bbb81 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -3143,6 +3143,45 @@ func (q *FakeQuerier) GetFileTemplates(_ context.Context, id uuid.UUID) ([]datab return rows, nil } +func (q *FakeQuerier) GetFilteredInboxNotificationsByUserID(_ context.Context, arg database.GetFilteredInboxNotificationsByUserIDParams) ([]database.InboxNotification, error) { + q.mutex.RLock() + defer q.mutex.RUnlock() + + notifications := make([]database.InboxNotification, 0) + for _, notification := range q.InboxNotification { + if notification.UserID == arg.UserID { + for _, template := range arg.Templates { + templateFound := false + if notification.TemplateID == template { + templateFound = true + } + + if !templateFound { + continue + } + } + + for _, target := range arg.Targets { + isFound := false + for _, insertedTarget := range notification.Targets { + if insertedTarget == target { + isFound = true + break + } + } + + if !isFound { + continue + } + + notifications = append(notifications, notification) + } + } + } + + return notifications, nil +} + func (q *FakeQuerier) GetGitSSHKey(_ context.Context, userID uuid.UUID) (database.GitSSHKey, error) { q.mutex.RLock() defer q.mutex.RUnlock() @@ -3368,45 +3407,6 @@ func (q *FakeQuerier) GetInboxNotificationsByUserID(_ context.Context, params da return notifications, nil } -func (q *FakeQuerier) GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(_ context.Context, arg database.GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - notifications := make([]database.InboxNotification, 0) - for _, notification := range q.InboxNotification { - if notification.UserID == arg.UserID { - for _, template := range arg.Templates { - templateFound := false - if notification.TemplateID == template { - templateFound = true - } - - if !templateFound { - continue - } - } - - for _, target := range arg.Targets { - isFound := false - for _, insertedTarget := range notification.Targets { - if insertedTarget == target { - isFound = true - break - } - } - - if !isFound { - continue - } - - notifications = append(notifications, notification) - } - } - } - - return notifications, nil -} - func (q *FakeQuerier) GetJFrogXrayScanByWorkspaceAndAgentID(_ context.Context, arg database.GetJFrogXrayScanByWorkspaceAndAgentIDParams) (database.JfrogXrayScan, error) { err := validateDatabaseType(arg) if err != nil { diff --git a/coderd/database/dbmetrics/querymetrics.go b/coderd/database/dbmetrics/querymetrics.go index 6d022b4e07a33..8deb0b9930a4c 100644 --- a/coderd/database/dbmetrics/querymetrics.go +++ b/coderd/database/dbmetrics/querymetrics.go @@ -717,6 +717,13 @@ func (m queryMetricsStore) GetFileTemplates(ctx context.Context, fileID uuid.UUI return rows, err } +func (m queryMetricsStore) GetFilteredInboxNotificationsByUserID(ctx context.Context, arg database.GetFilteredInboxNotificationsByUserIDParams) ([]database.InboxNotification, error) { + start := time.Now() + r0, r1 := m.s.GetFilteredInboxNotificationsByUserID(ctx, arg) + m.queryLatencies.WithLabelValues("GetFilteredInboxNotificationsByUserID").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m queryMetricsStore) GetGitSSHKey(ctx context.Context, userID uuid.UUID) (database.GitSSHKey, error) { start := time.Now() key, err := m.s.GetGitSSHKey(ctx, userID) @@ -794,13 +801,6 @@ func (m queryMetricsStore) GetInboxNotificationsByUserID(ctx context.Context, us return r0, r1 } -func (m queryMetricsStore) GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { - start := time.Now() - r0, r1 := m.s.GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx, arg) - m.queryLatencies.WithLabelValues("GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets").Observe(time.Since(start).Seconds()) - return r0, r1 -} - func (m queryMetricsStore) GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.GetJFrogXrayScanByWorkspaceAndAgentIDParams) (database.JfrogXrayScan, error) { start := time.Now() r0, r1 := m.s.GetJFrogXrayScanByWorkspaceAndAgentID(ctx, arg) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index 6e7af1c0a042a..a8ccdb1e63f37 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -1432,6 +1432,21 @@ func (mr *MockStoreMockRecorder) GetFileTemplates(ctx, fileID any) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFileTemplates", reflect.TypeOf((*MockStore)(nil).GetFileTemplates), ctx, fileID) } +// GetFilteredInboxNotificationsByUserID mocks base method. +func (m *MockStore) GetFilteredInboxNotificationsByUserID(ctx context.Context, arg database.GetFilteredInboxNotificationsByUserIDParams) ([]database.InboxNotification, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFilteredInboxNotificationsByUserID", ctx, arg) + ret0, _ := ret[0].([]database.InboxNotification) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetFilteredInboxNotificationsByUserID indicates an expected call of GetFilteredInboxNotificationsByUserID. +func (mr *MockStoreMockRecorder) GetFilteredInboxNotificationsByUserID(ctx, arg any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFilteredInboxNotificationsByUserID", reflect.TypeOf((*MockStore)(nil).GetFilteredInboxNotificationsByUserID), ctx, arg) +} + // GetGitSSHKey mocks base method. func (m *MockStore) GetGitSSHKey(ctx context.Context, userID uuid.UUID) (database.GitSSHKey, error) { m.ctrl.T.Helper() @@ -1597,21 +1612,6 @@ func (mr *MockStoreMockRecorder) GetInboxNotificationsByUserID(ctx, arg any) *go return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInboxNotificationsByUserID", reflect.TypeOf((*MockStore)(nil).GetInboxNotificationsByUserID), ctx, arg) } -// GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets mocks base method. -func (m *MockStore) GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg database.GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]database.InboxNotification, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets", ctx, arg) - ret0, _ := ret[0].([]database.InboxNotification) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets indicates an expected call of GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets. -func (mr *MockStoreMockRecorder) GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx, arg any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets", reflect.TypeOf((*MockStore)(nil).GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets), ctx, arg) -} - // GetJFrogXrayScanByWorkspaceAndAgentID mocks base method. func (m *MockStore) GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.GetJFrogXrayScanByWorkspaceAndAgentIDParams) (database.JfrogXrayScan, error) { m.ctrl.T.Helper() diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 9667622cb6e23..c35a30ae2d866 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -66,6 +66,12 @@ CREATE TYPE group_source AS ENUM ( 'oidc' ); +CREATE TYPE inbox_notification_read_status AS ENUM ( + 'all', + 'unread', + 'read' +); + CREATE TYPE log_level AS ENUM ( 'trace', 'debug', diff --git a/coderd/database/migrations/000297_notifications_inbox.down.sql b/coderd/database/migrations/000297_notifications_inbox.down.sql index 803d67939a5b4..9d39b226c8a2c 100644 --- a/coderd/database/migrations/000297_notifications_inbox.down.sql +++ b/coderd/database/migrations/000297_notifications_inbox.down.sql @@ -1 +1,3 @@ DROP TABLE IF EXISTS inbox_notifications; + +DROP TYPE IF EXISTS inbox_notification_read_status; diff --git a/coderd/database/migrations/000297_notifications_inbox.up.sql b/coderd/database/migrations/000297_notifications_inbox.up.sql index 7c6b81afa89d9..c3754c53674df 100644 --- a/coderd/database/migrations/000297_notifications_inbox.up.sql +++ b/coderd/database/migrations/000297_notifications_inbox.up.sql @@ -1,3 +1,5 @@ +CREATE TYPE inbox_notification_read_status AS ENUM ('all', 'unread', 'read'); + CREATE TABLE inbox_notifications ( id UUID PRIMARY KEY, user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, diff --git a/coderd/database/models.go b/coderd/database/models.go index 8a23abc377777..3e0f59e6e9391 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -543,6 +543,67 @@ func AllGroupSourceValues() []GroupSource { } } +type InboxNotificationReadStatus string + +const ( + InboxNotificationReadStatusAll InboxNotificationReadStatus = "all" + InboxNotificationReadStatusUnread InboxNotificationReadStatus = "unread" + InboxNotificationReadStatusRead InboxNotificationReadStatus = "read" +) + +func (e *InboxNotificationReadStatus) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = InboxNotificationReadStatus(s) + case string: + *e = InboxNotificationReadStatus(s) + default: + return fmt.Errorf("unsupported scan type for InboxNotificationReadStatus: %T", src) + } + return nil +} + +type NullInboxNotificationReadStatus struct { + InboxNotificationReadStatus InboxNotificationReadStatus `json:"inbox_notification_read_status"` + Valid bool `json:"valid"` // Valid is true if InboxNotificationReadStatus is not NULL +} + +// Scan implements the Scanner interface. +func (ns *NullInboxNotificationReadStatus) Scan(value interface{}) error { + if value == nil { + ns.InboxNotificationReadStatus, ns.Valid = "", false + return nil + } + ns.Valid = true + return ns.InboxNotificationReadStatus.Scan(value) +} + +// Value implements the driver Valuer interface. +func (ns NullInboxNotificationReadStatus) Value() (driver.Value, error) { + if !ns.Valid { + return nil, nil + } + return string(ns.InboxNotificationReadStatus), nil +} + +func (e InboxNotificationReadStatus) Valid() bool { + switch e { + case InboxNotificationReadStatusAll, + InboxNotificationReadStatusUnread, + InboxNotificationReadStatusRead: + return true + } + return false +} + +func AllInboxNotificationReadStatusValues() []InboxNotificationReadStatus { + return []InboxNotificationReadStatus{ + InboxNotificationReadStatusAll, + InboxNotificationReadStatusUnread, + InboxNotificationReadStatusRead, + } +} + type LogLevel string const ( diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 8766f9662570e..71698a77d8006 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -159,6 +159,14 @@ type sqlcQuerier interface { GetFileByID(ctx context.Context, id uuid.UUID) (File, error) // Get all templates that use a file. GetFileTemplates(ctx context.Context, fileID uuid.UUID) ([]GetFileTemplatesRow, error) + // Fetches inbox notifications for a user filtered by templates and targets + // param user_id: The user ID + // param templates: The template IDs to filter by - the template_id = ANY(@templates::UUID[]) condition checks if the template_id is in the @templates array + // param targets: The target IDs to filter by - the targets @> COALESCE(@targets, ARRAY[]::UUID[]) condition checks if the targets array (from the DB) contains all the elements in the @targets array + // param read_status: The read status to filter by - can be any of 'ALL', 'UNREAD', 'READ' + // param created_at_opt: The created_at timestamp to filter by. This parameter is usd for pagination - it fetches notifications created before the specified timestamp if it is not the zero value + // param limit_opt: The limit of notifications to fetch. If the limit is not specified, it defaults to 25 + GetFilteredInboxNotificationsByUserID(ctx context.Context, arg GetFilteredInboxNotificationsByUserIDParams) ([]InboxNotification, error) GetGitSSHKey(ctx context.Context, userID uuid.UUID) (GitSSHKey, error) GetGroupByID(ctx context.Context, id uuid.UUID) (Group, error) GetGroupByOrgAndName(ctx context.Context, arg GetGroupByOrgAndNameParams) (Group, error) @@ -172,12 +180,12 @@ type sqlcQuerier interface { GetHealthSettings(ctx context.Context) (string, error) GetHungProvisionerJobs(ctx context.Context, updatedAt time.Time) ([]ProvisionerJob, error) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (InboxNotification, error) - GetInboxNotificationsByUserID(ctx context.Context, arg GetInboxNotificationsByUserIDParams) ([]InboxNotification, error) // Fetches inbox notifications for a user filtered by templates and targets // param user_id: The user ID - // param templates: The template IDs to filter by - the template_id = ANY(@templates::UUID[]) condition checks if the template_id is in the @templates array - // param targets: The target IDs to filter by - the targets @> COALESCE(@targets, ARRAY[]::UUID[]) condition checks if the targets array (from the DB) contains all the elements in the @targets array - GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]InboxNotification, error) + // param read_status: The read status to filter by - can be any of 'ALL', 'UNREAD', 'READ' + // param created_at_opt: The created_at timestamp to filter by. This parameter is usd for pagination - it fetches notifications created before the specified timestamp if it is not the zero value + // param limit_opt: The limit of notifications to fetch. If the limit is not specified, it defaults to 25 + GetInboxNotificationsByUserID(ctx context.Context, arg GetInboxNotificationsByUserIDParams) ([]InboxNotification, error) GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg GetJFrogXrayScanByWorkspaceAndAgentIDParams) (JfrogXrayScan, error) GetLastUpdateCheck(ctx context.Context) (string, error) GetLatestCryptoKeyByFeature(ctx context.Context, feature CryptoKeyFeature) (CryptoKey, error) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index ed73bc122506d..9a30fa089e07d 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -4309,47 +4309,38 @@ func (q *sqlQuerier) CountUnreadInboxNotificationsByUserID(ctx context.Context, return count, err } -const getInboxNotificationByID = `-- name: GetInboxNotificationByID :one -SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE id = $1 -` - -func (q *sqlQuerier) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (InboxNotification, error) { - row := q.db.QueryRowContext(ctx, getInboxNotificationByID, id) - var i InboxNotification - err := row.Scan( - &i.ID, - &i.UserID, - &i.TemplateID, - pq.Array(&i.Targets), - &i.Title, - &i.Content, - &i.Icon, - &i.Actions, - &i.ReadAt, - &i.CreatedAt, - ) - return i, err -} - -const getInboxNotificationsByUserID = `-- name: GetInboxNotificationsByUserID :many +const getFilteredInboxNotificationsByUserID = `-- name: GetFilteredInboxNotificationsByUserID :many SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 AND - ($2::text = 'ALL' OR ($2::text = 'UNREAD' AND read_at IS NULL) OR ($2::text = 'READ' AND read_at IS NOT NULL)) AND - ($3::TIMESTAMPTZ = '0001-01-01 00:00:00Z' OR created_at < $3::TIMESTAMPTZ) + template_id = ANY($2::UUID[]) AND + targets @> COALESCE($3, ARRAY[]::UUID[]) AND + ($4::inbox_notification_read_status = 'all' OR ($4::inbox_notification_read_status = 'unread' AND read_at IS NULL) OR ($4::inbox_notification_read_status = 'read' AND read_at IS NOT NULL)) AND + ($5::TIMESTAMPTZ = '0001-01-01 00:00:00Z' OR created_at < $5::TIMESTAMPTZ) ORDER BY created_at DESC - LIMIT (COALESCE(NULLIF($4 :: INT, 0), 25)) + LIMIT (COALESCE(NULLIF($6 :: INT, 0), 25)) ` -type GetInboxNotificationsByUserIDParams struct { - UserID uuid.UUID `db:"user_id" json:"user_id"` - ReadStatus string `db:"read_status" json:"read_status"` - CreatedAtOpt time.Time `db:"created_at_opt" json:"created_at_opt"` - LimitOpt int32 `db:"limit_opt" json:"limit_opt"` +type GetFilteredInboxNotificationsByUserIDParams struct { + UserID uuid.UUID `db:"user_id" json:"user_id"` + Templates []uuid.UUID `db:"templates" json:"templates"` + Targets []uuid.UUID `db:"targets" json:"targets"` + ReadStatus InboxNotificationReadStatus `db:"read_status" json:"read_status"` + CreatedAtOpt time.Time `db:"created_at_opt" json:"created_at_opt"` + LimitOpt int32 `db:"limit_opt" json:"limit_opt"` } -func (q *sqlQuerier) GetInboxNotificationsByUserID(ctx context.Context, arg GetInboxNotificationsByUserIDParams) ([]InboxNotification, error) { - rows, err := q.db.QueryContext(ctx, getInboxNotificationsByUserID, +// Fetches inbox notifications for a user filtered by templates and targets +// param user_id: The user ID +// param templates: The template IDs to filter by - the template_id = ANY(@templates::UUID[]) condition checks if the template_id is in the @templates array +// param targets: The target IDs to filter by - the targets @> COALESCE(@targets, ARRAY[]::UUID[]) condition checks if the targets array (from the DB) contains all the elements in the @targets array +// param read_status: The read status to filter by - can be any of 'ALL', 'UNREAD', 'READ' +// param created_at_opt: The created_at timestamp to filter by. This parameter is usd for pagination - it fetches notifications created before the specified timestamp if it is not the zero value +// param limit_opt: The limit of notifications to fetch. If the limit is not specified, it defaults to 25 +func (q *sqlQuerier) GetFilteredInboxNotificationsByUserID(ctx context.Context, arg GetFilteredInboxNotificationsByUserIDParams) ([]InboxNotification, error) { + rows, err := q.db.QueryContext(ctx, getFilteredInboxNotificationsByUserID, arg.UserID, + pq.Array(arg.Templates), + pq.Array(arg.Targets), arg.ReadStatus, arg.CreatedAtOpt, arg.LimitOpt, @@ -4386,35 +4377,52 @@ func (q *sqlQuerier) GetInboxNotificationsByUserID(ctx context.Context, arg GetI return items, nil } -const getInboxNotificationsByUserIDFilteredByTemplatesAndTargets = `-- name: GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many +const getInboxNotificationByID = `-- name: GetInboxNotificationByID :one +SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE id = $1 +` + +func (q *sqlQuerier) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (InboxNotification, error) { + row := q.db.QueryRowContext(ctx, getInboxNotificationByID, id) + var i InboxNotification + err := row.Scan( + &i.ID, + &i.UserID, + &i.TemplateID, + pq.Array(&i.Targets), + &i.Title, + &i.Content, + &i.Icon, + &i.Actions, + &i.ReadAt, + &i.CreatedAt, + ) + return i, err +} + +const getInboxNotificationsByUserID = `-- name: GetInboxNotificationsByUserID :many SELECT id, user_id, template_id, targets, title, content, icon, actions, read_at, created_at FROM inbox_notifications WHERE user_id = $1 AND - template_id = ANY($2::UUID[]) AND - targets @> COALESCE($3, ARRAY[]::UUID[]) AND - ($4::text = 'ALL' OR ($4::text = 'UNREAD' AND read_at IS NULL) OR ($4::text = 'READ' AND read_at IS NOT NULL)) AND - ($5::TIMESTAMPTZ = '0001-01-01 00:00:00Z' OR created_at < $5::TIMESTAMPTZ) + ($2::inbox_notification_read_status = 'all' OR ($2::inbox_notification_read_status = 'unread' AND read_at IS NULL) OR ($2::inbox_notification_read_status = 'read' AND read_at IS NOT NULL)) AND + ($3::TIMESTAMPTZ = '0001-01-01 00:00:00Z' OR created_at < $3::TIMESTAMPTZ) ORDER BY created_at DESC - LIMIT (COALESCE(NULLIF($6 :: INT, 0), 25)) + LIMIT (COALESCE(NULLIF($4 :: INT, 0), 25)) ` -type GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams struct { - UserID uuid.UUID `db:"user_id" json:"user_id"` - Templates []uuid.UUID `db:"templates" json:"templates"` - Targets []uuid.UUID `db:"targets" json:"targets"` - ReadStatus string `db:"read_status" json:"read_status"` - CreatedAtOpt time.Time `db:"created_at_opt" json:"created_at_opt"` - LimitOpt int32 `db:"limit_opt" json:"limit_opt"` +type GetInboxNotificationsByUserIDParams struct { + UserID uuid.UUID `db:"user_id" json:"user_id"` + ReadStatus InboxNotificationReadStatus `db:"read_status" json:"read_status"` + CreatedAtOpt time.Time `db:"created_at_opt" json:"created_at_opt"` + LimitOpt int32 `db:"limit_opt" json:"limit_opt"` } // Fetches inbox notifications for a user filtered by templates and targets // param user_id: The user ID -// param templates: The template IDs to filter by - the template_id = ANY(@templates::UUID[]) condition checks if the template_id is in the @templates array -// param targets: The target IDs to filter by - the targets @> COALESCE(@targets, ARRAY[]::UUID[]) condition checks if the targets array (from the DB) contains all the elements in the @targets array -func (q *sqlQuerier) GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets(ctx context.Context, arg GetInboxNotificationsByUserIDFilteredByTemplatesAndTargetsParams) ([]InboxNotification, error) { - rows, err := q.db.QueryContext(ctx, getInboxNotificationsByUserIDFilteredByTemplatesAndTargets, +// param read_status: The read status to filter by - can be any of 'ALL', 'UNREAD', 'READ' +// param created_at_opt: The created_at timestamp to filter by. This parameter is usd for pagination - it fetches notifications created before the specified timestamp if it is not the zero value +// param limit_opt: The limit of notifications to fetch. If the limit is not specified, it defaults to 25 +func (q *sqlQuerier) GetInboxNotificationsByUserID(ctx context.Context, arg GetInboxNotificationsByUserIDParams) ([]InboxNotification, error) { + rows, err := q.db.QueryContext(ctx, getInboxNotificationsByUserID, arg.UserID, - pq.Array(arg.Templates), - pq.Array(arg.Targets), arg.ReadStatus, arg.CreatedAtOpt, arg.LimitOpt, diff --git a/coderd/database/queries/notificationsinbox.sql b/coderd/database/queries/notificationsinbox.sql index 7aae7972274cd..cdaf1cf78cb7f 100644 --- a/coderd/database/queries/notificationsinbox.sql +++ b/coderd/database/queries/notificationsinbox.sql @@ -6,12 +6,12 @@ -- param limit_opt: The limit of notifications to fetch. If the limit is not specified, it defaults to 25 SELECT * FROM inbox_notifications WHERE user_id = @user_id AND - (@read_status::text = 'ALL' OR (@read_status::text = 'UNREAD' AND read_at IS NULL) OR (@read_status::text = 'READ' AND read_at IS NOT NULL)) AND + (@read_status::inbox_notification_read_status = 'all' OR (@read_status::inbox_notification_read_status = 'unread' AND read_at IS NULL) OR (@read_status::inbox_notification_read_status = 'read' AND read_at IS NOT NULL)) AND (@created_at_opt::TIMESTAMPTZ = '0001-01-01 00:00:00Z' OR created_at < @created_at_opt::TIMESTAMPTZ) ORDER BY created_at DESC LIMIT (COALESCE(NULLIF(@limit_opt :: INT, 0), 25)); --- name: GetInboxNotificationsByUserIDFilteredByTemplatesAndTargets :many +-- name: GetFilteredInboxNotificationsByUserID :many -- Fetches inbox notifications for a user filtered by templates and targets -- param user_id: The user ID -- param templates: The template IDs to filter by - the template_id = ANY(@templates::UUID[]) condition checks if the template_id is in the @templates array @@ -23,7 +23,7 @@ SELECT * FROM inbox_notifications WHERE user_id = @user_id AND template_id = ANY(@templates::UUID[]) AND targets @> COALESCE(@targets, ARRAY[]::UUID[]) AND - (@read_status::text = 'ALL' OR (@read_status::text = 'UNREAD' AND read_at IS NULL) OR (@read_status::text = 'READ' AND read_at IS NOT NULL)) AND + (@read_status::inbox_notification_read_status = 'all' OR (@read_status::inbox_notification_read_status = 'unread' AND read_at IS NULL) OR (@read_status::inbox_notification_read_status = 'read' AND read_at IS NOT NULL)) AND (@created_at_opt::TIMESTAMPTZ = '0001-01-01 00:00:00Z' OR created_at < @created_at_opt::TIMESTAMPTZ) ORDER BY created_at DESC LIMIT (COALESCE(NULLIF(@limit_opt :: INT, 0), 25)); diff --git a/coderd/database/types.go b/coderd/database/types.go index 92fad78c6d2a1..2528a30aa3fe8 100644 --- a/coderd/database/types.go +++ b/coderd/database/types.go @@ -35,40 +35,6 @@ type NotificationsSettings struct { NotifierPaused bool `db:"notifier_paused" json:"notifier_paused"` } -type InboxNotificationReadStatus string - -const ( - InboxNotificationReadStatusRead InboxNotificationReadStatus = "READ" - InboxNotificationReadStatusUnread InboxNotificationReadStatus = "UNREAD" - InboxNotificationReadStatusAll InboxNotificationReadStatus = "ALL" -) - -func ParseInboxNotificationReadStatus(s string) (InboxNotificationReadStatus, error) { - switch s { - case "READ": - return InboxNotificationReadStatusRead, nil - case "UNREAD": - return InboxNotificationReadStatusUnread, nil - case "ALL": - return InboxNotificationReadStatusAll, nil - default: - return "", xerrors.Errorf("invalid InboxNotificationReadStatus: %s", s) - } -} - -func (s InboxNotificationReadStatus) String() string { - switch s { - case InboxNotificationReadStatusRead: - return "READ" - case InboxNotificationReadStatusUnread: - return "UNREAD" - case InboxNotificationReadStatusAll: - return "ALL" - default: - return "" - } -} - type Actions []policy.Action func (a *Actions) Scan(src interface{}) error {