mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add notification preferences database & audit support (#14100)
This commit is contained in:
@@ -1474,6 +1474,23 @@ func (q *querier) GetNotificationMessagesByStatus(ctx context.Context, arg datab
|
||||
return q.db.GetNotificationMessagesByStatus(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) GetNotificationTemplateByID(ctx context.Context, id uuid.UUID) (database.NotificationTemplate, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceNotificationTemplate); err != nil {
|
||||
return database.NotificationTemplate{}, err
|
||||
}
|
||||
return q.db.GetNotificationTemplateByID(ctx, id)
|
||||
}
|
||||
|
||||
func (q *querier) GetNotificationTemplatesByKind(ctx context.Context, kind database.NotificationTemplateKind) ([]database.NotificationTemplate, error) {
|
||||
// TODO: restrict 'system' kind to admins only?
|
||||
// All notification templates share the same rbac.Object, so there is no need
|
||||
// to authorize them individually. If this passes, all notification templates can be read.
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceNotificationTemplate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetNotificationTemplatesByKind(ctx, kind)
|
||||
}
|
||||
|
||||
func (q *querier) GetNotificationsSettings(ctx context.Context) (string, error) {
|
||||
// No authz checks
|
||||
return q.db.GetNotificationsSettings(ctx)
|
||||
@@ -2085,6 +2102,13 @@ func (q *querier) GetUserLinksByUserID(ctx context.Context, userID uuid.UUID) ([
|
||||
return q.db.GetUserLinksByUserID(ctx, userID)
|
||||
}
|
||||
|
||||
func (q *querier) GetUserNotificationPreferences(ctx context.Context, userID uuid.UUID) ([]database.NotificationPreference, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceNotificationPreference.WithOwner(userID.String())); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetUserNotificationPreferences(ctx, userID)
|
||||
}
|
||||
|
||||
func (q *querier) GetUserWorkspaceBuildParameters(ctx context.Context, params database.GetUserWorkspaceBuildParametersParams) ([]database.GetUserWorkspaceBuildParametersRow, error) {
|
||||
u, err := q.db.GetUserByID(ctx, params.OwnerID)
|
||||
if err != nil {
|
||||
@@ -3011,6 +3035,13 @@ func (q *querier) UpdateMemberRoles(ctx context.Context, arg database.UpdateMemb
|
||||
return q.db.UpdateMemberRoles(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateNotificationTemplateMethodByID(ctx context.Context, arg database.UpdateNotificationTemplateMethodByIDParams) (database.NotificationTemplate, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceNotificationTemplate); err != nil {
|
||||
return database.NotificationTemplate{}, err
|
||||
}
|
||||
return q.db.UpdateNotificationTemplateMethodByID(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateOAuth2ProviderAppByID(ctx context.Context, arg database.UpdateOAuth2ProviderAppByIDParams) (database.OAuth2ProviderApp, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceOauth2App); err != nil {
|
||||
return database.OAuth2ProviderApp{}, err
|
||||
@@ -3326,6 +3357,13 @@ func (q *querier) UpdateUserLoginType(ctx context.Context, arg database.UpdateUs
|
||||
return q.db.UpdateUserLoginType(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateUserNotificationPreferences(ctx context.Context, arg database.UpdateUserNotificationPreferencesParams) (int64, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceNotificationPreference.WithOwner(arg.UserID.String())); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return q.db.UpdateUserNotificationPreferences(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateUserProfile(ctx context.Context, arg database.UpdateUserProfileParams) (database.User, error) {
|
||||
u, err := q.db.GetUserByID(ctx, arg.ID)
|
||||
if err != nil {
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"cdr.dev/slog"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/database/db2sdk"
|
||||
"github.com/coder/coder/v2/coderd/notifications"
|
||||
"github.com/coder/coder/v2/coderd/rbac/policy"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
|
||||
@@ -2561,6 +2562,10 @@ func (s *MethodTestSuite) TestSystemFunctions() {
|
||||
AgentID: uuid.New(),
|
||||
}).Asserts(tpl, policy.ActionCreate)
|
||||
}))
|
||||
}
|
||||
|
||||
func (s *MethodTestSuite) TestNotifications() {
|
||||
// System functions
|
||||
s.Run("AcquireNotificationMessages", s.Subtest(func(db database.Store, check *expects) {
|
||||
// TODO: update this test once we have a specific role for notifications
|
||||
check.Args(database.AcquireNotificationMessagesParams{}).Asserts(rbac.ResourceSystem, policy.ActionUpdate)
|
||||
@@ -2596,6 +2601,40 @@ func (s *MethodTestSuite) TestSystemFunctions() {
|
||||
Limit: 10,
|
||||
}).Asserts(rbac.ResourceSystem, policy.ActionRead)
|
||||
}))
|
||||
|
||||
// Notification templates
|
||||
s.Run("GetNotificationTemplateByID", s.Subtest(func(db database.Store, check *expects) {
|
||||
user := dbgen.User(s.T(), db, database.User{})
|
||||
check.Args(user.ID).Asserts(rbac.ResourceNotificationTemplate, policy.ActionRead).
|
||||
Errors(dbmem.ErrUnimplemented)
|
||||
}))
|
||||
s.Run("GetNotificationTemplatesByKind", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args(database.NotificationTemplateKindSystem).
|
||||
Asserts(rbac.ResourceNotificationTemplate, policy.ActionRead).
|
||||
Errors(dbmem.ErrUnimplemented)
|
||||
}))
|
||||
s.Run("UpdateNotificationTemplateMethodByID", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args(database.UpdateNotificationTemplateMethodByIDParams{
|
||||
Method: database.NullNotificationMethod{NotificationMethod: database.NotificationMethodWebhook, Valid: true},
|
||||
ID: notifications.TemplateWorkspaceDormant,
|
||||
}).Asserts(rbac.ResourceNotificationTemplate, policy.ActionUpdate).
|
||||
Errors(dbmem.ErrUnimplemented)
|
||||
}))
|
||||
|
||||
// Notification preferences
|
||||
s.Run("GetUserNotificationPreferences", s.Subtest(func(db database.Store, check *expects) {
|
||||
user := dbgen.User(s.T(), db, database.User{})
|
||||
check.Args(user.ID).
|
||||
Asserts(rbac.ResourceNotificationPreference.WithOwner(user.ID.String()), policy.ActionRead)
|
||||
}))
|
||||
s.Run("UpdateUserNotificationPreferences", s.Subtest(func(db database.Store, check *expects) {
|
||||
user := dbgen.User(s.T(), db, database.User{})
|
||||
check.Args(database.UpdateUserNotificationPreferencesParams{
|
||||
UserID: user.ID,
|
||||
NotificationTemplateIds: []uuid.UUID{notifications.TemplateWorkspaceAutoUpdated, notifications.TemplateWorkspaceDeleted},
|
||||
Disableds: []bool{true, false},
|
||||
}).Asserts(rbac.ResourceNotificationPreference.WithOwner(user.ID.String()), policy.ActionUpdate)
|
||||
}))
|
||||
}
|
||||
|
||||
func (s *MethodTestSuite) TestOAuth2ProviderApps() {
|
||||
|
||||
@@ -65,6 +65,7 @@ func New() database.Store {
|
||||
files: make([]database.File, 0),
|
||||
gitSSHKey: make([]database.GitSSHKey, 0),
|
||||
notificationMessages: make([]database.NotificationMessage, 0),
|
||||
notificationPreferences: make([]database.NotificationPreference, 0),
|
||||
parameterSchemas: make([]database.ParameterSchema, 0),
|
||||
provisionerDaemons: make([]database.ProvisionerDaemon, 0),
|
||||
workspaceAgents: make([]database.WorkspaceAgent, 0),
|
||||
@@ -160,6 +161,7 @@ type data struct {
|
||||
jfrogXRayScans []database.JfrogXrayScan
|
||||
licenses []database.License
|
||||
notificationMessages []database.NotificationMessage
|
||||
notificationPreferences []database.NotificationPreference
|
||||
oauth2ProviderApps []database.OAuth2ProviderApp
|
||||
oauth2ProviderAppSecrets []database.OAuth2ProviderAppSecret
|
||||
oauth2ProviderAppCodes []database.OAuth2ProviderAppCode
|
||||
@@ -2708,6 +2710,18 @@ func (q *FakeQuerier) GetNotificationMessagesByStatus(_ context.Context, arg dat
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (*FakeQuerier) GetNotificationTemplateByID(_ context.Context, _ uuid.UUID) (database.NotificationTemplate, error) {
|
||||
// Not implementing this function because it relies on state in the database which is created with migrations.
|
||||
// We could consider using code-generation to align the database state and dbmem, but it's not worth it right now.
|
||||
return database.NotificationTemplate{}, ErrUnimplemented
|
||||
}
|
||||
|
||||
func (*FakeQuerier) GetNotificationTemplatesByKind(_ context.Context, _ database.NotificationTemplateKind) ([]database.NotificationTemplate, error) {
|
||||
// Not implementing this function because it relies on state in the database which is created with migrations.
|
||||
// We could consider using code-generation to align the database state and dbmem, but it's not worth it right now.
|
||||
return nil, ErrUnimplemented
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetNotificationsSettings(_ context.Context) (string, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
@@ -4853,6 +4867,22 @@ func (q *FakeQuerier) GetUserLinksByUserID(_ context.Context, userID uuid.UUID)
|
||||
return uls, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetUserNotificationPreferences(_ context.Context, userID uuid.UUID) ([]database.NotificationPreference, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
out := make([]database.NotificationPreference, 0, len(q.notificationPreferences))
|
||||
for _, np := range q.notificationPreferences {
|
||||
if np.UserID != userID {
|
||||
continue
|
||||
}
|
||||
|
||||
out = append(out, np)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetUserWorkspaceBuildParameters(_ context.Context, params database.GetUserWorkspaceBuildParametersParams) ([]database.GetUserWorkspaceBuildParametersRow, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
@@ -7520,6 +7550,12 @@ func (q *FakeQuerier) UpdateMemberRoles(_ context.Context, arg database.UpdateMe
|
||||
return database.OrganizationMember{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (*FakeQuerier) UpdateNotificationTemplateMethodByID(_ context.Context, _ database.UpdateNotificationTemplateMethodByIDParams) (database.NotificationTemplate, error) {
|
||||
// Not implementing this function because it relies on state in the database which is created with migrations.
|
||||
// We could consider using code-generation to align the database state and dbmem, but it's not worth it right now.
|
||||
return database.NotificationTemplate{}, ErrUnimplemented
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) UpdateOAuth2ProviderAppByID(_ context.Context, arg database.UpdateOAuth2ProviderAppByIDParams) (database.OAuth2ProviderApp, error) {
|
||||
err := validateDatabaseType(arg)
|
||||
if err != nil {
|
||||
@@ -8114,6 +8150,57 @@ func (q *FakeQuerier) UpdateUserLoginType(_ context.Context, arg database.Update
|
||||
return database.User{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) UpdateUserNotificationPreferences(_ context.Context, arg database.UpdateUserNotificationPreferencesParams) (int64, error) {
|
||||
err := validateDatabaseType(arg)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
var upserted int64
|
||||
for i := range arg.NotificationTemplateIds {
|
||||
var (
|
||||
found bool
|
||||
templateID = arg.NotificationTemplateIds[i]
|
||||
disabled = arg.Disableds[i]
|
||||
)
|
||||
|
||||
for j, np := range q.notificationPreferences {
|
||||
if np.UserID != arg.UserID {
|
||||
continue
|
||||
}
|
||||
|
||||
if np.NotificationTemplateID != templateID {
|
||||
continue
|
||||
}
|
||||
|
||||
np.Disabled = disabled
|
||||
np.UpdatedAt = dbtime.Now()
|
||||
q.notificationPreferences[j] = np
|
||||
|
||||
upserted++
|
||||
found = true
|
||||
break
|
||||
}
|
||||
|
||||
if !found {
|
||||
np := database.NotificationPreference{
|
||||
Disabled: disabled,
|
||||
UserID: arg.UserID,
|
||||
NotificationTemplateID: templateID,
|
||||
CreatedAt: dbtime.Now(),
|
||||
UpdatedAt: dbtime.Now(),
|
||||
}
|
||||
q.notificationPreferences = append(q.notificationPreferences, np)
|
||||
upserted++
|
||||
}
|
||||
}
|
||||
|
||||
return upserted, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) UpdateUserProfile(_ context.Context, arg database.UpdateUserProfileParams) (database.User, error) {
|
||||
if err := validateDatabaseType(arg); err != nil {
|
||||
return database.User{}, err
|
||||
|
||||
@@ -746,6 +746,20 @@ func (m metricsStore) GetNotificationMessagesByStatus(ctx context.Context, arg d
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m metricsStore) GetNotificationTemplateByID(ctx context.Context, id uuid.UUID) (database.NotificationTemplate, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetNotificationTemplateByID(ctx, id)
|
||||
m.queryLatencies.WithLabelValues("GetNotificationTemplateByID").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m metricsStore) GetNotificationTemplatesByKind(ctx context.Context, kind database.NotificationTemplateKind) ([]database.NotificationTemplate, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetNotificationTemplatesByKind(ctx, kind)
|
||||
m.queryLatencies.WithLabelValues("GetNotificationTemplatesByKind").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m metricsStore) GetNotificationsSettings(ctx context.Context) (string, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetNotificationsSettings(ctx)
|
||||
@@ -1222,6 +1236,13 @@ func (m metricsStore) GetUserLinksByUserID(ctx context.Context, userID uuid.UUID
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m metricsStore) GetUserNotificationPreferences(ctx context.Context, userID uuid.UUID) ([]database.NotificationPreference, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetUserNotificationPreferences(ctx, userID)
|
||||
m.queryLatencies.WithLabelValues("GetUserNotificationPreferences").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m metricsStore) GetUserWorkspaceBuildParameters(ctx context.Context, ownerID database.GetUserWorkspaceBuildParametersParams) ([]database.GetUserWorkspaceBuildParametersRow, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetUserWorkspaceBuildParameters(ctx, ownerID)
|
||||
@@ -1957,6 +1978,13 @@ func (m metricsStore) UpdateMemberRoles(ctx context.Context, arg database.Update
|
||||
return member, err
|
||||
}
|
||||
|
||||
func (m metricsStore) UpdateNotificationTemplateMethodByID(ctx context.Context, arg database.UpdateNotificationTemplateMethodByIDParams) (database.NotificationTemplate, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.UpdateNotificationTemplateMethodByID(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("UpdateNotificationTemplateMethodByID").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m metricsStore) UpdateOAuth2ProviderAppByID(ctx context.Context, arg database.UpdateOAuth2ProviderAppByIDParams) (database.OAuth2ProviderApp, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.UpdateOAuth2ProviderAppByID(ctx, arg)
|
||||
@@ -2139,6 +2167,13 @@ func (m metricsStore) UpdateUserLoginType(ctx context.Context, arg database.Upda
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m metricsStore) UpdateUserNotificationPreferences(ctx context.Context, arg database.UpdateUserNotificationPreferencesParams) (int64, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.UpdateUserNotificationPreferences(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("UpdateUserNotificationPreferences").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m metricsStore) UpdateUserProfile(ctx context.Context, arg database.UpdateUserProfileParams) (database.User, error) {
|
||||
start := time.Now()
|
||||
user, err := m.s.UpdateUserProfile(ctx, arg)
|
||||
|
||||
@@ -1495,6 +1495,36 @@ func (mr *MockStoreMockRecorder) GetNotificationMessagesByStatus(arg0, arg1 any)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNotificationMessagesByStatus", reflect.TypeOf((*MockStore)(nil).GetNotificationMessagesByStatus), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetNotificationTemplateByID mocks base method.
|
||||
func (m *MockStore) GetNotificationTemplateByID(arg0 context.Context, arg1 uuid.UUID) (database.NotificationTemplate, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetNotificationTemplateByID", arg0, arg1)
|
||||
ret0, _ := ret[0].(database.NotificationTemplate)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetNotificationTemplateByID indicates an expected call of GetNotificationTemplateByID.
|
||||
func (mr *MockStoreMockRecorder) GetNotificationTemplateByID(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNotificationTemplateByID", reflect.TypeOf((*MockStore)(nil).GetNotificationTemplateByID), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetNotificationTemplatesByKind mocks base method.
|
||||
func (m *MockStore) GetNotificationTemplatesByKind(arg0 context.Context, arg1 database.NotificationTemplateKind) ([]database.NotificationTemplate, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetNotificationTemplatesByKind", arg0, arg1)
|
||||
ret0, _ := ret[0].([]database.NotificationTemplate)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetNotificationTemplatesByKind indicates an expected call of GetNotificationTemplatesByKind.
|
||||
func (mr *MockStoreMockRecorder) GetNotificationTemplatesByKind(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNotificationTemplatesByKind", reflect.TypeOf((*MockStore)(nil).GetNotificationTemplatesByKind), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetNotificationsSettings mocks base method.
|
||||
func (m *MockStore) GetNotificationsSettings(arg0 context.Context) (string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -2545,6 +2575,21 @@ func (mr *MockStoreMockRecorder) GetUserLinksByUserID(arg0, arg1 any) *gomock.Ca
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserLinksByUserID", reflect.TypeOf((*MockStore)(nil).GetUserLinksByUserID), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetUserNotificationPreferences mocks base method.
|
||||
func (m *MockStore) GetUserNotificationPreferences(arg0 context.Context, arg1 uuid.UUID) ([]database.NotificationPreference, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetUserNotificationPreferences", arg0, arg1)
|
||||
ret0, _ := ret[0].([]database.NotificationPreference)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetUserNotificationPreferences indicates an expected call of GetUserNotificationPreferences.
|
||||
func (mr *MockStoreMockRecorder) GetUserNotificationPreferences(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserNotificationPreferences", reflect.TypeOf((*MockStore)(nil).GetUserNotificationPreferences), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetUserWorkspaceBuildParameters mocks base method.
|
||||
func (m *MockStore) GetUserWorkspaceBuildParameters(arg0 context.Context, arg1 database.GetUserWorkspaceBuildParametersParams) ([]database.GetUserWorkspaceBuildParametersRow, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -4131,6 +4176,21 @@ func (mr *MockStoreMockRecorder) UpdateMemberRoles(arg0, arg1 any) *gomock.Call
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateMemberRoles", reflect.TypeOf((*MockStore)(nil).UpdateMemberRoles), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpdateNotificationTemplateMethodByID mocks base method.
|
||||
func (m *MockStore) UpdateNotificationTemplateMethodByID(arg0 context.Context, arg1 database.UpdateNotificationTemplateMethodByIDParams) (database.NotificationTemplate, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdateNotificationTemplateMethodByID", arg0, arg1)
|
||||
ret0, _ := ret[0].(database.NotificationTemplate)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// UpdateNotificationTemplateMethodByID indicates an expected call of UpdateNotificationTemplateMethodByID.
|
||||
func (mr *MockStoreMockRecorder) UpdateNotificationTemplateMethodByID(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateNotificationTemplateMethodByID", reflect.TypeOf((*MockStore)(nil).UpdateNotificationTemplateMethodByID), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpdateOAuth2ProviderAppByID mocks base method.
|
||||
func (m *MockStore) UpdateOAuth2ProviderAppByID(arg0 context.Context, arg1 database.UpdateOAuth2ProviderAppByIDParams) (database.OAuth2ProviderApp, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -4504,6 +4564,21 @@ func (mr *MockStoreMockRecorder) UpdateUserLoginType(arg0, arg1 any) *gomock.Cal
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUserLoginType", reflect.TypeOf((*MockStore)(nil).UpdateUserLoginType), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpdateUserNotificationPreferences mocks base method.
|
||||
func (m *MockStore) UpdateUserNotificationPreferences(arg0 context.Context, arg1 database.UpdateUserNotificationPreferencesParams) (int64, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdateUserNotificationPreferences", arg0, arg1)
|
||||
ret0, _ := ret[0].(int64)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// UpdateUserNotificationPreferences indicates an expected call of UpdateUserNotificationPreferences.
|
||||
func (mr *MockStoreMockRecorder) UpdateUserNotificationPreferences(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUserNotificationPreferences", reflect.TypeOf((*MockStore)(nil).UpdateUserNotificationPreferences), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpdateUserProfile mocks base method.
|
||||
func (m *MockStore) UpdateUserProfile(arg0 context.Context, arg1 database.UpdateUserProfileParams) (database.User, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
Generated
+49
-3
@@ -84,7 +84,8 @@ CREATE TYPE notification_message_status AS ENUM (
|
||||
'sent',
|
||||
'permanent_failure',
|
||||
'temporary_failure',
|
||||
'unknown'
|
||||
'unknown',
|
||||
'inhibited'
|
||||
);
|
||||
|
||||
CREATE TYPE notification_method AS ENUM (
|
||||
@@ -92,6 +93,10 @@ CREATE TYPE notification_method AS ENUM (
|
||||
'webhook'
|
||||
);
|
||||
|
||||
CREATE TYPE notification_template_kind AS ENUM (
|
||||
'system'
|
||||
);
|
||||
|
||||
CREATE TYPE parameter_destination_scheme AS ENUM (
|
||||
'none',
|
||||
'environment_variable',
|
||||
@@ -164,7 +169,8 @@ CREATE TYPE resource_type AS ENUM (
|
||||
'oauth2_provider_app_secret',
|
||||
'custom_role',
|
||||
'organization_member',
|
||||
'notifications_settings'
|
||||
'notifications_settings',
|
||||
'notification_template'
|
||||
);
|
||||
|
||||
CREATE TYPE startup_script_behavior AS ENUM (
|
||||
@@ -249,6 +255,23 @@ BEGIN
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE FUNCTION inhibit_enqueue_if_disabled() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
-- Fail the insertion if the user has disabled this notification.
|
||||
IF EXISTS (SELECT 1
|
||||
FROM notification_preferences
|
||||
WHERE disabled = TRUE
|
||||
AND user_id = NEW.user_id
|
||||
AND notification_template_id = NEW.notification_template_id) THEN
|
||||
RAISE EXCEPTION 'cannot enqueue message: user has disabled this notification';
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE FUNCTION insert_apikey_fail_if_user_deleted() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
@@ -567,17 +590,29 @@ CREATE TABLE notification_messages (
|
||||
queued_seconds double precision
|
||||
);
|
||||
|
||||
CREATE TABLE notification_preferences (
|
||||
user_id uuid NOT NULL,
|
||||
notification_template_id uuid NOT NULL,
|
||||
disabled boolean DEFAULT false NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE notification_templates (
|
||||
id uuid NOT NULL,
|
||||
name text NOT NULL,
|
||||
title_template text NOT NULL,
|
||||
body_template text NOT NULL,
|
||||
actions jsonb,
|
||||
"group" text
|
||||
"group" text,
|
||||
method notification_method,
|
||||
kind notification_template_kind DEFAULT 'system'::notification_template_kind NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON TABLE notification_templates IS 'Templates from which to create notification messages.';
|
||||
|
||||
COMMENT ON COLUMN notification_templates.method IS 'NULL defers to the deployment-level method';
|
||||
|
||||
CREATE TABLE oauth2_provider_app_codes (
|
||||
id uuid NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
@@ -1536,6 +1571,9 @@ ALTER TABLE ONLY licenses
|
||||
ALTER TABLE ONLY notification_messages
|
||||
ADD CONSTRAINT notification_messages_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY notification_preferences
|
||||
ADD CONSTRAINT notification_preferences_pkey PRIMARY KEY (user_id, notification_template_id);
|
||||
|
||||
ALTER TABLE ONLY notification_templates
|
||||
ADD CONSTRAINT notification_templates_name_key UNIQUE (name);
|
||||
|
||||
@@ -1798,6 +1836,8 @@ CREATE INDEX workspace_resources_job_id_idx ON workspace_resources USING btree (
|
||||
|
||||
CREATE UNIQUE INDEX workspaces_owner_id_lower_idx ON workspaces USING btree (owner_id, lower((name)::text)) WHERE (deleted = false);
|
||||
|
||||
CREATE TRIGGER inhibit_enqueue_if_disabled BEFORE INSERT ON notification_messages FOR EACH ROW EXECUTE FUNCTION inhibit_enqueue_if_disabled();
|
||||
|
||||
CREATE TRIGGER tailnet_notify_agent_change AFTER INSERT OR DELETE OR UPDATE ON tailnet_agents FOR EACH ROW EXECUTE FUNCTION tailnet_notify_agent_change();
|
||||
|
||||
CREATE TRIGGER tailnet_notify_client_change AFTER INSERT OR DELETE OR UPDATE ON tailnet_clients FOR EACH ROW EXECUTE FUNCTION tailnet_notify_client_change();
|
||||
@@ -1851,6 +1891,12 @@ ALTER TABLE ONLY notification_messages
|
||||
ALTER TABLE ONLY notification_messages
|
||||
ADD CONSTRAINT notification_messages_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY notification_preferences
|
||||
ADD CONSTRAINT notification_preferences_notification_template_id_fkey FOREIGN KEY (notification_template_id) REFERENCES notification_templates(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY notification_preferences
|
||||
ADD CONSTRAINT notification_preferences_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY oauth2_provider_app_codes
|
||||
ADD CONSTRAINT oauth2_provider_app_codes_app_id_fkey FOREIGN KEY (app_id) REFERENCES oauth2_provider_apps(id) ON DELETE CASCADE;
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ const (
|
||||
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;
|
||||
ForeignKeyNotificationMessagesUserID ForeignKeyConstraint = "notification_messages_user_id_fkey" // ALTER TABLE ONLY notification_messages ADD CONSTRAINT notification_messages_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
ForeignKeyNotificationPreferencesNotificationTemplateID ForeignKeyConstraint = "notification_preferences_notification_template_id_fkey" // ALTER TABLE ONLY notification_preferences ADD CONSTRAINT notification_preferences_notification_template_id_fkey FOREIGN KEY (notification_template_id) REFERENCES notification_templates(id) ON DELETE CASCADE;
|
||||
ForeignKeyNotificationPreferencesUserID ForeignKeyConstraint = "notification_preferences_user_id_fkey" // ALTER TABLE ONLY notification_preferences ADD CONSTRAINT notification_preferences_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
ForeignKeyOauth2ProviderAppCodesAppID ForeignKeyConstraint = "oauth2_provider_app_codes_app_id_fkey" // ALTER TABLE ONLY oauth2_provider_app_codes ADD CONSTRAINT oauth2_provider_app_codes_app_id_fkey FOREIGN KEY (app_id) REFERENCES oauth2_provider_apps(id) ON DELETE CASCADE;
|
||||
ForeignKeyOauth2ProviderAppCodesUserID ForeignKeyConstraint = "oauth2_provider_app_codes_user_id_fkey" // ALTER TABLE ONLY oauth2_provider_app_codes ADD CONSTRAINT oauth2_provider_app_codes_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
ForeignKeyOauth2ProviderAppSecretsAppID ForeignKeyConstraint = "oauth2_provider_app_secrets_app_id_fkey" // ALTER TABLE ONLY oauth2_provider_app_secrets ADD CONSTRAINT oauth2_provider_app_secrets_app_id_fkey FOREIGN KEY (app_id) REFERENCES oauth2_provider_apps(id) ON DELETE CASCADE;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
ALTER TABLE notification_templates
|
||||
DROP COLUMN IF EXISTS method,
|
||||
DROP COLUMN IF EXISTS kind;
|
||||
|
||||
DROP TABLE IF EXISTS notification_preferences;
|
||||
DROP TYPE IF EXISTS notification_template_kind;
|
||||
|
||||
DROP TRIGGER IF EXISTS inhibit_enqueue_if_disabled ON notification_messages;
|
||||
DROP FUNCTION IF EXISTS inhibit_enqueue_if_disabled;
|
||||
@@ -0,0 +1,52 @@
|
||||
CREATE TABLE notification_preferences
|
||||
(
|
||||
user_id uuid REFERENCES users ON DELETE CASCADE NOT NULL,
|
||||
notification_template_id uuid REFERENCES notification_templates ON DELETE CASCADE NOT NULL,
|
||||
disabled bool NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (user_id, notification_template_id)
|
||||
);
|
||||
|
||||
-- Add a new type (to be expanded upon later) which specifies the kind of notification template.
|
||||
CREATE TYPE notification_template_kind AS ENUM (
|
||||
'system'
|
||||
);
|
||||
|
||||
ALTER TABLE notification_templates
|
||||
-- Allow per-template notification method (enterprise only).
|
||||
ADD COLUMN method notification_method,
|
||||
-- Update all existing notification templates to be system templates.
|
||||
ADD COLUMN kind notification_template_kind DEFAULT 'system'::notification_template_kind NOT NULL;
|
||||
COMMENT ON COLUMN notification_templates.method IS 'NULL defers to the deployment-level method';
|
||||
|
||||
-- No equivalent in down migration because ENUM values cannot be deleted.
|
||||
ALTER TYPE notification_message_status ADD VALUE IF NOT EXISTS 'inhibited';
|
||||
|
||||
-- Function to prevent enqueuing notifications unnecessarily.
|
||||
CREATE OR REPLACE FUNCTION inhibit_enqueue_if_disabled()
|
||||
RETURNS TRIGGER AS
|
||||
$$
|
||||
BEGIN
|
||||
-- Fail the insertion if the user has disabled this notification.
|
||||
IF EXISTS (SELECT 1
|
||||
FROM notification_preferences
|
||||
WHERE disabled = TRUE
|
||||
AND user_id = NEW.user_id
|
||||
AND notification_template_id = NEW.notification_template_id) THEN
|
||||
RAISE EXCEPTION 'cannot enqueue message: user has disabled this notification';
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Trigger to execute above function on insertion.
|
||||
CREATE TRIGGER inhibit_enqueue_if_disabled
|
||||
BEFORE INSERT
|
||||
ON notification_messages
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION inhibit_enqueue_if_disabled();
|
||||
|
||||
-- Allow modifications to notification templates to be audited.
|
||||
ALTER TYPE resource_type ADD VALUE IF NOT EXISTS 'notification_template';
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
INSERT INTO notification_templates (id, name, title_template, body_template, "group")
|
||||
VALUES ('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'A', 'title', 'body', 'Group 1') ON CONFLICT DO NOTHING;
|
||||
|
||||
INSERT INTO notification_preferences (user_id, notification_template_id, disabled, created_at, updated_at)
|
||||
VALUES ('a0061a8e-7db7-4585-838c-3116a003dd21', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', FALSE, '2024-07-15 10:30:00+00', '2024-07-15 10:30:00+00');
|
||||
@@ -669,6 +669,7 @@ const (
|
||||
NotificationMessageStatusPermanentFailure NotificationMessageStatus = "permanent_failure"
|
||||
NotificationMessageStatusTemporaryFailure NotificationMessageStatus = "temporary_failure"
|
||||
NotificationMessageStatusUnknown NotificationMessageStatus = "unknown"
|
||||
NotificationMessageStatusInhibited NotificationMessageStatus = "inhibited"
|
||||
)
|
||||
|
||||
func (e *NotificationMessageStatus) Scan(src interface{}) error {
|
||||
@@ -713,7 +714,8 @@ func (e NotificationMessageStatus) Valid() bool {
|
||||
NotificationMessageStatusSent,
|
||||
NotificationMessageStatusPermanentFailure,
|
||||
NotificationMessageStatusTemporaryFailure,
|
||||
NotificationMessageStatusUnknown:
|
||||
NotificationMessageStatusUnknown,
|
||||
NotificationMessageStatusInhibited:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -727,6 +729,7 @@ func AllNotificationMessageStatusValues() []NotificationMessageStatus {
|
||||
NotificationMessageStatusPermanentFailure,
|
||||
NotificationMessageStatusTemporaryFailure,
|
||||
NotificationMessageStatusUnknown,
|
||||
NotificationMessageStatusInhibited,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -788,6 +791,61 @@ func AllNotificationMethodValues() []NotificationMethod {
|
||||
}
|
||||
}
|
||||
|
||||
type NotificationTemplateKind string
|
||||
|
||||
const (
|
||||
NotificationTemplateKindSystem NotificationTemplateKind = "system"
|
||||
)
|
||||
|
||||
func (e *NotificationTemplateKind) Scan(src interface{}) error {
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
*e = NotificationTemplateKind(s)
|
||||
case string:
|
||||
*e = NotificationTemplateKind(s)
|
||||
default:
|
||||
return fmt.Errorf("unsupported scan type for NotificationTemplateKind: %T", src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NullNotificationTemplateKind struct {
|
||||
NotificationTemplateKind NotificationTemplateKind `json:"notification_template_kind"`
|
||||
Valid bool `json:"valid"` // Valid is true if NotificationTemplateKind is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (ns *NullNotificationTemplateKind) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
ns.NotificationTemplateKind, ns.Valid = "", false
|
||||
return nil
|
||||
}
|
||||
ns.Valid = true
|
||||
return ns.NotificationTemplateKind.Scan(value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (ns NullNotificationTemplateKind) Value() (driver.Value, error) {
|
||||
if !ns.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return string(ns.NotificationTemplateKind), nil
|
||||
}
|
||||
|
||||
func (e NotificationTemplateKind) Valid() bool {
|
||||
switch e {
|
||||
case NotificationTemplateKindSystem:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func AllNotificationTemplateKindValues() []NotificationTemplateKind {
|
||||
return []NotificationTemplateKind{
|
||||
NotificationTemplateKindSystem,
|
||||
}
|
||||
}
|
||||
|
||||
type ParameterDestinationScheme string
|
||||
|
||||
const (
|
||||
@@ -1353,6 +1411,7 @@ const (
|
||||
ResourceTypeCustomRole ResourceType = "custom_role"
|
||||
ResourceTypeOrganizationMember ResourceType = "organization_member"
|
||||
ResourceTypeNotificationsSettings ResourceType = "notifications_settings"
|
||||
ResourceTypeNotificationTemplate ResourceType = "notification_template"
|
||||
)
|
||||
|
||||
func (e *ResourceType) Scan(src interface{}) error {
|
||||
@@ -1409,7 +1468,8 @@ func (e ResourceType) Valid() bool {
|
||||
ResourceTypeOauth2ProviderAppSecret,
|
||||
ResourceTypeCustomRole,
|
||||
ResourceTypeOrganizationMember,
|
||||
ResourceTypeNotificationsSettings:
|
||||
ResourceTypeNotificationsSettings,
|
||||
ResourceTypeNotificationTemplate:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -1435,6 +1495,7 @@ func AllResourceTypeValues() []ResourceType {
|
||||
ResourceTypeCustomRole,
|
||||
ResourceTypeOrganizationMember,
|
||||
ResourceTypeNotificationsSettings,
|
||||
ResourceTypeNotificationTemplate,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2034,6 +2095,14 @@ type NotificationMessage struct {
|
||||
QueuedSeconds sql.NullFloat64 `db:"queued_seconds" json:"queued_seconds"`
|
||||
}
|
||||
|
||||
type NotificationPreference struct {
|
||||
UserID uuid.UUID `db:"user_id" json:"user_id"`
|
||||
NotificationTemplateID uuid.UUID `db:"notification_template_id" json:"notification_template_id"`
|
||||
Disabled bool `db:"disabled" json:"disabled"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
// Templates from which to create notification messages.
|
||||
type NotificationTemplate struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
@@ -2042,6 +2111,9 @@ type NotificationTemplate struct {
|
||||
BodyTemplate string `db:"body_template" json:"body_template"`
|
||||
Actions []byte `db:"actions" json:"actions"`
|
||||
Group sql.NullString `db:"group" json:"group"`
|
||||
// NULL defers to the deployment-level method
|
||||
Method NullNotificationMethod `db:"method" json:"method"`
|
||||
Kind NotificationTemplateKind `db:"kind" json:"kind"`
|
||||
}
|
||||
|
||||
// A table used to configure apps that can use Coder as an OAuth2 provider, the reverse of what we are calling external authentication.
|
||||
|
||||
@@ -162,6 +162,8 @@ type sqlcQuerier interface {
|
||||
GetLicenses(ctx context.Context) ([]License, error)
|
||||
GetLogoURL(ctx context.Context) (string, error)
|
||||
GetNotificationMessagesByStatus(ctx context.Context, arg GetNotificationMessagesByStatusParams) ([]NotificationMessage, 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)
|
||||
@@ -265,6 +267,7 @@ type sqlcQuerier interface {
|
||||
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)
|
||||
GetUserWorkspaceBuildParameters(ctx context.Context, arg GetUserWorkspaceBuildParametersParams) ([]GetUserWorkspaceBuildParametersRow, error)
|
||||
// This will never return deleted users.
|
||||
GetUsers(ctx context.Context, arg GetUsersParams) ([]GetUsersRow, error)
|
||||
@@ -401,6 +404,7 @@ type sqlcQuerier interface {
|
||||
UpdateGroupByID(ctx context.Context, arg UpdateGroupByIDParams) (Group, error)
|
||||
UpdateInactiveUsersToDormant(ctx context.Context, arg UpdateInactiveUsersToDormantParams) ([]UpdateInactiveUsersToDormantRow, error)
|
||||
UpdateMemberRoles(ctx context.Context, arg UpdateMemberRolesParams) (OrganizationMember, 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)
|
||||
@@ -427,6 +431,7 @@ type sqlcQuerier interface {
|
||||
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)
|
||||
|
||||
+172
-12
@@ -3335,14 +3335,18 @@ SELECT
|
||||
nm.id,
|
||||
nm.payload,
|
||||
nm.method,
|
||||
nm.attempt_count::int AS attempt_count,
|
||||
nm.queued_seconds::float AS queued_seconds,
|
||||
nm.attempt_count::int AS attempt_count,
|
||||
nm.queued_seconds::float AS queued_seconds,
|
||||
-- template
|
||||
nt.id AS template_id,
|
||||
nt.id AS template_id,
|
||||
nt.title_template,
|
||||
nt.body_template
|
||||
nt.body_template,
|
||||
-- preferences
|
||||
(CASE WHEN np.disabled IS NULL THEN false ELSE np.disabled END)::bool AS disabled
|
||||
FROM acquired nm
|
||||
JOIN notification_templates nt ON nm.notification_template_id = nt.id
|
||||
LEFT JOIN notification_preferences AS np
|
||||
ON (np.user_id = nm.user_id AND np.notification_template_id = nm.notification_template_id)
|
||||
`
|
||||
|
||||
type AcquireNotificationMessagesParams struct {
|
||||
@@ -3361,6 +3365,7 @@ type AcquireNotificationMessagesRow struct {
|
||||
TemplateID uuid.UUID `db:"template_id" json:"template_id"`
|
||||
TitleTemplate string `db:"title_template" json:"title_template"`
|
||||
BodyTemplate string `db:"body_template" json:"body_template"`
|
||||
Disabled bool `db:"disabled" json:"disabled"`
|
||||
}
|
||||
|
||||
// Acquires the lease for a given count of notification messages, to enable concurrent dequeuing and subsequent sending.
|
||||
@@ -3396,6 +3401,7 @@ func (q *sqlQuerier) AcquireNotificationMessages(ctx context.Context, arg Acquir
|
||||
&i.TemplateID,
|
||||
&i.TitleTemplate,
|
||||
&i.BodyTemplate,
|
||||
&i.Disabled,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -3534,10 +3540,11 @@ func (q *sqlQuerier) EnqueueNotificationMessage(ctx context.Context, arg Enqueue
|
||||
const fetchNewMessageMetadata = `-- name: FetchNewMessageMetadata :one
|
||||
SELECT nt.name AS notification_name,
|
||||
nt.actions AS actions,
|
||||
nt.method AS custom_method,
|
||||
u.id AS user_id,
|
||||
u.email AS user_email,
|
||||
COALESCE(NULLIF(u.name, ''), NULLIF(u.username, ''))::text AS user_name,
|
||||
COALESCE(u.username, '') AS user_username
|
||||
u.username AS user_username
|
||||
FROM notification_templates nt,
|
||||
users u
|
||||
WHERE nt.id = $1
|
||||
@@ -3550,12 +3557,13 @@ type FetchNewMessageMetadataParams struct {
|
||||
}
|
||||
|
||||
type FetchNewMessageMetadataRow struct {
|
||||
NotificationName string `db:"notification_name" json:"notification_name"`
|
||||
Actions []byte `db:"actions" json:"actions"`
|
||||
UserID uuid.UUID `db:"user_id" json:"user_id"`
|
||||
UserEmail string `db:"user_email" json:"user_email"`
|
||||
UserName string `db:"user_name" json:"user_name"`
|
||||
UserUsername string `db:"user_username" json:"user_username"`
|
||||
NotificationName string `db:"notification_name" json:"notification_name"`
|
||||
Actions []byte `db:"actions" json:"actions"`
|
||||
CustomMethod NullNotificationMethod `db:"custom_method" json:"custom_method"`
|
||||
UserID uuid.UUID `db:"user_id" json:"user_id"`
|
||||
UserEmail string `db:"user_email" json:"user_email"`
|
||||
UserName string `db:"user_name" json:"user_name"`
|
||||
UserUsername string `db:"user_username" json:"user_username"`
|
||||
}
|
||||
|
||||
// This is used to build up the notification_message's JSON payload.
|
||||
@@ -3565,6 +3573,7 @@ func (q *sqlQuerier) FetchNewMessageMetadata(ctx context.Context, arg FetchNewMe
|
||||
err := row.Scan(
|
||||
&i.NotificationName,
|
||||
&i.Actions,
|
||||
&i.CustomMethod,
|
||||
&i.UserID,
|
||||
&i.UserEmail,
|
||||
&i.UserName,
|
||||
@@ -3574,7 +3583,10 @@ func (q *sqlQuerier) FetchNewMessageMetadata(ctx context.Context, arg FetchNewMe
|
||||
}
|
||||
|
||||
const getNotificationMessagesByStatus = `-- name: GetNotificationMessagesByStatus :many
|
||||
SELECT id, notification_template_id, user_id, method, status, status_reason, created_by, payload, attempt_count, targets, created_at, updated_at, leased_until, next_retry_after, queued_seconds FROM notification_messages WHERE status = $1 LIMIT $2::int
|
||||
SELECT id, notification_template_id, user_id, method, status, status_reason, created_by, payload, attempt_count, targets, created_at, updated_at, leased_until, next_retry_after, queued_seconds
|
||||
FROM notification_messages
|
||||
WHERE status = $1
|
||||
LIMIT $2::int
|
||||
`
|
||||
|
||||
type GetNotificationMessagesByStatusParams struct {
|
||||
@@ -3621,6 +3633,154 @@ func (q *sqlQuerier) GetNotificationMessagesByStatus(ctx context.Context, arg Ge
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getNotificationTemplateByID = `-- name: GetNotificationTemplateByID :one
|
||||
SELECT id, name, title_template, body_template, actions, "group", method, kind
|
||||
FROM notification_templates
|
||||
WHERE id = $1::uuid
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetNotificationTemplateByID(ctx context.Context, id uuid.UUID) (NotificationTemplate, error) {
|
||||
row := q.db.QueryRowContext(ctx, getNotificationTemplateByID, id)
|
||||
var i NotificationTemplate
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.TitleTemplate,
|
||||
&i.BodyTemplate,
|
||||
&i.Actions,
|
||||
&i.Group,
|
||||
&i.Method,
|
||||
&i.Kind,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getNotificationTemplatesByKind = `-- name: GetNotificationTemplatesByKind :many
|
||||
SELECT id, name, title_template, body_template, actions, "group", method, kind
|
||||
FROM notification_templates
|
||||
WHERE kind = $1::notification_template_kind
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetNotificationTemplatesByKind(ctx context.Context, kind NotificationTemplateKind) ([]NotificationTemplate, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getNotificationTemplatesByKind, kind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []NotificationTemplate
|
||||
for rows.Next() {
|
||||
var i NotificationTemplate
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.TitleTemplate,
|
||||
&i.BodyTemplate,
|
||||
&i.Actions,
|
||||
&i.Group,
|
||||
&i.Method,
|
||||
&i.Kind,
|
||||
); 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 getUserNotificationPreferences = `-- name: GetUserNotificationPreferences :many
|
||||
SELECT user_id, notification_template_id, disabled, created_at, updated_at
|
||||
FROM notification_preferences
|
||||
WHERE user_id = $1::uuid
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetUserNotificationPreferences(ctx context.Context, userID uuid.UUID) ([]NotificationPreference, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getUserNotificationPreferences, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []NotificationPreference
|
||||
for rows.Next() {
|
||||
var i NotificationPreference
|
||||
if err := rows.Scan(
|
||||
&i.UserID,
|
||||
&i.NotificationTemplateID,
|
||||
&i.Disabled,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
); 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 updateNotificationTemplateMethodByID = `-- name: UpdateNotificationTemplateMethodByID :one
|
||||
UPDATE notification_templates
|
||||
SET method = $1::notification_method
|
||||
WHERE id = $2::uuid
|
||||
RETURNING id, name, title_template, body_template, actions, "group", method, kind
|
||||
`
|
||||
|
||||
type UpdateNotificationTemplateMethodByIDParams struct {
|
||||
Method NullNotificationMethod `db:"method" json:"method"`
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateNotificationTemplateMethodByID(ctx context.Context, arg UpdateNotificationTemplateMethodByIDParams) (NotificationTemplate, error) {
|
||||
row := q.db.QueryRowContext(ctx, updateNotificationTemplateMethodByID, arg.Method, arg.ID)
|
||||
var i NotificationTemplate
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.TitleTemplate,
|
||||
&i.BodyTemplate,
|
||||
&i.Actions,
|
||||
&i.Group,
|
||||
&i.Method,
|
||||
&i.Kind,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updateUserNotificationPreferences = `-- name: UpdateUserNotificationPreferences :execrows
|
||||
INSERT
|
||||
INTO notification_preferences (user_id, notification_template_id, disabled)
|
||||
SELECT $1::uuid, new_values.notification_template_id, new_values.disabled
|
||||
FROM (SELECT UNNEST($2::uuid[]) AS notification_template_id,
|
||||
UNNEST($3::bool[]) AS disabled) AS new_values
|
||||
ON CONFLICT (user_id, notification_template_id) DO UPDATE
|
||||
SET disabled = EXCLUDED.disabled,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
`
|
||||
|
||||
type UpdateUserNotificationPreferencesParams struct {
|
||||
UserID uuid.UUID `db:"user_id" json:"user_id"`
|
||||
NotificationTemplateIds []uuid.UUID `db:"notification_template_ids" json:"notification_template_ids"`
|
||||
Disableds []bool `db:"disableds" json:"disableds"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateUserNotificationPreferences(ctx context.Context, arg UpdateUserNotificationPreferencesParams) (int64, error) {
|
||||
result, err := q.db.ExecContext(ctx, updateUserNotificationPreferences, arg.UserID, pq.Array(arg.NotificationTemplateIds), pq.Array(arg.Disableds))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected()
|
||||
}
|
||||
|
||||
const deleteOAuth2ProviderAppByID = `-- name: DeleteOAuth2ProviderAppByID :exec
|
||||
DELETE FROM oauth2_provider_apps WHERE id = $1
|
||||
`
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
-- This is used to build up the notification_message's JSON payload.
|
||||
SELECT nt.name AS notification_name,
|
||||
nt.actions AS actions,
|
||||
nt.method AS custom_method,
|
||||
u.id AS user_id,
|
||||
u.email AS user_email,
|
||||
COALESCE(NULLIF(u.name, ''), NULLIF(u.username, ''))::text AS user_name,
|
||||
COALESCE(u.username, '') AS user_username
|
||||
u.username AS user_username
|
||||
FROM notification_templates nt,
|
||||
users u
|
||||
WHERE nt.id = @notification_template_id
|
||||
@@ -79,14 +80,18 @@ SELECT
|
||||
nm.id,
|
||||
nm.payload,
|
||||
nm.method,
|
||||
nm.attempt_count::int AS attempt_count,
|
||||
nm.queued_seconds::float AS queued_seconds,
|
||||
nm.attempt_count::int AS attempt_count,
|
||||
nm.queued_seconds::float AS queued_seconds,
|
||||
-- template
|
||||
nt.id AS template_id,
|
||||
nt.id AS template_id,
|
||||
nt.title_template,
|
||||
nt.body_template
|
||||
nt.body_template,
|
||||
-- preferences
|
||||
(CASE WHEN np.disabled IS NULL THEN false ELSE np.disabled END)::bool AS disabled
|
||||
FROM acquired nm
|
||||
JOIN notification_templates nt ON nm.notification_template_id = nt.id;
|
||||
JOIN notification_templates nt ON nm.notification_template_id = nt.id
|
||||
LEFT JOIN notification_preferences AS np
|
||||
ON (np.user_id = nm.user_id AND np.notification_template_id = nm.notification_template_id);
|
||||
|
||||
-- name: BulkMarkNotificationMessagesFailed :execrows
|
||||
UPDATE notification_messages
|
||||
@@ -131,4 +136,38 @@ WHERE id IN
|
||||
WHERE nested.updated_at < NOW() - INTERVAL '7 days');
|
||||
|
||||
-- name: GetNotificationMessagesByStatus :many
|
||||
SELECT * FROM notification_messages WHERE status = @status LIMIT sqlc.arg('limit')::int;
|
||||
SELECT *
|
||||
FROM notification_messages
|
||||
WHERE status = @status
|
||||
LIMIT sqlc.arg('limit')::int;
|
||||
|
||||
-- name: GetUserNotificationPreferences :many
|
||||
SELECT *
|
||||
FROM notification_preferences
|
||||
WHERE user_id = @user_id::uuid;
|
||||
|
||||
-- name: UpdateUserNotificationPreferences :execrows
|
||||
INSERT
|
||||
INTO notification_preferences (user_id, notification_template_id, disabled)
|
||||
SELECT @user_id::uuid, new_values.notification_template_id, new_values.disabled
|
||||
FROM (SELECT UNNEST(@notification_template_ids::uuid[]) AS notification_template_id,
|
||||
UNNEST(@disableds::bool[]) AS disabled) AS new_values
|
||||
ON CONFLICT (user_id, notification_template_id) DO UPDATE
|
||||
SET disabled = EXCLUDED.disabled,
|
||||
updated_at = CURRENT_TIMESTAMP;
|
||||
|
||||
-- name: UpdateNotificationTemplateMethodByID :one
|
||||
UPDATE notification_templates
|
||||
SET method = sqlc.narg('method')::notification_method
|
||||
WHERE id = @id::uuid
|
||||
RETURNING *;
|
||||
|
||||
-- name: GetNotificationTemplateByID :one
|
||||
SELECT *
|
||||
FROM notification_templates
|
||||
WHERE id = @id::uuid;
|
||||
|
||||
-- name: GetNotificationTemplatesByKind :many
|
||||
SELECT *
|
||||
FROM notification_templates
|
||||
WHERE kind = @kind::notification_template_kind;
|
||||
|
||||
@@ -24,6 +24,7 @@ const (
|
||||
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);
|
||||
UniqueNotificationMessagesPkey UniqueConstraint = "notification_messages_pkey" // ALTER TABLE ONLY notification_messages ADD CONSTRAINT notification_messages_pkey PRIMARY KEY (id);
|
||||
UniqueNotificationPreferencesPkey UniqueConstraint = "notification_preferences_pkey" // ALTER TABLE ONLY notification_preferences ADD CONSTRAINT notification_preferences_pkey PRIMARY KEY (user_id, 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);
|
||||
UniqueOauth2ProviderAppCodesPkey UniqueConstraint = "oauth2_provider_app_codes_pkey" // ALTER TABLE ONLY oauth2_provider_app_codes ADD CONSTRAINT oauth2_provider_app_codes_pkey PRIMARY KEY (id);
|
||||
|
||||
Reference in New Issue
Block a user