feat(coderd): add mark-all-as-read endpoint for inbox notifications (#16976)

[Resolve this issue](https://github.com/coder/internal/issues/506)

Add a mark-all-as-read endpoint which is marking as read all
notifications that are not read for the authenticated user.
Also adds the DB logic.
This commit is contained in:
Vincent Vielle
2025-03-20 13:41:54 +01:00
committed by GitHub
parent d8d4b9b86e
commit 4960a1e85a
15 changed files with 262 additions and 0 deletions
+10
View File
@@ -3554,6 +3554,16 @@ func (q *querier) ListWorkspaceAgentPortShares(ctx context.Context, workspaceID
return q.db.ListWorkspaceAgentPortShares(ctx, workspaceID)
}
func (q *querier) MarkAllInboxNotificationsAsRead(ctx context.Context, arg database.MarkAllInboxNotificationsAsReadParams) error {
resource := rbac.ResourceInboxNotification.WithOwner(arg.UserID.String())
if err := q.authorizeContext(ctx, policy.ActionUpdate, resource); err != nil {
return err
}
return q.db.MarkAllInboxNotificationsAsRead(ctx, arg)
}
func (q *querier) OIDCClaimFieldValues(ctx context.Context, args database.OIDCClaimFieldValuesParams) ([]string, error) {
resource := rbac.ResourceIdpsyncSettings
if args.OrganizationID != uuid.Nil {
+9
View File
@@ -4653,6 +4653,15 @@ func (s *MethodTestSuite) TestNotifications() {
ReadAt: sql.NullTime{Time: readAt, Valid: true},
}).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionUpdate)
}))
s.Run("MarkAllInboxNotificationsAsRead", s.Subtest(func(db database.Store, check *expects) {
u := dbgen.User(s.T(), db, database.User{})
check.Args(database.MarkAllInboxNotificationsAsReadParams{
UserID: u.ID,
ReadAt: sql.NullTime{Time: dbtestutil.NowInDefaultTimezone(), Valid: true},
}).Asserts(rbac.ResourceInboxNotification.WithOwner(u.ID.String()), policy.ActionUpdate)
}))
}
func (s *MethodTestSuite) TestOAuth2ProviderApps() {
+15
View File
@@ -9500,6 +9500,21 @@ func (q *FakeQuerier) ListWorkspaceAgentPortShares(_ context.Context, workspaceI
return shares, nil
}
func (q *FakeQuerier) MarkAllInboxNotificationsAsRead(_ context.Context, arg database.MarkAllInboxNotificationsAsReadParams) error {
err := validateDatabaseType(arg)
if err != nil {
return err
}
for idx, notif := range q.inboxNotifications {
if notif.UserID == arg.UserID && !notif.ReadAt.Valid {
q.inboxNotifications[idx].ReadAt = arg.ReadAt
}
}
return nil
}
// nolint:forcetypeassert
func (q *FakeQuerier) OIDCClaimFieldValues(_ context.Context, args database.OIDCClaimFieldValuesParams) ([]string, error) {
orgMembers := q.getOrganizationMemberNoLock(args.OrganizationID)
@@ -2257,6 +2257,13 @@ func (m queryMetricsStore) ListWorkspaceAgentPortShares(ctx context.Context, wor
return r0, r1
}
func (m queryMetricsStore) MarkAllInboxNotificationsAsRead(ctx context.Context, arg database.MarkAllInboxNotificationsAsReadParams) error {
start := time.Now()
r0 := m.s.MarkAllInboxNotificationsAsRead(ctx, arg)
m.queryLatencies.WithLabelValues("MarkAllInboxNotificationsAsRead").Observe(time.Since(start).Seconds())
return r0
}
func (m queryMetricsStore) OIDCClaimFieldValues(ctx context.Context, organizationID database.OIDCClaimFieldValuesParams) ([]string, error) {
start := time.Now()
r0, r1 := m.s.OIDCClaimFieldValues(ctx, organizationID)
+14
View File
@@ -4763,6 +4763,20 @@ func (mr *MockStoreMockRecorder) ListWorkspaceAgentPortShares(ctx, workspaceID a
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListWorkspaceAgentPortShares", reflect.TypeOf((*MockStore)(nil).ListWorkspaceAgentPortShares), ctx, workspaceID)
}
// MarkAllInboxNotificationsAsRead mocks base method.
func (m *MockStore) MarkAllInboxNotificationsAsRead(ctx context.Context, arg database.MarkAllInboxNotificationsAsReadParams) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "MarkAllInboxNotificationsAsRead", ctx, arg)
ret0, _ := ret[0].(error)
return ret0
}
// MarkAllInboxNotificationsAsRead indicates an expected call of MarkAllInboxNotificationsAsRead.
func (mr *MockStoreMockRecorder) MarkAllInboxNotificationsAsRead(ctx, arg any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkAllInboxNotificationsAsRead", reflect.TypeOf((*MockStore)(nil).MarkAllInboxNotificationsAsRead), ctx, arg)
}
// OIDCClaimFieldValues mocks base method.
func (m *MockStore) OIDCClaimFieldValues(ctx context.Context, arg database.OIDCClaimFieldValuesParams) ([]string, error) {
m.ctrl.T.Helper()
+1
View File
@@ -469,6 +469,7 @@ type sqlcQuerier interface {
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)
MarkAllInboxNotificationsAsRead(ctx context.Context, arg MarkAllInboxNotificationsAsReadParams) 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.
+19
View File
@@ -4511,6 +4511,25 @@ func (q *sqlQuerier) InsertInboxNotification(ctx context.Context, arg InsertInbo
return i, err
}
const markAllInboxNotificationsAsRead = `-- name: MarkAllInboxNotificationsAsRead :exec
UPDATE
inbox_notifications
SET
read_at = $1
WHERE
user_id = $2 and read_at IS NULL
`
type MarkAllInboxNotificationsAsReadParams struct {
ReadAt sql.NullTime `db:"read_at" json:"read_at"`
UserID uuid.UUID `db:"user_id" json:"user_id"`
}
func (q *sqlQuerier) MarkAllInboxNotificationsAsRead(ctx context.Context, arg MarkAllInboxNotificationsAsReadParams) error {
_, err := q.db.ExecContext(ctx, markAllInboxNotificationsAsRead, arg.ReadAt, arg.UserID)
return err
}
const updateInboxNotificationReadStatus = `-- name: UpdateInboxNotificationReadStatus :exec
UPDATE
inbox_notifications
@@ -57,3 +57,11 @@ SET
read_at = $1
WHERE
id = $2;
-- name: MarkAllInboxNotificationsAsRead :exec
UPDATE
inbox_notifications
SET
read_at = $1
WHERE
user_id = $2 and read_at IS NULL;