Commit 1ea4cc45 authored by Steven's avatar Steven

refactor: workspace setting service

parent da906c66
...@@ -3,6 +3,10 @@ syntax = "proto3"; ...@@ -3,6 +3,10 @@ syntax = "proto3";
package memos.api.v1; package memos.api.v1;
import "google/api/annotations.proto"; import "google/api/annotations.proto";
import "google/api/client.proto";
import "google/api/field_behavior.proto";
import "google/api/resource.proto";
import "google/protobuf/field_mask.proto";
option go_package = "gen/api/v1"; option go_package = "gen/api/v1";
...@@ -11,6 +15,21 @@ service WorkspaceService { ...@@ -11,6 +15,21 @@ service WorkspaceService {
rpc GetWorkspaceProfile(GetWorkspaceProfileRequest) returns (WorkspaceProfile) { rpc GetWorkspaceProfile(GetWorkspaceProfileRequest) returns (WorkspaceProfile) {
option (google.api.http) = {get: "/api/v1/workspace/profile"}; option (google.api.http) = {get: "/api/v1/workspace/profile"};
} }
// Gets a workspace setting.
rpc GetWorkspaceSetting(GetWorkspaceSettingRequest) returns (WorkspaceSetting) {
option (google.api.http) = {get: "/api/v1/{name=workspace/settings/*}"};
option (google.api.method_signature) = "name";
}
// Updates a workspace setting.
rpc UpdateWorkspaceSetting(UpdateWorkspaceSettingRequest) returns (WorkspaceSetting) {
option (google.api.http) = {
patch: "/api/v1/{setting.name=workspace/settings/*}"
body: "setting"
};
option (google.api.method_signature) = "setting,update_mask";
}
} }
// Workspace profile message containing basic workspace information. // Workspace profile message containing basic workspace information.
...@@ -31,3 +50,127 @@ message WorkspaceProfile { ...@@ -31,3 +50,127 @@ message WorkspaceProfile {
// Request for workspace profile. // Request for workspace profile.
message GetWorkspaceProfileRequest {} message GetWorkspaceProfileRequest {}
// A workspace setting resource.
message WorkspaceSetting {
option (google.api.resource) = {
type: "api.memos.dev/WorkspaceSetting"
pattern: "workspace/settings/{setting}"
singular: "workspaceSetting"
plural: "workspaceSettings"
};
// The name of the workspace setting.
// Format: workspace/settings/{setting}
string name = 1 [(google.api.field_behavior) = IDENTIFIER];
oneof value {
WorkspaceGeneralSetting general_setting = 2;
WorkspaceStorageSetting storage_setting = 3;
WorkspaceMemoRelatedSetting memo_related_setting = 4;
}
}
message WorkspaceGeneralSetting {
// disallow_user_registration disallows user registration.
bool disallow_user_registration = 1;
// disallow_password_auth disallows password authentication.
bool disallow_password_auth = 2;
// additional_script is the additional script.
string additional_script = 3;
// additional_style is the additional style.
string additional_style = 4;
// custom_profile is the custom profile.
WorkspaceCustomProfile custom_profile = 5;
// week_start_day_offset is the week start day offset from Sunday.
// 0: Sunday, 1: Monday, 2: Tuesday, 3: Wednesday, 4: Thursday, 5: Friday, 6: Saturday
// Default is Sunday.
int32 week_start_day_offset = 6;
// disallow_change_username disallows changing username.
bool disallow_change_username = 7;
// disallow_change_nickname disallows changing nickname.
bool disallow_change_nickname = 8;
}
message WorkspaceCustomProfile {
string title = 1;
string description = 2;
string logo_url = 3;
string locale = 4;
string appearance = 5;
}
message WorkspaceStorageSetting {
enum StorageType {
STORAGE_TYPE_UNSPECIFIED = 0;
// DATABASE is the database storage type.
DATABASE = 1;
// LOCAL is the local storage type.
LOCAL = 2;
// S3 is the S3 storage type.
S3 = 3;
}
// storage_type is the storage type.
StorageType storage_type = 1;
// The template of file path.
// e.g. assets/{timestamp}_{filename}
string filepath_template = 2;
// The max upload size in megabytes.
int64 upload_size_limit_mb = 3;
// Reference: https://developers.cloudflare.com/r2/examples/aws/aws-sdk-go/
message S3Config {
string access_key_id = 1;
string access_key_secret = 2;
string endpoint = 3;
string region = 4;
string bucket = 5;
bool use_path_style = 6;
}
// The S3 config.
S3Config s3_config = 4;
}
message WorkspaceMemoRelatedSetting {
reserved 4, 8;
// disallow_public_visibility disallows set memo as public visibility.
bool disallow_public_visibility = 1;
// display_with_update_time orders and displays memo with update time.
bool display_with_update_time = 2;
// content_length_limit is the limit of content length. Unit is byte.
int32 content_length_limit = 3;
// enable_double_click_edit enables editing on double click.
bool enable_double_click_edit = 5;
// enable_link_preview enables links preview.
bool enable_link_preview = 6;
// enable_comment enables comment.
bool enable_comment = 7;
// reactions is the list of reactions.
repeated string reactions = 10;
// disable_markdown_shortcuts disallow the registration of markdown shortcuts.
bool disable_markdown_shortcuts = 11;
// enable_blur_nsfw_content enables blurring of content marked as not safe for work (NSFW).
bool enable_blur_nsfw_content = 12;
// nsfw_tags is the list of tags that mark content as NSFW for blurring.
repeated string nsfw_tags = 13;
}
// Request message for GetWorkspaceSetting method.
message GetWorkspaceSettingRequest {
// The resource name of the workspace setting.
// Format: workspace/settings/{setting}
string name = 1 [
(google.api.field_behavior) = REQUIRED,
(google.api.resource_reference) = {type: "api.memos.dev/WorkspaceSetting"}
];
}
// Request message for UpdateWorkspaceSetting method.
message UpdateWorkspaceSettingRequest {
// The workspace setting resource which replaces the resource on the server.
WorkspaceSetting setting = 1 [(google.api.field_behavior) = REQUIRED];
// The list of fields to update.
google.protobuf.FieldMask update_mask = 2 [(google.api.field_behavior) = OPTIONAL];
}
syntax = "proto3";
package memos.api.v1;
import "google/api/annotations.proto";
import "google/api/client.proto";
import "google/api/field_behavior.proto";
option go_package = "gen/api/v1";
service WorkspaceSettingService {
// GetWorkspaceSetting returns the setting by name.
rpc GetWorkspaceSetting(GetWorkspaceSettingRequest) returns (WorkspaceSetting) {
option (google.api.http) = {get: "/api/v1/workspace/{name=settings/*}"};
option (google.api.method_signature) = "name";
}
// SetWorkspaceSetting updates the setting.
rpc SetWorkspaceSetting(SetWorkspaceSettingRequest) returns (WorkspaceSetting) {
option (google.api.http) = {
patch: "/api/v1/workspace/{setting.name=settings/*}"
body: "setting"
};
option (google.api.method_signature) = "setting";
}
}
message WorkspaceSetting {
// name is the name of the setting.
// Format: settings/{setting}
string name = 1;
oneof value {
WorkspaceGeneralSetting general_setting = 2;
WorkspaceStorageSetting storage_setting = 3;
WorkspaceMemoRelatedSetting memo_related_setting = 4;
}
}
message WorkspaceGeneralSetting {
// disallow_user_registration disallows user registration.
bool disallow_user_registration = 1;
// disallow_password_auth disallows password authentication.
bool disallow_password_auth = 2;
// additional_script is the additional script.
string additional_script = 3;
// additional_style is the additional style.
string additional_style = 4;
// custom_profile is the custom profile.
WorkspaceCustomProfile custom_profile = 5;
// week_start_day_offset is the week start day offset from Sunday.
// 0: Sunday, 1: Monday, 2: Tuesday, 3: Wednesday, 4: Thursday, 5: Friday, 6: Saturday
// Default is Sunday.
int32 week_start_day_offset = 6;
// disallow_change_username disallows changing username.
bool disallow_change_username = 7;
// disallow_change_nickname disallows changing nickname.
bool disallow_change_nickname = 8;
}
message WorkspaceCustomProfile {
string title = 1;
string description = 2;
string logo_url = 3;
string locale = 4;
string appearance = 5;
}
message WorkspaceStorageSetting {
enum StorageType {
STORAGE_TYPE_UNSPECIFIED = 0;
// DATABASE is the database storage type.
DATABASE = 1;
// LOCAL is the local storage type.
LOCAL = 2;
// S3 is the S3 storage type.
S3 = 3;
}
// storage_type is the storage type.
StorageType storage_type = 1;
// The template of file path.
// e.g. assets/{timestamp}_{filename}
string filepath_template = 2;
// The max upload size in megabytes.
int64 upload_size_limit_mb = 3;
// Reference: https://developers.cloudflare.com/r2/examples/aws/aws-sdk-go/
message S3Config {
string access_key_id = 1;
string access_key_secret = 2;
string endpoint = 3;
string region = 4;
string bucket = 5;
bool use_path_style = 6;
}
// The S3 config.
S3Config s3_config = 4;
}
message WorkspaceMemoRelatedSetting {
reserved 4, 8;
// disallow_public_visibility disallows set memo as public visibility.
bool disallow_public_visibility = 1;
// display_with_update_time orders and displays memo with update time.
bool display_with_update_time = 2;
// content_length_limit is the limit of content length. Unit is byte.
int32 content_length_limit = 3;
// enable_double_click_edit enables editing on double click.
bool enable_double_click_edit = 5;
// enable_link_preview enables links preview.
bool enable_link_preview = 6;
// enable_comment enables comment.
bool enable_comment = 7;
// reactions is the list of reactions.
repeated string reactions = 10;
// disable_markdown_shortcuts disallow the registration of markdown shortcuts.
bool disable_markdown_shortcuts = 11;
// enable_blur_nsfw_content enables blurring of content marked as not safe for work (NSFW).
bool enable_blur_nsfw_content = 12;
// nsfw_tags is the list of tags that mark content as NSFW for blurring.
repeated string nsfw_tags = 13;
}
message GetWorkspaceSettingRequest {
// The resource name of the workspace setting.
// Format: settings/{setting}
string name = 1 [(google.api.field_behavior) = REQUIRED];
}
message SetWorkspaceSettingRequest {
// setting is the setting to update.
WorkspaceSetting setting = 1;
}
This diff is collapsed.
This diff is collapsed.
...@@ -19,7 +19,9 @@ import ( ...@@ -19,7 +19,9 @@ import (
const _ = grpc.SupportPackageIsVersion9 const _ = grpc.SupportPackageIsVersion9
const ( const (
WorkspaceService_GetWorkspaceProfile_FullMethodName = "/memos.api.v1.WorkspaceService/GetWorkspaceProfile" WorkspaceService_GetWorkspaceProfile_FullMethodName = "/memos.api.v1.WorkspaceService/GetWorkspaceProfile"
WorkspaceService_GetWorkspaceSetting_FullMethodName = "/memos.api.v1.WorkspaceService/GetWorkspaceSetting"
WorkspaceService_UpdateWorkspaceSetting_FullMethodName = "/memos.api.v1.WorkspaceService/UpdateWorkspaceSetting"
) )
// WorkspaceServiceClient is the client API for WorkspaceService service. // WorkspaceServiceClient is the client API for WorkspaceService service.
...@@ -28,6 +30,10 @@ const ( ...@@ -28,6 +30,10 @@ const (
type WorkspaceServiceClient interface { type WorkspaceServiceClient interface {
// Gets the workspace profile. // Gets the workspace profile.
GetWorkspaceProfile(ctx context.Context, in *GetWorkspaceProfileRequest, opts ...grpc.CallOption) (*WorkspaceProfile, error) GetWorkspaceProfile(ctx context.Context, in *GetWorkspaceProfileRequest, opts ...grpc.CallOption) (*WorkspaceProfile, error)
// Gets a workspace setting.
GetWorkspaceSetting(ctx context.Context, in *GetWorkspaceSettingRequest, opts ...grpc.CallOption) (*WorkspaceSetting, error)
// Updates a workspace setting.
UpdateWorkspaceSetting(ctx context.Context, in *UpdateWorkspaceSettingRequest, opts ...grpc.CallOption) (*WorkspaceSetting, error)
} }
type workspaceServiceClient struct { type workspaceServiceClient struct {
...@@ -48,12 +54,36 @@ func (c *workspaceServiceClient) GetWorkspaceProfile(ctx context.Context, in *Ge ...@@ -48,12 +54,36 @@ func (c *workspaceServiceClient) GetWorkspaceProfile(ctx context.Context, in *Ge
return out, nil return out, nil
} }
func (c *workspaceServiceClient) GetWorkspaceSetting(ctx context.Context, in *GetWorkspaceSettingRequest, opts ...grpc.CallOption) (*WorkspaceSetting, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(WorkspaceSetting)
err := c.cc.Invoke(ctx, WorkspaceService_GetWorkspaceSetting_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *workspaceServiceClient) UpdateWorkspaceSetting(ctx context.Context, in *UpdateWorkspaceSettingRequest, opts ...grpc.CallOption) (*WorkspaceSetting, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(WorkspaceSetting)
err := c.cc.Invoke(ctx, WorkspaceService_UpdateWorkspaceSetting_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// WorkspaceServiceServer is the server API for WorkspaceService service. // WorkspaceServiceServer is the server API for WorkspaceService service.
// All implementations must embed UnimplementedWorkspaceServiceServer // All implementations must embed UnimplementedWorkspaceServiceServer
// for forward compatibility. // for forward compatibility.
type WorkspaceServiceServer interface { type WorkspaceServiceServer interface {
// Gets the workspace profile. // Gets the workspace profile.
GetWorkspaceProfile(context.Context, *GetWorkspaceProfileRequest) (*WorkspaceProfile, error) GetWorkspaceProfile(context.Context, *GetWorkspaceProfileRequest) (*WorkspaceProfile, error)
// Gets a workspace setting.
GetWorkspaceSetting(context.Context, *GetWorkspaceSettingRequest) (*WorkspaceSetting, error)
// Updates a workspace setting.
UpdateWorkspaceSetting(context.Context, *UpdateWorkspaceSettingRequest) (*WorkspaceSetting, error)
mustEmbedUnimplementedWorkspaceServiceServer() mustEmbedUnimplementedWorkspaceServiceServer()
} }
...@@ -67,6 +97,12 @@ type UnimplementedWorkspaceServiceServer struct{} ...@@ -67,6 +97,12 @@ type UnimplementedWorkspaceServiceServer struct{}
func (UnimplementedWorkspaceServiceServer) GetWorkspaceProfile(context.Context, *GetWorkspaceProfileRequest) (*WorkspaceProfile, error) { func (UnimplementedWorkspaceServiceServer) GetWorkspaceProfile(context.Context, *GetWorkspaceProfileRequest) (*WorkspaceProfile, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetWorkspaceProfile not implemented") return nil, status.Errorf(codes.Unimplemented, "method GetWorkspaceProfile not implemented")
} }
func (UnimplementedWorkspaceServiceServer) GetWorkspaceSetting(context.Context, *GetWorkspaceSettingRequest) (*WorkspaceSetting, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetWorkspaceSetting not implemented")
}
func (UnimplementedWorkspaceServiceServer) UpdateWorkspaceSetting(context.Context, *UpdateWorkspaceSettingRequest) (*WorkspaceSetting, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateWorkspaceSetting not implemented")
}
func (UnimplementedWorkspaceServiceServer) mustEmbedUnimplementedWorkspaceServiceServer() {} func (UnimplementedWorkspaceServiceServer) mustEmbedUnimplementedWorkspaceServiceServer() {}
func (UnimplementedWorkspaceServiceServer) testEmbeddedByValue() {} func (UnimplementedWorkspaceServiceServer) testEmbeddedByValue() {}
...@@ -106,6 +142,42 @@ func _WorkspaceService_GetWorkspaceProfile_Handler(srv interface{}, ctx context. ...@@ -106,6 +142,42 @@ func _WorkspaceService_GetWorkspaceProfile_Handler(srv interface{}, ctx context.
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _WorkspaceService_GetWorkspaceSetting_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetWorkspaceSettingRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WorkspaceServiceServer).GetWorkspaceSetting(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: WorkspaceService_GetWorkspaceSetting_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WorkspaceServiceServer).GetWorkspaceSetting(ctx, req.(*GetWorkspaceSettingRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WorkspaceService_UpdateWorkspaceSetting_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UpdateWorkspaceSettingRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WorkspaceServiceServer).UpdateWorkspaceSetting(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: WorkspaceService_UpdateWorkspaceSetting_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WorkspaceServiceServer).UpdateWorkspaceSetting(ctx, req.(*UpdateWorkspaceSettingRequest))
}
return interceptor(ctx, in, info, handler)
}
// WorkspaceService_ServiceDesc is the grpc.ServiceDesc for WorkspaceService service. // WorkspaceService_ServiceDesc is the grpc.ServiceDesc for WorkspaceService service.
// It's only intended for direct use with grpc.RegisterService, // It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy) // and not to be introspected or modified (even as a copy)
...@@ -117,6 +189,14 @@ var WorkspaceService_ServiceDesc = grpc.ServiceDesc{ ...@@ -117,6 +189,14 @@ var WorkspaceService_ServiceDesc = grpc.ServiceDesc{
MethodName: "GetWorkspaceProfile", MethodName: "GetWorkspaceProfile",
Handler: _WorkspaceService_GetWorkspaceProfile_Handler, Handler: _WorkspaceService_GetWorkspaceProfile_Handler,
}, },
{
MethodName: "GetWorkspaceSetting",
Handler: _WorkspaceService_GetWorkspaceSetting_Handler,
},
{
MethodName: "UpdateWorkspaceSetting",
Handler: _WorkspaceService_UpdateWorkspaceSetting_Handler,
},
}, },
Streams: []grpc.StreamDesc{}, Streams: []grpc.StreamDesc{},
Metadata: "api/v1/workspace_service.proto", Metadata: "api/v1/workspace_service.proto",
......
This diff is collapsed.
This diff is collapsed.
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc (unknown)
// source: api/v1/workspace_setting_service.proto
package apiv1
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
WorkspaceSettingService_GetWorkspaceSetting_FullMethodName = "/memos.api.v1.WorkspaceSettingService/GetWorkspaceSetting"
WorkspaceSettingService_SetWorkspaceSetting_FullMethodName = "/memos.api.v1.WorkspaceSettingService/SetWorkspaceSetting"
)
// WorkspaceSettingServiceClient is the client API for WorkspaceSettingService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type WorkspaceSettingServiceClient interface {
// GetWorkspaceSetting returns the setting by name.
GetWorkspaceSetting(ctx context.Context, in *GetWorkspaceSettingRequest, opts ...grpc.CallOption) (*WorkspaceSetting, error)
// SetWorkspaceSetting updates the setting.
SetWorkspaceSetting(ctx context.Context, in *SetWorkspaceSettingRequest, opts ...grpc.CallOption) (*WorkspaceSetting, error)
}
type workspaceSettingServiceClient struct {
cc grpc.ClientConnInterface
}
func NewWorkspaceSettingServiceClient(cc grpc.ClientConnInterface) WorkspaceSettingServiceClient {
return &workspaceSettingServiceClient{cc}
}
func (c *workspaceSettingServiceClient) GetWorkspaceSetting(ctx context.Context, in *GetWorkspaceSettingRequest, opts ...grpc.CallOption) (*WorkspaceSetting, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(WorkspaceSetting)
err := c.cc.Invoke(ctx, WorkspaceSettingService_GetWorkspaceSetting_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *workspaceSettingServiceClient) SetWorkspaceSetting(ctx context.Context, in *SetWorkspaceSettingRequest, opts ...grpc.CallOption) (*WorkspaceSetting, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(WorkspaceSetting)
err := c.cc.Invoke(ctx, WorkspaceSettingService_SetWorkspaceSetting_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// WorkspaceSettingServiceServer is the server API for WorkspaceSettingService service.
// All implementations must embed UnimplementedWorkspaceSettingServiceServer
// for forward compatibility.
type WorkspaceSettingServiceServer interface {
// GetWorkspaceSetting returns the setting by name.
GetWorkspaceSetting(context.Context, *GetWorkspaceSettingRequest) (*WorkspaceSetting, error)
// SetWorkspaceSetting updates the setting.
SetWorkspaceSetting(context.Context, *SetWorkspaceSettingRequest) (*WorkspaceSetting, error)
mustEmbedUnimplementedWorkspaceSettingServiceServer()
}
// UnimplementedWorkspaceSettingServiceServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedWorkspaceSettingServiceServer struct{}
func (UnimplementedWorkspaceSettingServiceServer) GetWorkspaceSetting(context.Context, *GetWorkspaceSettingRequest) (*WorkspaceSetting, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetWorkspaceSetting not implemented")
}
func (UnimplementedWorkspaceSettingServiceServer) SetWorkspaceSetting(context.Context, *SetWorkspaceSettingRequest) (*WorkspaceSetting, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetWorkspaceSetting not implemented")
}
func (UnimplementedWorkspaceSettingServiceServer) mustEmbedUnimplementedWorkspaceSettingServiceServer() {
}
func (UnimplementedWorkspaceSettingServiceServer) testEmbeddedByValue() {}
// UnsafeWorkspaceSettingServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to WorkspaceSettingServiceServer will
// result in compilation errors.
type UnsafeWorkspaceSettingServiceServer interface {
mustEmbedUnimplementedWorkspaceSettingServiceServer()
}
func RegisterWorkspaceSettingServiceServer(s grpc.ServiceRegistrar, srv WorkspaceSettingServiceServer) {
// If the following call pancis, it indicates UnimplementedWorkspaceSettingServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&WorkspaceSettingService_ServiceDesc, srv)
}
func _WorkspaceSettingService_GetWorkspaceSetting_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetWorkspaceSettingRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WorkspaceSettingServiceServer).GetWorkspaceSetting(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: WorkspaceSettingService_GetWorkspaceSetting_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WorkspaceSettingServiceServer).GetWorkspaceSetting(ctx, req.(*GetWorkspaceSettingRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WorkspaceSettingService_SetWorkspaceSetting_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SetWorkspaceSettingRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WorkspaceSettingServiceServer).SetWorkspaceSetting(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: WorkspaceSettingService_SetWorkspaceSetting_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WorkspaceSettingServiceServer).SetWorkspaceSetting(ctx, req.(*SetWorkspaceSettingRequest))
}
return interceptor(ctx, in, info, handler)
}
// WorkspaceSettingService_ServiceDesc is the grpc.ServiceDesc for WorkspaceSettingService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var WorkspaceSettingService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "memos.api.v1.WorkspaceSettingService",
HandlerType: (*WorkspaceSettingServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "GetWorkspaceSetting",
Handler: _WorkspaceSettingService_GetWorkspaceSetting_Handler,
},
{
MethodName: "SetWorkspaceSetting",
Handler: _WorkspaceSettingService_SetWorkspaceSetting_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "api/v1/workspace_setting_service.proto",
}
...@@ -14,7 +14,6 @@ tags: ...@@ -14,7 +14,6 @@ tags:
- name: ShortcutService - name: ShortcutService
- name: WebhookService - name: WebhookService
- name: WorkspaceService - name: WorkspaceService
- name: WorkspaceSettingService
consumes: consumes:
- application/json - application/json
produces: produces:
...@@ -707,68 +706,6 @@ paths: ...@@ -707,68 +706,6 @@ paths:
$ref: '#/definitions/googlerpcStatus' $ref: '#/definitions/googlerpcStatus'
tags: tags:
- WorkspaceService - WorkspaceService
/api/v1/workspace/{name}:
get:
summary: GetWorkspaceSetting returns the setting by name.
operationId: WorkspaceSettingService_GetWorkspaceSetting
responses:
"200":
description: A successful response.
schema:
$ref: '#/definitions/apiv1WorkspaceSetting'
default:
description: An unexpected error response.
schema:
$ref: '#/definitions/googlerpcStatus'
parameters:
- name: name
description: |-
The resource name of the workspace setting.
Format: settings/{setting}
in: path
required: true
type: string
pattern: settings/[^/]+
tags:
- WorkspaceSettingService
/api/v1/workspace/{setting.name}:
patch:
summary: SetWorkspaceSetting updates the setting.
operationId: WorkspaceSettingService_SetWorkspaceSetting
responses:
"200":
description: A successful response.
schema:
$ref: '#/definitions/apiv1WorkspaceSetting'
default:
description: An unexpected error response.
schema:
$ref: '#/definitions/googlerpcStatus'
parameters:
- name: setting.name
description: |-
name is the name of the setting.
Format: settings/{setting}
in: path
required: true
type: string
pattern: settings/[^/]+
- name: setting
description: setting is the setting to update.
in: body
required: true
schema:
type: object
properties:
generalSetting:
$ref: '#/definitions/apiv1WorkspaceGeneralSetting'
storageSetting:
$ref: '#/definitions/apiv1WorkspaceStorageSetting'
memoRelatedSetting:
$ref: '#/definitions/apiv1WorkspaceMemoRelatedSetting'
title: setting is the setting to update.
tags:
- WorkspaceSettingService
/api/v1/{identityProvider.name}: /api/v1/{identityProvider.name}:
patch: patch:
summary: UpdateIdentityProvider updates an identity provider. summary: UpdateIdentityProvider updates an identity provider.
...@@ -1199,6 +1136,29 @@ paths: ...@@ -1199,6 +1136,29 @@ paths:
tags: tags:
- MemoService - MemoService
/api/v1/{name_6}: /api/v1/{name_6}:
get:
summary: Gets a workspace setting.
operationId: WorkspaceService_GetWorkspaceSetting
responses:
"200":
description: A successful response.
schema:
$ref: '#/definitions/apiv1WorkspaceSetting'
default:
description: An unexpected error response.
schema:
$ref: '#/definitions/googlerpcStatus'
parameters:
- name: name_6
description: |-
The resource name of the workspace setting.
Format: workspace/settings/{setting}
in: path
required: true
type: string
pattern: workspace/settings/[^/]+
tags:
- WorkspaceService
delete: delete:
summary: DeleteWebhook deletes a webhook. summary: DeleteWebhook deletes a webhook.
operationId: WebhookService_DeleteWebhook operationId: WebhookService_DeleteWebhook
...@@ -1929,6 +1889,46 @@ paths: ...@@ -1929,6 +1889,46 @@ paths:
description: The related memo. Refer to `Memo.name`. description: The related memo. Refer to `Memo.name`.
tags: tags:
- ResourceService - ResourceService
/api/v1/{setting.name}:
patch:
summary: Updates a workspace setting.
operationId: WorkspaceService_UpdateWorkspaceSetting
responses:
"200":
description: A successful response.
schema:
$ref: '#/definitions/apiv1WorkspaceSetting'
default:
description: An unexpected error response.
schema:
$ref: '#/definitions/googlerpcStatus'
parameters:
- name: setting.name
description: |-
The name of the workspace setting.
Format: workspace/settings/{setting}
in: path
required: true
type: string
pattern: workspace/settings/[^/]+
- name: setting
description: The workspace setting resource which replaces the resource on the server.
in: body
required: true
schema:
type: object
properties:
generalSetting:
$ref: '#/definitions/apiv1WorkspaceGeneralSetting'
storageSetting:
$ref: '#/definitions/apiv1WorkspaceStorageSetting'
memoRelatedSetting:
$ref: '#/definitions/apiv1WorkspaceMemoRelatedSetting'
title: The workspace setting resource which replaces the resource on the server.
required:
- setting
tags:
- WorkspaceService
/api/v1/{setting.name}:updateSetting: /api/v1/{setting.name}:updateSetting:
patch: patch:
summary: UpdateUserSetting updates the user setting. summary: UpdateUserSetting updates the user setting.
...@@ -2591,14 +2591,15 @@ definitions: ...@@ -2591,14 +2591,15 @@ definitions:
name: name:
type: string type: string
title: |- title: |-
name is the name of the setting. The name of the workspace setting.
Format: settings/{setting} Format: workspace/settings/{setting}
generalSetting: generalSetting:
$ref: '#/definitions/apiv1WorkspaceGeneralSetting' $ref: '#/definitions/apiv1WorkspaceGeneralSetting'
storageSetting: storageSetting:
$ref: '#/definitions/apiv1WorkspaceStorageSetting' $ref: '#/definitions/apiv1WorkspaceStorageSetting'
memoRelatedSetting: memoRelatedSetting:
$ref: '#/definitions/apiv1WorkspaceMemoRelatedSetting' $ref: '#/definitions/apiv1WorkspaceMemoRelatedSetting'
description: A workspace setting resource.
apiv1WorkspaceStorageSetting: apiv1WorkspaceStorageSetting:
type: object type: object
properties: properties:
......
...@@ -2,8 +2,7 @@ package v1 ...@@ -2,8 +2,7 @@ package v1
var authenticationAllowlistMethods = map[string]bool{ var authenticationAllowlistMethods = map[string]bool{
"/memos.api.v1.WorkspaceService/GetWorkspaceProfile": true, "/memos.api.v1.WorkspaceService/GetWorkspaceProfile": true,
"/memos.api.v1.WorkspaceSettingService/GetWorkspaceSetting": true, "/memos.api.v1.WorkspaceService/GetWorkspaceSetting": true,
"/memos.api.v1.WorkspaceSettingService/ListWorkspaceSettings": true,
"/memos.api.v1.IdentityProviderService/GetIdentityProvider": true, "/memos.api.v1.IdentityProviderService/GetIdentityProvider": true,
"/memos.api.v1.IdentityProviderService/ListIdentityProviders": true, "/memos.api.v1.IdentityProviderService/ListIdentityProviders": true,
"/memos.api.v1.AuthService/GetAuthStatus": true, "/memos.api.v1.AuthService/GetAuthStatus": true,
...@@ -28,8 +27,8 @@ func isUnauthorizeAllowedMethod(fullMethodName string) bool { ...@@ -28,8 +27,8 @@ func isUnauthorizeAllowedMethod(fullMethodName string) bool {
} }
var allowedMethodsOnlyForAdmin = map[string]bool{ var allowedMethodsOnlyForAdmin = map[string]bool{
"/memos.api.v1.UserService/CreateUser": true, "/memos.api.v1.UserService/CreateUser": true,
"/memos.api.v1.WorkspaceSettingService/SetWorkspaceSetting": true, "/memos.api.v1.WorkspaceService/UpdateWorkspaceSetting": true,
} }
// isOnlyForAdminAllowedMethod returns true if the method is allowed to be called only by admin. // isOnlyForAdminAllowedMethod returns true if the method is allowed to be called only by admin.
......
...@@ -10,7 +10,7 @@ import ( ...@@ -10,7 +10,7 @@ import (
) )
const ( const (
WorkspaceSettingNamePrefix = "settings/" WorkspaceSettingNamePrefix = "workspace/settings/"
UserNamePrefix = "users/" UserNamePrefix = "users/"
MemoNamePrefix = "memos/" MemoNamePrefix = "memos/"
ResourceNamePrefix = "resources/" ResourceNamePrefix = "resources/"
...@@ -41,11 +41,22 @@ func GetNameParentTokens(name string, tokenPrefixes ...string) ([]string, error) ...@@ -41,11 +41,22 @@ func GetNameParentTokens(name string, tokenPrefixes ...string) ([]string, error)
} }
func ExtractWorkspaceSettingKeyFromName(name string) (string, error) { func ExtractWorkspaceSettingKeyFromName(name string) (string, error) {
tokens, err := GetNameParentTokens(name, WorkspaceSettingNamePrefix) const prefix = "workspace/settings/"
if err != nil { if !strings.HasPrefix(name, prefix) {
return "", err return "", errors.Errorf("invalid workspace setting name: expected prefix %q, got %q", prefix, name)
}
settingKey := strings.TrimPrefix(name, prefix)
if settingKey == "" {
return "", errors.Errorf("invalid workspace setting name: empty setting key in %q", name)
} }
return tokens[0], nil
// Ensure there are no additional path segments
if strings.Contains(settingKey, "/") {
return "", errors.Errorf("invalid workspace setting name: setting key cannot contain '/' in %q", name)
}
return settingKey, nil
} }
// ExtractUserIDFromName returns the uid from a resource name. // ExtractUserIDFromName returns the uid from a resource name.
......
...@@ -341,7 +341,7 @@ func (s *APIV1Service) GetUserSetting(ctx context.Context, request *v1pb.GetUser ...@@ -341,7 +341,7 @@ func (s *APIV1Service) GetUserSetting(ctx context.Context, request *v1pb.GetUser
} }
userSettingMessage := getDefaultUserSetting() userSettingMessage := getDefaultUserSetting()
userSettingMessage.Name = fmt.Sprintf("users/%d/setting", userID) userSettingMessage.Name = fmt.Sprintf("users/%d", userID)
for _, setting := range userSettings { for _, setting := range userSettings {
if setting.Key == storepb.UserSettingKey_LOCALE { if setting.Key == storepb.UserSettingKey_LOCALE {
...@@ -357,12 +357,7 @@ func (s *APIV1Service) GetUserSetting(ctx context.Context, request *v1pb.GetUser ...@@ -357,12 +357,7 @@ func (s *APIV1Service) GetUserSetting(ctx context.Context, request *v1pb.GetUser
func (s *APIV1Service) UpdateUserSetting(ctx context.Context, request *v1pb.UpdateUserSettingRequest) (*v1pb.UserSetting, error) { func (s *APIV1Service) UpdateUserSetting(ctx context.Context, request *v1pb.UpdateUserSettingRequest) (*v1pb.UserSetting, error) {
// Extract user ID from the setting resource name // Extract user ID from the setting resource name
parts := strings.Split(request.Setting.Name, "/") userID, err := ExtractUserIDFromName(request.Setting.Name)
if len(parts) != 3 || parts[0] != "users" || parts[2] != "setting" {
return nil, status.Errorf(codes.InvalidArgument, "invalid setting name format: %s", request.Setting.Name)
}
userID, err := ExtractUserIDFromName(fmt.Sprintf("users/%s", parts[1]))
if err != nil { if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err) return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err)
} }
......
...@@ -23,7 +23,6 @@ type APIV1Service struct { ...@@ -23,7 +23,6 @@ type APIV1Service struct {
grpc_health_v1.UnimplementedHealthServer grpc_health_v1.UnimplementedHealthServer
v1pb.UnimplementedWorkspaceServiceServer v1pb.UnimplementedWorkspaceServiceServer
v1pb.UnimplementedWorkspaceSettingServiceServer
v1pb.UnimplementedAuthServiceServer v1pb.UnimplementedAuthServiceServer
v1pb.UnimplementedUserServiceServer v1pb.UnimplementedUserServiceServer
v1pb.UnimplementedMemoServiceServer v1pb.UnimplementedMemoServiceServer
...@@ -52,7 +51,6 @@ func NewAPIV1Service(secret string, profile *profile.Profile, store *store.Store ...@@ -52,7 +51,6 @@ func NewAPIV1Service(secret string, profile *profile.Profile, store *store.Store
} }
grpc_health_v1.RegisterHealthServer(grpcServer, apiv1Service) grpc_health_v1.RegisterHealthServer(grpcServer, apiv1Service)
v1pb.RegisterWorkspaceServiceServer(grpcServer, apiv1Service) v1pb.RegisterWorkspaceServiceServer(grpcServer, apiv1Service)
v1pb.RegisterWorkspaceSettingServiceServer(grpcServer, apiv1Service)
v1pb.RegisterAuthServiceServer(grpcServer, apiv1Service) v1pb.RegisterAuthServiceServer(grpcServer, apiv1Service)
v1pb.RegisterUserServiceServer(grpcServer, apiv1Service) v1pb.RegisterUserServiceServer(grpcServer, apiv1Service)
v1pb.RegisterMemoServiceServer(grpcServer, apiv1Service) v1pb.RegisterMemoServiceServer(grpcServer, apiv1Service)
...@@ -88,9 +86,6 @@ func (s *APIV1Service) RegisterGateway(ctx context.Context, echoServer *echo.Ech ...@@ -88,9 +86,6 @@ func (s *APIV1Service) RegisterGateway(ctx context.Context, echoServer *echo.Ech
if err := v1pb.RegisterWorkspaceServiceHandler(ctx, gwMux, conn); err != nil { if err := v1pb.RegisterWorkspaceServiceHandler(ctx, gwMux, conn); err != nil {
return err return err
} }
if err := v1pb.RegisterWorkspaceSettingServiceHandler(ctx, gwMux, conn); err != nil {
return err
}
if err := v1pb.RegisterAuthServiceHandler(ctx, gwMux, conn); err != nil { if err := v1pb.RegisterAuthServiceHandler(ctx, gwMux, conn); err != nil {
return err return err
} }
......
This diff is collapsed.
This diff is collapsed.
...@@ -8,7 +8,7 @@ import { toast } from "react-hot-toast"; ...@@ -8,7 +8,7 @@ import { toast } from "react-hot-toast";
import { workspaceSettingNamePrefix } from "@/store/common"; import { workspaceSettingNamePrefix } from "@/store/common";
import { workspaceStore } from "@/store/v2"; import { workspaceStore } from "@/store/v2";
import { WorkspaceSettingKey } from "@/store/v2/workspace"; import { WorkspaceSettingKey } from "@/store/v2/workspace";
import { WorkspaceMemoRelatedSetting } from "@/types/proto/api/v1/workspace_setting_service"; import { WorkspaceMemoRelatedSetting } from "@/types/proto/api/v1/workspace_service";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
const MemoRelatedSettings = observer(() => { const MemoRelatedSettings = observer(() => {
......
...@@ -13,7 +13,7 @@ import { ...@@ -13,7 +13,7 @@ import {
WorkspaceStorageSetting, WorkspaceStorageSetting,
WorkspaceStorageSetting_S3Config, WorkspaceStorageSetting_S3Config,
WorkspaceStorageSetting_StorageType, WorkspaceStorageSetting_StorageType,
} from "@/types/proto/api/v1/workspace_setting_service"; } from "@/types/proto/api/v1/workspace_service";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
const StorageSection = observer(() => { const StorageSection = observer(() => {
......
...@@ -11,7 +11,7 @@ import { workspaceSettingNamePrefix } from "@/store/common"; ...@@ -11,7 +11,7 @@ import { workspaceSettingNamePrefix } from "@/store/common";
import { workspaceStore } from "@/store/v2"; import { workspaceStore } from "@/store/v2";
import { WorkspaceSettingKey } from "@/store/v2/workspace"; import { WorkspaceSettingKey } from "@/store/v2/workspace";
import { IdentityProvider } from "@/types/proto/api/v1/idp_service"; import { IdentityProvider } from "@/types/proto/api/v1/idp_service";
import { WorkspaceGeneralSetting } from "@/types/proto/api/v1/workspace_setting_service"; import { WorkspaceGeneralSetting } from "@/types/proto/api/v1/workspace_service";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import showUpdateCustomizedProfileDialog from "../UpdateCustomizedProfileDialog"; import showUpdateCustomizedProfileDialog from "../UpdateCustomizedProfileDialog";
......
...@@ -5,7 +5,7 @@ import { toast } from "react-hot-toast"; ...@@ -5,7 +5,7 @@ import { toast } from "react-hot-toast";
import { workspaceSettingNamePrefix } from "@/store/common"; import { workspaceSettingNamePrefix } from "@/store/common";
import { workspaceStore } from "@/store/v2"; import { workspaceStore } from "@/store/v2";
import { WorkspaceSettingKey } from "@/store/v2/workspace"; import { WorkspaceSettingKey } from "@/store/v2/workspace";
import { WorkspaceCustomProfile } from "@/types/proto/api/v1/workspace_setting_service"; import { WorkspaceCustomProfile } from "@/types/proto/api/v1/workspace_service";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import AppearanceSelect from "./AppearanceSelect"; import AppearanceSelect from "./AppearanceSelect";
import { generateDialog } from "./Dialog"; import { generateDialog } from "./Dialog";
......
...@@ -10,7 +10,6 @@ import { ShortcutServiceDefinition } from "./types/proto/api/v1/shortcut_service ...@@ -10,7 +10,6 @@ import { ShortcutServiceDefinition } from "./types/proto/api/v1/shortcut_service
import { UserServiceDefinition } from "./types/proto/api/v1/user_service"; import { UserServiceDefinition } from "./types/proto/api/v1/user_service";
import { WebhookServiceDefinition } from "./types/proto/api/v1/webhook_service"; import { WebhookServiceDefinition } from "./types/proto/api/v1/webhook_service";
import { WorkspaceServiceDefinition } from "./types/proto/api/v1/workspace_service"; import { WorkspaceServiceDefinition } from "./types/proto/api/v1/workspace_service";
import { WorkspaceSettingServiceDefinition } from "./types/proto/api/v1/workspace_setting_service";
const channel = createChannel( const channel = createChannel(
window.location.origin, window.location.origin,
...@@ -23,8 +22,6 @@ const clientFactory = createClientFactory(); ...@@ -23,8 +22,6 @@ const clientFactory = createClientFactory();
export const workspaceServiceClient = clientFactory.create(WorkspaceServiceDefinition, channel); export const workspaceServiceClient = clientFactory.create(WorkspaceServiceDefinition, channel);
export const workspaceSettingServiceClient = clientFactory.create(WorkspaceSettingServiceDefinition, channel);
export const authServiceClient = clientFactory.create(AuthServiceDefinition, channel); export const authServiceClient = clientFactory.create(AuthServiceDefinition, channel);
export const userServiceClient = clientFactory.create(UserServiceDefinition, channel); export const userServiceClient = clientFactory.create(UserServiceDefinition, channel);
......
export const workspaceSettingNamePrefix = "settings/"; export const workspaceSettingNamePrefix = "workspace/settings/";
export const userNamePrefix = "users/"; export const userNamePrefix = "users/";
export const memoNamePrefix = "memos/"; export const memoNamePrefix = "memos/";
export const identityProviderNamePrefix = "identityProviders/"; export const identityProviderNamePrefix = "identityProviders/";
......
...@@ -134,7 +134,7 @@ const userStore = (() => { ...@@ -134,7 +134,7 @@ const userStore = (() => {
// Ensure the setting has the proper resource name // Ensure the setting has the proper resource name
const settingWithName = { const settingWithName = {
...userSetting, ...userSetting,
name: `${state.currentUser}/setting`, name: state.currentUser,
}; };
const updatedUserSetting = await userServiceClient.updateUserSetting({ const updatedUserSetting = await userServiceClient.updateUserSetting({
setting: settingWithName, setting: settingWithName,
......
import { uniqBy } from "lodash-es"; import { uniqBy } from "lodash-es";
import { makeAutoObservable } from "mobx"; import { makeAutoObservable } from "mobx";
import { workspaceServiceClient, workspaceSettingServiceClient } from "@/grpcweb"; import { workspaceServiceClient } from "@/grpcweb";
import { WorkspaceProfile } from "@/types/proto/api/v1/workspace_service"; import { WorkspaceProfile } from "@/types/proto/api/v1/workspace_service";
import { WorkspaceGeneralSetting, WorkspaceMemoRelatedSetting, WorkspaceSetting } from "@/types/proto/api/v1/workspace_setting_service"; import { WorkspaceGeneralSetting, WorkspaceMemoRelatedSetting, WorkspaceSetting } from "@/types/proto/api/v1/workspace_service";
import { isValidateLocale } from "@/utils/i18n"; import { isValidateLocale } from "@/utils/i18n";
import { workspaceSettingNamePrefix } from "../common"; import { workspaceSettingNamePrefix } from "../common";
...@@ -60,14 +60,14 @@ const workspaceStore = (() => { ...@@ -60,14 +60,14 @@ const workspaceStore = (() => {
const state = new LocalState(); const state = new LocalState();
const fetchWorkspaceSetting = async (settingKey: WorkspaceSettingKey) => { const fetchWorkspaceSetting = async (settingKey: WorkspaceSettingKey) => {
const setting = await workspaceSettingServiceClient.getWorkspaceSetting({ name: `${workspaceSettingNamePrefix}${settingKey}` }); const setting = await workspaceServiceClient.getWorkspaceSetting({ name: `${workspaceSettingNamePrefix}${settingKey}` });
state.setPartial({ state.setPartial({
settings: uniqBy([setting, ...state.settings], "name"), settings: uniqBy([setting, ...state.settings], "name"),
}); });
}; };
const upsertWorkspaceSetting = async (setting: WorkspaceSetting) => { const upsertWorkspaceSetting = async (setting: WorkspaceSetting) => {
await workspaceSettingServiceClient.setWorkspaceSetting({ setting }); await workspaceServiceClient.updateWorkspaceSetting({ setting });
state.setPartial({ state.setPartial({
settings: uniqBy([setting, ...state.settings], "name"), settings: uniqBy([setting, ...state.settings], "name"),
}); });
......
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