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 { ...@@ -71,6 +71,7 @@ message InstanceSetting {
StorageSetting storage_setting = 3; StorageSetting storage_setting = 3;
MemoRelatedSetting memo_related_setting = 4; MemoRelatedSetting memo_related_setting = 4;
TagsSetting tags_setting = 5; TagsSetting tags_setting = 5;
NotificationSetting notification_setting = 6;
} }
// Enumeration of instance setting keys. // Enumeration of instance setting keys.
...@@ -84,6 +85,8 @@ message InstanceSetting { ...@@ -84,6 +85,8 @@ message InstanceSetting {
MEMO_RELATED = 3; MEMO_RELATED = 3;
// TAGS is the key for tag metadata. // TAGS is the key for tag metadata.
TAGS = 4; TAGS = 4;
// NOTIFICATION is the key for notification transport settings.
NOTIFICATION = 5;
} }
// General instance settings configuration. // General instance settings configuration.
...@@ -174,6 +177,25 @@ message InstanceSetting { ...@@ -174,6 +177,25 @@ message InstanceSetting {
message TagsSetting { message TagsSetting {
map<string, TagMetadata> tags = 1; 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. // Request message for GetInstanceSetting method.
......
This diff is collapsed.
...@@ -2192,6 +2192,8 @@ components: ...@@ -2192,6 +2192,8 @@ components:
$ref: '#/components/schemas/InstanceSetting_MemoRelatedSetting' $ref: '#/components/schemas/InstanceSetting_MemoRelatedSetting'
tagsSetting: tagsSetting:
$ref: '#/components/schemas/InstanceSetting_TagsSetting' $ref: '#/components/schemas/InstanceSetting_TagsSetting'
notificationSetting:
$ref: '#/components/schemas/InstanceSetting_NotificationSetting'
description: An instance setting resource. description: An instance setting resource.
InstanceSetting_GeneralSetting: InstanceSetting_GeneralSetting:
type: object type: object
...@@ -2248,6 +2250,12 @@ components: ...@@ -2248,6 +2250,12 @@ components:
type: string type: string
description: reactions is the list of reactions. description: reactions is the list of reactions.
description: Memo-related instance settings and policies. description: Memo-related instance settings and policies.
InstanceSetting_NotificationSetting:
type: object
properties:
email:
$ref: '#/components/schemas/NotificationSetting_EmailSetting'
description: Notification transport configuration.
InstanceSetting_StorageSetting: InstanceSetting_StorageSetting:
type: object type: object
properties: properties:
...@@ -2626,6 +2634,31 @@ components: ...@@ -2626,6 +2634,31 @@ components:
type: string type: string
description: The title extracted from the first H1 heading, if present. description: The title extracted from the first H1 heading, if present.
description: Computed properties of a memo. 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: OAuth2Config:
type: object type: object
properties: properties:
......
This diff is collapsed.
...@@ -18,6 +18,8 @@ enum InstanceSettingKey { ...@@ -18,6 +18,8 @@ enum InstanceSettingKey {
MEMO_RELATED = 4; MEMO_RELATED = 4;
// TAGS is the key for tag metadata. // TAGS is the key for tag metadata.
TAGS = 5; TAGS = 5;
// NOTIFICATION is the key for notification transport settings.
NOTIFICATION = 6;
} }
message InstanceSetting { message InstanceSetting {
...@@ -28,6 +30,7 @@ message InstanceSetting { ...@@ -28,6 +30,7 @@ message InstanceSetting {
InstanceStorageSetting storage_setting = 4; InstanceStorageSetting storage_setting = 4;
InstanceMemoRelatedSetting memo_related_setting = 5; InstanceMemoRelatedSetting memo_related_setting = 5;
InstanceTagsSetting tags_setting = 6; InstanceTagsSetting tags_setting = 6;
InstanceNotificationSetting notification_setting = 7;
} }
} }
...@@ -117,3 +120,20 @@ message InstanceTagMetadata { ...@@ -117,3 +120,20 @@ message InstanceTagMetadata {
message InstanceTagsSetting { message InstanceTagsSetting {
map<string, InstanceTagMetadata> tags = 1; 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 ...@@ -51,6 +51,8 @@ func (s *APIV1Service) GetInstanceSetting(ctx context.Context, request *v1pb.Get
_, err = s.Store.GetInstanceStorageSetting(ctx) _, err = s.Store.GetInstanceStorageSetting(ctx)
case storepb.InstanceSettingKey_TAGS: case storepb.InstanceSettingKey_TAGS:
_, err = s.Store.GetInstanceTagsSetting(ctx) _, err = s.Store.GetInstanceTagsSetting(ctx)
case storepb.InstanceSettingKey_NOTIFICATION:
_, err = s.Store.GetInstanceNotificationSetting(ctx)
default: default:
return nil, status.Errorf(codes.InvalidArgument, "unsupported instance setting key: %v", instanceSettingKey) return nil, status.Errorf(codes.InvalidArgument, "unsupported instance setting key: %v", instanceSettingKey)
} }
...@@ -134,6 +136,10 @@ func convertInstanceSettingFromStore(setting *storepb.InstanceSetting) *v1pb.Ins ...@@ -134,6 +136,10 @@ func convertInstanceSettingFromStore(setting *storepb.InstanceSetting) *v1pb.Ins
instanceSetting.Value = &v1pb.InstanceSetting_TagsSetting_{ instanceSetting.Value = &v1pb.InstanceSetting_TagsSetting_{
TagsSetting: convertInstanceTagsSettingFromStore(setting.GetTagsSetting()), TagsSetting: convertInstanceTagsSettingFromStore(setting.GetTagsSetting()),
} }
case *storepb.InstanceSetting_NotificationSetting:
instanceSetting.Value = &v1pb.InstanceSetting_NotificationSetting_{
NotificationSetting: convertInstanceNotificationSettingFromStore(setting.GetNotificationSetting()),
}
default: default:
// Leave Value unset for unsupported setting variants. // Leave Value unset for unsupported setting variants.
} }
...@@ -165,6 +171,10 @@ func convertInstanceSettingToStore(setting *v1pb.InstanceSetting) *storepb.Insta ...@@ -165,6 +171,10 @@ func convertInstanceSettingToStore(setting *v1pb.InstanceSetting) *storepb.Insta
instanceSetting.Value = &storepb.InstanceSetting_TagsSetting{ instanceSetting.Value = &storepb.InstanceSetting_TagsSetting{
TagsSetting: convertInstanceTagsSettingToStore(setting.GetTagsSetting()), TagsSetting: convertInstanceTagsSettingToStore(setting.GetTagsSetting()),
} }
case storepb.InstanceSettingKey_NOTIFICATION:
instanceSetting.Value = &storepb.InstanceSetting_NotificationSetting{
NotificationSetting: convertInstanceNotificationSettingToStore(setting.GetNotificationSetting()),
}
default: default:
// Keep the default GeneralSetting value // Keep the default GeneralSetting value
} }
...@@ -318,6 +328,52 @@ func convertInstanceTagsSettingToStore(setting *v1pb.InstanceSetting_TagsSetting ...@@ -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 { func validateInstanceSetting(setting *v1pb.InstanceSetting) error {
key, err := ExtractInstanceSettingKeyFromName(setting.Name) key, err := ExtractInstanceSettingKeyFromName(setting.Name)
if err != nil { if err != nil {
......
...@@ -204,6 +204,23 @@ func TestGetInstanceSetting(t *testing.T) { ...@@ -204,6 +204,23 @@ func TestGetInstanceSetting(t *testing.T) {
require.Empty(t, resp.GetTagsSetting().GetTags()) 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) { t.Run("GetInstanceSetting - invalid setting name", func(t *testing.T) {
// Create test service for this specific test // Create test service for this specific test
ts := NewTestService(t) ts := NewTestService(t)
...@@ -283,4 +300,39 @@ func TestUpdateInstanceSetting(t *testing.T) { ...@@ -283,4 +300,39 @@ func TestUpdateInstanceSetting(t *testing.T) {
require.Error(t, err) require.Error(t, err)
require.Contains(t, err.Error(), "invalid instance setting") 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 ...@@ -39,6 +39,8 @@ func (s *Store) UpsertInstanceSetting(ctx context.Context, upsert *storepb.Insta
valueBytes, err = protojson.Marshal(upsert.GetMemoRelatedSetting()) valueBytes, err = protojson.Marshal(upsert.GetMemoRelatedSetting())
} else if upsert.Key == storepb.InstanceSettingKey_TAGS { } else if upsert.Key == storepb.InstanceSettingKey_TAGS {
valueBytes, err = protojson.Marshal(upsert.GetTagsSetting()) valueBytes, err = protojson.Marshal(upsert.GetTagsSetting())
} else if upsert.Key == storepb.InstanceSettingKey_NOTIFICATION {
valueBytes, err = protojson.Marshal(upsert.GetNotificationSetting())
} else { } else {
return nil, errors.Errorf("unsupported instance setting key: %v", upsert.Key) return nil, errors.Errorf("unsupported instance setting key: %v", upsert.Key)
} }
...@@ -192,6 +194,28 @@ func (s *Store) GetInstanceTagsSetting(ctx context.Context) (*storepb.InstanceTa ...@@ -192,6 +194,28 @@ func (s *Store) GetInstanceTagsSetting(ctx context.Context) (*storepb.InstanceTa
return instanceTagsSetting, nil 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 ( const (
defaultInstanceStorageType = storepb.InstanceStorageSetting_LOCAL defaultInstanceStorageType = storepb.InstanceStorageSetting_LOCAL
defaultInstanceUploadSizeLimitMb = 30 defaultInstanceUploadSizeLimitMb = 30
...@@ -261,6 +285,12 @@ func convertInstanceSettingFromRaw(instanceSettingRaw *InstanceSetting) (*storep ...@@ -261,6 +285,12 @@ func convertInstanceSettingFromRaw(instanceSettingRaw *InstanceSetting) (*storep
return nil, err return nil, err
} }
instanceSetting.Value = &storepb.InstanceSetting_TagsSetting{TagsSetting: tagsSetting} 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: default:
// Skip unsupported instance setting key. // Skip unsupported instance setting key.
return nil, nil return nil, nil
......
...@@ -257,6 +257,47 @@ func TestInstanceSettingTagsSetting(t *testing.T) { ...@@ -257,6 +257,47 @@ func TestInstanceSettingTagsSetting(t *testing.T) {
ts.Close() 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) { func TestInstanceSettingListAll(t *testing.T) {
t.Parallel() t.Parallel()
ctx := context.Background() ctx := context.Background()
...@@ -284,10 +325,18 @@ func TestInstanceSettingListAll(t *testing.T) { ...@@ -284,10 +325,18 @@ func TestInstanceSettingListAll(t *testing.T) {
}) })
require.NoError(t, err) 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{}) list, err := ts.ListInstanceSettings(ctx, &store.FindInstanceSetting{})
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, initialCount+2, len(list)) require.Equal(t, initialCount+3, len(list))
ts.Close() 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