Skip to content

Commit c074f77

Browse files
authored
feat: add notifications inbox db (#16599)
This PR is linked [to the following issue](coder/internal#334). The objective is to create the DB layer and migration for the new `Coder Inbox`.
1 parent d0e2060 commit c074f77

27 files changed

+966
-0
lines changed

coderd/apidoc/docs.go

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/dbauthz/dbauthz.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ var (
281281
DisplayName: "Notifier",
282282
Site: rbac.Permissions(map[string][]policy.Action{
283283
rbac.ResourceNotificationMessage.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
284+
rbac.ResourceInboxNotification.Type: {policy.ActionCreate},
284285
}),
285286
Org: map[string][]rbac.Permission{},
286287
User: []rbac.Permission{},
@@ -1126,6 +1127,14 @@ func (q *querier) CleanTailnetTunnels(ctx context.Context) error {
11261127
return q.db.CleanTailnetTunnels(ctx)
11271128
}
11281129

1130+
func (q *querier) CountUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) (int64, error) {
1131+
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceInboxNotification.WithOwner(userID.String())); err != nil {
1132+
return 0, err
1133+
}
1134+
return q.db.CountUnreadInboxNotificationsByUserID(ctx, userID)
1135+
}
1136+
1137+
// TODO: Handle org scoped lookups
11291138
func (q *querier) CustomRoles(ctx context.Context, arg database.CustomRolesParams) ([]database.CustomRole, error) {
11301139
roleObject := rbac.ResourceAssignRole
11311140
if arg.OrganizationID != uuid.Nil {
@@ -1689,6 +1698,10 @@ func (q *querier) GetFileTemplates(ctx context.Context, fileID uuid.UUID) ([]dat
16891698
return q.db.GetFileTemplates(ctx, fileID)
16901699
}
16911700

1701+
func (q *querier) GetFilteredInboxNotificationsByUserID(ctx context.Context, arg database.GetFilteredInboxNotificationsByUserIDParams) ([]database.InboxNotification, error) {
1702+
return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetFilteredInboxNotificationsByUserID)(ctx, arg)
1703+
}
1704+
16921705
func (q *querier) GetGitSSHKey(ctx context.Context, userID uuid.UUID) (database.GitSSHKey, error) {
16931706
return fetchWithAction(q.log, q.auth, policy.ActionReadPersonal, q.db.GetGitSSHKey)(ctx, userID)
16941707
}
@@ -1748,6 +1761,14 @@ func (q *querier) GetHungProvisionerJobs(ctx context.Context, hungSince time.Tim
17481761
return q.db.GetHungProvisionerJobs(ctx, hungSince)
17491762
}
17501763

1764+
func (q *querier) GetInboxNotificationByID(ctx context.Context, id uuid.UUID) (database.InboxNotification, error) {
1765+
return fetchWithAction(q.log, q.auth, policy.ActionRead, q.db.GetInboxNotificationByID)(ctx, id)
1766+
}
1767+
1768+
func (q *querier) GetInboxNotificationsByUserID(ctx context.Context, userID database.GetInboxNotificationsByUserIDParams) ([]database.InboxNotification, error) {
1769+
return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetInboxNotificationsByUserID)(ctx, userID)
1770+
}
1771+
17511772
func (q *querier) GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.GetJFrogXrayScanByWorkspaceAndAgentIDParams) (database.JfrogXrayScan, error) {
17521773
if _, err := fetch(q.log, q.auth, q.db.GetWorkspaceByID)(ctx, arg.WorkspaceID); err != nil {
17531774
return database.JfrogXrayScan{}, err
@@ -3079,6 +3100,10 @@ func (q *querier) InsertGroupMember(ctx context.Context, arg database.InsertGrou
30793100
return update(q.log, q.auth, fetch, q.db.InsertGroupMember)(ctx, arg)
30803101
}
30813102

3103+
func (q *querier) InsertInboxNotification(ctx context.Context, arg database.InsertInboxNotificationParams) (database.InboxNotification, error) {
3104+
return insert(q.log, q.auth, rbac.ResourceInboxNotification.WithOwner(arg.UserID.String()), q.db.InsertInboxNotification)(ctx, arg)
3105+
}
3106+
30823107
func (q *querier) InsertLicense(ctx context.Context, arg database.InsertLicenseParams) (database.License, error) {
30833108
if err := q.authorizeContext(ctx, policy.ActionCreate, rbac.ResourceLicense); err != nil {
30843109
return database.License{}, err
@@ -3666,6 +3691,14 @@ func (q *querier) UpdateInactiveUsersToDormant(ctx context.Context, lastSeenAfte
36663691
return q.db.UpdateInactiveUsersToDormant(ctx, lastSeenAfter)
36673692
}
36683693

3694+
func (q *querier) UpdateInboxNotificationReadStatus(ctx context.Context, args database.UpdateInboxNotificationReadStatusParams) error {
3695+
fetchFunc := func(ctx context.Context, args database.UpdateInboxNotificationReadStatusParams) (database.InboxNotification, error) {
3696+
return q.db.GetInboxNotificationByID(ctx, args.ID)
3697+
}
3698+
3699+
return update(q.log, q.auth, fetchFunc, q.db.UpdateInboxNotificationReadStatus)(ctx, args)
3700+
}
3701+
36693702
func (q *querier) UpdateMemberRoles(ctx context.Context, arg database.UpdateMemberRolesParams) (database.OrganizationMember, error) {
36703703
// Authorized fetch will check that the actor has read access to the org member since the org member is returned.
36713704
member, err := database.ExpectOne(q.OrganizationMembers(ctx, database.OrganizationMembersParams{

coderd/database/dbauthz/dbauthz_test.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4466,6 +4466,141 @@ func (s *MethodTestSuite) TestNotifications() {
44664466
Disableds: []bool{true, false},
44674467
}).Asserts(rbac.ResourceNotificationPreference.WithOwner(user.ID.String()), policy.ActionUpdate)
44684468
}))
4469+
4470+
s.Run("GetInboxNotificationsByUserID", s.Subtest(func(db database.Store, check *expects) {
4471+
u := dbgen.User(s.T(), db, database.User{})
4472+
4473+
notifID := uuid.New()
4474+
4475+
notif := dbgen.NotificationInbox(s.T(), db, database.InsertInboxNotificationParams{
4476+
ID: notifID,
4477+
UserID: u.ID,
4478+
TemplateID: notifications.TemplateWorkspaceAutoUpdated,
4479+
Title: "test title",
4480+
Content: "test content notification",
4481+
Icon: "https://coder.com/favicon.ico",
4482+
Actions: json.RawMessage("{}"),
4483+
})
4484+
4485+
check.Args(database.GetInboxNotificationsByUserIDParams{
4486+
UserID: u.ID,
4487+
ReadStatus: database.InboxNotificationReadStatusAll,
4488+
}).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif})
4489+
}))
4490+
4491+
s.Run("GetFilteredInboxNotificationsByUserID", s.Subtest(func(db database.Store, check *expects) {
4492+
u := dbgen.User(s.T(), db, database.User{})
4493+
4494+
notifID := uuid.New()
4495+
4496+
targets := []uuid.UUID{u.ID, notifications.TemplateWorkspaceAutoUpdated}
4497+
4498+
notif := dbgen.NotificationInbox(s.T(), db, database.InsertInboxNotificationParams{
4499+
ID: notifID,
4500+
UserID: u.ID,
4501+
TemplateID: notifications.TemplateWorkspaceAutoUpdated,
4502+
Targets: targets,
4503+
Title: "test title",
4504+
Content: "test content notification",
4505+
Icon: "https://coder.com/favicon.ico",
4506+
Actions: json.RawMessage("{}"),
4507+
})
4508+
4509+
check.Args(database.GetFilteredInboxNotificationsByUserIDParams{
4510+
UserID: u.ID,
4511+
Templates: []uuid.UUID{notifications.TemplateWorkspaceAutoUpdated},
4512+
Targets: []uuid.UUID{u.ID},
4513+
ReadStatus: database.InboxNotificationReadStatusAll,
4514+
}).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif})
4515+
}))
4516+
4517+
s.Run("GetInboxNotificationByID", s.Subtest(func(db database.Store, check *expects) {
4518+
u := dbgen.User(s.T(), db, database.User{})
4519+
4520+
notifID := uuid.New()
4521+
4522+
targets := []uuid.UUID{u.ID, notifications.TemplateWorkspaceAutoUpdated}
4523+
4524+
notif := dbgen.NotificationInbox(s.T(), db, database.InsertInboxNotificationParams{
4525+
ID: notifID,
4526+
UserID: u.ID,
4527+
TemplateID: notifications.TemplateWorkspaceAutoUpdated,
4528+
Targets: targets,
4529+
Title: "test title",
4530+
Content: "test content notification",
4531+
Icon: "https://coder.com/favicon.ico",
4532+
Actions: json.RawMessage("{}"),
4533+
})
4534+
4535+
check.Args(notifID).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns(notif)
4536+
}))
4537+
4538+
s.Run("CountUnreadInboxNotificationsByUserID", s.Subtest(func(db database.Store, check *expects) {
4539+
u := dbgen.User(s.T(), db, database.User{})
4540+
4541+
notifID := uuid.New()
4542+
4543+
targets := []uuid.UUID{u.ID, notifications.TemplateWorkspaceAutoUpdated}
4544+
4545+
_ = dbgen.NotificationInbox(s.T(), db, database.InsertInboxNotificationParams{
4546+
ID: notifID,
4547+
UserID: u.ID,
4548+
TemplateID: notifications.TemplateWorkspaceAutoUpdated,
4549+
Targets: targets,
4550+
Title: "test title",
4551+
Content: "test content notification",
4552+
Icon: "https://coder.com/favicon.ico",
4553+
Actions: json.RawMessage("{}"),
4554+
})
4555+
4556+
check.Args(u.ID).Asserts(rbac.ResourceInboxNotification.WithOwner(u.ID.String()), policy.ActionRead).Returns(int64(1))
4557+
}))
4558+
4559+
s.Run("InsertInboxNotification", s.Subtest(func(db database.Store, check *expects) {
4560+
u := dbgen.User(s.T(), db, database.User{})
4561+
4562+
notifID := uuid.New()
4563+
4564+
targets := []uuid.UUID{u.ID, notifications.TemplateWorkspaceAutoUpdated}
4565+
4566+
check.Args(database.InsertInboxNotificationParams{
4567+
ID: notifID,
4568+
UserID: u.ID,
4569+
TemplateID: notifications.TemplateWorkspaceAutoUpdated,
4570+
Targets: targets,
4571+
Title: "test title",
4572+
Content: "test content notification",
4573+
Icon: "https://coder.com/favicon.ico",
4574+
Actions: json.RawMessage("{}"),
4575+
}).Asserts(rbac.ResourceInboxNotification.WithOwner(u.ID.String()), policy.ActionCreate)
4576+
}))
4577+
4578+
s.Run("UpdateInboxNotificationReadStatus", s.Subtest(func(db database.Store, check *expects) {
4579+
u := dbgen.User(s.T(), db, database.User{})
4580+
4581+
notifID := uuid.New()
4582+
4583+
targets := []uuid.UUID{u.ID, notifications.TemplateWorkspaceAutoUpdated}
4584+
readAt := dbtestutil.NowInDefaultTimezone()
4585+
4586+
notif := dbgen.NotificationInbox(s.T(), db, database.InsertInboxNotificationParams{
4587+
ID: notifID,
4588+
UserID: u.ID,
4589+
TemplateID: notifications.TemplateWorkspaceAutoUpdated,
4590+
Targets: targets,
4591+
Title: "test title",
4592+
Content: "test content notification",
4593+
Icon: "https://coder.com/favicon.ico",
4594+
Actions: json.RawMessage("{}"),
4595+
})
4596+
4597+
notif.ReadAt = sql.NullTime{Time: readAt, Valid: true}
4598+
4599+
check.Args(database.UpdateInboxNotificationReadStatusParams{
4600+
ID: notifID,
4601+
ReadAt: sql.NullTime{Time: readAt, Valid: true},
4602+
}).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionUpdate)
4603+
}))
44694604
}
44704605

44714606
func (s *MethodTestSuite) TestOAuth2ProviderApps() {

coderd/database/dbgen/dbgen.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,22 @@ func OrganizationMember(t testing.TB, db database.Store, orig database.Organizat
450450
return mem
451451
}
452452

453+
func NotificationInbox(t testing.TB, db database.Store, orig database.InsertInboxNotificationParams) database.InboxNotification {
454+
notification, err := db.InsertInboxNotification(genCtx, database.InsertInboxNotificationParams{
455+
ID: takeFirst(orig.ID, uuid.New()),
456+
UserID: takeFirst(orig.UserID, uuid.New()),
457+
TemplateID: takeFirst(orig.TemplateID, uuid.New()),
458+
Targets: takeFirstSlice(orig.Targets, []uuid.UUID{}),
459+
Title: takeFirst(orig.Title, testutil.GetRandomName(t)),
460+
Content: takeFirst(orig.Content, testutil.GetRandomName(t)),
461+
Icon: takeFirst(orig.Icon, ""),
462+
Actions: orig.Actions,
463+
CreatedAt: takeFirst(orig.CreatedAt, dbtime.Now()),
464+
})
465+
require.NoError(t, err, "insert notification")
466+
return notification
467+
}
468+
453469
func Group(t testing.TB, db database.Store, orig database.Group) database.Group {
454470
t.Helper()
455471

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy