Unverified Commit a249d06e authored by memoclaw's avatar memoclaw Committed by GitHub

feat(instance): add notification transport setting (#5737)

Co-authored-by: 's avatarmemoclaw <265580040+memoclaw@users.noreply.github.com>
parent 65d14fbb
......@@ -71,6 +71,7 @@ message InstanceSetting {
StorageSetting storage_setting = 3;
MemoRelatedSetting memo_related_setting = 4;
TagsSetting tags_setting = 5;
NotificationSetting notification_setting = 6;
}
// Enumeration of instance setting keys.
......@@ -84,6 +85,8 @@ message InstanceSetting {
MEMO_RELATED = 3;
// TAGS is the key for tag metadata.
TAGS = 4;
// NOTIFICATION is the key for notification transport settings.
NOTIFICATION = 5;
}
// General instance settings configuration.
......@@ -174,6 +177,25 @@ message InstanceSetting {
message TagsSetting {
map<string, TagMetadata> tags = 1;
}
// Notification transport configuration.
message NotificationSetting {
EmailSetting email = 1;
// Email delivery configuration for notifications.
message EmailSetting {
bool enabled = 1;
string smtp_host = 2;
int32 smtp_port = 3;
string smtp_username = 4;
string smtp_password = 5;
string from_email = 6;
string from_name = 7;
string reply_to = 8;
bool use_tls = 9;
bool use_ssl = 10;
}
}
}
// Request message for GetInstanceSetting method.
......
This diff is collapsed.
......@@ -2192,6 +2192,8 @@ components:
$ref: '#/components/schemas/InstanceSetting_MemoRelatedSetting'
tagsSetting:
$ref: '#/components/schemas/InstanceSetting_TagsSetting'
notificationSetting:
$ref: '#/components/schemas/InstanceSetting_NotificationSetting'
description: An instance setting resource.
InstanceSetting_GeneralSetting:
type: object
......@@ -2248,6 +2250,12 @@ components:
type: string
description: reactions is the list of reactions.
description: Memo-related instance settings and policies.
InstanceSetting_NotificationSetting:
type: object
properties:
email:
$ref: '#/components/schemas/NotificationSetting_EmailSetting'
description: Notification transport configuration.
InstanceSetting_StorageSetting:
type: object
properties:
......@@ -2626,6 +2634,31 @@ components:
type: string
description: The title extracted from the first H1 heading, if present.
description: Computed properties of a memo.
NotificationSetting_EmailSetting:
type: object
properties:
enabled:
type: boolean
smtpHost:
type: string
smtpPort:
type: integer
format: int32
smtpUsername:
type: string
smtpPassword:
type: string
fromEmail:
type: string
fromName:
type: string
replyTo:
type: string
useTls:
type: boolean
useSsl:
type: boolean
description: Email delivery configuration for notifications.
OAuth2Config:
type: object
properties:
......
This diff is collapsed.
......@@ -18,6 +18,8 @@ enum InstanceSettingKey {
MEMO_RELATED = 4;
// TAGS is the key for tag metadata.
TAGS = 5;
// NOTIFICATION is the key for notification transport settings.
NOTIFICATION = 6;
}
message InstanceSetting {
......@@ -28,6 +30,7 @@ message InstanceSetting {
InstanceStorageSetting storage_setting = 4;
InstanceMemoRelatedSetting memo_related_setting = 5;
InstanceTagsSetting tags_setting = 6;
InstanceNotificationSetting notification_setting = 7;
}
}
......@@ -117,3 +120,20 @@ message InstanceTagMetadata {
message InstanceTagsSetting {
map<string, InstanceTagMetadata> tags = 1;
}
message InstanceNotificationSetting {
EmailSetting email = 1;
message EmailSetting {
bool enabled = 1;
string smtp_host = 2;
int32 smtp_port = 3;
string smtp_username = 4;
string smtp_password = 5;
string from_email = 6;
string from_name = 7;
string reply_to = 8;
bool use_tls = 9;
bool use_ssl = 10;
}
}
......@@ -51,6 +51,8 @@ func (s *APIV1Service) GetInstanceSetting(ctx context.Context, request *v1pb.Get
_, err = s.Store.GetInstanceStorageSetting(ctx)
case storepb.InstanceSettingKey_TAGS:
_, err = s.Store.GetInstanceTagsSetting(ctx)
case storepb.InstanceSettingKey_NOTIFICATION:
_, err = s.Store.GetInstanceNotificationSetting(ctx)
default:
return nil, status.Errorf(codes.InvalidArgument, "unsupported instance setting key: %v", instanceSettingKey)
}
......@@ -134,6 +136,10 @@ func convertInstanceSettingFromStore(setting *storepb.InstanceSetting) *v1pb.Ins
instanceSetting.Value = &v1pb.InstanceSetting_TagsSetting_{
TagsSetting: convertInstanceTagsSettingFromStore(setting.GetTagsSetting()),
}
case *storepb.InstanceSetting_NotificationSetting:
instanceSetting.Value = &v1pb.InstanceSetting_NotificationSetting_{
NotificationSetting: convertInstanceNotificationSettingFromStore(setting.GetNotificationSetting()),
}
default:
// Leave Value unset for unsupported setting variants.
}
......@@ -165,6 +171,10 @@ func convertInstanceSettingToStore(setting *v1pb.InstanceSetting) *storepb.Insta
instanceSetting.Value = &storepb.InstanceSetting_TagsSetting{
TagsSetting: convertInstanceTagsSettingToStore(setting.GetTagsSetting()),
}
case storepb.InstanceSettingKey_NOTIFICATION:
instanceSetting.Value = &storepb.InstanceSetting_NotificationSetting{
NotificationSetting: convertInstanceNotificationSettingToStore(setting.GetNotificationSetting()),
}
default:
// Keep the default GeneralSetting value
}
......@@ -318,6 +328,52 @@ func convertInstanceTagsSettingToStore(setting *v1pb.InstanceSetting_TagsSetting
}
}
func convertInstanceNotificationSettingFromStore(setting *storepb.InstanceNotificationSetting) *v1pb.InstanceSetting_NotificationSetting {
if setting == nil {
return nil
}
notificationSetting := &v1pb.InstanceSetting_NotificationSetting{}
if setting.Email != nil {
notificationSetting.Email = &v1pb.InstanceSetting_NotificationSetting_EmailSetting{
Enabled: setting.Email.Enabled,
SmtpHost: setting.Email.SmtpHost,
SmtpPort: setting.Email.SmtpPort,
SmtpUsername: setting.Email.SmtpUsername,
SmtpPassword: setting.Email.SmtpPassword,
FromEmail: setting.Email.FromEmail,
FromName: setting.Email.FromName,
ReplyTo: setting.Email.ReplyTo,
UseTls: setting.Email.UseTls,
UseSsl: setting.Email.UseSsl,
}
}
return notificationSetting
}
func convertInstanceNotificationSettingToStore(setting *v1pb.InstanceSetting_NotificationSetting) *storepb.InstanceNotificationSetting {
if setting == nil {
return nil
}
notificationSetting := &storepb.InstanceNotificationSetting{}
if setting.Email != nil {
notificationSetting.Email = &storepb.InstanceNotificationSetting_EmailSetting{
Enabled: setting.Email.Enabled,
SmtpHost: setting.Email.SmtpHost,
SmtpPort: setting.Email.SmtpPort,
SmtpUsername: setting.Email.SmtpUsername,
SmtpPassword: setting.Email.SmtpPassword,
FromEmail: setting.Email.FromEmail,
FromName: setting.Email.FromName,
ReplyTo: setting.Email.ReplyTo,
UseTls: setting.Email.UseTls,
UseSsl: setting.Email.UseSsl,
}
}
return notificationSetting
}
func validateInstanceSetting(setting *v1pb.InstanceSetting) error {
key, err := ExtractInstanceSettingKeyFromName(setting.Name)
if err != nil {
......
......@@ -204,6 +204,23 @@ func TestGetInstanceSetting(t *testing.T) {
require.Empty(t, resp.GetTagsSetting().GetTags())
})
t.Run("GetInstanceSetting - notification setting", func(t *testing.T) {
ts := NewTestService(t)
defer ts.Cleanup()
req := &v1pb.GetInstanceSettingRequest{
Name: "instance/settings/NOTIFICATION",
}
resp, err := ts.Service.GetInstanceSetting(ctx, req)
require.NoError(t, err)
require.NotNil(t, resp)
require.Equal(t, "instance/settings/NOTIFICATION", resp.Name)
require.NotNil(t, resp.GetNotificationSetting())
require.NotNil(t, resp.GetNotificationSetting().GetEmail())
require.False(t, resp.GetNotificationSetting().GetEmail().GetEnabled())
})
t.Run("GetInstanceSetting - invalid setting name", func(t *testing.T) {
// Create test service for this specific test
ts := NewTestService(t)
......@@ -283,4 +300,39 @@ func TestUpdateInstanceSetting(t *testing.T) {
require.Error(t, err)
require.Contains(t, err.Error(), "invalid instance setting")
})
t.Run("UpdateInstanceSetting - notification setting", func(t *testing.T) {
ts := NewTestService(t)
defer ts.Cleanup()
hostUser, err := ts.CreateHostUser(ctx, "admin")
require.NoError(t, err)
resp, err := ts.Service.UpdateInstanceSetting(ts.CreateUserContext(ctx, hostUser.ID), &v1pb.UpdateInstanceSettingRequest{
Setting: &v1pb.InstanceSetting{
Name: "instance/settings/NOTIFICATION",
Value: &v1pb.InstanceSetting_NotificationSetting_{
NotificationSetting: &v1pb.InstanceSetting_NotificationSetting{
Email: &v1pb.InstanceSetting_NotificationSetting_EmailSetting{
Enabled: true,
SmtpHost: "smtp.example.com",
SmtpPort: 587,
SmtpUsername: "bot@example.com",
SmtpPassword: "secret",
FromEmail: "bot@example.com",
FromName: "Memos Bot",
ReplyTo: "support@example.com",
UseTls: true,
},
},
},
},
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"notification_setting"}},
})
require.NoError(t, err)
require.NotNil(t, resp.GetNotificationSetting())
require.NotNil(t, resp.GetNotificationSetting().GetEmail())
require.True(t, resp.GetNotificationSetting().GetEmail().GetEnabled())
require.Equal(t, "smtp.example.com", resp.GetNotificationSetting().GetEmail().GetSmtpHost())
})
}
......@@ -39,6 +39,8 @@ func (s *Store) UpsertInstanceSetting(ctx context.Context, upsert *storepb.Insta
valueBytes, err = protojson.Marshal(upsert.GetMemoRelatedSetting())
} else if upsert.Key == storepb.InstanceSettingKey_TAGS {
valueBytes, err = protojson.Marshal(upsert.GetTagsSetting())
} else if upsert.Key == storepb.InstanceSettingKey_NOTIFICATION {
valueBytes, err = protojson.Marshal(upsert.GetNotificationSetting())
} else {
return nil, errors.Errorf("unsupported instance setting key: %v", upsert.Key)
}
......@@ -192,6 +194,28 @@ func (s *Store) GetInstanceTagsSetting(ctx context.Context) (*storepb.InstanceTa
return instanceTagsSetting, nil
}
func (s *Store) GetInstanceNotificationSetting(ctx context.Context) (*storepb.InstanceNotificationSetting, error) {
instanceSetting, err := s.GetInstanceSetting(ctx, &FindInstanceSetting{
Name: storepb.InstanceSettingKey_NOTIFICATION.String(),
})
if err != nil {
return nil, errors.Wrap(err, "failed to get instance notification setting")
}
instanceNotificationSetting := &storepb.InstanceNotificationSetting{}
if instanceSetting != nil {
instanceNotificationSetting = instanceSetting.GetNotificationSetting()
}
if instanceNotificationSetting.Email == nil {
instanceNotificationSetting.Email = &storepb.InstanceNotificationSetting_EmailSetting{}
}
s.instanceSettingCache.Set(ctx, storepb.InstanceSettingKey_NOTIFICATION.String(), &storepb.InstanceSetting{
Key: storepb.InstanceSettingKey_NOTIFICATION,
Value: &storepb.InstanceSetting_NotificationSetting{NotificationSetting: instanceNotificationSetting},
})
return instanceNotificationSetting, nil
}
const (
defaultInstanceStorageType = storepb.InstanceStorageSetting_LOCAL
defaultInstanceUploadSizeLimitMb = 30
......@@ -261,6 +285,12 @@ func convertInstanceSettingFromRaw(instanceSettingRaw *InstanceSetting) (*storep
return nil, err
}
instanceSetting.Value = &storepb.InstanceSetting_TagsSetting{TagsSetting: tagsSetting}
case storepb.InstanceSettingKey_NOTIFICATION.String():
notificationSetting := &storepb.InstanceNotificationSetting{}
if err := protojsonUnmarshaler.Unmarshal([]byte(instanceSettingRaw.Value), notificationSetting); err != nil {
return nil, err
}
instanceSetting.Value = &storepb.InstanceSetting_NotificationSetting{NotificationSetting: notificationSetting}
default:
// Skip unsupported instance setting key.
return nil, nil
......
......@@ -257,6 +257,47 @@ func TestInstanceSettingTagsSetting(t *testing.T) {
ts.Close()
}
func TestInstanceSettingNotificationSetting(t *testing.T) {
t.Parallel()
ctx := context.Background()
ts := NewTestingStore(ctx, t)
notificationSetting, err := ts.GetInstanceNotificationSetting(ctx)
require.NoError(t, err)
require.NotNil(t, notificationSetting)
require.NotNil(t, notificationSetting.Email)
require.False(t, notificationSetting.Email.Enabled)
_, err = ts.UpsertInstanceSetting(ctx, &storepb.InstanceSetting{
Key: storepb.InstanceSettingKey_NOTIFICATION,
Value: &storepb.InstanceSetting_NotificationSetting{
NotificationSetting: &storepb.InstanceNotificationSetting{
Email: &storepb.InstanceNotificationSetting_EmailSetting{
Enabled: true,
SmtpHost: "smtp.example.com",
SmtpPort: 587,
SmtpUsername: "bot@example.com",
SmtpPassword: "secret",
FromEmail: "bot@example.com",
FromName: "Memos Bot",
ReplyTo: "support@example.com",
UseTls: true,
},
},
},
})
require.NoError(t, err)
notificationSetting, err = ts.GetInstanceNotificationSetting(ctx)
require.NoError(t, err)
require.True(t, notificationSetting.Email.Enabled)
require.Equal(t, "smtp.example.com", notificationSetting.Email.SmtpHost)
require.Equal(t, int32(587), notificationSetting.Email.SmtpPort)
require.Equal(t, "bot@example.com", notificationSetting.Email.FromEmail)
ts.Close()
}
func TestInstanceSettingListAll(t *testing.T) {
t.Parallel()
ctx := context.Background()
......@@ -284,10 +325,18 @@ func TestInstanceSettingListAll(t *testing.T) {
})
require.NoError(t, err)
// List all - should have 2 more than initial
_, err = ts.UpsertInstanceSetting(ctx, &storepb.InstanceSetting{
Key: storepb.InstanceSettingKey_NOTIFICATION,
Value: &storepb.InstanceSetting_NotificationSetting{
NotificationSetting: &storepb.InstanceNotificationSetting{},
},
})
require.NoError(t, err)
// List all - should have 3 more than initial
list, err := ts.ListInstanceSettings(ctx, &store.FindInstanceSetting{})
require.NoError(t, err)
require.Equal(t, initialCount+2, len(list))
require.Equal(t, initialCount+3, len(list))
ts.Close()
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment