mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add notifications inbox db (#16599)
This PR is linked [to the following issue](https://github.com/coder/internal/issues/334). The objective is to create the DB layer and migration for the new `Coder Inbox`.
This commit is contained in:
Generated
+2
@@ -13740,6 +13740,7 @@ const docTemplate = `{
|
||||
"group",
|
||||
"group_member",
|
||||
"idpsync_settings",
|
||||
"inbox_notification",
|
||||
"license",
|
||||
"notification_message",
|
||||
"notification_preference",
|
||||
@@ -13775,6 +13776,7 @@ const docTemplate = `{
|
||||
"ResourceGroup",
|
||||
"ResourceGroupMember",
|
||||
"ResourceIdpsyncSettings",
|
||||
"ResourceInboxNotification",
|
||||
"ResourceLicense",
|
||||
"ResourceNotificationMessage",
|
||||
"ResourceNotificationPreference",
|
||||
|
||||
Generated
+2
@@ -12429,6 +12429,7 @@
|
||||
"group",
|
||||
"group_member",
|
||||
"idpsync_settings",
|
||||
"inbox_notification",
|
||||
"license",
|
||||
"notification_message",
|
||||
"notification_preference",
|
||||
@@ -12464,6 +12465,7 @@
|
||||
"ResourceGroup",
|
||||
"ResourceGroupMember",
|
||||
"ResourceIdpsyncSettings",
|
||||
"ResourceInboxNotification",
|
||||
"ResourceLicense",
|
||||
"ResourceNotificationMessage",
|
||||
"ResourceNotificationPreference",
|
||||
|
||||
@@ -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{},
|
||||
@@ -1126,6 +1127,14 @@ 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.ResourceInboxNotification.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) {
|
||||
roleObject := rbac.ResourceAssignRole
|
||||
if arg.OrganizationID != uuid.Nil {
|
||||
@@ -1689,6 +1698,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)
|
||||
}
|
||||
@@ -1748,6 +1761,14 @@ 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.InboxNotification, error) {
|
||||
return fetchWithAction(q.log, q.auth, policy.ActionRead, q.db.GetInboxNotificationByID)(ctx, id)
|
||||
}
|
||||
|
||||
func (q *querier) GetInboxNotificationsByUserID(ctx context.Context, userID database.GetInboxNotificationsByUserIDParams) ([]database.InboxNotification, error) {
|
||||
return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetInboxNotificationsByUserID)(ctx, userID)
|
||||
}
|
||||
|
||||
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
|
||||
@@ -3079,6 +3100,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.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) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionCreate, rbac.ResourceLicense); err != nil {
|
||||
return database.License{}, err
|
||||
@@ -3666,6 +3691,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{
|
||||
|
||||
@@ -4466,6 +4466,141 @@ func (s *MethodTestSuite) TestNotifications() {
|
||||
Disableds: []bool{true, false},
|
||||
}).Asserts(rbac.ResourceNotificationPreference.WithOwner(user.ID.String()), policy.ActionUpdate)
|
||||
}))
|
||||
|
||||
s.Run("GetInboxNotificationsByUserID", 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.GetInboxNotificationsByUserIDParams{
|
||||
UserID: u.ID,
|
||||
ReadStatus: database.InboxNotificationReadStatusAll,
|
||||
}).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionRead).Returns([]database.InboxNotification{notif})
|
||||
}))
|
||||
|
||||
s.Run("GetFilteredInboxNotificationsByUserID", 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.GetFilteredInboxNotificationsByUserIDParams{
|
||||
UserID: u.ID,
|
||||
Templates: []uuid.UUID{notifications.TemplateWorkspaceAutoUpdated},
|
||||
Targets: []uuid.UUID{u.ID},
|
||||
ReadStatus: database.InboxNotificationReadStatusAll,
|
||||
}).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{})
|
||||
|
||||
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(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{})
|
||||
|
||||
notifID := uuid.New()
|
||||
|
||||
targets := []uuid.UUID{u.ID, notifications.TemplateWorkspaceAutoUpdated}
|
||||
|
||||
check.Args(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("{}"),
|
||||
}).Asserts(rbac.ResourceInboxNotification.WithOwner(u.ID.String()), policy.ActionCreate)
|
||||
}))
|
||||
|
||||
s.Run("UpdateInboxNotificationReadStatus", 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}
|
||||
readAt := dbtestutil.NowInDefaultTimezone()
|
||||
|
||||
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("{}"),
|
||||
})
|
||||
|
||||
notif.ReadAt = sql.NullTime{Time: readAt, Valid: true}
|
||||
|
||||
check.Args(database.UpdateInboxNotificationReadStatusParams{
|
||||
ID: notifID,
|
||||
ReadAt: sql.NullTime{Time: readAt, Valid: true},
|
||||
}).Asserts(rbac.ResourceInboxNotification.WithID(notifID).WithOwner(u.ID.String()), policy.ActionUpdate)
|
||||
}))
|
||||
}
|
||||
|
||||
func (s *MethodTestSuite) TestOAuth2ProviderApps() {
|
||||
|
||||
@@ -450,6 +450,22 @@ 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.InboxNotification {
|
||||
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, ""),
|
||||
Actions: orig.Actions,
|
||||
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()
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ func New() database.Store {
|
||||
gitSSHKey: make([]database.GitSSHKey, 0),
|
||||
notificationMessages: make([]database.NotificationMessage, 0),
|
||||
notificationPreferences: make([]database.NotificationPreference, 0),
|
||||
InboxNotification: make([]database.InboxNotification, 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
|
||||
InboxNotification []database.InboxNotification
|
||||
oauth2ProviderApps []database.OAuth2ProviderApp
|
||||
oauth2ProviderAppSecrets []database.OAuth2ProviderAppSecret
|
||||
oauth2ProviderAppCodes []database.OAuth2ProviderAppCode
|
||||
@@ -1606,6 +1608,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()
|
||||
@@ -3130,6 +3152,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()
|
||||
@@ -3328,6 +3389,33 @@ func (q *FakeQuerier) GetHungProvisionerJobs(_ context.Context, hungSince time.T
|
||||
return hungJobs, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetInboxNotificationByID(_ context.Context, id uuid.UUID) (database.InboxNotification, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
for _, notification := range q.InboxNotification {
|
||||
if notification.ID == id {
|
||||
return notification, nil
|
||||
}
|
||||
}
|
||||
|
||||
return database.InboxNotification{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
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 == params.UserID {
|
||||
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 {
|
||||
@@ -7965,6 +8053,30 @@ func (q *FakeQuerier) InsertGroupMember(_ context.Context, arg database.InsertGr
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) InsertInboxNotification(_ context.Context, arg database.InsertInboxNotificationParams) (database.InboxNotification, error) {
|
||||
if err := validateDatabaseType(arg); err != nil {
|
||||
return database.InboxNotification{}, err
|
||||
}
|
||||
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
notification := database.InboxNotification{
|
||||
ID: arg.ID,
|
||||
UserID: arg.UserID,
|
||||
TemplateID: arg.TemplateID,
|
||||
Targets: arg.Targets,
|
||||
Title: arg.Title,
|
||||
Content: arg.Content,
|
||||
Icon: arg.Icon,
|
||||
Actions: arg.Actions,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
q.InboxNotification = append(q.InboxNotification, notification)
|
||||
return notification, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) InsertLicense(
|
||||
_ context.Context, arg database.InsertLicenseParams,
|
||||
) (database.License, error) {
|
||||
@@ -9679,6 +9791,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
|
||||
|
||||
@@ -178,6 +178,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)
|
||||
@@ -710,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)
|
||||
@@ -773,6 +787,20 @@ func (m queryMetricsStore) GetHungProvisionerJobs(ctx context.Context, hungSince
|
||||
return jobs, err
|
||||
}
|
||||
|
||||
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())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
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())
|
||||
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)
|
||||
@@ -1879,6 +1907,13 @@ func (m queryMetricsStore) InsertGroupMember(ctx context.Context, arg database.I
|
||||
return err
|
||||
}
|
||||
|
||||
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())
|
||||
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)
|
||||
@@ -2334,6 +2369,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)
|
||||
|
||||
@@ -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()
|
||||
@@ -1417,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()
|
||||
@@ -1552,6 +1582,36 @@ 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.InboxNotification, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetInboxNotificationByID", ctx, id)
|
||||
ret0, _ := ret[0].(database.InboxNotification)
|
||||
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)
|
||||
}
|
||||
|
||||
// GetInboxNotificationsByUserID mocks base method.
|
||||
func (m *MockStore) GetInboxNotificationsByUserID(ctx context.Context, arg database.GetInboxNotificationsByUserIDParams) ([]database.InboxNotification, error) {
|
||||
m.ctrl.T.Helper()
|
||||
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, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInboxNotificationsByUserID", reflect.TypeOf((*MockStore)(nil).GetInboxNotificationsByUserID), ctx, arg)
|
||||
}
|
||||
|
||||
// GetJFrogXrayScanByWorkspaceAndAgentID mocks base method.
|
||||
func (m *MockStore) GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.GetJFrogXrayScanByWorkspaceAndAgentIDParams) (database.JfrogXrayScan, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -3962,6 +4022,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.InboxNotification, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "InsertInboxNotification", ctx, arg)
|
||||
ret0, _ := ret[0].(database.InboxNotification)
|
||||
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()
|
||||
@@ -4951,6 +5026,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()
|
||||
|
||||
Generated
+32
@@ -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',
|
||||
@@ -899,6 +905,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,
|
||||
@@ -2048,6 +2067,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);
|
||||
|
||||
@@ -2278,6 +2300,10 @@ 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_inbox_notifications_user_id_read_at ON inbox_notifications 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_notification_messages_status ON notification_messages USING btree (status);
|
||||
|
||||
CREATE INDEX idx_organization_member_organization_id_uuid ON organization_members USING btree (organization_id);
|
||||
@@ -2474,6 +2500,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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
DROP TABLE IF EXISTS inbox_notifications;
|
||||
|
||||
DROP TYPE IF EXISTS inbox_notification_read_status;
|
||||
@@ -0,0 +1,17 @@
|
||||
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,
|
||||
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);
|
||||
CREATE INDEX idx_inbox_notifications_user_id_template_id_targets ON inbox_notifications(user_id, template_id, targets);
|
||||
+25
@@ -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
|
||||
'5755e622-fadd-44ca-98da-5df070491844', -- uuid
|
||||
'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', -- uuid
|
||||
ARRAY[]::UUID[], -- uuid[]
|
||||
'Test Notification',
|
||||
'This is a test notification',
|
||||
'https://test.coder.com/favicon.ico',
|
||||
'{}',
|
||||
'2025-01-01 00:00:00',
|
||||
'2025-01-01 00:00:00'
|
||||
);
|
||||
@@ -168,6 +168,12 @@ func (TemplateVersion) RBACObject(template Template) rbac.Object {
|
||||
return template.RBACObject()
|
||||
}
|
||||
|
||||
func (i InboxNotification) RBACObject() rbac.Object {
|
||||
return rbac.ResourceInboxNotification.
|
||||
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)
|
||||
|
||||
@@ -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 (
|
||||
@@ -2557,6 +2618,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"`
|
||||
|
||||
@@ -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
|
||||
@@ -158,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)
|
||||
@@ -170,6 +179,13 @@ 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) (InboxNotification, error)
|
||||
// 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
|
||||
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)
|
||||
@@ -396,6 +412,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) (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
|
||||
@@ -479,6 +496,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)
|
||||
|
||||
@@ -4298,6 +4298,243 @@ 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 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
|
||||
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($6 :: INT, 0), 25))
|
||||
`
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
// 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,
|
||||
)
|
||||
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 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
|
||||
($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($4 :: INT, 0), 25))
|
||||
`
|
||||
|
||||
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 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,
|
||||
arg.ReadStatus,
|
||||
arg.CreatedAtOpt,
|
||||
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 insertInboxNotification = `-- name: InsertInboxNotification :one
|
||||
INSERT INTO
|
||||
inbox_notifications (
|
||||
id,
|
||||
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, 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"`
|
||||
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"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertInboxNotification(ctx context.Context, arg InsertInboxNotificationParams) (InboxNotification, error) {
|
||||
row := q.db.QueryRowContext(ctx, insertInboxNotification,
|
||||
arg.ID,
|
||||
arg.UserID,
|
||||
arg.TemplateID,
|
||||
pq.Array(arg.Targets),
|
||||
arg.Title,
|
||||
arg.Content,
|
||||
arg.Icon,
|
||||
arg.Actions,
|
||||
arg.CreatedAt,
|
||||
)
|
||||
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 updateInboxNotificationReadStatus = `-- name: UpdateInboxNotificationReadStatus :exec
|
||||
UPDATE
|
||||
inbox_notifications
|
||||
SET
|
||||
read_at = $1
|
||||
WHERE
|
||||
id = $2
|
||||
`
|
||||
|
||||
type UpdateInboxNotificationReadStatusParams struct {
|
||||
ReadAt sql.NullTime `db:"read_at" json:"read_at"`
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateInboxNotificationReadStatus(ctx context.Context, arg UpdateInboxNotificationReadStatusParams) error {
|
||||
_, err := q.db.ExecContext(ctx, updateInboxNotificationReadStatus, arg.ReadAt, arg.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteOAuth2ProviderAppByID = `-- name: DeleteOAuth2ProviderAppByID :exec
|
||||
DELETE FROM oauth2_provider_apps WHERE id = $1
|
||||
`
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
-- 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::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: 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
|
||||
-- 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
|
||||
targets @> COALESCE(@targets, ARRAY[]::UUID[]) 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: 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 (
|
||||
id,
|
||||
user_id,
|
||||
template_id,
|
||||
targets,
|
||||
title,
|
||||
content,
|
||||
icon,
|
||||
actions,
|
||||
created_at
|
||||
)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *;
|
||||
|
||||
-- name: UpdateInboxNotificationReadStatus :exec
|
||||
UPDATE
|
||||
inbox_notifications
|
||||
SET
|
||||
read_at = $1
|
||||
WHERE
|
||||
id = $2;
|
||||
@@ -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);
|
||||
|
||||
@@ -119,6 +119,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
|
||||
@@ -334,6 +343,7 @@ func AllResources() []Objecter {
|
||||
ResourceGroup,
|
||||
ResourceGroupMember,
|
||||
ResourceIdpsyncSettings,
|
||||
ResourceInboxNotification,
|
||||
ResourceLicense,
|
||||
ResourceNotificationMessage,
|
||||
ResourceNotificationPreference,
|
||||
|
||||
@@ -280,6 +280,13 @@ var RBACPermissions = map[string]PermissionDefinition{
|
||||
ActionUpdate: actDef("update notification preferences"),
|
||||
},
|
||||
},
|
||||
"inbox_notification": {
|
||||
Actions: map[Action]ActionDefinition{
|
||||
ActionCreate: actDef("create inbox notifications"),
|
||||
ActionRead: actDef("read inbox notifications"),
|
||||
ActionUpdate: actDef("update inbox notifications"),
|
||||
},
|
||||
},
|
||||
"crypto_key": {
|
||||
Actions: map[Action]ActionDefinition{
|
||||
ActionRead: actDef("read crypto keys"),
|
||||
|
||||
@@ -365,6 +365,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},
|
||||
|
||||
@@ -17,6 +17,7 @@ const (
|
||||
ResourceGroup RBACResource = "group"
|
||||
ResourceGroupMember RBACResource = "group_member"
|
||||
ResourceIdpsyncSettings RBACResource = "idpsync_settings"
|
||||
ResourceInboxNotification RBACResource = "inbox_notification"
|
||||
ResourceLicense RBACResource = "license"
|
||||
ResourceNotificationMessage RBACResource = "notification_message"
|
||||
ResourceNotificationPreference RBACResource = "notification_preference"
|
||||
@@ -74,6 +75,7 @@ var RBACResourceActions = map[RBACResource][]RBACAction{
|
||||
ResourceGroup: {ActionCreate, ActionDelete, ActionRead, ActionUpdate},
|
||||
ResourceGroupMember: {ActionRead},
|
||||
ResourceIdpsyncSettings: {ActionRead, ActionUpdate},
|
||||
ResourceInboxNotification: {ActionCreate, ActionRead, ActionUpdate},
|
||||
ResourceLicense: {ActionCreate, ActionDelete, ActionRead},
|
||||
ResourceNotificationMessage: {ActionCreate, ActionDelete, ActionRead, ActionUpdate},
|
||||
ResourceNotificationPreference: {ActionRead, ActionUpdate},
|
||||
|
||||
Generated
+5
@@ -193,6 +193,7 @@ Status Code **200**
|
||||
| `resource_type` | `group` |
|
||||
| `resource_type` | `group_member` |
|
||||
| `resource_type` | `idpsync_settings` |
|
||||
| `resource_type` | `inbox_notification` |
|
||||
| `resource_type` | `license` |
|
||||
| `resource_type` | `notification_message` |
|
||||
| `resource_type` | `notification_preference` |
|
||||
@@ -356,6 +357,7 @@ Status Code **200**
|
||||
| `resource_type` | `group` |
|
||||
| `resource_type` | `group_member` |
|
||||
| `resource_type` | `idpsync_settings` |
|
||||
| `resource_type` | `inbox_notification` |
|
||||
| `resource_type` | `license` |
|
||||
| `resource_type` | `notification_message` |
|
||||
| `resource_type` | `notification_preference` |
|
||||
@@ -519,6 +521,7 @@ Status Code **200**
|
||||
| `resource_type` | `group` |
|
||||
| `resource_type` | `group_member` |
|
||||
| `resource_type` | `idpsync_settings` |
|
||||
| `resource_type` | `inbox_notification` |
|
||||
| `resource_type` | `license` |
|
||||
| `resource_type` | `notification_message` |
|
||||
| `resource_type` | `notification_preference` |
|
||||
@@ -651,6 +654,7 @@ Status Code **200**
|
||||
| `resource_type` | `group` |
|
||||
| `resource_type` | `group_member` |
|
||||
| `resource_type` | `idpsync_settings` |
|
||||
| `resource_type` | `inbox_notification` |
|
||||
| `resource_type` | `license` |
|
||||
| `resource_type` | `notification_message` |
|
||||
| `resource_type` | `notification_preference` |
|
||||
@@ -915,6 +919,7 @@ Status Code **200**
|
||||
| `resource_type` | `group` |
|
||||
| `resource_type` | `group_member` |
|
||||
| `resource_type` | `idpsync_settings` |
|
||||
| `resource_type` | `inbox_notification` |
|
||||
| `resource_type` | `license` |
|
||||
| `resource_type` | `notification_message` |
|
||||
| `resource_type` | `notification_preference` |
|
||||
|
||||
Generated
+1
@@ -5137,6 +5137,7 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith
|
||||
| `group` |
|
||||
| `group_member` |
|
||||
| `idpsync_settings` |
|
||||
| `inbox_notification` |
|
||||
| `license` |
|
||||
| `notification_message` |
|
||||
| `notification_preference` |
|
||||
|
||||
@@ -64,6 +64,11 @@ 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",
|
||||
|
||||
Generated
+2
@@ -1895,6 +1895,7 @@ export type RBACResource =
|
||||
| "group"
|
||||
| "group_member"
|
||||
| "idpsync_settings"
|
||||
| "inbox_notification"
|
||||
| "license"
|
||||
| "notification_message"
|
||||
| "notification_preference"
|
||||
@@ -1930,6 +1931,7 @@ export const RBACResources: RBACResource[] = [
|
||||
"group",
|
||||
"group_member",
|
||||
"idpsync_settings",
|
||||
"inbox_notification",
|
||||
"license",
|
||||
"notification_message",
|
||||
"notification_preference",
|
||||
|
||||
Reference in New Issue
Block a user