From f3f059b2f78666e3532c2f7639f7307f1756bcf8 Mon Sep 17 00:00:00 2001 From: boojack Date: Sat, 9 May 2026 09:30:28 +0800 Subject: [PATCH] chore: add batch get settings API --- proto/api/v1/instance_service.proto | 24 ++ .../apiv1connect/instance_service.connect.go | 31 ++ proto/gen/api/v1/instance_service.pb.go | 312 ++++++++++++------ proto/gen/api/v1/instance_service.pb.gw.go | 66 ++++ proto/gen/api/v1/instance_service_grpc.pb.go | 40 +++ proto/gen/openapi.yaml | 47 +++ server/router/api/v1/acl_config.go | 5 +- server/router/api/v1/acl_config_test.go | 1 + server/router/api/v1/connect_services.go | 8 + server/router/api/v1/instance_service.go | 48 ++- .../api/v1/test/instance_service_test.go | 65 ++++ web/src/contexts/InstanceContext.tsx | 45 ++- web/src/hooks/useInstanceQueries.ts | 15 + web/src/pages/Setting.tsx | 8 +- .../types/proto/api/v1/instance_service_pb.ts | 65 +++- web/tests/filtered-memo-stats.test.ts | 7 +- 16 files changed, 660 insertions(+), 127 deletions(-) diff --git a/proto/api/v1/instance_service.proto b/proto/api/v1/instance_service.proto index 6b4bcbaa4..b1accc5e6 100644 --- a/proto/api/v1/instance_service.proto +++ b/proto/api/v1/instance_service.proto @@ -26,6 +26,14 @@ service InstanceService { option (google.api.method_signature) = "name"; } + // Batch gets instance settings. + rpc BatchGetInstanceSettings(BatchGetInstanceSettingsRequest) returns (BatchGetInstanceSettingsResponse) { + option (google.api.http) = { + post: "/api/v1/instance/settings:batchGet" + body: "*" + }; + } + // Updates an instance setting. rpc UpdateInstanceSetting(UpdateInstanceSettingRequest) returns (InstanceSetting) { option (google.api.http) = { @@ -284,6 +292,22 @@ message GetInstanceSettingRequest { ]; } +// Request message for BatchGetInstanceSettings method. +message BatchGetInstanceSettingsRequest { + // The resource names of the instance settings. + // Format: instance/settings/{setting} + repeated string names = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = {type: "memos.api.v1/InstanceSetting"} + ]; +} + +// Response message for BatchGetInstanceSettings method. +message BatchGetInstanceSettingsResponse { + // The instance settings in the same order as the input names. + repeated InstanceSetting settings = 1; +} + // Request message for UpdateInstanceSetting method. message UpdateInstanceSettingRequest { // The instance setting resource which replaces the resource on the server. diff --git a/proto/gen/api/v1/apiv1connect/instance_service.connect.go b/proto/gen/api/v1/apiv1connect/instance_service.connect.go index 5cf6835ab..75466c402 100644 --- a/proto/gen/api/v1/apiv1connect/instance_service.connect.go +++ b/proto/gen/api/v1/apiv1connect/instance_service.connect.go @@ -40,6 +40,9 @@ const ( // InstanceServiceGetInstanceSettingProcedure is the fully-qualified name of the InstanceService's // GetInstanceSetting RPC. InstanceServiceGetInstanceSettingProcedure = "/memos.api.v1.InstanceService/GetInstanceSetting" + // InstanceServiceBatchGetInstanceSettingsProcedure is the fully-qualified name of the + // InstanceService's BatchGetInstanceSettings RPC. + InstanceServiceBatchGetInstanceSettingsProcedure = "/memos.api.v1.InstanceService/BatchGetInstanceSettings" // InstanceServiceUpdateInstanceSettingProcedure is the fully-qualified name of the // InstanceService's UpdateInstanceSetting RPC. InstanceServiceUpdateInstanceSettingProcedure = "/memos.api.v1.InstanceService/UpdateInstanceSetting" @@ -57,6 +60,8 @@ type InstanceServiceClient interface { GetInstanceProfile(context.Context, *connect.Request[v1.GetInstanceProfileRequest]) (*connect.Response[v1.InstanceProfile], error) // Gets an instance setting. GetInstanceSetting(context.Context, *connect.Request[v1.GetInstanceSettingRequest]) (*connect.Response[v1.InstanceSetting], error) + // Batch gets instance settings. + BatchGetInstanceSettings(context.Context, *connect.Request[v1.BatchGetInstanceSettingsRequest]) (*connect.Response[v1.BatchGetInstanceSettingsResponse], error) // Updates an instance setting. UpdateInstanceSetting(context.Context, *connect.Request[v1.UpdateInstanceSettingRequest]) (*connect.Response[v1.InstanceSetting], error) // Tests notification email delivery with the provided or stored SMTP settings. @@ -88,6 +93,12 @@ func NewInstanceServiceClient(httpClient connect.HTTPClient, baseURL string, opt connect.WithSchema(instanceServiceMethods.ByName("GetInstanceSetting")), connect.WithClientOptions(opts...), ), + batchGetInstanceSettings: connect.NewClient[v1.BatchGetInstanceSettingsRequest, v1.BatchGetInstanceSettingsResponse]( + httpClient, + baseURL+InstanceServiceBatchGetInstanceSettingsProcedure, + connect.WithSchema(instanceServiceMethods.ByName("BatchGetInstanceSettings")), + connect.WithClientOptions(opts...), + ), updateInstanceSetting: connect.NewClient[v1.UpdateInstanceSettingRequest, v1.InstanceSetting]( httpClient, baseURL+InstanceServiceUpdateInstanceSettingProcedure, @@ -113,6 +124,7 @@ func NewInstanceServiceClient(httpClient connect.HTTPClient, baseURL string, opt type instanceServiceClient struct { getInstanceProfile *connect.Client[v1.GetInstanceProfileRequest, v1.InstanceProfile] getInstanceSetting *connect.Client[v1.GetInstanceSettingRequest, v1.InstanceSetting] + batchGetInstanceSettings *connect.Client[v1.BatchGetInstanceSettingsRequest, v1.BatchGetInstanceSettingsResponse] updateInstanceSetting *connect.Client[v1.UpdateInstanceSettingRequest, v1.InstanceSetting] testInstanceEmailSetting *connect.Client[v1.TestInstanceEmailSettingRequest, emptypb.Empty] getInstanceStats *connect.Client[v1.GetInstanceStatsRequest, v1.InstanceStats] @@ -128,6 +140,11 @@ func (c *instanceServiceClient) GetInstanceSetting(ctx context.Context, req *con return c.getInstanceSetting.CallUnary(ctx, req) } +// BatchGetInstanceSettings calls memos.api.v1.InstanceService.BatchGetInstanceSettings. +func (c *instanceServiceClient) BatchGetInstanceSettings(ctx context.Context, req *connect.Request[v1.BatchGetInstanceSettingsRequest]) (*connect.Response[v1.BatchGetInstanceSettingsResponse], error) { + return c.batchGetInstanceSettings.CallUnary(ctx, req) +} + // UpdateInstanceSetting calls memos.api.v1.InstanceService.UpdateInstanceSetting. func (c *instanceServiceClient) UpdateInstanceSetting(ctx context.Context, req *connect.Request[v1.UpdateInstanceSettingRequest]) (*connect.Response[v1.InstanceSetting], error) { return c.updateInstanceSetting.CallUnary(ctx, req) @@ -149,6 +166,8 @@ type InstanceServiceHandler interface { GetInstanceProfile(context.Context, *connect.Request[v1.GetInstanceProfileRequest]) (*connect.Response[v1.InstanceProfile], error) // Gets an instance setting. GetInstanceSetting(context.Context, *connect.Request[v1.GetInstanceSettingRequest]) (*connect.Response[v1.InstanceSetting], error) + // Batch gets instance settings. + BatchGetInstanceSettings(context.Context, *connect.Request[v1.BatchGetInstanceSettingsRequest]) (*connect.Response[v1.BatchGetInstanceSettingsResponse], error) // Updates an instance setting. UpdateInstanceSetting(context.Context, *connect.Request[v1.UpdateInstanceSettingRequest]) (*connect.Response[v1.InstanceSetting], error) // Tests notification email delivery with the provided or stored SMTP settings. @@ -176,6 +195,12 @@ func NewInstanceServiceHandler(svc InstanceServiceHandler, opts ...connect.Handl connect.WithSchema(instanceServiceMethods.ByName("GetInstanceSetting")), connect.WithHandlerOptions(opts...), ) + instanceServiceBatchGetInstanceSettingsHandler := connect.NewUnaryHandler( + InstanceServiceBatchGetInstanceSettingsProcedure, + svc.BatchGetInstanceSettings, + connect.WithSchema(instanceServiceMethods.ByName("BatchGetInstanceSettings")), + connect.WithHandlerOptions(opts...), + ) instanceServiceUpdateInstanceSettingHandler := connect.NewUnaryHandler( InstanceServiceUpdateInstanceSettingProcedure, svc.UpdateInstanceSetting, @@ -200,6 +225,8 @@ func NewInstanceServiceHandler(svc InstanceServiceHandler, opts ...connect.Handl instanceServiceGetInstanceProfileHandler.ServeHTTP(w, r) case InstanceServiceGetInstanceSettingProcedure: instanceServiceGetInstanceSettingHandler.ServeHTTP(w, r) + case InstanceServiceBatchGetInstanceSettingsProcedure: + instanceServiceBatchGetInstanceSettingsHandler.ServeHTTP(w, r) case InstanceServiceUpdateInstanceSettingProcedure: instanceServiceUpdateInstanceSettingHandler.ServeHTTP(w, r) case InstanceServiceTestInstanceEmailSettingProcedure: @@ -223,6 +250,10 @@ func (UnimplementedInstanceServiceHandler) GetInstanceSetting(context.Context, * return nil, connect.NewError(connect.CodeUnimplemented, errors.New("memos.api.v1.InstanceService.GetInstanceSetting is not implemented")) } +func (UnimplementedInstanceServiceHandler) BatchGetInstanceSettings(context.Context, *connect.Request[v1.BatchGetInstanceSettingsRequest]) (*connect.Response[v1.BatchGetInstanceSettingsResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("memos.api.v1.InstanceService.BatchGetInstanceSettings is not implemented")) +} + func (UnimplementedInstanceServiceHandler) UpdateInstanceSetting(context.Context, *connect.Request[v1.UpdateInstanceSettingRequest]) (*connect.Response[v1.InstanceSetting], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("memos.api.v1.InstanceService.UpdateInstanceSetting is not implemented")) } diff --git a/proto/gen/api/v1/instance_service.pb.go b/proto/gen/api/v1/instance_service.pb.go index 312c13cb3..dfd7dcea3 100644 --- a/proto/gen/api/v1/instance_service.pb.go +++ b/proto/gen/api/v1/instance_service.pb.go @@ -524,6 +524,99 @@ func (x *GetInstanceSettingRequest) GetName() string { return "" } +// Request message for BatchGetInstanceSettings method. +type BatchGetInstanceSettingsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The resource names of the instance settings. + // Format: instance/settings/{setting} + Names []string `protobuf:"bytes,1,rep,name=names,proto3" json:"names,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BatchGetInstanceSettingsRequest) Reset() { + *x = BatchGetInstanceSettingsRequest{} + mi := &file_api_v1_instance_service_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BatchGetInstanceSettingsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BatchGetInstanceSettingsRequest) ProtoMessage() {} + +func (x *BatchGetInstanceSettingsRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_v1_instance_service_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BatchGetInstanceSettingsRequest.ProtoReflect.Descriptor instead. +func (*BatchGetInstanceSettingsRequest) Descriptor() ([]byte, []int) { + return file_api_v1_instance_service_proto_rawDescGZIP(), []int{4} +} + +func (x *BatchGetInstanceSettingsRequest) GetNames() []string { + if x != nil { + return x.Names + } + return nil +} + +// Response message for BatchGetInstanceSettings method. +type BatchGetInstanceSettingsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The instance settings in the same order as the input names. + Settings []*InstanceSetting `protobuf:"bytes,1,rep,name=settings,proto3" json:"settings,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BatchGetInstanceSettingsResponse) Reset() { + *x = BatchGetInstanceSettingsResponse{} + mi := &file_api_v1_instance_service_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BatchGetInstanceSettingsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BatchGetInstanceSettingsResponse) ProtoMessage() {} + +func (x *BatchGetInstanceSettingsResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_v1_instance_service_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BatchGetInstanceSettingsResponse.ProtoReflect.Descriptor instead. +func (*BatchGetInstanceSettingsResponse) Descriptor() ([]byte, []int) { + return file_api_v1_instance_service_proto_rawDescGZIP(), []int{5} +} + +func (x *BatchGetInstanceSettingsResponse) GetSettings() []*InstanceSetting { + if x != nil { + return x.Settings + } + return nil +} + // Request message for UpdateInstanceSetting method. type UpdateInstanceSettingRequest struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -537,7 +630,7 @@ type UpdateInstanceSettingRequest struct { func (x *UpdateInstanceSettingRequest) Reset() { *x = UpdateInstanceSettingRequest{} - mi := &file_api_v1_instance_service_proto_msgTypes[4] + mi := &file_api_v1_instance_service_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -549,7 +642,7 @@ func (x *UpdateInstanceSettingRequest) String() string { func (*UpdateInstanceSettingRequest) ProtoMessage() {} func (x *UpdateInstanceSettingRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_instance_service_proto_msgTypes[4] + mi := &file_api_v1_instance_service_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -562,7 +655,7 @@ func (x *UpdateInstanceSettingRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateInstanceSettingRequest.ProtoReflect.Descriptor instead. func (*UpdateInstanceSettingRequest) Descriptor() ([]byte, []int) { - return file_api_v1_instance_service_proto_rawDescGZIP(), []int{4} + return file_api_v1_instance_service_proto_rawDescGZIP(), []int{6} } func (x *UpdateInstanceSettingRequest) GetSetting() *InstanceSetting { @@ -592,7 +685,7 @@ type TestInstanceEmailSettingRequest struct { func (x *TestInstanceEmailSettingRequest) Reset() { *x = TestInstanceEmailSettingRequest{} - mi := &file_api_v1_instance_service_proto_msgTypes[5] + mi := &file_api_v1_instance_service_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -604,7 +697,7 @@ func (x *TestInstanceEmailSettingRequest) String() string { func (*TestInstanceEmailSettingRequest) ProtoMessage() {} func (x *TestInstanceEmailSettingRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_instance_service_proto_msgTypes[5] + mi := &file_api_v1_instance_service_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -617,7 +710,7 @@ func (x *TestInstanceEmailSettingRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use TestInstanceEmailSettingRequest.ProtoReflect.Descriptor instead. func (*TestInstanceEmailSettingRequest) Descriptor() ([]byte, []int) { - return file_api_v1_instance_service_proto_rawDescGZIP(), []int{5} + return file_api_v1_instance_service_proto_rawDescGZIP(), []int{7} } func (x *TestInstanceEmailSettingRequest) GetEmail() *InstanceSetting_NotificationSetting_EmailSetting { @@ -643,7 +736,7 @@ type GetInstanceStatsRequest struct { func (x *GetInstanceStatsRequest) Reset() { *x = GetInstanceStatsRequest{} - mi := &file_api_v1_instance_service_proto_msgTypes[6] + mi := &file_api_v1_instance_service_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -655,7 +748,7 @@ func (x *GetInstanceStatsRequest) String() string { func (*GetInstanceStatsRequest) ProtoMessage() {} func (x *GetInstanceStatsRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_instance_service_proto_msgTypes[6] + mi := &file_api_v1_instance_service_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -668,7 +761,7 @@ func (x *GetInstanceStatsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetInstanceStatsRequest.ProtoReflect.Descriptor instead. func (*GetInstanceStatsRequest) Descriptor() ([]byte, []int) { - return file_api_v1_instance_service_proto_rawDescGZIP(), []int{6} + return file_api_v1_instance_service_proto_rawDescGZIP(), []int{8} } // Resource usage statistics for the instance. @@ -685,7 +778,7 @@ type InstanceStats struct { func (x *InstanceStats) Reset() { *x = InstanceStats{} - mi := &file_api_v1_instance_service_proto_msgTypes[7] + mi := &file_api_v1_instance_service_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -697,7 +790,7 @@ func (x *InstanceStats) String() string { func (*InstanceStats) ProtoMessage() {} func (x *InstanceStats) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_instance_service_proto_msgTypes[7] + mi := &file_api_v1_instance_service_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -710,7 +803,7 @@ func (x *InstanceStats) ProtoReflect() protoreflect.Message { // Deprecated: Use InstanceStats.ProtoReflect.Descriptor instead. func (*InstanceStats) Descriptor() ([]byte, []int) { - return file_api_v1_instance_service_proto_rawDescGZIP(), []int{7} + return file_api_v1_instance_service_proto_rawDescGZIP(), []int{9} } func (x *InstanceStats) GetDatabase() *InstanceStats_DatabaseStats { @@ -761,7 +854,7 @@ type InstanceSetting_GeneralSetting struct { func (x *InstanceSetting_GeneralSetting) Reset() { *x = InstanceSetting_GeneralSetting{} - mi := &file_api_v1_instance_service_proto_msgTypes[8] + mi := &file_api_v1_instance_service_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -773,7 +866,7 @@ func (x *InstanceSetting_GeneralSetting) String() string { func (*InstanceSetting_GeneralSetting) ProtoMessage() {} func (x *InstanceSetting_GeneralSetting) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_instance_service_proto_msgTypes[8] + mi := &file_api_v1_instance_service_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -863,7 +956,7 @@ type InstanceSetting_StorageSetting struct { func (x *InstanceSetting_StorageSetting) Reset() { *x = InstanceSetting_StorageSetting{} - mi := &file_api_v1_instance_service_proto_msgTypes[9] + mi := &file_api_v1_instance_service_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -875,7 +968,7 @@ func (x *InstanceSetting_StorageSetting) String() string { func (*InstanceSetting_StorageSetting) ProtoMessage() {} func (x *InstanceSetting_StorageSetting) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_instance_service_proto_msgTypes[9] + mi := &file_api_v1_instance_service_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -934,7 +1027,7 @@ type InstanceSetting_MemoRelatedSetting struct { func (x *InstanceSetting_MemoRelatedSetting) Reset() { *x = InstanceSetting_MemoRelatedSetting{} - mi := &file_api_v1_instance_service_proto_msgTypes[10] + mi := &file_api_v1_instance_service_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -946,7 +1039,7 @@ func (x *InstanceSetting_MemoRelatedSetting) String() string { func (*InstanceSetting_MemoRelatedSetting) ProtoMessage() {} func (x *InstanceSetting_MemoRelatedSetting) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_instance_service_proto_msgTypes[10] + mi := &file_api_v1_instance_service_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -997,7 +1090,7 @@ type InstanceSetting_TagMetadata struct { func (x *InstanceSetting_TagMetadata) Reset() { *x = InstanceSetting_TagMetadata{} - mi := &file_api_v1_instance_service_proto_msgTypes[11] + mi := &file_api_v1_instance_service_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1009,7 +1102,7 @@ func (x *InstanceSetting_TagMetadata) String() string { func (*InstanceSetting_TagMetadata) ProtoMessage() {} func (x *InstanceSetting_TagMetadata) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_instance_service_proto_msgTypes[11] + mi := &file_api_v1_instance_service_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1053,7 +1146,7 @@ type InstanceSetting_TagsSetting struct { func (x *InstanceSetting_TagsSetting) Reset() { *x = InstanceSetting_TagsSetting{} - mi := &file_api_v1_instance_service_proto_msgTypes[12] + mi := &file_api_v1_instance_service_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1065,7 +1158,7 @@ func (x *InstanceSetting_TagsSetting) String() string { func (*InstanceSetting_TagsSetting) ProtoMessage() {} func (x *InstanceSetting_TagsSetting) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_instance_service_proto_msgTypes[12] + mi := &file_api_v1_instance_service_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1098,7 +1191,7 @@ type InstanceSetting_NotificationSetting struct { func (x *InstanceSetting_NotificationSetting) Reset() { *x = InstanceSetting_NotificationSetting{} - mi := &file_api_v1_instance_service_proto_msgTypes[13] + mi := &file_api_v1_instance_service_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1110,7 +1203,7 @@ func (x *InstanceSetting_NotificationSetting) String() string { func (*InstanceSetting_NotificationSetting) ProtoMessage() {} func (x *InstanceSetting_NotificationSetting) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_instance_service_proto_msgTypes[13] + mi := &file_api_v1_instance_service_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1147,7 +1240,7 @@ type InstanceSetting_AISetting struct { func (x *InstanceSetting_AISetting) Reset() { *x = InstanceSetting_AISetting{} - mi := &file_api_v1_instance_service_proto_msgTypes[14] + mi := &file_api_v1_instance_service_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1159,7 +1252,7 @@ func (x *InstanceSetting_AISetting) String() string { func (*InstanceSetting_AISetting) ProtoMessage() {} func (x *InstanceSetting_AISetting) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_instance_service_proto_msgTypes[14] + mi := &file_api_v1_instance_service_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1208,7 +1301,7 @@ type InstanceSetting_AIProviderConfig struct { func (x *InstanceSetting_AIProviderConfig) Reset() { *x = InstanceSetting_AIProviderConfig{} - mi := &file_api_v1_instance_service_proto_msgTypes[15] + mi := &file_api_v1_instance_service_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1220,7 +1313,7 @@ func (x *InstanceSetting_AIProviderConfig) String() string { func (*InstanceSetting_AIProviderConfig) ProtoMessage() {} func (x *InstanceSetting_AIProviderConfig) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_instance_service_proto_msgTypes[15] + mi := &file_api_v1_instance_service_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1306,7 +1399,7 @@ type InstanceSetting_TranscriptionConfig struct { func (x *InstanceSetting_TranscriptionConfig) Reset() { *x = InstanceSetting_TranscriptionConfig{} - mi := &file_api_v1_instance_service_proto_msgTypes[16] + mi := &file_api_v1_instance_service_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1318,7 +1411,7 @@ func (x *InstanceSetting_TranscriptionConfig) String() string { func (*InstanceSetting_TranscriptionConfig) ProtoMessage() {} func (x *InstanceSetting_TranscriptionConfig) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_instance_service_proto_msgTypes[16] + mi := &file_api_v1_instance_service_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1374,7 +1467,7 @@ type InstanceSetting_GeneralSetting_CustomProfile struct { func (x *InstanceSetting_GeneralSetting_CustomProfile) Reset() { *x = InstanceSetting_GeneralSetting_CustomProfile{} - mi := &file_api_v1_instance_service_proto_msgTypes[17] + mi := &file_api_v1_instance_service_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1386,7 +1479,7 @@ func (x *InstanceSetting_GeneralSetting_CustomProfile) String() string { func (*InstanceSetting_GeneralSetting_CustomProfile) ProtoMessage() {} func (x *InstanceSetting_GeneralSetting_CustomProfile) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_instance_service_proto_msgTypes[17] + mi := &file_api_v1_instance_service_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1439,7 +1532,7 @@ type InstanceSetting_StorageSetting_S3Config struct { func (x *InstanceSetting_StorageSetting_S3Config) Reset() { *x = InstanceSetting_StorageSetting_S3Config{} - mi := &file_api_v1_instance_service_proto_msgTypes[18] + mi := &file_api_v1_instance_service_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1451,7 +1544,7 @@ func (x *InstanceSetting_StorageSetting_S3Config) String() string { func (*InstanceSetting_StorageSetting_S3Config) ProtoMessage() {} func (x *InstanceSetting_StorageSetting_S3Config) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_instance_service_proto_msgTypes[18] + mi := &file_api_v1_instance_service_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1528,7 +1621,7 @@ type InstanceSetting_NotificationSetting_EmailSetting struct { func (x *InstanceSetting_NotificationSetting_EmailSetting) Reset() { *x = InstanceSetting_NotificationSetting_EmailSetting{} - mi := &file_api_v1_instance_service_proto_msgTypes[20] + mi := &file_api_v1_instance_service_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1540,7 +1633,7 @@ func (x *InstanceSetting_NotificationSetting_EmailSetting) String() string { func (*InstanceSetting_NotificationSetting_EmailSetting) ProtoMessage() {} func (x *InstanceSetting_NotificationSetting_EmailSetting) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_instance_service_proto_msgTypes[20] + mi := &file_api_v1_instance_service_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1639,7 +1732,7 @@ type InstanceStats_DatabaseStats struct { func (x *InstanceStats_DatabaseStats) Reset() { *x = InstanceStats_DatabaseStats{} - mi := &file_api_v1_instance_service_proto_msgTypes[21] + mi := &file_api_v1_instance_service_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1651,7 +1744,7 @@ func (x *InstanceStats_DatabaseStats) String() string { func (*InstanceStats_DatabaseStats) ProtoMessage() {} func (x *InstanceStats_DatabaseStats) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_instance_service_proto_msgTypes[21] + mi := &file_api_v1_instance_service_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1664,7 +1757,7 @@ func (x *InstanceStats_DatabaseStats) ProtoReflect() protoreflect.Message { // Deprecated: Use InstanceStats_DatabaseStats.ProtoReflect.Descriptor instead. func (*InstanceStats_DatabaseStats) Descriptor() ([]byte, []int) { - return file_api_v1_instance_service_proto_rawDescGZIP(), []int{7, 0} + return file_api_v1_instance_service_proto_rawDescGZIP(), []int{9, 0} } func (x *InstanceStats_DatabaseStats) GetDriver() string { @@ -1795,7 +1888,12 @@ const file_api_v1_instance_service_proto_rawDesc = "" + "\x05value\"U\n" + "\x19GetInstanceSettingRequest\x128\n" + "\x04name\x18\x01 \x01(\tB$\xe0A\x02\xfaA\x1e\n" + - "\x1cmemos.api.v1/InstanceSettingR\x04name\"\x9e\x01\n" + + "\x1cmemos.api.v1/InstanceSettingR\x04name\"]\n" + + "\x1fBatchGetInstanceSettingsRequest\x12:\n" + + "\x05names\x18\x01 \x03(\tB$\xe0A\x02\xfaA\x1e\n" + + "\x1cmemos.api.v1/InstanceSettingR\x05names\"]\n" + + " BatchGetInstanceSettingsResponse\x129\n" + + "\bsettings\x18\x01 \x03(\v2\x1d.memos.api.v1.InstanceSettingR\bsettings\"\x9e\x01\n" + "\x1cUpdateInstanceSettingRequest\x12<\n" + "\asetting\x18\x01 \x01(\v2\x1d.memos.api.v1.InstanceSettingB\x03\xe0A\x02R\asetting\x12@\n" + "\vupdate_mask\x18\x02 \x01(\v2\x1a.google.protobuf.FieldMaskB\x03\xe0A\x01R\n" + @@ -1811,10 +1909,11 @@ const file_api_v1_instance_service_proto_rawDesc = "" + "\rDatabaseStats\x12\x16\n" + "\x06driver\x18\x01 \x01(\tR\x06driver\x12\x1d\n" + "\n" + - "size_bytes\x18\x02 \x01(\x03R\tsizeBytes2\xf4\x05\n" + + "size_bytes\x18\x02 \x01(\x03R\tsizeBytes2\x9f\a\n" + "\x0fInstanceService\x12~\n" + "\x12GetInstanceProfile\x12'.memos.api.v1.GetInstanceProfileRequest\x1a\x1d.memos.api.v1.InstanceProfile\" \x82\xd3\xe4\x93\x02\x1a\x12\x18/api/v1/instance/profile\x12\x8f\x01\n" + - "\x12GetInstanceSetting\x12'.memos.api.v1.GetInstanceSettingRequest\x1a\x1d.memos.api.v1.InstanceSetting\"1\xdaA\x04name\x82\xd3\xe4\x93\x02$\x12\"/api/v1/{name=instance/settings/*}\x12\xb5\x01\n" + + "\x12GetInstanceSetting\x12'.memos.api.v1.GetInstanceSettingRequest\x1a\x1d.memos.api.v1.InstanceSetting\"1\xdaA\x04name\x82\xd3\xe4\x93\x02$\x12\"/api/v1/{name=instance/settings/*}\x12\xa8\x01\n" + + "\x18BatchGetInstanceSettings\x12-.memos.api.v1.BatchGetInstanceSettingsRequest\x1a..memos.api.v1.BatchGetInstanceSettingsResponse\"-\x82\xd3\xe4\x93\x02':\x01*\"\"/api/v1/instance/settings:batchGet\x12\xb5\x01\n" + "\x15UpdateInstanceSetting\x12*.memos.api.v1.UpdateInstanceSettingRequest\x1a\x1d.memos.api.v1.InstanceSetting\"Q\xdaA\x13setting,update_mask\x82\xd3\xe4\x93\x025:\asetting2*/api/v1/{setting.name=instance/settings/*}\x12\x9e\x01\n" + "\x18TestInstanceEmailSetting\x12-.memos.api.v1.TestInstanceEmailSettingRequest\x1a\x16.google.protobuf.Empty\";\x82\xd3\xe4\x93\x025:\x01*\"0/api/v1/instance/settings/notification:testEmail\x12v\n" + "\x10GetInstanceStats\x12%.memos.api.v1.GetInstanceStatsRequest\x1a\x1b.memos.api.v1.InstanceStats\"\x1e\x82\xd3\xe4\x93\x02\x18\x12\x16/api/v1/instance/statsB\xac\x01\n" + @@ -1833,7 +1932,7 @@ func file_api_v1_instance_service_proto_rawDescGZIP() []byte { } var file_api_v1_instance_service_proto_enumTypes = make([]protoimpl.EnumInfo, 3) -var file_api_v1_instance_service_proto_msgTypes = make([]protoimpl.MessageInfo, 22) +var file_api_v1_instance_service_proto_msgTypes = make([]protoimpl.MessageInfo, 24) var file_api_v1_instance_service_proto_goTypes = []any{ (InstanceSetting_Key)(0), // 0: memos.api.v1.InstanceSetting.Key (InstanceSetting_AIProviderType)(0), // 1: memos.api.v1.InstanceSetting.AIProviderType @@ -1842,68 +1941,73 @@ var file_api_v1_instance_service_proto_goTypes = []any{ (*GetInstanceProfileRequest)(nil), // 4: memos.api.v1.GetInstanceProfileRequest (*InstanceSetting)(nil), // 5: memos.api.v1.InstanceSetting (*GetInstanceSettingRequest)(nil), // 6: memos.api.v1.GetInstanceSettingRequest - (*UpdateInstanceSettingRequest)(nil), // 7: memos.api.v1.UpdateInstanceSettingRequest - (*TestInstanceEmailSettingRequest)(nil), // 8: memos.api.v1.TestInstanceEmailSettingRequest - (*GetInstanceStatsRequest)(nil), // 9: memos.api.v1.GetInstanceStatsRequest - (*InstanceStats)(nil), // 10: memos.api.v1.InstanceStats - (*InstanceSetting_GeneralSetting)(nil), // 11: memos.api.v1.InstanceSetting.GeneralSetting - (*InstanceSetting_StorageSetting)(nil), // 12: memos.api.v1.InstanceSetting.StorageSetting - (*InstanceSetting_MemoRelatedSetting)(nil), // 13: memos.api.v1.InstanceSetting.MemoRelatedSetting - (*InstanceSetting_TagMetadata)(nil), // 14: memos.api.v1.InstanceSetting.TagMetadata - (*InstanceSetting_TagsSetting)(nil), // 15: memos.api.v1.InstanceSetting.TagsSetting - (*InstanceSetting_NotificationSetting)(nil), // 16: memos.api.v1.InstanceSetting.NotificationSetting - (*InstanceSetting_AISetting)(nil), // 17: memos.api.v1.InstanceSetting.AISetting - (*InstanceSetting_AIProviderConfig)(nil), // 18: memos.api.v1.InstanceSetting.AIProviderConfig - (*InstanceSetting_TranscriptionConfig)(nil), // 19: memos.api.v1.InstanceSetting.TranscriptionConfig - (*InstanceSetting_GeneralSetting_CustomProfile)(nil), // 20: memos.api.v1.InstanceSetting.GeneralSetting.CustomProfile - (*InstanceSetting_StorageSetting_S3Config)(nil), // 21: memos.api.v1.InstanceSetting.StorageSetting.S3Config - nil, // 22: memos.api.v1.InstanceSetting.TagsSetting.TagsEntry - (*InstanceSetting_NotificationSetting_EmailSetting)(nil), // 23: memos.api.v1.InstanceSetting.NotificationSetting.EmailSetting - (*InstanceStats_DatabaseStats)(nil), // 24: memos.api.v1.InstanceStats.DatabaseStats - (*User)(nil), // 25: memos.api.v1.User - (*fieldmaskpb.FieldMask)(nil), // 26: google.protobuf.FieldMask - (*timestamppb.Timestamp)(nil), // 27: google.protobuf.Timestamp - (*color.Color)(nil), // 28: google.type.Color - (*emptypb.Empty)(nil), // 29: google.protobuf.Empty + (*BatchGetInstanceSettingsRequest)(nil), // 7: memos.api.v1.BatchGetInstanceSettingsRequest + (*BatchGetInstanceSettingsResponse)(nil), // 8: memos.api.v1.BatchGetInstanceSettingsResponse + (*UpdateInstanceSettingRequest)(nil), // 9: memos.api.v1.UpdateInstanceSettingRequest + (*TestInstanceEmailSettingRequest)(nil), // 10: memos.api.v1.TestInstanceEmailSettingRequest + (*GetInstanceStatsRequest)(nil), // 11: memos.api.v1.GetInstanceStatsRequest + (*InstanceStats)(nil), // 12: memos.api.v1.InstanceStats + (*InstanceSetting_GeneralSetting)(nil), // 13: memos.api.v1.InstanceSetting.GeneralSetting + (*InstanceSetting_StorageSetting)(nil), // 14: memos.api.v1.InstanceSetting.StorageSetting + (*InstanceSetting_MemoRelatedSetting)(nil), // 15: memos.api.v1.InstanceSetting.MemoRelatedSetting + (*InstanceSetting_TagMetadata)(nil), // 16: memos.api.v1.InstanceSetting.TagMetadata + (*InstanceSetting_TagsSetting)(nil), // 17: memos.api.v1.InstanceSetting.TagsSetting + (*InstanceSetting_NotificationSetting)(nil), // 18: memos.api.v1.InstanceSetting.NotificationSetting + (*InstanceSetting_AISetting)(nil), // 19: memos.api.v1.InstanceSetting.AISetting + (*InstanceSetting_AIProviderConfig)(nil), // 20: memos.api.v1.InstanceSetting.AIProviderConfig + (*InstanceSetting_TranscriptionConfig)(nil), // 21: memos.api.v1.InstanceSetting.TranscriptionConfig + (*InstanceSetting_GeneralSetting_CustomProfile)(nil), // 22: memos.api.v1.InstanceSetting.GeneralSetting.CustomProfile + (*InstanceSetting_StorageSetting_S3Config)(nil), // 23: memos.api.v1.InstanceSetting.StorageSetting.S3Config + nil, // 24: memos.api.v1.InstanceSetting.TagsSetting.TagsEntry + (*InstanceSetting_NotificationSetting_EmailSetting)(nil), // 25: memos.api.v1.InstanceSetting.NotificationSetting.EmailSetting + (*InstanceStats_DatabaseStats)(nil), // 26: memos.api.v1.InstanceStats.DatabaseStats + (*User)(nil), // 27: memos.api.v1.User + (*fieldmaskpb.FieldMask)(nil), // 28: google.protobuf.FieldMask + (*timestamppb.Timestamp)(nil), // 29: google.protobuf.Timestamp + (*color.Color)(nil), // 30: google.type.Color + (*emptypb.Empty)(nil), // 31: google.protobuf.Empty } var file_api_v1_instance_service_proto_depIdxs = []int32{ - 25, // 0: memos.api.v1.InstanceProfile.admin:type_name -> memos.api.v1.User - 11, // 1: memos.api.v1.InstanceSetting.general_setting:type_name -> memos.api.v1.InstanceSetting.GeneralSetting - 12, // 2: memos.api.v1.InstanceSetting.storage_setting:type_name -> memos.api.v1.InstanceSetting.StorageSetting - 13, // 3: memos.api.v1.InstanceSetting.memo_related_setting:type_name -> memos.api.v1.InstanceSetting.MemoRelatedSetting - 15, // 4: memos.api.v1.InstanceSetting.tags_setting:type_name -> memos.api.v1.InstanceSetting.TagsSetting - 16, // 5: memos.api.v1.InstanceSetting.notification_setting:type_name -> memos.api.v1.InstanceSetting.NotificationSetting - 17, // 6: memos.api.v1.InstanceSetting.ai_setting:type_name -> memos.api.v1.InstanceSetting.AISetting - 5, // 7: memos.api.v1.UpdateInstanceSettingRequest.setting:type_name -> memos.api.v1.InstanceSetting - 26, // 8: memos.api.v1.UpdateInstanceSettingRequest.update_mask:type_name -> google.protobuf.FieldMask - 23, // 9: memos.api.v1.TestInstanceEmailSettingRequest.email:type_name -> memos.api.v1.InstanceSetting.NotificationSetting.EmailSetting - 24, // 10: memos.api.v1.InstanceStats.database:type_name -> memos.api.v1.InstanceStats.DatabaseStats - 27, // 11: memos.api.v1.InstanceStats.generated_time:type_name -> google.protobuf.Timestamp - 20, // 12: memos.api.v1.InstanceSetting.GeneralSetting.custom_profile:type_name -> memos.api.v1.InstanceSetting.GeneralSetting.CustomProfile - 2, // 13: memos.api.v1.InstanceSetting.StorageSetting.storage_type:type_name -> memos.api.v1.InstanceSetting.StorageSetting.StorageType - 21, // 14: memos.api.v1.InstanceSetting.StorageSetting.s3_config:type_name -> memos.api.v1.InstanceSetting.StorageSetting.S3Config - 28, // 15: memos.api.v1.InstanceSetting.TagMetadata.background_color:type_name -> google.type.Color - 22, // 16: memos.api.v1.InstanceSetting.TagsSetting.tags:type_name -> memos.api.v1.InstanceSetting.TagsSetting.TagsEntry - 23, // 17: memos.api.v1.InstanceSetting.NotificationSetting.email:type_name -> memos.api.v1.InstanceSetting.NotificationSetting.EmailSetting - 18, // 18: memos.api.v1.InstanceSetting.AISetting.providers:type_name -> memos.api.v1.InstanceSetting.AIProviderConfig - 19, // 19: memos.api.v1.InstanceSetting.AISetting.transcription:type_name -> memos.api.v1.InstanceSetting.TranscriptionConfig - 1, // 20: memos.api.v1.InstanceSetting.AIProviderConfig.type:type_name -> memos.api.v1.InstanceSetting.AIProviderType - 14, // 21: memos.api.v1.InstanceSetting.TagsSetting.TagsEntry.value:type_name -> memos.api.v1.InstanceSetting.TagMetadata - 4, // 22: memos.api.v1.InstanceService.GetInstanceProfile:input_type -> memos.api.v1.GetInstanceProfileRequest - 6, // 23: memos.api.v1.InstanceService.GetInstanceSetting:input_type -> memos.api.v1.GetInstanceSettingRequest - 7, // 24: memos.api.v1.InstanceService.UpdateInstanceSetting:input_type -> memos.api.v1.UpdateInstanceSettingRequest - 8, // 25: memos.api.v1.InstanceService.TestInstanceEmailSetting:input_type -> memos.api.v1.TestInstanceEmailSettingRequest - 9, // 26: memos.api.v1.InstanceService.GetInstanceStats:input_type -> memos.api.v1.GetInstanceStatsRequest - 3, // 27: memos.api.v1.InstanceService.GetInstanceProfile:output_type -> memos.api.v1.InstanceProfile - 5, // 28: memos.api.v1.InstanceService.GetInstanceSetting:output_type -> memos.api.v1.InstanceSetting - 5, // 29: memos.api.v1.InstanceService.UpdateInstanceSetting:output_type -> memos.api.v1.InstanceSetting - 29, // 30: memos.api.v1.InstanceService.TestInstanceEmailSetting:output_type -> google.protobuf.Empty - 10, // 31: memos.api.v1.InstanceService.GetInstanceStats:output_type -> memos.api.v1.InstanceStats - 27, // [27:32] is the sub-list for method output_type - 22, // [22:27] is the sub-list for method input_type - 22, // [22:22] is the sub-list for extension type_name - 22, // [22:22] is the sub-list for extension extendee - 0, // [0:22] is the sub-list for field type_name + 27, // 0: memos.api.v1.InstanceProfile.admin:type_name -> memos.api.v1.User + 13, // 1: memos.api.v1.InstanceSetting.general_setting:type_name -> memos.api.v1.InstanceSetting.GeneralSetting + 14, // 2: memos.api.v1.InstanceSetting.storage_setting:type_name -> memos.api.v1.InstanceSetting.StorageSetting + 15, // 3: memos.api.v1.InstanceSetting.memo_related_setting:type_name -> memos.api.v1.InstanceSetting.MemoRelatedSetting + 17, // 4: memos.api.v1.InstanceSetting.tags_setting:type_name -> memos.api.v1.InstanceSetting.TagsSetting + 18, // 5: memos.api.v1.InstanceSetting.notification_setting:type_name -> memos.api.v1.InstanceSetting.NotificationSetting + 19, // 6: memos.api.v1.InstanceSetting.ai_setting:type_name -> memos.api.v1.InstanceSetting.AISetting + 5, // 7: memos.api.v1.BatchGetInstanceSettingsResponse.settings:type_name -> memos.api.v1.InstanceSetting + 5, // 8: memos.api.v1.UpdateInstanceSettingRequest.setting:type_name -> memos.api.v1.InstanceSetting + 28, // 9: memos.api.v1.UpdateInstanceSettingRequest.update_mask:type_name -> google.protobuf.FieldMask + 25, // 10: memos.api.v1.TestInstanceEmailSettingRequest.email:type_name -> memos.api.v1.InstanceSetting.NotificationSetting.EmailSetting + 26, // 11: memos.api.v1.InstanceStats.database:type_name -> memos.api.v1.InstanceStats.DatabaseStats + 29, // 12: memos.api.v1.InstanceStats.generated_time:type_name -> google.protobuf.Timestamp + 22, // 13: memos.api.v1.InstanceSetting.GeneralSetting.custom_profile:type_name -> memos.api.v1.InstanceSetting.GeneralSetting.CustomProfile + 2, // 14: memos.api.v1.InstanceSetting.StorageSetting.storage_type:type_name -> memos.api.v1.InstanceSetting.StorageSetting.StorageType + 23, // 15: memos.api.v1.InstanceSetting.StorageSetting.s3_config:type_name -> memos.api.v1.InstanceSetting.StorageSetting.S3Config + 30, // 16: memos.api.v1.InstanceSetting.TagMetadata.background_color:type_name -> google.type.Color + 24, // 17: memos.api.v1.InstanceSetting.TagsSetting.tags:type_name -> memos.api.v1.InstanceSetting.TagsSetting.TagsEntry + 25, // 18: memos.api.v1.InstanceSetting.NotificationSetting.email:type_name -> memos.api.v1.InstanceSetting.NotificationSetting.EmailSetting + 20, // 19: memos.api.v1.InstanceSetting.AISetting.providers:type_name -> memos.api.v1.InstanceSetting.AIProviderConfig + 21, // 20: memos.api.v1.InstanceSetting.AISetting.transcription:type_name -> memos.api.v1.InstanceSetting.TranscriptionConfig + 1, // 21: memos.api.v1.InstanceSetting.AIProviderConfig.type:type_name -> memos.api.v1.InstanceSetting.AIProviderType + 16, // 22: memos.api.v1.InstanceSetting.TagsSetting.TagsEntry.value:type_name -> memos.api.v1.InstanceSetting.TagMetadata + 4, // 23: memos.api.v1.InstanceService.GetInstanceProfile:input_type -> memos.api.v1.GetInstanceProfileRequest + 6, // 24: memos.api.v1.InstanceService.GetInstanceSetting:input_type -> memos.api.v1.GetInstanceSettingRequest + 7, // 25: memos.api.v1.InstanceService.BatchGetInstanceSettings:input_type -> memos.api.v1.BatchGetInstanceSettingsRequest + 9, // 26: memos.api.v1.InstanceService.UpdateInstanceSetting:input_type -> memos.api.v1.UpdateInstanceSettingRequest + 10, // 27: memos.api.v1.InstanceService.TestInstanceEmailSetting:input_type -> memos.api.v1.TestInstanceEmailSettingRequest + 11, // 28: memos.api.v1.InstanceService.GetInstanceStats:input_type -> memos.api.v1.GetInstanceStatsRequest + 3, // 29: memos.api.v1.InstanceService.GetInstanceProfile:output_type -> memos.api.v1.InstanceProfile + 5, // 30: memos.api.v1.InstanceService.GetInstanceSetting:output_type -> memos.api.v1.InstanceSetting + 8, // 31: memos.api.v1.InstanceService.BatchGetInstanceSettings:output_type -> memos.api.v1.BatchGetInstanceSettingsResponse + 5, // 32: memos.api.v1.InstanceService.UpdateInstanceSetting:output_type -> memos.api.v1.InstanceSetting + 31, // 33: memos.api.v1.InstanceService.TestInstanceEmailSetting:output_type -> google.protobuf.Empty + 12, // 34: memos.api.v1.InstanceService.GetInstanceStats:output_type -> memos.api.v1.InstanceStats + 29, // [29:35] is the sub-list for method output_type + 23, // [23:29] is the sub-list for method input_type + 23, // [23:23] is the sub-list for extension type_name + 23, // [23:23] is the sub-list for extension extendee + 0, // [0:23] is the sub-list for field type_name } func init() { file_api_v1_instance_service_proto_init() } @@ -1926,7 +2030,7 @@ func file_api_v1_instance_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_v1_instance_service_proto_rawDesc), len(file_api_v1_instance_service_proto_rawDesc)), NumEnums: 3, - NumMessages: 22, + NumMessages: 24, NumExtensions: 0, NumServices: 1, }, diff --git a/proto/gen/api/v1/instance_service.pb.gw.go b/proto/gen/api/v1/instance_service.pb.gw.go index fb7f909ad..350edfc6a 100644 --- a/proto/gen/api/v1/instance_service.pb.gw.go +++ b/proto/gen/api/v1/instance_service.pb.gw.go @@ -95,6 +95,33 @@ func local_request_InstanceService_GetInstanceSetting_0(ctx context.Context, mar return msg, metadata, err } +func request_InstanceService_BatchGetInstanceSettings_0(ctx context.Context, marshaler runtime.Marshaler, client InstanceServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq BatchGetInstanceSettingsRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.BatchGetInstanceSettings(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_InstanceService_BatchGetInstanceSettings_0(ctx context.Context, marshaler runtime.Marshaler, server InstanceServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq BatchGetInstanceSettingsRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.BatchGetInstanceSettings(ctx, &protoReq) + return msg, metadata, err +} + var filter_InstanceService_UpdateInstanceSetting_0 = &utilities.DoubleArray{Encoding: map[string]int{"setting": 0, "name": 1}, Base: []int{1, 2, 1, 0, 0}, Check: []int{0, 1, 2, 3, 2}} func request_InstanceService_UpdateInstanceSetting_0(ctx context.Context, marshaler runtime.Marshaler, client InstanceServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { @@ -270,6 +297,26 @@ func RegisterInstanceServiceHandlerServer(ctx context.Context, mux *runtime.Serv } forward_InstanceService_GetInstanceSetting_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) + mux.Handle(http.MethodPost, pattern_InstanceService_BatchGetInstanceSettings_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/memos.api.v1.InstanceService/BatchGetInstanceSettings", runtime.WithHTTPPathPattern("/api/v1/instance/settings:batchGet")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_InstanceService_BatchGetInstanceSettings_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_InstanceService_BatchGetInstanceSettings_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) mux.Handle(http.MethodPatch, pattern_InstanceService_UpdateInstanceSetting_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -404,6 +451,23 @@ func RegisterInstanceServiceHandlerClient(ctx context.Context, mux *runtime.Serv } forward_InstanceService_GetInstanceSetting_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) + mux.Handle(http.MethodPost, pattern_InstanceService_BatchGetInstanceSettings_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.InstanceService/BatchGetInstanceSettings", runtime.WithHTTPPathPattern("/api/v1/instance/settings:batchGet")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_InstanceService_BatchGetInstanceSettings_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_InstanceService_BatchGetInstanceSettings_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) mux.Handle(http.MethodPatch, pattern_InstanceService_UpdateInstanceSetting_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -461,6 +525,7 @@ func RegisterInstanceServiceHandlerClient(ctx context.Context, mux *runtime.Serv var ( pattern_InstanceService_GetInstanceProfile_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "instance", "profile"}, "")) pattern_InstanceService_GetInstanceSetting_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 3, 5, 4}, []string{"api", "v1", "instance", "settings", "name"}, "")) + pattern_InstanceService_BatchGetInstanceSettings_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "instance", "settings"}, "batchGet")) pattern_InstanceService_UpdateInstanceSetting_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 3, 5, 4}, []string{"api", "v1", "instance", "settings", "setting.name"}, "")) pattern_InstanceService_TestInstanceEmailSetting_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"api", "v1", "instance", "settings", "notification"}, "testEmail")) pattern_InstanceService_GetInstanceStats_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "instance", "stats"}, "")) @@ -469,6 +534,7 @@ var ( var ( forward_InstanceService_GetInstanceProfile_0 = runtime.ForwardResponseMessage forward_InstanceService_GetInstanceSetting_0 = runtime.ForwardResponseMessage + forward_InstanceService_BatchGetInstanceSettings_0 = runtime.ForwardResponseMessage forward_InstanceService_UpdateInstanceSetting_0 = runtime.ForwardResponseMessage forward_InstanceService_TestInstanceEmailSetting_0 = runtime.ForwardResponseMessage forward_InstanceService_GetInstanceStats_0 = runtime.ForwardResponseMessage diff --git a/proto/gen/api/v1/instance_service_grpc.pb.go b/proto/gen/api/v1/instance_service_grpc.pb.go index 25575dcec..5a2bed3b0 100644 --- a/proto/gen/api/v1/instance_service_grpc.pb.go +++ b/proto/gen/api/v1/instance_service_grpc.pb.go @@ -22,6 +22,7 @@ const _ = grpc.SupportPackageIsVersion9 const ( InstanceService_GetInstanceProfile_FullMethodName = "/memos.api.v1.InstanceService/GetInstanceProfile" InstanceService_GetInstanceSetting_FullMethodName = "/memos.api.v1.InstanceService/GetInstanceSetting" + InstanceService_BatchGetInstanceSettings_FullMethodName = "/memos.api.v1.InstanceService/BatchGetInstanceSettings" InstanceService_UpdateInstanceSetting_FullMethodName = "/memos.api.v1.InstanceService/UpdateInstanceSetting" InstanceService_TestInstanceEmailSetting_FullMethodName = "/memos.api.v1.InstanceService/TestInstanceEmailSetting" InstanceService_GetInstanceStats_FullMethodName = "/memos.api.v1.InstanceService/GetInstanceStats" @@ -35,6 +36,8 @@ type InstanceServiceClient interface { GetInstanceProfile(ctx context.Context, in *GetInstanceProfileRequest, opts ...grpc.CallOption) (*InstanceProfile, error) // Gets an instance setting. GetInstanceSetting(ctx context.Context, in *GetInstanceSettingRequest, opts ...grpc.CallOption) (*InstanceSetting, error) + // Batch gets instance settings. + BatchGetInstanceSettings(ctx context.Context, in *BatchGetInstanceSettingsRequest, opts ...grpc.CallOption) (*BatchGetInstanceSettingsResponse, error) // Updates an instance setting. UpdateInstanceSetting(ctx context.Context, in *UpdateInstanceSettingRequest, opts ...grpc.CallOption) (*InstanceSetting, error) // Tests notification email delivery with the provided or stored SMTP settings. @@ -71,6 +74,16 @@ func (c *instanceServiceClient) GetInstanceSetting(ctx context.Context, in *GetI return out, nil } +func (c *instanceServiceClient) BatchGetInstanceSettings(ctx context.Context, in *BatchGetInstanceSettingsRequest, opts ...grpc.CallOption) (*BatchGetInstanceSettingsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(BatchGetInstanceSettingsResponse) + err := c.cc.Invoke(ctx, InstanceService_BatchGetInstanceSettings_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *instanceServiceClient) UpdateInstanceSetting(ctx context.Context, in *UpdateInstanceSettingRequest, opts ...grpc.CallOption) (*InstanceSetting, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(InstanceSetting) @@ -109,6 +122,8 @@ type InstanceServiceServer interface { GetInstanceProfile(context.Context, *GetInstanceProfileRequest) (*InstanceProfile, error) // Gets an instance setting. GetInstanceSetting(context.Context, *GetInstanceSettingRequest) (*InstanceSetting, error) + // Batch gets instance settings. + BatchGetInstanceSettings(context.Context, *BatchGetInstanceSettingsRequest) (*BatchGetInstanceSettingsResponse, error) // Updates an instance setting. UpdateInstanceSetting(context.Context, *UpdateInstanceSettingRequest) (*InstanceSetting, error) // Tests notification email delivery with the provided or stored SMTP settings. @@ -131,6 +146,9 @@ func (UnimplementedInstanceServiceServer) GetInstanceProfile(context.Context, *G func (UnimplementedInstanceServiceServer) GetInstanceSetting(context.Context, *GetInstanceSettingRequest) (*InstanceSetting, error) { return nil, status.Error(codes.Unimplemented, "method GetInstanceSetting not implemented") } +func (UnimplementedInstanceServiceServer) BatchGetInstanceSettings(context.Context, *BatchGetInstanceSettingsRequest) (*BatchGetInstanceSettingsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method BatchGetInstanceSettings not implemented") +} func (UnimplementedInstanceServiceServer) UpdateInstanceSetting(context.Context, *UpdateInstanceSettingRequest) (*InstanceSetting, error) { return nil, status.Error(codes.Unimplemented, "method UpdateInstanceSetting not implemented") } @@ -197,6 +215,24 @@ func _InstanceService_GetInstanceSetting_Handler(srv interface{}, ctx context.Co return interceptor(ctx, in, info, handler) } +func _InstanceService_BatchGetInstanceSettings_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(BatchGetInstanceSettingsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InstanceServiceServer).BatchGetInstanceSettings(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: InstanceService_BatchGetInstanceSettings_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InstanceServiceServer).BatchGetInstanceSettings(ctx, req.(*BatchGetInstanceSettingsRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _InstanceService_UpdateInstanceSetting_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(UpdateInstanceSettingRequest) if err := dec(in); err != nil { @@ -266,6 +302,10 @@ var InstanceService_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetInstanceSetting", Handler: _InstanceService_GetInstanceSetting_Handler, }, + { + MethodName: "BatchGetInstanceSettings", + Handler: _InstanceService_BatchGetInstanceSettings_Handler, + }, { MethodName: "UpdateInstanceSetting", Handler: _InstanceService_UpdateInstanceSetting_Handler, diff --git a/proto/gen/openapi.yaml b/proto/gen/openapi.yaml index e84f3c643..5f7e84937 100644 --- a/proto/gen/openapi.yaml +++ b/proto/gen/openapi.yaml @@ -498,6 +498,31 @@ paths: application/json: schema: $ref: '#/components/schemas/Status' + /api/v1/instance/settings:batchGet: + post: + tags: + - InstanceService + description: Batch gets instance settings. + operationId: InstanceService_BatchGetInstanceSettings + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/BatchGetInstanceSettingsRequest' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/BatchGetInstanceSettingsResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' /api/v1/instance/stats: get: tags: @@ -2326,6 +2351,28 @@ components: type: array items: type: string + BatchGetInstanceSettingsRequest: + required: + - names + type: object + properties: + names: + type: array + items: + type: string + description: |- + The resource names of the instance settings. + Format: instance/settings/{setting} + description: Request message for BatchGetInstanceSettings method. + BatchGetInstanceSettingsResponse: + type: object + properties: + settings: + type: array + items: + $ref: '#/components/schemas/InstanceSetting' + description: The instance settings in the same order as the input names. + description: Response message for BatchGetInstanceSettings method. BatchGetLinkMetadataRequest: required: - urls diff --git a/server/router/api/v1/acl_config.go b/server/router/api/v1/acl_config.go index eabf83ba2..d937ef689 100644 --- a/server/router/api/v1/acl_config.go +++ b/server/router/api/v1/acl_config.go @@ -14,8 +14,9 @@ var PublicMethods = map[string]struct{}{ "/memos.api.v1.AuthService/RefreshToken": {}, // Token refresh uses cookie, must be accessible when access token expired // Instance Service - needed before login to show instance info - "/memos.api.v1.InstanceService/GetInstanceProfile": {}, - "/memos.api.v1.InstanceService/GetInstanceSetting": {}, + "/memos.api.v1.InstanceService/GetInstanceProfile": {}, + "/memos.api.v1.InstanceService/GetInstanceSetting": {}, + "/memos.api.v1.InstanceService/BatchGetInstanceSettings": {}, // User Service - public user profiles and stats "/memos.api.v1.UserService/CreateUser": {}, // Allow first user registration diff --git a/server/router/api/v1/acl_config_test.go b/server/router/api/v1/acl_config_test.go index 0007047eb..f0a484bc2 100644 --- a/server/router/api/v1/acl_config_test.go +++ b/server/router/api/v1/acl_config_test.go @@ -15,6 +15,7 @@ func TestPublicMethodsArePublic(t *testing.T) { // Instance Service "/memos.api.v1.InstanceService/GetInstanceProfile", "/memos.api.v1.InstanceService/GetInstanceSetting", + "/memos.api.v1.InstanceService/BatchGetInstanceSettings", // User Service "/memos.api.v1.UserService/CreateUser", "/memos.api.v1.UserService/GetUser", diff --git a/server/router/api/v1/connect_services.go b/server/router/api/v1/connect_services.go index 60bc0566b..67cf32a5a 100644 --- a/server/router/api/v1/connect_services.go +++ b/server/router/api/v1/connect_services.go @@ -31,6 +31,14 @@ func (s *ConnectServiceHandler) GetInstanceSetting(ctx context.Context, req *con return connect.NewResponse(resp), nil } +func (s *ConnectServiceHandler) BatchGetInstanceSettings(ctx context.Context, req *connect.Request[v1pb.BatchGetInstanceSettingsRequest]) (*connect.Response[v1pb.BatchGetInstanceSettingsResponse], error) { + resp, err := s.APIV1Service.BatchGetInstanceSettings(ctx, req.Msg) + if err != nil { + return nil, convertGRPCError(err) + } + return connect.NewResponse(resp), nil +} + func (s *ConnectServiceHandler) UpdateInstanceSetting(ctx context.Context, req *connect.Request[v1pb.UpdateInstanceSettingRequest]) (*connect.Response[v1pb.InstanceSetting], error) { resp, err := s.APIV1Service.UpdateInstanceSetting(ctx, req.Msg) if err != nil { diff --git a/server/router/api/v1/instance_service.go b/server/router/api/v1/instance_service.go index 3d0cfa2cf..62bbfab3b 100644 --- a/server/router/api/v1/instance_service.go +++ b/server/router/api/v1/instance_service.go @@ -24,8 +24,27 @@ const ( maxTranscriptionConfigModelLength = 256 maxTranscriptionConfigLanguageLength = 32 maxTranscriptionConfigPromptLength = 4096 + maxBatchGetInstanceSettings = 100 ) +type instanceSettingCaller struct { + user *store.User + loaded bool +} + +func (c *instanceSettingCaller) currentUser(ctx context.Context, service *APIV1Service) (*store.User, error) { + if c.loaded { + return c.user, nil + } + user, err := service.fetchCurrentUser(ctx) + if err != nil { + return nil, err + } + c.user = user + c.loaded = true + return c.user, nil +} + // GetInstanceProfile returns the instance profile. func (s *APIV1Service) GetInstanceProfile(ctx context.Context, _ *v1pb.GetInstanceProfileRequest) (*v1pb.InstanceProfile, error) { admin, err := s.GetInstanceAdmin(ctx) @@ -44,7 +63,30 @@ func (s *APIV1Service) GetInstanceProfile(ctx context.Context, _ *v1pb.GetInstan } func (s *APIV1Service) GetInstanceSetting(ctx context.Context, request *v1pb.GetInstanceSettingRequest) (*v1pb.InstanceSetting, error) { - instanceSettingKeyString, err := ExtractInstanceSettingKeyFromName(request.Name) + return s.getInstanceSetting(ctx, request.Name, &instanceSettingCaller{}) +} + +// BatchGetInstanceSettings returns multiple instance settings in request order. +func (s *APIV1Service) BatchGetInstanceSettings(ctx context.Context, request *v1pb.BatchGetInstanceSettingsRequest) (*v1pb.BatchGetInstanceSettingsResponse, error) { + if len(request.Names) > maxBatchGetInstanceSettings { + return nil, status.Errorf(codes.InvalidArgument, "too many instance setting names (max %d)", maxBatchGetInstanceSettings) + } + + caller := &instanceSettingCaller{} + settings := make([]*v1pb.InstanceSetting, 0, len(request.Names)) + for _, name := range request.Names { + setting, err := s.getInstanceSetting(ctx, name, caller) + if err != nil { + return nil, err + } + settings = append(settings, setting) + } + + return &v1pb.BatchGetInstanceSettingsResponse{Settings: settings}, nil +} + +func (s *APIV1Service) getInstanceSetting(ctx context.Context, name string, caller *instanceSettingCaller) (*v1pb.InstanceSetting, error) { + instanceSettingKeyString, err := ExtractInstanceSettingKeyFromName(name) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "invalid instance setting name: %v", err) } @@ -86,7 +128,7 @@ func (s *APIV1Service) GetInstanceSetting(ctx context.Context, request *v1pb.Get // Storage and notification settings contain credentials; restrict to admins only. if instanceSetting.Key == storepb.InstanceSettingKey_STORAGE || instanceSetting.Key == storepb.InstanceSettingKey_NOTIFICATION { - user, err := s.fetchCurrentUser(ctx) + user, err := caller.currentUser(ctx, s) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) } @@ -99,7 +141,7 @@ func (s *APIV1Service) GetInstanceSetting(ctx context.Context, request *v1pb.Get } isAdminCaller := false if instanceSetting.Key == storepb.InstanceSettingKey_AI { - user, err := s.fetchCurrentUser(ctx) + user, err := caller.currentUser(ctx, s) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) } diff --git a/server/router/api/v1/test/instance_service_test.go b/server/router/api/v1/test/instance_service_test.go index c52dbae94..192cd58f8 100644 --- a/server/router/api/v1/test/instance_service_test.go +++ b/server/router/api/v1/test/instance_service_test.go @@ -289,6 +289,71 @@ func TestGetInstanceSetting(t *testing.T) { }) } +func TestBatchGetInstanceSettings(t *testing.T) { + ctx := context.Background() + + t.Run("BatchGetInstanceSettings - returns settings in request order", func(t *testing.T) { + ts := NewTestService(t) + defer ts.Cleanup() + + resp, err := ts.Service.BatchGetInstanceSettings(ctx, &v1pb.BatchGetInstanceSettingsRequest{ + Names: []string{ + "instance/settings/TAGS", + "instance/settings/GENERAL", + "instance/settings/MEMO_RELATED", + }, + }) + + require.NoError(t, err) + require.NotNil(t, resp) + require.Len(t, resp.Settings, 3) + require.Equal(t, "instance/settings/TAGS", resp.Settings[0].Name) + require.NotNil(t, resp.Settings[0].GetTagsSetting()) + require.Equal(t, "instance/settings/GENERAL", resp.Settings[1].Name) + require.NotNil(t, resp.Settings[1].GetGeneralSetting()) + require.Equal(t, "instance/settings/MEMO_RELATED", resp.Settings[2].Name) + require.NotNil(t, resp.Settings[2].GetMemoRelatedSetting()) + }) + + t.Run("BatchGetInstanceSettings - admin-only setting requires admin", func(t *testing.T) { + ts := NewTestService(t) + defer ts.Cleanup() + + regularUser, err := ts.CreateRegularUser(ctx, "batch-user") + require.NoError(t, err) + userCtx := ts.CreateUserContext(ctx, regularUser.ID) + + _, err = ts.Service.BatchGetInstanceSettings(userCtx, &v1pb.BatchGetInstanceSettingsRequest{ + Names: []string{"instance/settings/GENERAL", "instance/settings/NOTIFICATION"}, + }) + require.Error(t, err) + require.Contains(t, err.Error(), "permission denied") + + admin, err := ts.CreateHostUser(ctx, "batch-admin") + require.NoError(t, err) + adminCtx := ts.CreateUserContext(ctx, admin.ID) + + resp, err := ts.Service.BatchGetInstanceSettings(adminCtx, &v1pb.BatchGetInstanceSettingsRequest{ + Names: []string{"instance/settings/GENERAL", "instance/settings/NOTIFICATION"}, + }) + require.NoError(t, err) + require.Len(t, resp.Settings, 2) + require.NotNil(t, resp.Settings[1].GetNotificationSetting()) + }) + + t.Run("BatchGetInstanceSettings - invalid setting name", func(t *testing.T) { + ts := NewTestService(t) + defer ts.Cleanup() + + _, err := ts.Service.BatchGetInstanceSettings(ctx, &v1pb.BatchGetInstanceSettingsRequest{ + Names: []string{"instance/settings/GENERAL", "invalid/setting/name"}, + }) + + require.Error(t, err) + require.Contains(t, err.Error(), "invalid instance setting name") + }) +} + func TestTestInstanceEmailSettingAuthorization(t *testing.T) { ctx := context.Background() ts := NewTestService(t) diff --git a/web/src/contexts/InstanceContext.tsx b/web/src/contexts/InstanceContext.tsx index 078fd4fbf..6841d9203 100644 --- a/web/src/contexts/InstanceContext.tsx +++ b/web/src/contexts/InstanceContext.tsx @@ -47,6 +47,7 @@ interface InstanceContextValue extends InstanceState { aiSetting: InstanceSetting_AISetting; initialize: () => Promise; fetchSetting: (key: InstanceSetting_Key) => Promise; + fetchSettings: (keys: InstanceSetting_Key[]) => Promise; updateSetting: (setting: InstanceSetting) => Promise; } @@ -117,15 +118,20 @@ export function InstanceProvider({ children }: { children: ReactNode }) { try { const profile = await instanceServiceClient.getInstanceProfile({}); - const [generalSetting, memoRelatedSettingResponse, tagsSettingResponse] = await Promise.all([ - instanceServiceClient.getInstanceSetting({ name: buildInstanceSettingName(InstanceSetting_Key.GENERAL) }), - instanceServiceClient.getInstanceSetting({ name: buildInstanceSettingName(InstanceSetting_Key.MEMO_RELATED) }), - instanceServiceClient.getInstanceSetting({ name: buildInstanceSettingName(InstanceSetting_Key.TAGS) }), - ]); + const settingsResponse = await instanceServiceClient.batchGetInstanceSettings({ + names: [ + buildInstanceSettingName(InstanceSetting_Key.GENERAL), + buildInstanceSettingName(InstanceSetting_Key.MEMO_RELATED), + buildInstanceSettingName(InstanceSetting_Key.TAGS), + ], + }); + for (const setting of settingsResponse.settings) { + fetchedSettingsRef.current.add(setting.name); + } setState({ profile, - settings: [generalSetting, memoRelatedSettingResponse, tagsSettingResponse], + settings: settingsResponse.settings, isInitialized: true, isLoading: false, profileLoaded: true, @@ -140,6 +146,31 @@ export function InstanceProvider({ children }: { children: ReactNode }) { } }, []); + const fetchSettings = useCallback(async (keys: InstanceSetting_Key[]) => { + const names = keys.map(buildInstanceSettingName).filter((name) => !fetchedSettingsRef.current.has(name)); + if (names.length === 0) { + return; + } + + for (const name of names) { + fetchedSettingsRef.current.add(name); + } + + try { + const response = await instanceServiceClient.batchGetInstanceSettings({ names }); + const fetchedNames = new Set(response.settings.map((setting) => setting.name)); + setState((prev) => ({ + ...prev, + settings: [...prev.settings.filter((setting) => !fetchedNames.has(setting.name)), ...response.settings], + })); + } catch (error) { + for (const name of names) { + fetchedSettingsRef.current.delete(name); + } + throw error; + } + }, []); + const fetchSetting = useCallback(async (key: InstanceSetting_Key) => { const name = buildInstanceSettingName(key); if (fetchedSettingsRef.current.has(name)) { @@ -178,6 +209,7 @@ export function InstanceProvider({ children }: { children: ReactNode }) { aiSetting, initialize, fetchSetting, + fetchSettings, updateSetting, }), [ @@ -190,6 +222,7 @@ export function InstanceProvider({ children }: { children: ReactNode }) { aiSetting, initialize, fetchSetting, + fetchSettings, updateSetting, ], ); diff --git a/web/src/hooks/useInstanceQueries.ts b/web/src/hooks/useInstanceQueries.ts index f48fba6e0..1df7c04fe 100644 --- a/web/src/hooks/useInstanceQueries.ts +++ b/web/src/hooks/useInstanceQueries.ts @@ -8,6 +8,7 @@ export const instanceKeys = { profile: () => [...instanceKeys.all, "profile"] as const, settings: () => [...instanceKeys.all, "settings"] as const, setting: (key: InstanceSetting_Key) => [...instanceKeys.settings(), key] as const, + settingsBatch: (keys: InstanceSetting_Key[]) => [...instanceKeys.settings(), "batch", ...keys] as const, stats: () => [...instanceKeys.all, "stats"] as const, }; @@ -52,6 +53,20 @@ export function useInstanceSetting(key: InstanceSetting_Key) { }); } +// Hook to fetch multiple instance settings +export function useInstanceSettings(keys: InstanceSetting_Key[]) { + return useQuery({ + queryKey: instanceKeys.settingsBatch(keys), + queryFn: async () => { + const response = await instanceServiceClient.batchGetInstanceSettings({ + names: keys.map(buildInstanceSettingName), + }); + return response.settings; + }, + staleTime: 1000 * 60 * 5, // 5 minutes + }); +} + // Hook to update instance setting export function useUpdateInstanceSetting() { const queryClient = useQueryClient(); diff --git a/web/src/pages/Setting.tsx b/web/src/pages/Setting.tsx index 93a83de5c..d98bf9d56 100644 --- a/web/src/pages/Setting.tsx +++ b/web/src/pages/Setting.tsx @@ -25,7 +25,7 @@ const Setting = () => { const sm = useMediaQuery("sm"); const location = useLocation(); const user = useCurrentUser(); - const { profile, fetchSetting } = useInstance(); + const { profile, fetchSettings } = useInstance(); const [selectedSection, setSelectedSection] = useState(DEFAULT_SETTING_SECTION); const isHost = user?.role === User_Role.ADMIN; const commitUrl = isCommitSha(profile.commit) ? `${GITHUB_COMMIT_URL_PREFIX}${profile.commit}` : ""; @@ -52,10 +52,8 @@ const Setting = () => { return; } const preloadSettingKeys = new Set(sectionGroups.admin.flatMap((section) => section.preloadSettingKeys ?? [])); - for (const key of preloadSettingKeys) { - fetchSetting(key); - } - }, [fetchSetting, isHost, sectionGroups.admin]); + void fetchSettings([...preloadSettingKeys]); + }, [fetchSettings, isHost, sectionGroups.admin]); const handleSectionSelectorItemClick = (section: SettingSectionKey) => { window.location.hash = section; diff --git a/web/src/types/proto/api/v1/instance_service_pb.ts b/web/src/types/proto/api/v1/instance_service_pb.ts index d2f96ebf7..da649d4b4 100644 --- a/web/src/types/proto/api/v1/instance_service_pb.ts +++ b/web/src/types/proto/api/v1/instance_service_pb.ts @@ -20,7 +20,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file api/v1/instance_service.proto. */ export const file_api_v1_instance_service: GenFile = /*@__PURE__*/ - fileDesc("Ch1hcGkvdjEvaW5zdGFuY2Vfc2VydmljZS5wcm90bxIMbWVtb3MuYXBpLnYxInkKD0luc3RhbmNlUHJvZmlsZRIPCgd2ZXJzaW9uGAIgASgJEgwKBGRlbW8YAyABKAgSFAoMaW5zdGFuY2VfdXJsGAYgASgJEiEKBWFkbWluGAcgASgLMhIubWVtb3MuYXBpLnYxLlVzZXISDgoGY29tbWl0GAggASgJIhsKGUdldEluc3RhbmNlUHJvZmlsZVJlcXVlc3Qi0xUKD0luc3RhbmNlU2V0dGluZxIRCgRuYW1lGAEgASgJQgPgQQgSRwoPZ2VuZXJhbF9zZXR0aW5nGAIgASgLMiwubWVtb3MuYXBpLnYxLkluc3RhbmNlU2V0dGluZy5HZW5lcmFsU2V0dGluZ0gAEkcKD3N0b3JhZ2Vfc2V0dGluZxgDIAEoCzIsLm1lbW9zLmFwaS52MS5JbnN0YW5jZVNldHRpbmcuU3RvcmFnZVNldHRpbmdIABJQChRtZW1vX3JlbGF0ZWRfc2V0dGluZxgEIAEoCzIwLm1lbW9zLmFwaS52MS5JbnN0YW5jZVNldHRpbmcuTWVtb1JlbGF0ZWRTZXR0aW5nSAASQQoMdGFnc19zZXR0aW5nGAUgASgLMikubWVtb3MuYXBpLnYxLkluc3RhbmNlU2V0dGluZy5UYWdzU2V0dGluZ0gAElEKFG5vdGlmaWNhdGlvbl9zZXR0aW5nGAYgASgLMjEubWVtb3MuYXBpLnYxLkluc3RhbmNlU2V0dGluZy5Ob3RpZmljYXRpb25TZXR0aW5nSAASPQoKYWlfc2V0dGluZxgHIAEoCzInLm1lbW9zLmFwaS52MS5JbnN0YW5jZVNldHRpbmcuQUlTZXR0aW5nSAAahwMKDkdlbmVyYWxTZXR0aW5nEiIKGmRpc2FsbG93X3VzZXJfcmVnaXN0cmF0aW9uGAIgASgIEh4KFmRpc2FsbG93X3Bhc3N3b3JkX2F1dGgYAyABKAgSGQoRYWRkaXRpb25hbF9zY3JpcHQYBCABKAkSGAoQYWRkaXRpb25hbF9zdHlsZRgFIAEoCRJSCg5jdXN0b21fcHJvZmlsZRgGIAEoCzI6Lm1lbW9zLmFwaS52MS5JbnN0YW5jZVNldHRpbmcuR2VuZXJhbFNldHRpbmcuQ3VzdG9tUHJvZmlsZRIdChV3ZWVrX3N0YXJ0X2RheV9vZmZzZXQYByABKAUSIAoYZGlzYWxsb3dfY2hhbmdlX3VzZXJuYW1lGAggASgIEiAKGGRpc2FsbG93X2NoYW5nZV9uaWNrbmFtZRgJIAEoCBpFCg1DdXN0b21Qcm9maWxlEg0KBXRpdGxlGAEgASgJEhMKC2Rlc2NyaXB0aW9uGAIgASgJEhAKCGxvZ29fdXJsGAMgASgJGr8DCg5TdG9yYWdlU2V0dGluZxJOCgxzdG9yYWdlX3R5cGUYASABKA4yOC5tZW1vcy5hcGkudjEuSW5zdGFuY2VTZXR0aW5nLlN0b3JhZ2VTZXR0aW5nLlN0b3JhZ2VUeXBlEhkKEWZpbGVwYXRoX3RlbXBsYXRlGAIgASgJEhwKFHVwbG9hZF9zaXplX2xpbWl0X21iGAMgASgDEkgKCXMzX2NvbmZpZxgEIAEoCzI1Lm1lbW9zLmFwaS52MS5JbnN0YW5jZVNldHRpbmcuU3RvcmFnZVNldHRpbmcuUzNDb25maWcaiwEKCFMzQ29uZmlnEhUKDWFjY2Vzc19rZXlfaWQYASABKAkSHgoRYWNjZXNzX2tleV9zZWNyZXQYAiABKAlCA+BBBBIQCghlbmRwb2ludBgDIAEoCRIOCgZyZWdpb24YBCABKAkSDgoGYnVja2V0GAUgASgJEhYKDnVzZV9wYXRoX3N0eWxlGAYgASgIIkwKC1N0b3JhZ2VUeXBlEhwKGFNUT1JBR0VfVFlQRV9VTlNQRUNJRklFRBAAEgwKCERBVEFCQVNFEAESCQoFTE9DQUwQAhIGCgJTMxADGocBChJNZW1vUmVsYXRlZFNldHRpbmcSHAoUY29udGVudF9sZW5ndGhfbGltaXQYAyABKAUSIAoYZW5hYmxlX2RvdWJsZV9jbGlja19lZGl0GAQgASgIEhEKCXJlYWN0aW9ucxgHIAMoCUoECAIQA1IYZGlzcGxheV93aXRoX3VwZGF0ZV90aW1lGlEKC1RhZ01ldGFkYXRhEiwKEGJhY2tncm91bmRfY29sb3IYASABKAsyEi5nb29nbGUudHlwZS5Db2xvchIUCgxibHVyX2NvbnRlbnQYAiABKAgaqAEKC1RhZ3NTZXR0aW5nEkEKBHRhZ3MYASADKAsyMy5tZW1vcy5hcGkudjEuSW5zdGFuY2VTZXR0aW5nLlRhZ3NTZXR0aW5nLlRhZ3NFbnRyeRpWCglUYWdzRW50cnkSCwoDa2V5GAEgASgJEjgKBXZhbHVlGAIgASgLMikubWVtb3MuYXBpLnYxLkluc3RhbmNlU2V0dGluZy5UYWdNZXRhZGF0YToCOAEaugIKE05vdGlmaWNhdGlvblNldHRpbmcSTQoFZW1haWwYASABKAsyPi5tZW1vcy5hcGkudjEuSW5zdGFuY2VTZXR0aW5nLk5vdGlmaWNhdGlvblNldHRpbmcuRW1haWxTZXR0aW5nGtMBCgxFbWFpbFNldHRpbmcSDwoHZW5hYmxlZBgBIAEoCBIRCglzbXRwX2hvc3QYAiABKAkSEQoJc210cF9wb3J0GAMgASgFEhUKDXNtdHBfdXNlcm5hbWUYBCABKAkSGgoNc210cF9wYXNzd29yZBgFIAEoCUID4EEEEhIKCmZyb21fZW1haWwYBiABKAkSEQoJZnJvbV9uYW1lGAcgASgJEhAKCHJlcGx5X3RvGAggASgJEg8KB3VzZV90bHMYCSABKAgSDwoHdXNlX3NzbBgKIAEoCBqYAQoJQUlTZXR0aW5nEkEKCXByb3ZpZGVycxgBIAMoCzIuLm1lbW9zLmFwaS52MS5JbnN0YW5jZVNldHRpbmcuQUlQcm92aWRlckNvbmZpZxJICg10cmFuc2NyaXB0aW9uGAIgASgLMjEubWVtb3MuYXBpLnYxLkluc3RhbmNlU2V0dGluZy5UcmFuc2NyaXB0aW9uQ29uZmlnGsYBChBBSVByb3ZpZGVyQ29uZmlnEgoKAmlkGAEgASgJEg0KBXRpdGxlGAIgASgJEjoKBHR5cGUYAyABKA4yLC5tZW1vcy5hcGkudjEuSW5zdGFuY2VTZXR0aW5nLkFJUHJvdmlkZXJUeXBlEhAKCGVuZHBvaW50GAQgASgJEhQKB2FwaV9rZXkYBSABKAlCA+BBBBIYCgthcGlfa2V5X3NldBgIIAEoCEID4EEDEhkKDGFwaV9rZXlfaGludBgJIAEoCUID4EEDGlsKE1RyYW5zY3JpcHRpb25Db25maWcSEwoLcHJvdmlkZXJfaWQYASABKAkSDQoFbW9kZWwYAiABKAkSEAoIbGFuZ3VhZ2UYAyABKAkSDgoGcHJvbXB0GAQgASgJImoKA0tleRITCg9LRVlfVU5TUEVDSUZJRUQQABILCgdHRU5FUkFMEAESCwoHU1RPUkFHRRACEhAKDE1FTU9fUkVMQVRFRBADEggKBFRBR1MQBBIQCgxOT1RJRklDQVRJT04QBRIGCgJBSRAGIkoKDkFJUHJvdmlkZXJUeXBlEiAKHEFJX1BST1ZJREVSX1RZUEVfVU5TUEVDSUZJRUQQABIKCgZPUEVOQUkQARIKCgZHRU1JTkkQAjph6kFeChxtZW1vcy5hcGkudjEvSW5zdGFuY2VTZXR0aW5nEhtpbnN0YW5jZS9zZXR0aW5ncy97c2V0dGluZ30qEGluc3RhbmNlU2V0dGluZ3MyD2luc3RhbmNlU2V0dGluZ0IHCgV2YWx1ZSJPChlHZXRJbnN0YW5jZVNldHRpbmdSZXF1ZXN0EjIKBG5hbWUYASABKAlCJOBBAvpBHgocbWVtb3MuYXBpLnYxL0luc3RhbmNlU2V0dGluZyKJAQocVXBkYXRlSW5zdGFuY2VTZXR0aW5nUmVxdWVzdBIzCgdzZXR0aW5nGAEgASgLMh0ubWVtb3MuYXBpLnYxLkluc3RhbmNlU2V0dGluZ0ID4EECEjQKC3VwZGF0ZV9tYXNrGAIgASgLMhouZ29vZ2xlLnByb3RvYnVmLkZpZWxkTWFza0ID4EEBIpMBCh9UZXN0SW5zdGFuY2VFbWFpbFNldHRpbmdSZXF1ZXN0ElIKBWVtYWlsGAEgASgLMj4ubWVtb3MuYXBpLnYxLkluc3RhbmNlU2V0dGluZy5Ob3RpZmljYXRpb25TZXR0aW5nLkVtYWlsU2V0dGluZ0ID4EEBEhwKD3JlY2lwaWVudF9lbWFpbBgCIAEoCUID4EEBIhkKF0dldEluc3RhbmNlU3RhdHNSZXF1ZXN0ItIBCg1JbnN0YW5jZVN0YXRzEjsKCGRhdGFiYXNlGAEgASgLMikubWVtb3MuYXBpLnYxLkluc3RhbmNlU3RhdHMuRGF0YWJhc2VTdGF0cxIbChNsb2NhbF9zdG9yYWdlX2J5dGVzGAIgASgDEjIKDmdlbmVyYXRlZF90aW1lGAQgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBozCg1EYXRhYmFzZVN0YXRzEg4KBmRyaXZlchgBIAEoCRISCgpzaXplX2J5dGVzGAIgASgDMvQFCg9JbnN0YW5jZVNlcnZpY2USfgoSR2V0SW5zdGFuY2VQcm9maWxlEicubWVtb3MuYXBpLnYxLkdldEluc3RhbmNlUHJvZmlsZVJlcXVlc3QaHS5tZW1vcy5hcGkudjEuSW5zdGFuY2VQcm9maWxlIiCC0+STAhoSGC9hcGkvdjEvaW5zdGFuY2UvcHJvZmlsZRKPAQoSR2V0SW5zdGFuY2VTZXR0aW5nEicubWVtb3MuYXBpLnYxLkdldEluc3RhbmNlU2V0dGluZ1JlcXVlc3QaHS5tZW1vcy5hcGkudjEuSW5zdGFuY2VTZXR0aW5nIjHaQQRuYW1lgtPkkwIkEiIvYXBpL3YxL3tuYW1lPWluc3RhbmNlL3NldHRpbmdzLyp9ErUBChVVcGRhdGVJbnN0YW5jZVNldHRpbmcSKi5tZW1vcy5hcGkudjEuVXBkYXRlSW5zdGFuY2VTZXR0aW5nUmVxdWVzdBodLm1lbW9zLmFwaS52MS5JbnN0YW5jZVNldHRpbmciUdpBE3NldHRpbmcsdXBkYXRlX21hc2uC0+STAjU6B3NldHRpbmcyKi9hcGkvdjEve3NldHRpbmcubmFtZT1pbnN0YW5jZS9zZXR0aW5ncy8qfRKeAQoYVGVzdEluc3RhbmNlRW1haWxTZXR0aW5nEi0ubWVtb3MuYXBpLnYxLlRlc3RJbnN0YW5jZUVtYWlsU2V0dGluZ1JlcXVlc3QaFi5nb29nbGUucHJvdG9idWYuRW1wdHkiO4LT5JMCNToBKiIwL2FwaS92MS9pbnN0YW5jZS9zZXR0aW5ncy9ub3RpZmljYXRpb246dGVzdEVtYWlsEnYKEEdldEluc3RhbmNlU3RhdHMSJS5tZW1vcy5hcGkudjEuR2V0SW5zdGFuY2VTdGF0c1JlcXVlc3QaGy5tZW1vcy5hcGkudjEuSW5zdGFuY2VTdGF0cyIegtPkkwIYEhYvYXBpL3YxL2luc3RhbmNlL3N0YXRzQqwBChBjb20ubWVtb3MuYXBpLnYxQhRJbnN0YW5jZVNlcnZpY2VQcm90b1ABWjBnaXRodWIuY29tL3VzZW1lbW9zL21lbW9zL3Byb3RvL2dlbi9hcGkvdjE7YXBpdjGiAgNNQViqAgxNZW1vcy5BcGkuVjHKAgxNZW1vc1xBcGlcVjHiAhhNZW1vc1xBcGlcVjFcR1BCTWV0YWRhdGHqAg5NZW1vczo6QXBpOjpWMWIGcHJvdG8z", [file_api_v1_user_service, file_google_api_annotations, file_google_api_client, file_google_api_field_behavior, file_google_api_resource, file_google_protobuf_empty, file_google_protobuf_field_mask, file_google_protobuf_timestamp, file_google_type_color]); + fileDesc("Ch1hcGkvdjEvaW5zdGFuY2Vfc2VydmljZS5wcm90bxIMbWVtb3MuYXBpLnYxInkKD0luc3RhbmNlUHJvZmlsZRIPCgd2ZXJzaW9uGAIgASgJEgwKBGRlbW8YAyABKAgSFAoMaW5zdGFuY2VfdXJsGAYgASgJEiEKBWFkbWluGAcgASgLMhIubWVtb3MuYXBpLnYxLlVzZXISDgoGY29tbWl0GAggASgJIhsKGUdldEluc3RhbmNlUHJvZmlsZVJlcXVlc3Qi0xUKD0luc3RhbmNlU2V0dGluZxIRCgRuYW1lGAEgASgJQgPgQQgSRwoPZ2VuZXJhbF9zZXR0aW5nGAIgASgLMiwubWVtb3MuYXBpLnYxLkluc3RhbmNlU2V0dGluZy5HZW5lcmFsU2V0dGluZ0gAEkcKD3N0b3JhZ2Vfc2V0dGluZxgDIAEoCzIsLm1lbW9zLmFwaS52MS5JbnN0YW5jZVNldHRpbmcuU3RvcmFnZVNldHRpbmdIABJQChRtZW1vX3JlbGF0ZWRfc2V0dGluZxgEIAEoCzIwLm1lbW9zLmFwaS52MS5JbnN0YW5jZVNldHRpbmcuTWVtb1JlbGF0ZWRTZXR0aW5nSAASQQoMdGFnc19zZXR0aW5nGAUgASgLMikubWVtb3MuYXBpLnYxLkluc3RhbmNlU2V0dGluZy5UYWdzU2V0dGluZ0gAElEKFG5vdGlmaWNhdGlvbl9zZXR0aW5nGAYgASgLMjEubWVtb3MuYXBpLnYxLkluc3RhbmNlU2V0dGluZy5Ob3RpZmljYXRpb25TZXR0aW5nSAASPQoKYWlfc2V0dGluZxgHIAEoCzInLm1lbW9zLmFwaS52MS5JbnN0YW5jZVNldHRpbmcuQUlTZXR0aW5nSAAahwMKDkdlbmVyYWxTZXR0aW5nEiIKGmRpc2FsbG93X3VzZXJfcmVnaXN0cmF0aW9uGAIgASgIEh4KFmRpc2FsbG93X3Bhc3N3b3JkX2F1dGgYAyABKAgSGQoRYWRkaXRpb25hbF9zY3JpcHQYBCABKAkSGAoQYWRkaXRpb25hbF9zdHlsZRgFIAEoCRJSCg5jdXN0b21fcHJvZmlsZRgGIAEoCzI6Lm1lbW9zLmFwaS52MS5JbnN0YW5jZVNldHRpbmcuR2VuZXJhbFNldHRpbmcuQ3VzdG9tUHJvZmlsZRIdChV3ZWVrX3N0YXJ0X2RheV9vZmZzZXQYByABKAUSIAoYZGlzYWxsb3dfY2hhbmdlX3VzZXJuYW1lGAggASgIEiAKGGRpc2FsbG93X2NoYW5nZV9uaWNrbmFtZRgJIAEoCBpFCg1DdXN0b21Qcm9maWxlEg0KBXRpdGxlGAEgASgJEhMKC2Rlc2NyaXB0aW9uGAIgASgJEhAKCGxvZ29fdXJsGAMgASgJGr8DCg5TdG9yYWdlU2V0dGluZxJOCgxzdG9yYWdlX3R5cGUYASABKA4yOC5tZW1vcy5hcGkudjEuSW5zdGFuY2VTZXR0aW5nLlN0b3JhZ2VTZXR0aW5nLlN0b3JhZ2VUeXBlEhkKEWZpbGVwYXRoX3RlbXBsYXRlGAIgASgJEhwKFHVwbG9hZF9zaXplX2xpbWl0X21iGAMgASgDEkgKCXMzX2NvbmZpZxgEIAEoCzI1Lm1lbW9zLmFwaS52MS5JbnN0YW5jZVNldHRpbmcuU3RvcmFnZVNldHRpbmcuUzNDb25maWcaiwEKCFMzQ29uZmlnEhUKDWFjY2Vzc19rZXlfaWQYASABKAkSHgoRYWNjZXNzX2tleV9zZWNyZXQYAiABKAlCA+BBBBIQCghlbmRwb2ludBgDIAEoCRIOCgZyZWdpb24YBCABKAkSDgoGYnVja2V0GAUgASgJEhYKDnVzZV9wYXRoX3N0eWxlGAYgASgIIkwKC1N0b3JhZ2VUeXBlEhwKGFNUT1JBR0VfVFlQRV9VTlNQRUNJRklFRBAAEgwKCERBVEFCQVNFEAESCQoFTE9DQUwQAhIGCgJTMxADGocBChJNZW1vUmVsYXRlZFNldHRpbmcSHAoUY29udGVudF9sZW5ndGhfbGltaXQYAyABKAUSIAoYZW5hYmxlX2RvdWJsZV9jbGlja19lZGl0GAQgASgIEhEKCXJlYWN0aW9ucxgHIAMoCUoECAIQA1IYZGlzcGxheV93aXRoX3VwZGF0ZV90aW1lGlEKC1RhZ01ldGFkYXRhEiwKEGJhY2tncm91bmRfY29sb3IYASABKAsyEi5nb29nbGUudHlwZS5Db2xvchIUCgxibHVyX2NvbnRlbnQYAiABKAgaqAEKC1RhZ3NTZXR0aW5nEkEKBHRhZ3MYASADKAsyMy5tZW1vcy5hcGkudjEuSW5zdGFuY2VTZXR0aW5nLlRhZ3NTZXR0aW5nLlRhZ3NFbnRyeRpWCglUYWdzRW50cnkSCwoDa2V5GAEgASgJEjgKBXZhbHVlGAIgASgLMikubWVtb3MuYXBpLnYxLkluc3RhbmNlU2V0dGluZy5UYWdNZXRhZGF0YToCOAEaugIKE05vdGlmaWNhdGlvblNldHRpbmcSTQoFZW1haWwYASABKAsyPi5tZW1vcy5hcGkudjEuSW5zdGFuY2VTZXR0aW5nLk5vdGlmaWNhdGlvblNldHRpbmcuRW1haWxTZXR0aW5nGtMBCgxFbWFpbFNldHRpbmcSDwoHZW5hYmxlZBgBIAEoCBIRCglzbXRwX2hvc3QYAiABKAkSEQoJc210cF9wb3J0GAMgASgFEhUKDXNtdHBfdXNlcm5hbWUYBCABKAkSGgoNc210cF9wYXNzd29yZBgFIAEoCUID4EEEEhIKCmZyb21fZW1haWwYBiABKAkSEQoJZnJvbV9uYW1lGAcgASgJEhAKCHJlcGx5X3RvGAggASgJEg8KB3VzZV90bHMYCSABKAgSDwoHdXNlX3NzbBgKIAEoCBqYAQoJQUlTZXR0aW5nEkEKCXByb3ZpZGVycxgBIAMoCzIuLm1lbW9zLmFwaS52MS5JbnN0YW5jZVNldHRpbmcuQUlQcm92aWRlckNvbmZpZxJICg10cmFuc2NyaXB0aW9uGAIgASgLMjEubWVtb3MuYXBpLnYxLkluc3RhbmNlU2V0dGluZy5UcmFuc2NyaXB0aW9uQ29uZmlnGsYBChBBSVByb3ZpZGVyQ29uZmlnEgoKAmlkGAEgASgJEg0KBXRpdGxlGAIgASgJEjoKBHR5cGUYAyABKA4yLC5tZW1vcy5hcGkudjEuSW5zdGFuY2VTZXR0aW5nLkFJUHJvdmlkZXJUeXBlEhAKCGVuZHBvaW50GAQgASgJEhQKB2FwaV9rZXkYBSABKAlCA+BBBBIYCgthcGlfa2V5X3NldBgIIAEoCEID4EEDEhkKDGFwaV9rZXlfaGludBgJIAEoCUID4EEDGlsKE1RyYW5zY3JpcHRpb25Db25maWcSEwoLcHJvdmlkZXJfaWQYASABKAkSDQoFbW9kZWwYAiABKAkSEAoIbGFuZ3VhZ2UYAyABKAkSDgoGcHJvbXB0GAQgASgJImoKA0tleRITCg9LRVlfVU5TUEVDSUZJRUQQABILCgdHRU5FUkFMEAESCwoHU1RPUkFHRRACEhAKDE1FTU9fUkVMQVRFRBADEggKBFRBR1MQBBIQCgxOT1RJRklDQVRJT04QBRIGCgJBSRAGIkoKDkFJUHJvdmlkZXJUeXBlEiAKHEFJX1BST1ZJREVSX1RZUEVfVU5TUEVDSUZJRUQQABIKCgZPUEVOQUkQARIKCgZHRU1JTkkQAjph6kFeChxtZW1vcy5hcGkudjEvSW5zdGFuY2VTZXR0aW5nEhtpbnN0YW5jZS9zZXR0aW5ncy97c2V0dGluZ30qEGluc3RhbmNlU2V0dGluZ3MyD2luc3RhbmNlU2V0dGluZ0IHCgV2YWx1ZSJPChlHZXRJbnN0YW5jZVNldHRpbmdSZXF1ZXN0EjIKBG5hbWUYASABKAlCJOBBAvpBHgocbWVtb3MuYXBpLnYxL0luc3RhbmNlU2V0dGluZyJWCh9CYXRjaEdldEluc3RhbmNlU2V0dGluZ3NSZXF1ZXN0EjMKBW5hbWVzGAEgAygJQiTgQQL6QR4KHG1lbW9zLmFwaS52MS9JbnN0YW5jZVNldHRpbmciUwogQmF0Y2hHZXRJbnN0YW5jZVNldHRpbmdzUmVzcG9uc2USLwoIc2V0dGluZ3MYASADKAsyHS5tZW1vcy5hcGkudjEuSW5zdGFuY2VTZXR0aW5nIokBChxVcGRhdGVJbnN0YW5jZVNldHRpbmdSZXF1ZXN0EjMKB3NldHRpbmcYASABKAsyHS5tZW1vcy5hcGkudjEuSW5zdGFuY2VTZXR0aW5nQgPgQQISNAoLdXBkYXRlX21hc2sYAiABKAsyGi5nb29nbGUucHJvdG9idWYuRmllbGRNYXNrQgPgQQEikwEKH1Rlc3RJbnN0YW5jZUVtYWlsU2V0dGluZ1JlcXVlc3QSUgoFZW1haWwYASABKAsyPi5tZW1vcy5hcGkudjEuSW5zdGFuY2VTZXR0aW5nLk5vdGlmaWNhdGlvblNldHRpbmcuRW1haWxTZXR0aW5nQgPgQQESHAoPcmVjaXBpZW50X2VtYWlsGAIgASgJQgPgQQEiGQoXR2V0SW5zdGFuY2VTdGF0c1JlcXVlc3Qi0gEKDUluc3RhbmNlU3RhdHMSOwoIZGF0YWJhc2UYASABKAsyKS5tZW1vcy5hcGkudjEuSW5zdGFuY2VTdGF0cy5EYXRhYmFzZVN0YXRzEhsKE2xvY2FsX3N0b3JhZ2VfYnl0ZXMYAiABKAMSMgoOZ2VuZXJhdGVkX3RpbWUYBCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wGjMKDURhdGFiYXNlU3RhdHMSDgoGZHJpdmVyGAEgASgJEhIKCnNpemVfYnl0ZXMYAiABKAMynwcKD0luc3RhbmNlU2VydmljZRJ+ChJHZXRJbnN0YW5jZVByb2ZpbGUSJy5tZW1vcy5hcGkudjEuR2V0SW5zdGFuY2VQcm9maWxlUmVxdWVzdBodLm1lbW9zLmFwaS52MS5JbnN0YW5jZVByb2ZpbGUiIILT5JMCGhIYL2FwaS92MS9pbnN0YW5jZS9wcm9maWxlEo8BChJHZXRJbnN0YW5jZVNldHRpbmcSJy5tZW1vcy5hcGkudjEuR2V0SW5zdGFuY2VTZXR0aW5nUmVxdWVzdBodLm1lbW9zLmFwaS52MS5JbnN0YW5jZVNldHRpbmciMdpBBG5hbWWC0+STAiQSIi9hcGkvdjEve25hbWU9aW5zdGFuY2Uvc2V0dGluZ3MvKn0SqAEKGEJhdGNoR2V0SW5zdGFuY2VTZXR0aW5ncxItLm1lbW9zLmFwaS52MS5CYXRjaEdldEluc3RhbmNlU2V0dGluZ3NSZXF1ZXN0Gi4ubWVtb3MuYXBpLnYxLkJhdGNoR2V0SW5zdGFuY2VTZXR0aW5nc1Jlc3BvbnNlIi2C0+STAic6ASoiIi9hcGkvdjEvaW5zdGFuY2Uvc2V0dGluZ3M6YmF0Y2hHZXQStQEKFVVwZGF0ZUluc3RhbmNlU2V0dGluZxIqLm1lbW9zLmFwaS52MS5VcGRhdGVJbnN0YW5jZVNldHRpbmdSZXF1ZXN0Gh0ubWVtb3MuYXBpLnYxLkluc3RhbmNlU2V0dGluZyJR2kETc2V0dGluZyx1cGRhdGVfbWFza4LT5JMCNToHc2V0dGluZzIqL2FwaS92MS97c2V0dGluZy5uYW1lPWluc3RhbmNlL3NldHRpbmdzLyp9Ep4BChhUZXN0SW5zdGFuY2VFbWFpbFNldHRpbmcSLS5tZW1vcy5hcGkudjEuVGVzdEluc3RhbmNlRW1haWxTZXR0aW5nUmVxdWVzdBoWLmdvb2dsZS5wcm90b2J1Zi5FbXB0eSI7gtPkkwI1OgEqIjAvYXBpL3YxL2luc3RhbmNlL3NldHRpbmdzL25vdGlmaWNhdGlvbjp0ZXN0RW1haWwSdgoQR2V0SW5zdGFuY2VTdGF0cxIlLm1lbW9zLmFwaS52MS5HZXRJbnN0YW5jZVN0YXRzUmVxdWVzdBobLm1lbW9zLmFwaS52MS5JbnN0YW5jZVN0YXRzIh6C0+STAhgSFi9hcGkvdjEvaW5zdGFuY2Uvc3RhdHNCrAEKEGNvbS5tZW1vcy5hcGkudjFCFEluc3RhbmNlU2VydmljZVByb3RvUAFaMGdpdGh1Yi5jb20vdXNlbWVtb3MvbWVtb3MvcHJvdG8vZ2VuL2FwaS92MTthcGl2MaICA01BWKoCDE1lbW9zLkFwaS5WMcoCDE1lbW9zXEFwaVxWMeICGE1lbW9zXEFwaVxWMVxHUEJNZXRhZGF0YeoCDk1lbW9zOjpBcGk6OlYxYgZwcm90bzM", [file_api_v1_user_service, file_google_api_annotations, file_google_api_client, file_google_api_field_behavior, file_google_api_resource, file_google_protobuf_empty, file_google_protobuf_field_mask, file_google_protobuf_timestamp, file_google_type_color]); /** * Instance profile message containing basic instance information. @@ -789,6 +789,49 @@ export type GetInstanceSettingRequest = Message<"memos.api.v1.GetInstanceSetting export const GetInstanceSettingRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_api_v1_instance_service, 3); +/** + * Request message for BatchGetInstanceSettings method. + * + * @generated from message memos.api.v1.BatchGetInstanceSettingsRequest + */ +export type BatchGetInstanceSettingsRequest = Message<"memos.api.v1.BatchGetInstanceSettingsRequest"> & { + /** + * The resource names of the instance settings. + * Format: instance/settings/{setting} + * + * @generated from field: repeated string names = 1; + */ + names: string[]; +}; + +/** + * Describes the message memos.api.v1.BatchGetInstanceSettingsRequest. + * Use `create(BatchGetInstanceSettingsRequestSchema)` to create a new message. + */ +export const BatchGetInstanceSettingsRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_api_v1_instance_service, 4); + +/** + * Response message for BatchGetInstanceSettings method. + * + * @generated from message memos.api.v1.BatchGetInstanceSettingsResponse + */ +export type BatchGetInstanceSettingsResponse = Message<"memos.api.v1.BatchGetInstanceSettingsResponse"> & { + /** + * The instance settings in the same order as the input names. + * + * @generated from field: repeated memos.api.v1.InstanceSetting settings = 1; + */ + settings: InstanceSetting[]; +}; + +/** + * Describes the message memos.api.v1.BatchGetInstanceSettingsResponse. + * Use `create(BatchGetInstanceSettingsResponseSchema)` to create a new message. + */ +export const BatchGetInstanceSettingsResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_api_v1_instance_service, 5); + /** * Request message for UpdateInstanceSetting method. * @@ -815,7 +858,7 @@ export type UpdateInstanceSettingRequest = Message<"memos.api.v1.UpdateInstanceS * Use `create(UpdateInstanceSettingRequestSchema)` to create a new message. */ export const UpdateInstanceSettingRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_api_v1_instance_service, 4); + messageDesc(file_api_v1_instance_service, 6); /** * Request message for TestInstanceEmailSetting method. @@ -843,7 +886,7 @@ export type TestInstanceEmailSettingRequest = Message<"memos.api.v1.TestInstance * Use `create(TestInstanceEmailSettingRequestSchema)` to create a new message. */ export const TestInstanceEmailSettingRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_api_v1_instance_service, 5); + messageDesc(file_api_v1_instance_service, 7); /** * Request message for GetInstanceStats. @@ -858,7 +901,7 @@ export type GetInstanceStatsRequest = Message<"memos.api.v1.GetInstanceStatsRequ * Use `create(GetInstanceStatsRequestSchema)` to create a new message. */ export const GetInstanceStatsRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_api_v1_instance_service, 6); + messageDesc(file_api_v1_instance_service, 8); /** * Resource usage statistics for the instance. @@ -891,7 +934,7 @@ export type InstanceStats = Message<"memos.api.v1.InstanceStats"> & { * Use `create(InstanceStatsSchema)` to create a new message. */ export const InstanceStatsSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_api_v1_instance_service, 7); + messageDesc(file_api_v1_instance_service, 9); /** * Database size statistics. @@ -919,7 +962,7 @@ export type InstanceStats_DatabaseStats = Message<"memos.api.v1.InstanceStats.Da * Use `create(InstanceStats_DatabaseStatsSchema)` to create a new message. */ export const InstanceStats_DatabaseStatsSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_api_v1_instance_service, 7, 0); + messageDesc(file_api_v1_instance_service, 9, 0); /** * @generated from service memos.api.v1.InstanceService @@ -945,6 +988,16 @@ export const InstanceService: GenService<{ input: typeof GetInstanceSettingRequestSchema; output: typeof InstanceSettingSchema; }, + /** + * Batch gets instance settings. + * + * @generated from rpc memos.api.v1.InstanceService.BatchGetInstanceSettings + */ + batchGetInstanceSettings: { + methodKind: "unary"; + input: typeof BatchGetInstanceSettingsRequestSchema; + output: typeof BatchGetInstanceSettingsResponseSchema; + }, /** * Updates an instance setting. * diff --git a/web/tests/filtered-memo-stats.test.ts b/web/tests/filtered-memo-stats.test.ts index 0e3f3a0b5..703822925 100644 --- a/web/tests/filtered-memo-stats.test.ts +++ b/web/tests/filtered-memo-stats.test.ts @@ -4,6 +4,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; // Mock dependencies BEFORE importing the hook under test. vi.mock("@/hooks/useUserQueries", () => ({ + useAllUserStats: vi.fn(), useUserStats: vi.fn(), })); vi.mock("@/hooks/useMemoQueries", () => ({ @@ -22,7 +23,7 @@ vi.mock("@/contexts/ViewContext", async () => { }; }); -import { useUserStats } from "@/hooks/useUserQueries"; +import { useAllUserStats, useUserStats } from "@/hooks/useUserQueries"; import { useFilteredMemoStats } from "@/hooks/useFilteredMemoStats"; const wrapper = ({ children }: { children: ReactNode }) => children as never; @@ -34,6 +35,10 @@ const ts = (year: number, month: number, day: number) => ({ describe("useFilteredMemoStats", () => { beforeEach(() => { + vi.mocked(useAllUserStats).mockReturnValue({ + data: [], + isLoading: false, + } as ReturnType); vi.mocked(useUserStats).mockReturnValue({ data: { memoCreatedTimestamps: [ts(2026, 5, 1), ts(2026, 5, 1), ts(2026, 5, 2)],