Commit 707e5caf authored by Steven's avatar Steven

chore: update workspace setting store

parent 17e8fc54
......@@ -93,9 +93,9 @@ message WorkspaceStorageSetting {
StorageType storage_type = 1;
// The id of actived external storage.
optional int32 actived_external_storage_id = 2;
// The local storage path for STORAGE_TYPE_LOCAL.
// The template of local storage path.
// e.g. assets/{timestamp}_{filename}
string local_storage_path = 3;
string local_storage_path_template = 3;
// The max upload size in megabytes.
int64 upload_size_limit_mb = 4;
......
......@@ -3084,7 +3084,7 @@ Used internally for obfuscating the page token.
| ----- | ---- | ----- | ----------- |
| storage_type | [WorkspaceStorageSetting.StorageType](#memos-api-v2-WorkspaceStorageSetting-StorageType) | | storage_type is the storage type. |
| actived_external_storage_id | [int32](#int32) | optional | The id of actived external storage. |
| local_storage_path | [string](#string) | | The local storage path for STORAGE_TYPE_LOCAL. e.g. assets/{timestamp}_{filename} |
| local_storage_path_template | [string](#string) | | The template of local storage path. e.g. assets/{timestamp}_{filename} |
| upload_size_limit_mb | [int64](#int64) | | The max upload size in megabytes. |
......
......@@ -636,7 +636,7 @@
| ----- | ---- | ----- | ----------- |
| storage_type | [WorkspaceStorageSetting.StorageType](#memos-store-WorkspaceStorageSetting-StorageType) | | storage_type is the storage type. |
| actived_external_storage_id | [int32](#int32) | optional | The id of actived external storage. |
| local_storage_path | [string](#string) | | The local storage path for STORAGE_TYPE_LOCAL. e.g. assets/{timestamp}_{filename} |
| local_storage_path_template | [string](#string) | | The template of local storage path. e.g. assets/{timestamp}_{filename} |
| upload_size_limit_mb | [int64](#int64) | | The max upload size in megabytes. |
......
This diff is collapsed.
......@@ -62,9 +62,9 @@ message WorkspaceStorageSetting {
StorageType storage_type = 1;
// The id of actived external storage.
optional int32 actived_external_storage_id = 2;
// The local storage path for STORAGE_TYPE_LOCAL.
// The template of local storage path.
// e.g. assets/{timestamp}_{filename}
string local_storage_path = 3;
string local_storage_path_template = 3;
// The max upload size in megabytes.
int64 upload_size_limit_mb = 4;
......
......@@ -2069,10 +2069,10 @@ definitions:
type: integer
format: int32
description: The id of actived external storage.
localStoragePath:
localStoragePathTemplate:
type: string
title: |-
The local storage path for STORAGE_TYPE_LOCAL.
The template of local storage path.
e.g. assets/{timestamp}_{filename}
uploadSizeLimitMb:
type: string
......
......@@ -260,8 +260,8 @@ func SaveResourceBlob(ctx context.Context, s *store.Store, create *store.Resourc
if workspaceStorageSetting.StorageType == storepb.WorkspaceStorageSetting_STORAGE_TYPE_LOCAL {
localStoragePath := "assets/{timestamp}_{filename}"
if workspaceStorageSetting.LocalStoragePath != "" {
localStoragePath = workspaceStorageSetting.LocalStoragePath
if workspaceStorageSetting.LocalStoragePathTemplate != "" {
localStoragePath = workspaceStorageSetting.LocalStoragePathTemplate
}
internalPath := localStoragePath
......
......@@ -176,9 +176,10 @@ func convertWorkspaceStorageSettingFromStore(setting *storepb.WorkspaceStorageSe
return nil
}
return &apiv2pb.WorkspaceStorageSetting{
StorageType: apiv2pb.WorkspaceStorageSetting_StorageType(setting.StorageType),
LocalStoragePath: setting.LocalStoragePath,
UploadSizeLimitMb: setting.UploadSizeLimitMb,
StorageType: apiv2pb.WorkspaceStorageSetting_StorageType(setting.StorageType),
LocalStoragePathTemplate: setting.LocalStoragePathTemplate,
UploadSizeLimitMb: setting.UploadSizeLimitMb,
ActivedExternalStorageId: setting.ActivedExternalStorageId,
}
}
......@@ -187,9 +188,10 @@ func convertWorkspaceStorageSettingToStore(setting *apiv2pb.WorkspaceStorageSett
return nil
}
return &storepb.WorkspaceStorageSetting{
StorageType: storepb.WorkspaceStorageSetting_StorageType(setting.StorageType),
LocalStoragePath: setting.LocalStoragePath,
UploadSizeLimitMb: setting.UploadSizeLimitMb,
StorageType: storepb.WorkspaceStorageSetting_StorageType(setting.StorageType),
LocalStoragePathTemplate: setting.LocalStoragePathTemplate,
UploadSizeLimitMb: setting.UploadSizeLimitMb,
ActivedExternalStorageId: setting.ActivedExternalStorageId,
}
}
......
package store
import "google.golang.org/protobuf/encoding/protojson"
var (
protojsonUnmarshaler = protojson.UnmarshalOptions{
AllowPartial: true,
DiscardUnknown: true,
}
)
// RowStatus is the status for a row.
type RowStatus string
......
......@@ -5,42 +5,19 @@ import (
"database/sql"
"strings"
"github.com/pkg/errors"
"google.golang.org/protobuf/encoding/protojson"
storepb "github.com/usememos/memos/proto/gen/store"
"github.com/usememos/memos/store"
)
func (d *DB) UpsertUserSetting(ctx context.Context, upsert *storepb.UserSetting) (*storepb.UserSetting, error) {
func (d *DB) UpsertUserSetting(ctx context.Context, upsert *store.UserSetting) (*store.UserSetting, error) {
stmt := "INSERT INTO `user_setting` (`user_id`, `key`, `value`) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE `value` = ?"
var valueString string
if upsert.Key == storepb.UserSettingKey_USER_SETTING_ACCESS_TOKENS {
valueBytes, err := protojson.Marshal(upsert.GetAccessTokens())
if err != nil {
return nil, err
}
valueString = string(valueBytes)
} else if upsert.Key == storepb.UserSettingKey_USER_SETTING_LOCALE {
valueString = upsert.GetLocale()
} else if upsert.Key == storepb.UserSettingKey_USER_SETTING_APPEARANCE {
valueString = upsert.GetAppearance()
} else if upsert.Key == storepb.UserSettingKey_USER_SETTING_MEMO_VISIBILITY {
valueString = upsert.GetMemoVisibility()
} else if upsert.Key == storepb.UserSettingKey_USER_SETTING_TELEGRAM_USER_ID {
valueString = upsert.GetTelegramUserId()
} else {
return nil, errors.Errorf("unknown user setting key: %s", upsert.Key.String())
}
if _, err := d.db.ExecContext(ctx, stmt, upsert.UserId, upsert.Key.String(), valueString, valueString); err != nil {
if _, err := d.db.ExecContext(ctx, stmt, upsert.UserID, upsert.Key.String(), upsert.Value, upsert.Value); err != nil {
return nil, err
}
return upsert, nil
}
func (d *DB) ListUserSettings(ctx context.Context, find *store.FindUserSetting) ([]*storepb.UserSetting, error) {
func (d *DB) ListUserSettings(ctx context.Context, find *store.FindUserSetting) ([]*store.UserSetting, error) {
where, args := []string{"1 = 1"}, []any{}
if v := find.Key; v != storepb.UserSettingKey_USER_SETTING_KEY_UNSPECIFIED {
......@@ -57,46 +34,18 @@ func (d *DB) ListUserSettings(ctx context.Context, find *store.FindUserSetting)
}
defer rows.Close()
userSettingList := make([]*storepb.UserSetting, 0)
userSettingList := make([]*store.UserSetting, 0)
for rows.Next() {
userSetting := &storepb.UserSetting{}
var keyString, valueString string
userSetting := &store.UserSetting{}
var keyString string
if err := rows.Scan(
&userSetting.UserId,
&userSetting.UserID,
&keyString,
&valueString,
&userSetting.Value,
); err != nil {
return nil, err
}
userSetting.Key = storepb.UserSettingKey(storepb.UserSettingKey_value[keyString])
if userSetting.Key == storepb.UserSettingKey_USER_SETTING_ACCESS_TOKENS {
accessTokensUserSetting := &storepb.AccessTokensUserSetting{}
if err := protojson.Unmarshal([]byte(valueString), accessTokensUserSetting); err != nil {
return nil, err
}
userSetting.Value = &storepb.UserSetting_AccessTokens{
AccessTokens: accessTokensUserSetting,
}
} else if userSetting.Key == storepb.UserSettingKey_USER_SETTING_LOCALE {
userSetting.Value = &storepb.UserSetting_Locale{
Locale: valueString,
}
} else if userSetting.Key == storepb.UserSettingKey_USER_SETTING_APPEARANCE {
userSetting.Value = &storepb.UserSetting_Appearance{
Appearance: valueString,
}
} else if userSetting.Key == storepb.UserSettingKey_USER_SETTING_MEMO_VISIBILITY {
userSetting.Value = &storepb.UserSetting_MemoVisibility{
MemoVisibility: valueString,
}
} else if userSetting.Key == storepb.UserSettingKey_USER_SETTING_TELEGRAM_USER_ID {
userSetting.Value = &storepb.UserSetting_TelegramUserId{
TelegramUserId: valueString,
}
} else {
// Skip unknown user setting key.
continue
}
userSettingList = append(userSettingList, userSetting)
}
......
......@@ -5,14 +5,11 @@ import (
"database/sql"
"strings"
"github.com/pkg/errors"
"google.golang.org/protobuf/encoding/protojson"
storepb "github.com/usememos/memos/proto/gen/store"
"github.com/usememos/memos/store"
)
func (d *DB) UpsertUserSetting(ctx context.Context, upsert *storepb.UserSetting) (*storepb.UserSetting, error) {
func (d *DB) UpsertUserSetting(ctx context.Context, upsert *store.UserSetting) (*store.UserSetting, error) {
stmt := `
INSERT INTO user_setting (
user_id, key, value
......@@ -21,33 +18,13 @@ func (d *DB) UpsertUserSetting(ctx context.Context, upsert *storepb.UserSetting)
ON CONFLICT(user_id, key) DO UPDATE
SET value = EXCLUDED.value
`
var valueString string
if upsert.Key == storepb.UserSettingKey_USER_SETTING_ACCESS_TOKENS {
valueBytes, err := protojson.Marshal(upsert.GetAccessTokens())
if err != nil {
return nil, err
}
valueString = string(valueBytes)
} else if upsert.Key == storepb.UserSettingKey_USER_SETTING_LOCALE {
valueString = upsert.GetLocale()
} else if upsert.Key == storepb.UserSettingKey_USER_SETTING_APPEARANCE {
valueString = upsert.GetAppearance()
} else if upsert.Key == storepb.UserSettingKey_USER_SETTING_MEMO_VISIBILITY {
valueString = upsert.GetMemoVisibility()
} else if upsert.Key == storepb.UserSettingKey_USER_SETTING_TELEGRAM_USER_ID {
valueString = upsert.GetTelegramUserId()
} else {
return nil, errors.Errorf("unknown user setting key: %s", upsert.Key.String())
}
if _, err := d.db.ExecContext(ctx, stmt, upsert.UserId, upsert.Key.String(), valueString); err != nil {
if _, err := d.db.ExecContext(ctx, stmt, upsert.UserID, upsert.Key.String(), upsert.Value); err != nil {
return nil, err
}
return upsert, nil
}
func (d *DB) ListUserSettings(ctx context.Context, find *store.FindUserSetting) ([]*storepb.UserSetting, error) {
func (d *DB) ListUserSettings(ctx context.Context, find *store.FindUserSetting) ([]*store.UserSetting, error) {
where, args := []string{"1 = 1"}, []any{}
if v := find.Key; v != storepb.UserSettingKey_USER_SETTING_KEY_UNSPECIFIED {
......@@ -70,46 +47,18 @@ func (d *DB) ListUserSettings(ctx context.Context, find *store.FindUserSetting)
}
defer rows.Close()
userSettingList := make([]*storepb.UserSetting, 0)
userSettingList := make([]*store.UserSetting, 0)
for rows.Next() {
userSetting := &storepb.UserSetting{}
var keyString, valueString string
userSetting := &store.UserSetting{}
var keyString string
if err := rows.Scan(
&userSetting.UserId,
&userSetting.UserID,
&keyString,
&valueString,
&userSetting.Value,
); err != nil {
return nil, err
}
userSetting.Key = storepb.UserSettingKey(storepb.UserSettingKey_value[keyString])
if userSetting.Key == storepb.UserSettingKey_USER_SETTING_ACCESS_TOKENS {
accessTokensUserSetting := &storepb.AccessTokensUserSetting{}
if err := protojson.Unmarshal([]byte(valueString), accessTokensUserSetting); err != nil {
return nil, err
}
userSetting.Value = &storepb.UserSetting_AccessTokens{
AccessTokens: accessTokensUserSetting,
}
} else if userSetting.Key == storepb.UserSettingKey_USER_SETTING_LOCALE {
userSetting.Value = &storepb.UserSetting_Locale{
Locale: valueString,
}
} else if userSetting.Key == storepb.UserSettingKey_USER_SETTING_APPEARANCE {
userSetting.Value = &storepb.UserSetting_Appearance{
Appearance: valueString,
}
} else if userSetting.Key == storepb.UserSettingKey_USER_SETTING_MEMO_VISIBILITY {
userSetting.Value = &storepb.UserSetting_MemoVisibility{
MemoVisibility: valueString,
}
} else if userSetting.Key == storepb.UserSettingKey_USER_SETTING_TELEGRAM_USER_ID {
userSetting.Value = &storepb.UserSetting_TelegramUserId{
TelegramUserId: valueString,
}
} else {
// Skip unknown user setting key.
continue
}
userSettingList = append(userSettingList, userSetting)
}
......
......@@ -5,14 +5,11 @@ import (
"database/sql"
"strings"
"github.com/pkg/errors"
"google.golang.org/protobuf/encoding/protojson"
storepb "github.com/usememos/memos/proto/gen/store"
"github.com/usememos/memos/store"
)
func (d *DB) UpsertUserSetting(ctx context.Context, upsert *storepb.UserSetting) (*storepb.UserSetting, error) {
func (d *DB) UpsertUserSetting(ctx context.Context, upsert *store.UserSetting) (*store.UserSetting, error) {
stmt := `
INSERT INTO user_setting (
user_id, key, value
......@@ -21,33 +18,13 @@ func (d *DB) UpsertUserSetting(ctx context.Context, upsert *storepb.UserSetting)
ON CONFLICT(user_id, key) DO UPDATE
SET value = EXCLUDED.value
`
var valueString string
if upsert.Key == storepb.UserSettingKey_USER_SETTING_ACCESS_TOKENS {
valueBytes, err := protojson.Marshal(upsert.GetAccessTokens())
if err != nil {
return nil, err
}
valueString = string(valueBytes)
} else if upsert.Key == storepb.UserSettingKey_USER_SETTING_LOCALE {
valueString = upsert.GetLocale()
} else if upsert.Key == storepb.UserSettingKey_USER_SETTING_APPEARANCE {
valueString = upsert.GetAppearance()
} else if upsert.Key == storepb.UserSettingKey_USER_SETTING_MEMO_VISIBILITY {
valueString = upsert.GetMemoVisibility()
} else if upsert.Key == storepb.UserSettingKey_USER_SETTING_TELEGRAM_USER_ID {
valueString = upsert.GetTelegramUserId()
} else {
return nil, errors.Errorf("unknown user setting key: %s", upsert.Key.String())
}
if _, err := d.db.ExecContext(ctx, stmt, upsert.UserId, upsert.Key.String(), valueString); err != nil {
if _, err := d.db.ExecContext(ctx, stmt, upsert.UserID, upsert.Key.String(), upsert.Value); err != nil {
return nil, err
}
return upsert, nil
}
func (d *DB) ListUserSettings(ctx context.Context, find *store.FindUserSetting) ([]*storepb.UserSetting, error) {
func (d *DB) ListUserSettings(ctx context.Context, find *store.FindUserSetting) ([]*store.UserSetting, error) {
where, args := []string{"1 = 1"}, []any{}
if v := find.Key; v != storepb.UserSettingKey_USER_SETTING_KEY_UNSPECIFIED {
......@@ -70,46 +47,18 @@ func (d *DB) ListUserSettings(ctx context.Context, find *store.FindUserSetting)
}
defer rows.Close()
userSettingList := make([]*storepb.UserSetting, 0)
userSettingList := make([]*store.UserSetting, 0)
for rows.Next() {
userSetting := &storepb.UserSetting{}
var keyString, valueString string
userSetting := &store.UserSetting{}
var keyString string
if err := rows.Scan(
&userSetting.UserId,
&userSetting.UserID,
&keyString,
&valueString,
&userSetting.Value,
); err != nil {
return nil, err
}
userSetting.Key = storepb.UserSettingKey(storepb.UserSettingKey_value[keyString])
if userSetting.Key == storepb.UserSettingKey_USER_SETTING_ACCESS_TOKENS {
accessTokensUserSetting := &storepb.AccessTokensUserSetting{}
if err := protojson.Unmarshal([]byte(valueString), accessTokensUserSetting); err != nil {
return nil, err
}
userSetting.Value = &storepb.UserSetting_AccessTokens{
AccessTokens: accessTokensUserSetting,
}
} else if userSetting.Key == storepb.UserSettingKey_USER_SETTING_LOCALE {
userSetting.Value = &storepb.UserSetting_Locale{
Locale: valueString,
}
} else if userSetting.Key == storepb.UserSettingKey_USER_SETTING_APPEARANCE {
userSetting.Value = &storepb.UserSetting_Appearance{
Appearance: valueString,
}
} else if userSetting.Key == storepb.UserSettingKey_USER_SETTING_MEMO_VISIBILITY {
userSetting.Value = &storepb.UserSetting_MemoVisibility{
MemoVisibility: valueString,
}
} else if userSetting.Key == storepb.UserSettingKey_USER_SETTING_TELEGRAM_USER_ID {
userSetting.Value = &storepb.UserSetting_TelegramUserId{
TelegramUserId: valueString,
}
} else {
// Skip unknown user setting key.
continue
}
userSettingList = append(userSettingList, userSetting)
}
if err := rows.Err(); err != nil {
......
......@@ -61,8 +61,8 @@ type Driver interface {
DeleteUser(ctx context.Context, delete *DeleteUser) error
// UserSetting model related methods.
UpsertUserSetting(ctx context.Context, upsert *storepb.UserSetting) (*storepb.UserSetting, error)
ListUserSettings(ctx context.Context, find *FindUserSetting) ([]*storepb.UserSetting, error)
UpsertUserSetting(ctx context.Context, upsert *UserSetting) (*UserSetting, error)
ListUserSettings(ctx context.Context, find *FindUserSetting) ([]*UserSetting, error)
// IdentityProvider model related methods.
CreateIdentityProvider(ctx context.Context, create *IdentityProvider) (*IdentityProvider, error)
......
......@@ -3,34 +3,56 @@ package store
import (
"context"
"github.com/pkg/errors"
storepb "github.com/usememos/memos/proto/gen/store"
"google.golang.org/protobuf/encoding/protojson"
)
type UserSetting struct {
UserID int32
Key storepb.UserSettingKey
Value string
}
type FindUserSetting struct {
UserID *int32
Key storepb.UserSettingKey
}
func (s *Store) UpsertUserSetting(ctx context.Context, upsert *storepb.UserSetting) (*storepb.UserSetting, error) {
userSettingMessage, err := s.driver.UpsertUserSetting(ctx, upsert)
userSettingRaw, err := convertUserSettingToRaw(upsert)
if err != nil {
return nil, err
}
userSettingRaw, err = s.driver.UpsertUserSetting(ctx, userSettingRaw)
if err != nil {
return nil, err
}
s.userSettingCache.Store(getUserSettingV1CacheKey(userSettingMessage.UserId, userSettingMessage.Key.String()), userSettingMessage)
return userSettingMessage, nil
userSetting, err := convertUserSettingFromRaw(userSettingRaw)
if err != nil {
return nil, err
}
s.userSettingCache.Store(getUserSettingV1CacheKey(userSetting.UserId, userSetting.Key.String()), userSetting)
return userSetting, nil
}
func (s *Store) ListUserSettings(ctx context.Context, find *FindUserSetting) ([]*storepb.UserSetting, error) {
userSettingList, err := s.driver.ListUserSettings(ctx, find)
userSettingRawList, err := s.driver.ListUserSettings(ctx, find)
if err != nil {
return nil, err
}
for _, userSetting := range userSettingList {
userSettings := []*storepb.UserSetting{}
for _, userSettingRaw := range userSettingRawList {
userSetting, err := convertUserSettingFromRaw(userSettingRaw)
if err != nil {
return nil, err
}
s.userSettingCache.Store(getUserSettingV1CacheKey(userSetting.UserId, userSetting.Key.String()), userSetting)
userSettings = append(userSettings, userSetting)
}
return userSettingList, nil
return userSettings, nil
}
func (s *Store) GetUserSetting(ctx context.Context, find *FindUserSetting) (*storepb.UserSetting, error) {
......@@ -44,10 +66,12 @@ func (s *Store) GetUserSetting(ctx context.Context, find *FindUserSetting) (*sto
if err != nil {
return nil, err
}
if len(list) == 0 {
return nil, nil
}
if len(list) > 1 {
return nil, errors.Errorf("expected 1 user setting, but got %d", len(list))
}
userSetting := list[0]
s.userSettingCache.Store(getUserSettingV1CacheKey(userSetting.UserId, userSetting.Key.String()), userSetting)
......@@ -97,3 +121,58 @@ func (s *Store) RemoveUserAccessToken(ctx context.Context, userID int32, token s
return err
}
func convertUserSettingFromRaw(raw *UserSetting) (*storepb.UserSetting, error) {
userSetting := &storepb.UserSetting{
UserId: raw.UserID,
Key: raw.Key,
}
switch raw.Key {
case storepb.UserSettingKey_USER_SETTING_ACCESS_TOKENS:
accessTokensUserSetting := &storepb.AccessTokensUserSetting{}
if err := protojsonUnmarshaler.Unmarshal([]byte(raw.Value), accessTokensUserSetting); err != nil {
return nil, err
}
userSetting.Value = &storepb.UserSetting_AccessTokens{AccessTokens: accessTokensUserSetting}
case storepb.UserSettingKey_USER_SETTING_LOCALE:
userSetting.Value = &storepb.UserSetting_Locale{Locale: raw.Value}
case storepb.UserSettingKey_USER_SETTING_APPEARANCE:
userSetting.Value = &storepb.UserSetting_Appearance{Appearance: raw.Value}
case storepb.UserSettingKey_USER_SETTING_MEMO_VISIBILITY:
userSetting.Value = &storepb.UserSetting_MemoVisibility{MemoVisibility: raw.Value}
case storepb.UserSettingKey_USER_SETTING_TELEGRAM_USER_ID:
userSetting.Value = &storepb.UserSetting_TelegramUserId{TelegramUserId: raw.Value}
default:
return nil, errors.Errorf("unsupported user setting key: %v", raw.Key)
}
return userSetting, nil
}
func convertUserSettingToRaw(userSetting *storepb.UserSetting) (*UserSetting, error) {
raw := &UserSetting{
UserID: userSetting.UserId,
Key: userSetting.Key,
}
switch userSetting.Key {
case storepb.UserSettingKey_USER_SETTING_ACCESS_TOKENS:
accessTokensUserSetting := userSetting.GetAccessTokens()
value, err := protojson.Marshal(accessTokensUserSetting)
if err != nil {
return nil, err
}
raw.Value = string(value)
case storepb.UserSettingKey_USER_SETTING_LOCALE:
raw.Value = userSetting.GetLocale()
case storepb.UserSettingKey_USER_SETTING_APPEARANCE:
raw.Value = userSetting.GetAppearance()
case storepb.UserSettingKey_USER_SETTING_MEMO_VISIBILITY:
raw.Value = userSetting.GetMemoVisibility()
case storepb.UserSettingKey_USER_SETTING_TELEGRAM_USER_ID:
raw.Value = userSetting.GetTelegramUserId()
default:
return nil, errors.Errorf("unsupported user setting key: %v", userSetting.Key)
}
return raw, nil
}
......@@ -186,6 +186,12 @@ func (s *Store) GetWorkspaceMemoRelatedSetting(ctx context.Context) (*storepb.Wo
return workspaceMemoRelatedSetting, nil
}
const (
defaultWorkspaceStorageType = storepb.WorkspaceStorageSetting_STORAGE_TYPE_DATABASE
defaultWorkspaceUploadSizeLimitMb = 30
defaultWorkspaceLocalStoragePathTemplate = "assets/{timestamp}_{filename}"
)
func (s *Store) GetWorkspaceStorageSetting(ctx context.Context) (*storepb.WorkspaceStorageSetting, error) {
workspaceSetting, err := s.GetWorkspaceSettingV1(ctx, &FindWorkspaceSetting{
Name: storepb.WorkspaceSettingKey_WORKSPACE_SETTING_STORAGE.String(),
......@@ -198,6 +204,15 @@ func (s *Store) GetWorkspaceStorageSetting(ctx context.Context) (*storepb.Worksp
if workspaceSetting != nil {
workspaceStorageSetting = workspaceSetting.GetStorageSetting()
}
if workspaceStorageSetting.StorageType == storepb.WorkspaceStorageSetting_STORAGE_TYPE_UNSPECIFIED {
workspaceStorageSetting.StorageType = defaultWorkspaceStorageType
}
if workspaceStorageSetting.UploadSizeLimitMb == 0 {
workspaceStorageSetting.UploadSizeLimitMb = defaultWorkspaceUploadSizeLimitMb
}
if workspaceStorageSetting.LocalStoragePathTemplate == "" {
workspaceStorageSetting.LocalStoragePathTemplate = defaultWorkspaceLocalStoragePathTemplate
}
return workspaceStorageSetting, nil
}
......@@ -208,31 +223,31 @@ func convertWorkspaceSettingFromRaw(workspaceSettingRaw *WorkspaceSetting) (*sto
switch workspaceSettingRaw.Name {
case storepb.WorkspaceSettingKey_WORKSPACE_SETTING_BASIC.String():
basicSetting := &storepb.WorkspaceBasicSetting{}
if err := protojson.Unmarshal([]byte(workspaceSettingRaw.Value), basicSetting); err != nil {
if err := protojsonUnmarshaler.Unmarshal([]byte(workspaceSettingRaw.Value), basicSetting); err != nil {
return nil, err
}
workspaceSetting.Value = &storepb.WorkspaceSetting_BasicSetting{BasicSetting: basicSetting}
case storepb.WorkspaceSettingKey_WORKSPACE_SETTING_GENERAL.String():
generalSetting := &storepb.WorkspaceGeneralSetting{}
if err := protojson.Unmarshal([]byte(workspaceSettingRaw.Value), generalSetting); err != nil {
if err := protojsonUnmarshaler.Unmarshal([]byte(workspaceSettingRaw.Value), generalSetting); err != nil {
return nil, err
}
workspaceSetting.Value = &storepb.WorkspaceSetting_GeneralSetting{GeneralSetting: generalSetting}
case storepb.WorkspaceSettingKey_WORKSPACE_SETTING_STORAGE.String():
storageSetting := &storepb.WorkspaceStorageSetting{}
if err := protojson.Unmarshal([]byte(workspaceSettingRaw.Value), storageSetting); err != nil {
if err := protojsonUnmarshaler.Unmarshal([]byte(workspaceSettingRaw.Value), storageSetting); err != nil {
return nil, err
}
workspaceSetting.Value = &storepb.WorkspaceSetting_StorageSetting{StorageSetting: storageSetting}
case storepb.WorkspaceSettingKey_WORKSPACE_SETTING_MEMO_RELATED.String():
memoRelatedSetting := &storepb.WorkspaceMemoRelatedSetting{}
if err := protojson.Unmarshal([]byte(workspaceSettingRaw.Value), memoRelatedSetting); err != nil {
if err := protojsonUnmarshaler.Unmarshal([]byte(workspaceSettingRaw.Value), memoRelatedSetting); err != nil {
return nil, err
}
workspaceSetting.Value = &storepb.WorkspaceSetting_MemoRelatedSetting{MemoRelatedSetting: memoRelatedSetting}
case storepb.WorkspaceSettingKey_WORKSPACE_SETTING_TELEGRAM_INTEGRATION.String():
telegramIntegrationSetting := &storepb.WorkspaceTelegramIntegrationSetting{}
if err := protojson.Unmarshal([]byte(workspaceSettingRaw.Value), telegramIntegrationSetting); err != nil {
if err := protojsonUnmarshaler.Unmarshal([]byte(workspaceSettingRaw.Value), telegramIntegrationSetting); err != nil {
return nil, err
}
workspaceSetting.Value = &storepb.WorkspaceSetting_TelegramIntegrationSetting{TelegramIntegrationSetting: telegramIntegrationSetting}
......
......@@ -2,7 +2,6 @@ import {
Button,
Divider,
Dropdown,
IconButton,
Input,
List,
ListItem,
......@@ -11,9 +10,12 @@ import {
MenuItem,
Radio,
RadioGroup,
Select,
Tooltip,
Option,
} from "@mui/joy";
import { useEffect, useState } from "react";
import { isEqual } from "lodash-es";
import { useEffect, useMemo, useState } from "react";
import { toast } from "react-hot-toast";
import { Link } from "react-router-dom";
import * as api from "@/helpers/api";
......@@ -25,7 +27,6 @@ import showCreateStorageServiceDialog from "../CreateStorageServiceDialog";
import { showCommonDialog } from "../Dialog/CommonDialog";
import Icon from "../Icon";
import LearnMore from "../LearnMore";
import showUpdateLocalStorageDialog from "../UpdateLocalStorageDialog";
const StorageSection = () => {
const t = useTranslate();
......@@ -37,6 +38,26 @@ const StorageSection = () => {
),
);
const allowSaveStorageSetting = useMemo(() => {
if (workspaceStorageSetting.uploadSizeLimitMb <= 0) {
return false;
}
const origin = WorkspaceStorageSetting.fromPartial(
workspaceSettingStore.getWorkspaceSettingByKey(WorkspaceSettingKey.WORKSPACE_SETTING_STORAGE)?.storageSetting || {},
);
if (workspaceStorageSetting.storageType === WorkspaceStorageSetting_StorageType.STORAGE_TYPE_LOCAL) {
if (workspaceStorageSetting.localStoragePathTemplate.length === 0) {
return false;
}
} else if (workspaceStorageSetting.storageType === WorkspaceStorageSetting_StorageType.STORAGE_TYPE_EXTERNAL) {
if (!workspaceStorageSetting.activedExternalStorageId || workspaceStorageSetting.activedExternalStorageId === 0) {
return false;
}
}
return !isEqual(origin, workspaceStorageSetting);
}, [workspaceStorageSetting, workspaceSettingStore.getState()]);
useEffect(() => {
fetchStorageList();
}, []);
......@@ -56,10 +77,14 @@ const StorageSection = () => {
uploadSizeLimitMb: num,
};
setWorkspaceStorageSetting(update);
workspaceSettingStore.setWorkspaceSetting({
name: `${WorkspaceSettingPrefix}${WorkspaceSettingKey.WORKSPACE_SETTING_STORAGE}`,
storageSetting: update,
});
};
const handleLocalStoragePathTemplateChanged = async (event: React.FocusEvent<HTMLInputElement>) => {
const update: WorkspaceStorageSetting = {
...workspaceStorageSetting,
localStoragePathTemplate: event.target.value,
};
setWorkspaceStorageSetting(update);
};
const handleStorageTypeChanged = async (storageType: WorkspaceStorageSetting_StorageType) => {
......@@ -68,10 +93,6 @@ const StorageSection = () => {
storageType: storageType,
};
setWorkspaceStorageSetting(update);
await workspaceSettingStore.setWorkspaceSetting({
name: `${WorkspaceSettingPrefix}${WorkspaceSettingKey.WORKSPACE_SETTING_STORAGE}`,
storageSetting: update,
});
};
const handleActivedExternalStorageIdChanged = async (activedExternalStorageId: number) => {
......@@ -80,10 +101,14 @@ const StorageSection = () => {
activedExternalStorageId: activedExternalStorageId,
};
setWorkspaceStorageSetting(update);
};
const saveWorkspaceStorageSetting = async () => {
await workspaceSettingStore.setWorkspaceSetting({
name: `${WorkspaceSettingPrefix}${WorkspaceSettingKey.WORKSPACE_SETTING_STORAGE}`,
storageSetting: update,
storageSetting: workspaceStorageSetting,
});
toast.success("Updated");
};
const handleDeleteStorage = (storage: ObjectStorage) => {
......@@ -106,10 +131,9 @@ const StorageSection = () => {
return (
<div className="w-full flex flex-col gap-2 pt-2 pb-4">
<div className="w-full flex flex-row justify-start items-center">
<span className="font-mono text-sm text-gray-400 mr-2 dark:text-gray-500">{t("setting.storage-section.current-storage")}</span>
</div>
<div className="font-medium text-gray-700 dark:text-gray-500">{t("setting.storage-section.current-storage")}</div>
<RadioGroup
orientation="horizontal"
className="w-full"
value={workspaceStorageSetting.storageType}
onChange={(event) => {
......@@ -117,28 +141,12 @@ const StorageSection = () => {
}}
>
<Radio value={WorkspaceStorageSetting_StorageType.STORAGE_TYPE_DATABASE} label={t("setting.storage-section.type-database")} />
<div>
<Radio value={WorkspaceStorageSetting_StorageType.STORAGE_TYPE_LOCAL} label={t("setting.storage-section.type-local")} />
<IconButton size="sm" onClick={() => showUpdateLocalStorageDialog()}>
<Icon.PenBox className="w-4 h-auto" />
</IconButton>
</div>
<Radio value={WorkspaceStorageSetting_StorageType.STORAGE_TYPE_EXTERNAL} label={"S3"} />
</RadioGroup>
<RadioGroup
className="w-full"
value={workspaceStorageSetting.activedExternalStorageId}
onChange={(event) => {
handleActivedExternalStorageIdChanged(Number(event.target.value));
}}
>
{storageList.map((storage) => (
<Radio key={storage.id} value={storage.id} label={storage.name} />
))}
<Radio value={WorkspaceStorageSetting_StorageType.STORAGE_TYPE_LOCAL} label={t("setting.storage-section.type-local")} />
<Radio value={WorkspaceStorageSetting_StorageType.STORAGE_TYPE_EXTERNAL} disabled={storageList.length === 0} label={"S3"} />
</RadioGroup>
<div className="w-full flex flex-row justify-between items-center">
<div className="flex flex-row items-center">
<span className="mr-1">{t("setting.system-section.max-upload-size")}</span>
<span className="text-gray-700 dark:text-gray-500 mr-1">{t("setting.system-section.max-upload-size")}</span>
<Tooltip title={t("setting.system-section.max-upload-size-hint")} placement="top">
<Icon.HelpCircle className="w-4 h-auto" />
</Tooltip>
......@@ -152,6 +160,36 @@ const StorageSection = () => {
onChange={handleMaxUploadSizeChanged}
/>
</div>
{workspaceStorageSetting.storageType === WorkspaceStorageSetting_StorageType.STORAGE_TYPE_LOCAL && (
<div className="w-full flex flex-row justify-between items-center">
<span className="text-gray-700 dark:text-gray-500 mr-1">Local file path template</span>
<Input
defaultValue={workspaceStorageSetting.localStoragePathTemplate}
placeholder="assets/{timestamp}_{filename}"
onChange={handleLocalStoragePathTemplateChanged}
/>
</div>
)}
{workspaceStorageSetting.storageType === WorkspaceStorageSetting_StorageType.STORAGE_TYPE_EXTERNAL && (
<div className="w-full flex flex-row justify-between items-center">
<span className="text-gray-700 dark:text-gray-500 mr-1">Actived storage</span>
<Select
onChange={(_, value) => handleActivedExternalStorageIdChanged(value as number)}
defaultValue={workspaceStorageSetting.activedExternalStorageId}
>
{storageList.map((storage) => (
<Option key={storage.id} value={storage.id}>
{storage.name}
</Option>
))}
</Select>
</div>
)}
<div>
<Button disabled={!allowSaveStorageSetting} onClick={saveWorkspaceStorageSetting}>
{t("common.save")}
</Button>
</div>
<Divider className="!my-2" />
<div className="mb-2 w-full flex flex-row justify-between items-center gap-1">
<div className="flex items-center gap-1">
......
import { Button, IconButton, Input } from "@mui/joy";
import { useState } from "react";
import { toast } from "react-hot-toast";
import { WorkspaceSettingPrefix, useWorkspaceSettingStore } from "@/store/v1";
import { WorkspaceSettingKey, WorkspaceStorageSetting } from "@/types/proto/store/workspace_setting";
import { useTranslate } from "@/utils/i18n";
import { generateDialog } from "./Dialog";
import Icon from "./Icon";
import LearnMore from "./LearnMore";
interface Props extends DialogProps {
confirmCallback?: () => void;
}
const UpdateLocalStorageDialog: React.FC<Props> = (props: Props) => {
const t = useTranslate();
const { destroy, confirmCallback } = props;
const workspaceSettingStore = useWorkspaceSettingStore();
const [workspaceStorageSetting, setWorkspaceStorageSetting] = useState<WorkspaceStorageSetting>(
WorkspaceStorageSetting.fromPartial(
workspaceSettingStore.getWorkspaceSettingByKey(WorkspaceSettingKey.WORKSPACE_SETTING_STORAGE)?.storageSetting || {},
),
);
const handleCloseBtnClick = () => {
destroy();
};
const handleConfirmBtnClick = async () => {
try {
await workspaceSettingStore.setWorkspaceSetting({
name: `${WorkspaceSettingPrefix}${WorkspaceSettingKey.WORKSPACE_SETTING_STORAGE}`,
storageSetting: workspaceStorageSetting,
});
} catch (error: any) {
console.error(error);
toast.error(error.details);
}
if (confirmCallback) {
confirmCallback();
}
destroy();
};
return (
<>
<div className="dialog-header-container">
<p className="title-text">{t("setting.storage-section.update-local-path")}</p>
<IconButton size="sm" onClick={handleCloseBtnClick}>
<Icon.X className="w-5 h-auto" />
</IconButton>
</div>
<div className="dialog-content-container max-w-xs">
<p className="text-sm break-words mb-1">{t("setting.storage-section.update-local-path-description")}</p>
<div className="flex flex-row items-center mb-2 gap-x-2">
<span className="text-sm text-gray-400 break-all">e.g. {"assets/{timestamp}_{filename}"}</span>
<LearnMore url="https://usememos.com/docs/advanced-settings/local-storage" />
</div>
<Input
className="mb-2"
placeholder={t("setting.storage-section.local-storage-path")}
fullWidth
value={workspaceStorageSetting.localStoragePath}
onChange={(e) => setWorkspaceStorageSetting({ ...workspaceStorageSetting, localStoragePath: e.target.value })}
/>
<div className="mt-2 w-full flex flex-row justify-end items-center space-x-1">
<Button variant="plain" color="neutral" onClick={handleCloseBtnClick}>
{t("common.cancel")}
</Button>
<Button onClick={handleConfirmBtnClick}>{t("common.update")}</Button>
</div>
</div>
</>
);
};
function showUpdateLocalStorageDialog(confirmCallback?: () => void) {
generateDialog(
{
className: "update-local-storage-dialog",
dialogName: "update-local-storage-dialog",
},
UpdateLocalStorageDialog,
{ confirmCallback },
);
}
export default showUpdateLocalStorageDialog;
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