From 4b6f80596abce3ac84c9cfd107b2ffe8e39303dd Mon Sep 17 00:00:00 2001 From: memoclaw <265580040+memoclaw@users.noreply.github.com> Date: Wed, 25 Mar 2026 09:03:40 +0800 Subject: [PATCH] fix: batch user lookups and harden username resource handling --- proto/api/v1/shortcut_service.proto | 2 +- proto/api/v1/user_service.proto | 2 +- .../v1/apiv1connect/user_service.connect.go | 12 +- proto/gen/api/v1/shortcut_service.pb.go | 16 +-- proto/gen/api/v1/user_service.pb.go | 16 +-- proto/gen/api/v1/user_service_grpc.pb.go | 12 +- proto/gen/openapi.yaml | 12 +- server/router/api/v1/memo_service.go | 20 +++- .../router/api/v1/memo_service_converter.go | 13 ++- server/router/api/v1/user_resource_name.go | 6 +- server/router/api/v1/user_service.go | 103 +++++++----------- server/router/api/v1/user_service_stats.go | 43 ++++++-- server/router/mcp/tools_attachment.go | 41 ++++++- server/router/mcp/tools_memo.go | 77 ++++++++++++- server/router/mcp/tools_reaction.go | 10 +- web/src/hooks/useMemoFilters.ts | 6 +- .../types/proto/api/v1/shortcut_service_pb.ts | 12 +- web/src/types/proto/api/v1/user_service_pb.ts | 17 +-- 18 files changed, 269 insertions(+), 151 deletions(-) diff --git a/proto/api/v1/shortcut_service.proto b/proto/api/v1/shortcut_service.proto index d95740123..2e21845b6 100644 --- a/proto/api/v1/shortcut_service.proto +++ b/proto/api/v1/shortcut_service.proto @@ -52,7 +52,7 @@ service ShortcutService { message Shortcut { option (google.api.resource) = { type: "memos.api.v1/Shortcut" - pattern: "users/{user}/shortcuts/{shortcut}" + pattern: "users/{username}/shortcuts/{shortcut}" singular: "shortcut" plural: "shortcuts" }; diff --git a/proto/api/v1/user_service.proto b/proto/api/v1/user_service.proto index 14d038f22..ac24d6c9d 100644 --- a/proto/api/v1/user_service.proto +++ b/proto/api/v1/user_service.proto @@ -357,7 +357,7 @@ message ListAllUserStatsResponse { message UserSetting { option (google.api.resource) = { type: "memos.api.v1/UserSetting" - pattern: "users/{user}/settings/{setting}" + pattern: "users/{username}/settings/{setting}" singular: "userSetting" plural: "userSettings" }; diff --git a/proto/gen/api/v1/apiv1connect/user_service.connect.go b/proto/gen/api/v1/apiv1connect/user_service.connect.go index bad608ffb..d93b9bfa1 100644 --- a/proto/gen/api/v1/apiv1connect/user_service.connect.go +++ b/proto/gen/api/v1/apiv1connect/user_service.connect.go @@ -95,10 +95,8 @@ const ( type UserServiceClient interface { // ListUsers returns a list of users. ListUsers(context.Context, *connect.Request[v1.ListUsersRequest]) (*connect.Response[v1.ListUsersResponse], error) - // GetUser gets a user by ID or username. - // Supports both numeric IDs and username strings: - // - users/{id} (e.g., users/101) - // - users/{username} (e.g., users/steven) + // GetUser gets a user by username. + // Format: users/{username} (e.g., users/steven) GetUser(context.Context, *connect.Request[v1.GetUserRequest]) (*connect.Response[v1.User], error) // CreateUser creates a new user. CreateUser(context.Context, *connect.Request[v1.CreateUserRequest]) (*connect.Response[v1.User], error) @@ -402,10 +400,8 @@ func (c *userServiceClient) DeleteUserNotification(ctx context.Context, req *con type UserServiceHandler interface { // ListUsers returns a list of users. ListUsers(context.Context, *connect.Request[v1.ListUsersRequest]) (*connect.Response[v1.ListUsersResponse], error) - // GetUser gets a user by ID or username. - // Supports both numeric IDs and username strings: - // - users/{id} (e.g., users/101) - // - users/{username} (e.g., users/steven) + // GetUser gets a user by username. + // Format: users/{username} (e.g., users/steven) GetUser(context.Context, *connect.Request[v1.GetUserRequest]) (*connect.Response[v1.User], error) // CreateUser creates a new user. CreateUser(context.Context, *connect.Request[v1.CreateUserRequest]) (*connect.Response[v1.User], error) diff --git a/proto/gen/api/v1/shortcut_service.pb.go b/proto/gen/api/v1/shortcut_service.pb.go index 708078b93..6550fdbd9 100644 --- a/proto/gen/api/v1/shortcut_service.pb.go +++ b/proto/gen/api/v1/shortcut_service.pb.go @@ -27,7 +27,7 @@ const ( type Shortcut struct { state protoimpl.MessageState `protogen:"open.v1"` // The resource name of the shortcut. - // Format: users/{user}/shortcuts/{shortcut} + // Format: users/{username}/shortcuts/{shortcut} Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // The title of the shortcut. Title string `protobuf:"bytes,2,opt,name=title,proto3" json:"title,omitempty"` @@ -91,7 +91,7 @@ func (x *Shortcut) GetFilter() string { type ListShortcutsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Required. The parent resource where shortcuts are listed. - // Format: users/{user} + // Format: users/{username} Parent string `protobuf:"bytes,1,opt,name=parent,proto3" json:"parent,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -182,7 +182,7 @@ func (x *ListShortcutsResponse) GetShortcuts() []*Shortcut { type GetShortcutRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Required. The resource name of the shortcut to retrieve. - // Format: users/{user}/shortcuts/{shortcut} + // Format: users/{username}/shortcuts/{shortcut} Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -228,7 +228,7 @@ func (x *GetShortcutRequest) GetName() string { type CreateShortcutRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Required. The parent resource where this shortcut will be created. - // Format: users/{user} + // Format: users/{username} Parent string `protobuf:"bytes,1,opt,name=parent,proto3" json:"parent,omitempty"` // Required. The shortcut to create. Shortcut *Shortcut `protobuf:"bytes,2,opt,name=shortcut,proto3" json:"shortcut,omitempty"` @@ -346,7 +346,7 @@ func (x *UpdateShortcutRequest) GetUpdateMask() *fieldmaskpb.FieldMask { type DeleteShortcutRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Required. The resource name of the shortcut to delete. - // Format: users/{user}/shortcuts/{shortcut} + // Format: users/{username}/shortcuts/{shortcut} Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -393,12 +393,12 @@ var File_api_v1_shortcut_service_proto protoreflect.FileDescriptor const file_api_v1_shortcut_service_proto_rawDesc = "" + "\n" + - "\x1dapi/v1/shortcut_service.proto\x12\fmemos.api.v1\x1a\x1cgoogle/api/annotations.proto\x1a\x17google/api/client.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a\x19google/api/resource.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a google/protobuf/field_mask.proto\"\xaf\x01\n" + + "\x1dapi/v1/shortcut_service.proto\x12\fmemos.api.v1\x1a\x1cgoogle/api/annotations.proto\x1a\x17google/api/client.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a\x19google/api/resource.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a google/protobuf/field_mask.proto\"\xb3\x01\n" + "\bShortcut\x12\x17\n" + "\x04name\x18\x01 \x01(\tB\x03\xe0A\bR\x04name\x12\x19\n" + "\x05title\x18\x02 \x01(\tB\x03\xe0A\x02R\x05title\x12\x1b\n" + - "\x06filter\x18\x03 \x01(\tB\x03\xe0A\x01R\x06filter:R\xeaAO\n" + - "\x15memos.api.v1/Shortcut\x12!users/{user}/shortcuts/{shortcut}*\tshortcuts2\bshortcut\"M\n" + + "\x06filter\x18\x03 \x01(\tB\x03\xe0A\x01R\x06filter:V\xeaAS\n" + + "\x15memos.api.v1/Shortcut\x12%users/{username}/shortcuts/{shortcut}*\tshortcuts2\bshortcut\"M\n" + "\x14ListShortcutsRequest\x125\n" + "\x06parent\x18\x01 \x01(\tB\x1d\xe0A\x02\xfaA\x17\x12\x15memos.api.v1/ShortcutR\x06parent\"M\n" + "\x15ListShortcutsResponse\x124\n" + diff --git a/proto/gen/api/v1/user_service.pb.go b/proto/gen/api/v1/user_service.pb.go index bb210e8ab..3ecc78b60 100644 --- a/proto/gen/api/v1/user_service.pb.go +++ b/proto/gen/api/v1/user_service.pb.go @@ -506,11 +506,7 @@ func (x *ListUsersResponse) GetTotalSize() int32 { type GetUserRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Required. The resource name of the user. - // Supports both numeric IDs and username strings: - // - users/{id} (e.g., users/101) - // - users/{username} (e.g., users/steven) - // - // Format: users/{id_or_username} + // Format: users/{username} Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // Optional. The fields to return in the response. // If not specified, all fields are returned. @@ -979,8 +975,8 @@ func (x *ListAllUserStatsResponse) GetStats() []*UserStats { type UserSetting struct { state protoimpl.MessageState `protogen:"open.v1"` // The name of the user setting. - // Format: users/{user}/settings/{setting}, {setting} is the key for the setting. - // For example, "users/123/settings/GENERAL" for general settings. + // Format: users/{username}/settings/{setting}, {setting} is the key for the setting. + // For example, "users/steven/settings/GENERAL" for general settings. Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // Types that are valid to be assigned to Value: // @@ -2658,7 +2654,7 @@ const file_api_v1_user_service_proto_rawDesc = "" + "\x11memos.api.v1/UserR\x04name\"\x19\n" + "\x17ListAllUserStatsRequest\"I\n" + "\x18ListAllUserStatsResponse\x12-\n" + - "\x05stats\x18\x01 \x03(\v2\x17.memos.api.v1.UserStatsR\x05stats\"\xb0\x04\n" + + "\x05stats\x18\x01 \x03(\v2\x17.memos.api.v1.UserStatsR\x05stats\"\xb4\x04\n" + "\vUserSetting\x12\x17\n" + "\x04name\x18\x01 \x01(\tB\x03\xe0A\bR\x04name\x12S\n" + "\x0fgeneral_setting\x18\x02 \x01(\v2(.memos.api.v1.UserSetting.GeneralSettingH\x00R\x0egeneralSetting\x12V\n" + @@ -2672,8 +2668,8 @@ const file_api_v1_user_service_proto_rawDesc = "" + "\x03Key\x12\x13\n" + "\x0fKEY_UNSPECIFIED\x10\x00\x12\v\n" + "\aGENERAL\x10\x01\x12\f\n" + - "\bWEBHOOKS\x10\x04:Y\xeaAV\n" + - "\x18memos.api.v1/UserSetting\x12\x1fusers/{user}/settings/{setting}*\fuserSettings2\vuserSettingB\a\n" + + "\bWEBHOOKS\x10\x04:]\xeaAZ\n" + + "\x18memos.api.v1/UserSetting\x12#users/{username}/settings/{setting}*\fuserSettings2\vuserSettingB\a\n" + "\x05value\"M\n" + "\x15GetUserSettingRequest\x124\n" + "\x04name\x18\x01 \x01(\tB \xe0A\x02\xfaA\x1a\n" + diff --git a/proto/gen/api/v1/user_service_grpc.pb.go b/proto/gen/api/v1/user_service_grpc.pb.go index 09d5274e7..332709d3f 100644 --- a/proto/gen/api/v1/user_service_grpc.pb.go +++ b/proto/gen/api/v1/user_service_grpc.pb.go @@ -48,10 +48,8 @@ const ( type UserServiceClient interface { // ListUsers returns a list of users. ListUsers(ctx context.Context, in *ListUsersRequest, opts ...grpc.CallOption) (*ListUsersResponse, error) - // GetUser gets a user by ID or username. - // Supports both numeric IDs and username strings: - // - users/{id} (e.g., users/101) - // - users/{username} (e.g., users/steven) + // GetUser gets a user by username. + // Format: users/{username} (e.g., users/steven) GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*User, error) // CreateUser creates a new user. CreateUser(ctx context.Context, in *CreateUserRequest, opts ...grpc.CallOption) (*User, error) @@ -307,10 +305,8 @@ func (c *userServiceClient) DeleteUserNotification(ctx context.Context, in *Dele type UserServiceServer interface { // ListUsers returns a list of users. ListUsers(context.Context, *ListUsersRequest) (*ListUsersResponse, error) - // GetUser gets a user by ID or username. - // Supports both numeric IDs and username strings: - // - users/{id} (e.g., users/101) - // - users/{username} (e.g., users/steven) + // GetUser gets a user by username. + // Format: users/{username} (e.g., users/steven) GetUser(context.Context, *GetUserRequest) (*User, error) // CreateUser creates a new user. CreateUser(context.Context, *CreateUserRequest) (*User, error) diff --git a/proto/gen/openapi.yaml b/proto/gen/openapi.yaml index d8a033ae0..6a6901410 100644 --- a/proto/gen/openapi.yaml +++ b/proto/gen/openapi.yaml @@ -1206,10 +1206,8 @@ paths: tags: - UserService description: |- - GetUser gets a user by ID or username. - Supports both numeric IDs and username strings: - - users/{id} (e.g., users/101) - - users/{username} (e.g., users/steven) + GetUser gets a user by username. + Format: users/{username} (e.g., users/steven) operationId: UserService_GetUser parameters: - name: user @@ -2939,7 +2937,7 @@ components: type: string description: |- The resource name of the shortcut. - Format: users/{user}/shortcuts/{shortcut} + Format: users/{username}/shortcuts/{shortcut} title: type: string description: The title of the shortcut. @@ -3178,8 +3176,8 @@ components: type: string description: |- The name of the user setting. - Format: users/{user}/settings/{setting}, {setting} is the key for the setting. - For example, "users/123/settings/GENERAL" for general settings. + Format: users/{username}/settings/{setting}, {setting} is the key for the setting. + For example, "users/steven/settings/GENERAL" for general settings. generalSetting: $ref: '#/components/schemas/UserSetting_GeneralSetting' webhooksSetting: diff --git a/server/router/api/v1/memo_service.go b/server/router/api/v1/memo_service.go index 2a9485edf..03890ae15 100644 --- a/server/router/api/v1/memo_service.go +++ b/server/router/api/v1/memo_service.go @@ -278,6 +278,14 @@ func (s *APIV1Service) ListMemos(ctx context.Context, request *v1pb.ListMemosReq if err != nil { return nil, status.Errorf(codes.Internal, "failed to batch load memo relations") } + creatorIDs := make([]int32, 0, len(memos)) + for _, memo := range memos { + creatorIDs = append(creatorIDs, memo.CreatorID) + } + creatorMap, err := s.listUsersByID(ctx, creatorIDs) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to list memo creators: %v", err) + } for _, memo := range memos { memoName := fmt.Sprintf("%s%s", MemoNamePrefix, memo.UID) @@ -285,7 +293,7 @@ func (s *APIV1Service) ListMemos(ctx context.Context, request *v1pb.ListMemosReq attachments := attachmentMap[memo.ID] relations := relationMap[memo.ID] - memoMessage, err := s.convertMemoFromStore(ctx, memo, reactions, attachments, relations) + memoMessage, err := s.convertMemoFromStoreWithCreators(ctx, memo, reactions, attachments, relations, creatorMap) if err != nil { return nil, errors.Wrap(err, "failed to convert memo") } @@ -753,6 +761,14 @@ func (s *APIV1Service) ListMemoComments(ctx context.Context, request *v1pb.ListM if err != nil { return nil, status.Errorf(codes.Internal, "failed to batch load memo relations") } + creatorIDs := make([]int32, 0, len(memos)) + for _, memo := range memos { + creatorIDs = append(creatorIDs, memo.CreatorID) + } + creatorMap, err := s.listUsersByID(ctx, creatorIDs) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to list memo creators: %v", err) + } var memosResponse []*v1pb.Memo for _, m := range memos { @@ -761,7 +777,7 @@ func (s *APIV1Service) ListMemoComments(ctx context.Context, request *v1pb.ListM attachments := attachmentMap[m.ID] relations := relationMap[m.ID] - memoMessage, err := s.convertMemoFromStore(ctx, m, reactions, attachments, relations) + memoMessage, err := s.convertMemoFromStoreWithCreators(ctx, m, reactions, attachments, relations, creatorMap) if err != nil { return nil, errors.Wrap(err, "failed to convert memo") } diff --git a/server/router/api/v1/memo_service_converter.go b/server/router/api/v1/memo_service_converter.go index 60e72d015..d20588a7a 100644 --- a/server/router/api/v1/memo_service_converter.go +++ b/server/router/api/v1/memo_service_converter.go @@ -14,6 +14,14 @@ import ( ) func (s *APIV1Service) convertMemoFromStore(ctx context.Context, memo *store.Memo, reactions []*store.Reaction, attachments []*store.Attachment, relations []*v1pb.MemoRelation) (*v1pb.Memo, error) { + creatorMap, err := s.listUsersByID(ctx, []int32{memo.CreatorID}) + if err != nil { + return nil, errors.Wrap(err, "failed to list memo creators") + } + return s.convertMemoFromStoreWithCreators(ctx, memo, reactions, attachments, relations, creatorMap) +} + +func (s *APIV1Service) convertMemoFromStoreWithCreators(ctx context.Context, memo *store.Memo, reactions []*store.Reaction, attachments []*store.Attachment, relations []*v1pb.MemoRelation, creatorMap map[int32]*store.User) (*v1pb.Memo, error) { displayTs := memo.CreatedTs instanceMemoRelatedSetting, err := s.Store.GetInstanceMemoRelatedSetting(ctx) if err != nil { @@ -24,10 +32,7 @@ func (s *APIV1Service) convertMemoFromStore(ctx context.Context, memo *store.Mem } name := fmt.Sprintf("%s%s", MemoNamePrefix, memo.UID) - creator, err := s.Store.GetUser(ctx, &store.FindUser{ID: &memo.CreatorID}) - if err != nil { - return nil, errors.Wrap(err, "failed to get memo creator") - } + creator := creatorMap[memo.CreatorID] if creator == nil { return nil, errors.New("memo creator not found") } diff --git a/server/router/api/v1/user_resource_name.go b/server/router/api/v1/user_resource_name.go index 9445b719c..cf91e5be4 100644 --- a/server/router/api/v1/user_resource_name.go +++ b/server/router/api/v1/user_resource_name.go @@ -41,5 +41,9 @@ func ResolveUserByName(ctx context.Context, stores *store.Store, name string) (* if err != nil { return nil, err } - return stores.GetUser(ctx, &store.FindUser{Username: &username}) + user, err := stores.GetUser(ctx, &store.FindUser{Username: &username}) + if err != nil { + return nil, errors.Wrap(err, "resolve user by name: GetUser failed") + } + return user, nil } diff --git a/server/router/api/v1/user_service.go b/server/router/api/v1/user_service.go index d9102f947..fd0e17564 100644 --- a/server/router/api/v1/user_service.go +++ b/server/router/api/v1/user_service.go @@ -165,6 +165,12 @@ func (s *APIV1Service) UpdateUser(ctx context.Context, request *v1pb.UpdateUserR if err != nil { return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err) } + if user == nil { + if request.AllowMissing { + return nil, status.Errorf(codes.NotFound, "user not found") + } + return nil, status.Errorf(codes.NotFound, "user not found") + } userID := user.ID currentUser, err := s.fetchCurrentUser(ctx) if err != nil { @@ -179,15 +185,6 @@ func (s *APIV1Service) UpdateUser(ctx context.Context, request *v1pb.UpdateUserR return nil, status.Errorf(codes.PermissionDenied, "permission denied") } - if user == nil { - // Handle allow_missing field - if request.AllowMissing { - // Could create user if missing, but for now return not found - return nil, status.Errorf(codes.NotFound, "user not found") - } - return nil, status.Errorf(codes.NotFound, "user not found") - } - currentTs := time.Now().Unix() update := &store.UpdateUser{ ID: user.ID, @@ -271,6 +268,9 @@ func (s *APIV1Service) DeleteUser(ctx context.Context, request *v1pb.DeleteUserR if err != nil { return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err) } + if user == nil { + return nil, status.Errorf(codes.NotFound, "user not found") + } userID := user.ID currentUser, err := s.fetchCurrentUser(ctx) if err != nil { @@ -283,10 +283,6 @@ func (s *APIV1Service) DeleteUser(ctx context.Context, request *v1pb.DeleteUserR return nil, status.Errorf(codes.PermissionDenied, "permission denied") } - if user == nil { - return nil, status.Errorf(codes.NotFound, "user not found") - } - if err := s.Store.DeleteUser(ctx, &store.DeleteUser{ ID: user.ID, }); err != nil { @@ -991,26 +987,6 @@ func extractImageInfo(dataURI string) (string, string, error) { return imageType, base64Data, nil } -// Helper functions for user settings - -// ExtractUserIDAndSettingKeyFromName extracts user ID and setting key from a legacy numeric resource name. -// e.g., "users/123/settings/general" -> 123, "general". -func ExtractUserIDAndSettingKeyFromName(name string) (int32, string, error) { - // Expected format: users/{user}/settings/{setting} - parts := strings.Split(name, "/") - if len(parts) != 4 || parts[0] != "users" || parts[2] != "settings" { - return 0, "", errors.Errorf("invalid resource name format: %s", name) - } - - userID, err := util.ConvertStringToInt32(parts[1]) - if err != nil { - return 0, "", errors.Errorf("invalid user ID: %s", parts[1]) - } - - settingKey := parts[3] - return userID, settingKey, nil -} - // convertSettingKeyToStore converts API setting key to store enum. func convertSettingKeyToStore(key string) (storepb.UserSetting_Key, error) { switch key { @@ -1084,14 +1060,17 @@ func convertUserSettingFromStore(storeSetting *storepb.UserSetting, user *store. } case storepb.UserSetting_WEBHOOKS: webhooks := storeSetting.GetWebhooks() - apiWebhooks := make([]*v1pb.UserWebhook, 0, len(webhooks.Webhooks)) - for _, webhook := range webhooks.Webhooks { - apiWebhook := &v1pb.UserWebhook{ - Name: fmt.Sprintf("%s/webhooks/%s", BuildUserName(user.Username), webhook.Id), - Url: webhook.Url, - DisplayName: webhook.Title, + apiWebhooks := make([]*v1pb.UserWebhook, 0) + if webhooks != nil { + apiWebhooks = make([]*v1pb.UserWebhook, 0, len(webhooks.Webhooks)) + for _, webhook := range webhooks.Webhooks { + apiWebhook := &v1pb.UserWebhook{ + Name: fmt.Sprintf("%s/webhooks/%s", BuildUserName(user.Username), webhook.Id), + Url: webhook.Url, + DisplayName: webhook.Title, + } + apiWebhooks = append(apiWebhooks, apiWebhook) } - apiWebhooks = append(apiWebhooks, apiWebhook) } setting.Value = &v1pb.UserSetting_WebhooksSetting_{ WebhooksSetting: &v1pb.UserSetting_WebhooksSetting{ @@ -1290,10 +1269,19 @@ func (s *APIV1Service) ListUserNotifications(ctx context.Context, request *v1pb. return nil, status.Errorf(codes.Internal, "failed to list inboxes: %v", err) } - // Convert storage layer inboxes to API notifications + // Convert storage layer inboxes to API notifications. + userIDs := make([]int32, 0, len(inboxes)*2) + for _, inbox := range inboxes { + userIDs = append(userIDs, inbox.ReceiverID, inbox.SenderID) + } + usersByID, err := s.listUsersByID(ctx, userIDs) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to list notification users: %v", err) + } + notifications := []*v1pb.UserNotification{} for _, inbox := range inboxes { - notification, err := s.convertInboxToUserNotification(ctx, inbox) + notification, err := s.convertInboxToUserNotificationWithUsers(ctx, inbox, usersByID) if err != nil { return nil, status.Errorf(codes.Internal, "failed to convert inbox: %v", err) } @@ -1426,17 +1414,19 @@ func (s *APIV1Service) DeleteUserNotification(ctx context.Context, request *v1pb // convertInboxToUserNotification converts a storage-layer inbox to an API notification. // This handles the mapping between the internal inbox representation and the public API. func (s *APIV1Service) convertInboxToUserNotification(ctx context.Context, inbox *store.Inbox) (*v1pb.UserNotification, error) { - receiver, err := s.Store.GetUser(ctx, &store.FindUser{ID: &inbox.ReceiverID}) + usersByID, err := s.listUsersByID(ctx, []int32{inbox.ReceiverID, inbox.SenderID}) if err != nil { - return nil, status.Errorf(codes.Internal, "failed to get notification receiver: %v", err) + return nil, status.Errorf(codes.Internal, "failed to list notification users: %v", err) } + return s.convertInboxToUserNotificationWithUsers(ctx, inbox, usersByID) +} + +func (s *APIV1Service) convertInboxToUserNotificationWithUsers(ctx context.Context, inbox *store.Inbox, usersByID map[int32]*store.User) (*v1pb.UserNotification, error) { + receiver := usersByID[inbox.ReceiverID] if receiver == nil { return nil, status.Errorf(codes.NotFound, "notification receiver not found") } - sender, err := s.Store.GetUser(ctx, &store.FindUser{ID: &inbox.SenderID}) - if err != nil { - return nil, status.Errorf(codes.Internal, "failed to get notification sender: %v", err) - } + sender := usersByID[inbox.SenderID] if sender == nil { return nil, status.Errorf(codes.NotFound, "notification sender not found") } @@ -1513,20 +1503,3 @@ func (s *APIV1Service) convertUserNotificationPayload(ctx context.Context, messa RelatedMemo: fmt.Sprintf("%s%s", MemoNamePrefix, relatedMemo.UID), }, nil } - -// ExtractNotificationIDFromName extracts the notification ID from a resource name. -// Expected format: users/{user_id}/notifications/{notification_id}. -func ExtractNotificationIDFromName(name string) (int32, error) { - pattern := regexp.MustCompile(`^users/(\d+)/notifications/(\d+)$`) - matches := pattern.FindStringSubmatch(name) - if len(matches) != 3 { - return 0, errors.Errorf("invalid notification name: %s", name) - } - - id, err := strconv.Atoi(matches[2]) - if err != nil { - return 0, errors.Errorf("invalid notification id: %s", matches[2]) - } - - return int32(id), nil -} diff --git a/server/router/api/v1/user_service_stats.go b/server/router/api/v1/user_service_stats.go index f8f1e660a..c953d8b32 100644 --- a/server/router/api/v1/user_service_stats.go +++ b/server/router/api/v1/user_service_stats.go @@ -3,7 +3,6 @@ package v1 import ( "context" "fmt" - "strings" "time" "github.com/pkg/errors" @@ -15,18 +14,41 @@ import ( "github.com/usememos/memos/store" ) -func (s *APIV1Service) listUsernamesByID(ctx context.Context, userIDs []int32) (map[int32]string, error) { +func (s *APIV1Service) listUsersByID(ctx context.Context, userIDs []int32) (map[int32]*store.User, error) { if len(userIDs) == 0 { - return map[int32]string{}, nil + return map[int32]*store.User{}, nil } - users, err := s.Store.ListUsers(ctx, &store.FindUser{IDList: userIDs}) + uniqueUserIDs := make([]int32, 0, len(userIDs)) + seenUserIDs := make(map[int32]struct{}, len(userIDs)) + for _, userID := range userIDs { + if _, seen := seenUserIDs[userID]; seen { + continue + } + seenUserIDs[userID] = struct{}{} + uniqueUserIDs = append(uniqueUserIDs, userID) + } + + users, err := s.Store.ListUsers(ctx, &store.FindUser{IDList: uniqueUserIDs}) if err != nil { return nil, err } - usernamesByID := make(map[int32]string, len(users)) + usersByID := make(map[int32]*store.User, len(users)) for _, user := range users { + usersByID[user.ID] = user + } + return usersByID, nil +} + +func (s *APIV1Service) listUsernamesByID(ctx context.Context, userIDs []int32) (map[int32]string, error) { + usersByID, err := s.listUsersByID(ctx, userIDs) + if err != nil { + return nil, err + } + + usernamesByID := make(map[int32]string, len(usersByID)) + for _, user := range usersByID { usernamesByID[user.ID] = user.Username } return usernamesByID, nil @@ -62,6 +84,7 @@ func (s *APIV1Service) ListAllUserStats(ctx context.Context, _ *v1pb.ListAllUser } userMemoStatMap := make(map[int32]*v1pb.UserStats) + pinnedMemoIDsByUserID := make(map[int32][]int32) limit := 1000 offset := 0 memoFind.Limit = &limit @@ -128,7 +151,7 @@ func (s *APIV1Service) ListAllUserStats(ctx context.Context, _ *v1pb.ListAllUser // Track pinned memos if memo.Pinned { - stats.PinnedMemos = append(stats.PinnedMemos, fmt.Sprintf("users/%d/memos/%d", memo.CreatorID, memo.ID)) + pinnedMemoIDsByUserID[memo.CreatorID] = append(pinnedMemoIDsByUserID[memo.CreatorID], memo.ID) } } @@ -150,12 +173,8 @@ func (s *APIV1Service) ListAllUserStats(ctx context.Context, _ *v1pb.ListAllUser return nil, status.Errorf(codes.Internal, "failed to resolve user stats name") } userMemoStat.Name = fmt.Sprintf("%s/stats", BuildUserName(username)) - for i, pinnedMemo := range userMemoStat.PinnedMemos { - parts := strings.Split(pinnedMemo, "/") - if len(parts) != 4 { - return nil, status.Errorf(codes.Internal, "failed to resolve pinned memo name") - } - userMemoStat.PinnedMemos[i] = fmt.Sprintf("%s/memos/%s", BuildUserName(username), parts[3]) + for _, memoID := range pinnedMemoIDsByUserID[userID] { + userMemoStat.PinnedMemos = append(userMemoStat.PinnedMemos, fmt.Sprintf("%s/memos/%d", BuildUserName(username), memoID)) } userMemoStats = append(userMemoStats, userMemoStat) } diff --git a/server/router/mcp/tools_attachment.go b/server/router/mcp/tools_attachment.go index 4358feab2..5e41197b3 100644 --- a/server/router/mcp/tools_attachment.go +++ b/server/router/mcp/tools_attachment.go @@ -57,6 +57,37 @@ func storeAttachmentToJSON(ctx context.Context, stores *store.Store, a *store.At return j, nil } +func storeAttachmentToJSONWithUsernames(a *store.Attachment, usernamesByID map[int32]string) (attachmentJSON, error) { + creator, err := lookupUsernameFromCache(usernamesByID, a.CreatorID) + if err != nil { + return attachmentJSON{}, err + } + j := attachmentJSON{ + Name: "attachments/" + a.UID, + Creator: creator, + CreateTime: a.CreatedTs, + Filename: a.Filename, + Type: a.Type, + Size: a.Size, + } + switch a.StorageType { + case storepb.AttachmentStorageType_LOCAL: + j.StorageType = "LOCAL" + case storepb.AttachmentStorageType_S3: + j.StorageType = "S3" + j.ExternalLink = a.Reference + case storepb.AttachmentStorageType_EXTERNAL: + j.StorageType = "EXTERNAL" + j.ExternalLink = a.Reference + default: + j.StorageType = "DATABASE" + } + if a.MemoUID != nil && *a.MemoUID != "" { + j.Memo = "memos/" + *a.MemoUID + } + return j, nil +} + func parseAttachmentUID(name string) (string, error) { uid, ok := strings.CutPrefix(name, "attachments/") if !ok || uid == "" { @@ -140,10 +171,18 @@ func (s *MCPService) handleListAttachments(ctx context.Context, req mcp.CallTool if hasMore { attachments = attachments[:pageSize] } + creatorIDs := make([]int32, 0, len(attachments)) + for _, attachment := range attachments { + creatorIDs = append(creatorIDs, attachment.CreatorID) + } + usernamesByID, err := preloadUsernames(ctx, s.store, creatorIDs) + if err != nil { + return mcp.NewToolResultError(fmt.Sprintf("failed to preload attachment creators: %v", err)), nil + } results := make([]attachmentJSON, len(attachments)) for i, a := range attachments { - result, err := storeAttachmentToJSON(ctx, s.store, a) + result, err := storeAttachmentToJSONWithUsernames(a, usernamesByID) if err != nil { return mcp.NewToolResultError(fmt.Sprintf("failed to resolve attachment creator: %v", err)), nil } diff --git a/server/router/mcp/tools_memo.go b/server/router/mcp/tools_memo.go index cae7c9c15..2fab7fbe5 100644 --- a/server/router/mcp/tools_memo.go +++ b/server/router/mcp/tools_memo.go @@ -113,6 +113,41 @@ func lookupUsername(ctx context.Context, stores *store.Store, userID int32) (str return "users/" + user.Username, nil } +func preloadUsernames(ctx context.Context, stores *store.Store, userIDs []int32) (map[int32]string, error) { + if len(userIDs) == 0 { + return map[int32]string{}, nil + } + + uniqueUserIDs := make([]int32, 0, len(userIDs)) + seenUserIDs := make(map[int32]struct{}, len(userIDs)) + for _, userID := range userIDs { + if _, seen := seenUserIDs[userID]; seen { + continue + } + seenUserIDs[userID] = struct{}{} + uniqueUserIDs = append(uniqueUserIDs, userID) + } + + users, err := stores.ListUsers(ctx, &store.FindUser{IDList: uniqueUserIDs}) + if err != nil { + return nil, errors.Wrap(err, "failed to list creator users") + } + + usernamesByID := make(map[int32]string, len(users)) + for _, user := range users { + usernamesByID[user.ID] = "users/" + user.Username + } + return usernamesByID, nil +} + +func lookupUsernameFromCache(usernamesByID map[int32]string, userID int32) (string, error) { + username, ok := usernamesByID[userID] + if !ok { + return "", errors.Errorf("creator user %d not found", userID) + } + return username, nil +} + func storeMemoToJSONWithStore(ctx context.Context, stores *store.Store, m *store.Memo) (memoJSON, error) { j := storeMemoToJSON(m) creator, err := lookupUsername(ctx, stores, m.CreatorID) @@ -123,6 +158,16 @@ func storeMemoToJSONWithStore(ctx context.Context, stores *store.Store, m *store return j, nil } +func storeMemoToJSONWithUsernames(m *store.Memo, usernamesByID map[int32]string) (memoJSON, error) { + j := storeMemoToJSON(m) + creator, err := lookupUsernameFromCache(usernamesByID, m.CreatorID) + if err != nil { + return memoJSON{}, err + } + j.Creator = creator + return j, nil +} + // checkMemoAccess returns an error if the caller cannot read memo. // userID == 0 means anonymous. func checkMemoAccess(memo *store.Memo, userID int32) error { @@ -306,10 +351,18 @@ func (s *MCPService) handleListMemos(ctx context.Context, req mcp.CallToolReques if hasMore { memos = memos[:pageSize] } + creatorIDs := make([]int32, 0, len(memos)) + for _, memo := range memos { + creatorIDs = append(creatorIDs, memo.CreatorID) + } + usernamesByID, err := preloadUsernames(ctx, s.store, creatorIDs) + if err != nil { + return mcp.NewToolResultError(fmt.Sprintf("failed to preload memo creators: %v", err)), nil + } results := make([]memoJSON, len(memos)) for i, m := range memos { - result, err := storeMemoToJSONWithStore(ctx, s.store, m) + result, err := storeMemoToJSONWithUsernames(m, usernamesByID) if err != nil { return mcp.NewToolResultError(fmt.Sprintf("failed to resolve memo creator: %v", err)), nil } @@ -514,10 +567,18 @@ func (s *MCPService) handleSearchMemos(ctx context.Context, req mcp.CallToolRequ if err != nil { return mcp.NewToolResultError(fmt.Sprintf("failed to search memos: %v", err)), nil } + creatorIDs := make([]int32, 0, len(memos)) + for _, memo := range memos { + creatorIDs = append(creatorIDs, memo.CreatorID) + } + usernamesByID, err := preloadUsernames(ctx, s.store, creatorIDs) + if err != nil { + return mcp.NewToolResultError(fmt.Sprintf("failed to preload memo creators: %v", err)), nil + } results := make([]memoJSON, len(memos)) for i, m := range memos { - result, err := storeMemoToJSONWithStore(ctx, s.store, m) + result, err := storeMemoToJSONWithUsernames(m, usernamesByID) if err != nil { return mcp.NewToolResultError(fmt.Sprintf("failed to resolve memo creator: %v", err)), nil } @@ -571,11 +632,21 @@ func (s *MCPService) handleListMemoComments(ctx context.Context, req mcp.CallToo if err != nil { return mcp.NewToolResultError(fmt.Sprintf("failed to list comments: %v", err)), nil } + creatorIDs := make([]int32, 0, len(memos)) + for _, memo := range memos { + if checkMemoAccess(memo, userID) == nil { + creatorIDs = append(creatorIDs, memo.CreatorID) + } + } + usernamesByID, err := preloadUsernames(ctx, s.store, creatorIDs) + if err != nil { + return mcp.NewToolResultError(fmt.Sprintf("failed to preload memo creators: %v", err)), nil + } results := make([]memoJSON, 0, len(memos)) for _, m := range memos { if checkMemoAccess(m, userID) == nil { - result, err := storeMemoToJSONWithStore(ctx, s.store, m) + result, err := storeMemoToJSONWithUsernames(m, usernamesByID) if err != nil { return mcp.NewToolResultError(fmt.Sprintf("failed to resolve memo creator: %v", err)), nil } diff --git a/server/router/mcp/tools_reaction.go b/server/router/mcp/tools_reaction.go index 3c102235a..46e4c5d44 100644 --- a/server/router/mcp/tools_reaction.go +++ b/server/router/mcp/tools_reaction.go @@ -60,10 +60,18 @@ func (s *MCPService) handleListReactions(ctx context.Context, req mcp.CallToolRe if err != nil { return mcp.NewToolResultError(fmt.Sprintf("failed to list reactions: %v", err)), nil } + creatorIDs := make([]int32, 0, len(reactions)) + for _, reaction := range reactions { + creatorIDs = append(creatorIDs, reaction.CreatorID) + } + usernamesByID, err := preloadUsernames(ctx, s.store, creatorIDs) + if err != nil { + return mcp.NewToolResultError(fmt.Sprintf("failed to preload reaction creators: %v", err)), nil + } results := make([]reactionJSON, len(reactions)) for i, r := range reactions { - creator, err := lookupUsername(ctx, s.store, r.CreatorID) + creator, err := lookupUsernameFromCache(usernamesByID, r.CreatorID) if err != nil { return mcp.NewToolResultError(fmt.Sprintf("failed to resolve reaction creator: %v", err)), nil } diff --git a/web/src/hooks/useMemoFilters.ts b/web/src/hooks/useMemoFilters.ts index 964500d9d..76c60d07e 100644 --- a/web/src/hooks/useMemoFilters.ts +++ b/web/src/hooks/useMemoFilters.ts @@ -23,6 +23,8 @@ const getShortcutId = (name: string): string => { return parts.length === 4 ? parts[3] : ""; }; +const escapeFilterValue = (value: string): string => JSON.stringify(value); + export interface UseMemoFiltersOptions { creatorName?: string; includeShortcuts?: boolean; @@ -63,9 +65,9 @@ export const useMemoFilters = (options: UseMemoFiltersOptions = {}): string | un // Add active filters from context for (const filter of filters) { if (filter.factor === "contentSearch") { - conditions.push(`content.contains("${filter.value}")`); + conditions.push(`content.contains(${escapeFilterValue(filter.value)})`); } else if (filter.factor === "tagSearch") { - conditions.push(`tag in ["${filter.value}"]`); + conditions.push(`tag in [${escapeFilterValue(filter.value)}]`); } else if (filter.factor === "pinned") { if (includePinned) { conditions.push(`pinned`); diff --git a/web/src/types/proto/api/v1/shortcut_service_pb.ts b/web/src/types/proto/api/v1/shortcut_service_pb.ts index ad4145246..8b87635b4 100644 --- a/web/src/types/proto/api/v1/shortcut_service_pb.ts +++ b/web/src/types/proto/api/v1/shortcut_service_pb.ts @@ -16,7 +16,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file api/v1/shortcut_service.proto. */ export const file_api_v1_shortcut_service: GenFile = /*@__PURE__*/ - fileDesc("Ch1hcGkvdjEvc2hvcnRjdXRfc2VydmljZS5wcm90bxIMbWVtb3MuYXBpLnYxIpoBCghTaG9ydGN1dBIRCgRuYW1lGAEgASgJQgPgQQgSEgoFdGl0bGUYAiABKAlCA+BBAhITCgZmaWx0ZXIYAyABKAlCA+BBATpS6kFPChVtZW1vcy5hcGkudjEvU2hvcnRjdXQSIXVzZXJzL3t1c2VyfS9zaG9ydGN1dHMve3Nob3J0Y3V0fSoJc2hvcnRjdXRzMghzaG9ydGN1dCJFChRMaXN0U2hvcnRjdXRzUmVxdWVzdBItCgZwYXJlbnQYASABKAlCHeBBAvpBFxIVbWVtb3MuYXBpLnYxL1Nob3J0Y3V0IkIKFUxpc3RTaG9ydGN1dHNSZXNwb25zZRIpCglzaG9ydGN1dHMYASADKAsyFi5tZW1vcy5hcGkudjEuU2hvcnRjdXQiQQoSR2V0U2hvcnRjdXRSZXF1ZXN0EisKBG5hbWUYASABKAlCHeBBAvpBFwoVbWVtb3MuYXBpLnYxL1Nob3J0Y3V0IpEBChVDcmVhdGVTaG9ydGN1dFJlcXVlc3QSLQoGcGFyZW50GAEgASgJQh3gQQL6QRcSFW1lbW9zLmFwaS52MS9TaG9ydGN1dBItCghzaG9ydGN1dBgCIAEoCzIWLm1lbW9zLmFwaS52MS5TaG9ydGN1dEID4EECEhoKDXZhbGlkYXRlX29ubHkYAyABKAhCA+BBASJ8ChVVcGRhdGVTaG9ydGN1dFJlcXVlc3QSLQoIc2hvcnRjdXQYASABKAsyFi5tZW1vcy5hcGkudjEuU2hvcnRjdXRCA+BBAhI0Cgt1cGRhdGVfbWFzaxgCIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5GaWVsZE1hc2tCA+BBASJEChVEZWxldGVTaG9ydGN1dFJlcXVlc3QSKwoEbmFtZRgBIAEoCUId4EEC+kEXChVtZW1vcy5hcGkudjEvU2hvcnRjdXQy3gUKD1Nob3J0Y3V0U2VydmljZRKNAQoNTGlzdFNob3J0Y3V0cxIiLm1lbW9zLmFwaS52MS5MaXN0U2hvcnRjdXRzUmVxdWVzdBojLm1lbW9zLmFwaS52MS5MaXN0U2hvcnRjdXRzUmVzcG9uc2UiM9pBBnBhcmVudILT5JMCJBIiL2FwaS92MS97cGFyZW50PXVzZXJzLyp9L3Nob3J0Y3V0cxJ6CgtHZXRTaG9ydGN1dBIgLm1lbW9zLmFwaS52MS5HZXRTaG9ydGN1dFJlcXVlc3QaFi5tZW1vcy5hcGkudjEuU2hvcnRjdXQiMdpBBG5hbWWC0+STAiQSIi9hcGkvdjEve25hbWU9dXNlcnMvKi9zaG9ydGN1dHMvKn0SlQEKDkNyZWF0ZVNob3J0Y3V0EiMubWVtb3MuYXBpLnYxLkNyZWF0ZVNob3J0Y3V0UmVxdWVzdBoWLm1lbW9zLmFwaS52MS5TaG9ydGN1dCJG2kEPcGFyZW50LHNob3J0Y3V0gtPkkwIuOghzaG9ydGN1dCIiL2FwaS92MS97cGFyZW50PXVzZXJzLyp9L3Nob3J0Y3V0cxKjAQoOVXBkYXRlU2hvcnRjdXQSIy5tZW1vcy5hcGkudjEuVXBkYXRlU2hvcnRjdXRSZXF1ZXN0GhYubWVtb3MuYXBpLnYxLlNob3J0Y3V0IlTaQRRzaG9ydGN1dCx1cGRhdGVfbWFza4LT5JMCNzoIc2hvcnRjdXQyKy9hcGkvdjEve3Nob3J0Y3V0Lm5hbWU9dXNlcnMvKi9zaG9ydGN1dHMvKn0SgAEKDkRlbGV0ZVNob3J0Y3V0EiMubWVtb3MuYXBpLnYxLkRlbGV0ZVNob3J0Y3V0UmVxdWVzdBoWLmdvb2dsZS5wcm90b2J1Zi5FbXB0eSIx2kEEbmFtZYLT5JMCJCoiL2FwaS92MS97bmFtZT11c2Vycy8qL3Nob3J0Y3V0cy8qfUKsAQoQY29tLm1lbW9zLmFwaS52MUIUU2hvcnRjdXRTZXJ2aWNlUHJvdG9QAVowZ2l0aHViLmNvbS91c2VtZW1vcy9tZW1vcy9wcm90by9nZW4vYXBpL3YxO2FwaXYxogIDTUFYqgIMTWVtb3MuQXBpLlYxygIMTWVtb3NcQXBpXFYx4gIYTWVtb3NcQXBpXFYxXEdQQk1ldGFkYXRh6gIOTWVtb3M6OkFwaTo6VjFiBnByb3RvMw", [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]); + fileDesc("Ch1hcGkvdjEvc2hvcnRjdXRfc2VydmljZS5wcm90bxIMbWVtb3MuYXBpLnYxIp4BCghTaG9ydGN1dBIRCgRuYW1lGAEgASgJQgPgQQgSEgoFdGl0bGUYAiABKAlCA+BBAhITCgZmaWx0ZXIYAyABKAlCA+BBATpW6kFTChVtZW1vcy5hcGkudjEvU2hvcnRjdXQSJXVzZXJzL3t1c2VybmFtZX0vc2hvcnRjdXRzL3tzaG9ydGN1dH0qCXNob3J0Y3V0czIIc2hvcnRjdXQiRQoUTGlzdFNob3J0Y3V0c1JlcXVlc3QSLQoGcGFyZW50GAEgASgJQh3gQQL6QRcSFW1lbW9zLmFwaS52MS9TaG9ydGN1dCJCChVMaXN0U2hvcnRjdXRzUmVzcG9uc2USKQoJc2hvcnRjdXRzGAEgAygLMhYubWVtb3MuYXBpLnYxLlNob3J0Y3V0IkEKEkdldFNob3J0Y3V0UmVxdWVzdBIrCgRuYW1lGAEgASgJQh3gQQL6QRcKFW1lbW9zLmFwaS52MS9TaG9ydGN1dCKRAQoVQ3JlYXRlU2hvcnRjdXRSZXF1ZXN0Ei0KBnBhcmVudBgBIAEoCUId4EEC+kEXEhVtZW1vcy5hcGkudjEvU2hvcnRjdXQSLQoIc2hvcnRjdXQYAiABKAsyFi5tZW1vcy5hcGkudjEuU2hvcnRjdXRCA+BBAhIaCg12YWxpZGF0ZV9vbmx5GAMgASgIQgPgQQEifAoVVXBkYXRlU2hvcnRjdXRSZXF1ZXN0Ei0KCHNob3J0Y3V0GAEgASgLMhYubWVtb3MuYXBpLnYxLlNob3J0Y3V0QgPgQQISNAoLdXBkYXRlX21hc2sYAiABKAsyGi5nb29nbGUucHJvdG9idWYuRmllbGRNYXNrQgPgQQEiRAoVRGVsZXRlU2hvcnRjdXRSZXF1ZXN0EisKBG5hbWUYASABKAlCHeBBAvpBFwoVbWVtb3MuYXBpLnYxL1Nob3J0Y3V0Mt4FCg9TaG9ydGN1dFNlcnZpY2USjQEKDUxpc3RTaG9ydGN1dHMSIi5tZW1vcy5hcGkudjEuTGlzdFNob3J0Y3V0c1JlcXVlc3QaIy5tZW1vcy5hcGkudjEuTGlzdFNob3J0Y3V0c1Jlc3BvbnNlIjPaQQZwYXJlbnSC0+STAiQSIi9hcGkvdjEve3BhcmVudD11c2Vycy8qfS9zaG9ydGN1dHMSegoLR2V0U2hvcnRjdXQSIC5tZW1vcy5hcGkudjEuR2V0U2hvcnRjdXRSZXF1ZXN0GhYubWVtb3MuYXBpLnYxLlNob3J0Y3V0IjHaQQRuYW1lgtPkkwIkEiIvYXBpL3YxL3tuYW1lPXVzZXJzLyovc2hvcnRjdXRzLyp9EpUBCg5DcmVhdGVTaG9ydGN1dBIjLm1lbW9zLmFwaS52MS5DcmVhdGVTaG9ydGN1dFJlcXVlc3QaFi5tZW1vcy5hcGkudjEuU2hvcnRjdXQiRtpBD3BhcmVudCxzaG9ydGN1dILT5JMCLjoIc2hvcnRjdXQiIi9hcGkvdjEve3BhcmVudD11c2Vycy8qfS9zaG9ydGN1dHMSowEKDlVwZGF0ZVNob3J0Y3V0EiMubWVtb3MuYXBpLnYxLlVwZGF0ZVNob3J0Y3V0UmVxdWVzdBoWLm1lbW9zLmFwaS52MS5TaG9ydGN1dCJU2kEUc2hvcnRjdXQsdXBkYXRlX21hc2uC0+STAjc6CHNob3J0Y3V0MisvYXBpL3YxL3tzaG9ydGN1dC5uYW1lPXVzZXJzLyovc2hvcnRjdXRzLyp9EoABCg5EZWxldGVTaG9ydGN1dBIjLm1lbW9zLmFwaS52MS5EZWxldGVTaG9ydGN1dFJlcXVlc3QaFi5nb29nbGUucHJvdG9idWYuRW1wdHkiMdpBBG5hbWWC0+STAiQqIi9hcGkvdjEve25hbWU9dXNlcnMvKi9zaG9ydGN1dHMvKn1CrAEKEGNvbS5tZW1vcy5hcGkudjFCFFNob3J0Y3V0U2VydmljZVByb3RvUAFaMGdpdGh1Yi5jb20vdXNlbWVtb3MvbWVtb3MvcHJvdG8vZ2VuL2FwaS92MTthcGl2MaICA01BWKoCDE1lbW9zLkFwaS5WMcoCDE1lbW9zXEFwaVxWMeICGE1lbW9zXEFwaVxWMVxHUEJNZXRhZGF0YeoCDk1lbW9zOjpBcGk6OlYxYgZwcm90bzM", [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]); /** * @generated from message memos.api.v1.Shortcut @@ -24,7 +24,7 @@ export const file_api_v1_shortcut_service: GenFile = /*@__PURE__*/ export type Shortcut = Message<"memos.api.v1.Shortcut"> & { /** * The resource name of the shortcut. - * Format: users/{user}/shortcuts/{shortcut} + * Format: users/{username}/shortcuts/{shortcut} * * @generated from field: string name = 1; */ @@ -58,7 +58,7 @@ export const ShortcutSchema: GenMessage = /*@__PURE__*/ export type ListShortcutsRequest = Message<"memos.api.v1.ListShortcutsRequest"> & { /** * Required. The parent resource where shortcuts are listed. - * Format: users/{user} + * Format: users/{username} * * @generated from field: string parent = 1; */ @@ -97,7 +97,7 @@ export const ListShortcutsResponseSchema: GenMessage = /* export type GetShortcutRequest = Message<"memos.api.v1.GetShortcutRequest"> & { /** * Required. The resource name of the shortcut to retrieve. - * Format: users/{user}/shortcuts/{shortcut} + * Format: users/{username}/shortcuts/{shortcut} * * @generated from field: string name = 1; */ @@ -117,7 +117,7 @@ export const GetShortcutRequestSchema: GenMessage = /*@__PUR export type CreateShortcutRequest = Message<"memos.api.v1.CreateShortcutRequest"> & { /** * Required. The parent resource where this shortcut will be created. - * Format: users/{user} + * Format: users/{username} * * @generated from field: string parent = 1; */ @@ -177,7 +177,7 @@ export const UpdateShortcutRequestSchema: GenMessage = /* export type DeleteShortcutRequest = Message<"memos.api.v1.DeleteShortcutRequest"> & { /** * Required. The resource name of the shortcut to delete. - * Format: users/{user}/shortcuts/{shortcut} + * Format: users/{username}/shortcuts/{shortcut} * * @generated from field: string name = 1; */ diff --git a/web/src/types/proto/api/v1/user_service_pb.ts b/web/src/types/proto/api/v1/user_service_pb.ts index 4a1bc85f6..1d0dcd944 100644 --- a/web/src/types/proto/api/v1/user_service_pb.ts +++ b/web/src/types/proto/api/v1/user_service_pb.ts @@ -18,7 +18,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file api/v1/user_service.proto. */ export const file_api_v1_user_service: GenFile = /*@__PURE__*/ - fileDesc("ChlhcGkvdjEvdXNlcl9zZXJ2aWNlLnByb3RvEgxtZW1vcy5hcGkudjEi1gMKBFVzZXISEQoEbmFtZRgBIAEoCUID4EEIEioKBHJvbGUYAiABKA4yFy5tZW1vcy5hcGkudjEuVXNlci5Sb2xlQgPgQQISFQoIdXNlcm5hbWUYAyABKAlCA+BBAhISCgVlbWFpbBgEIAEoCUID4EEBEhkKDGRpc3BsYXlfbmFtZRgFIAEoCUID4EEBEhcKCmF2YXRhcl91cmwYBiABKAlCA+BBARIYCgtkZXNjcmlwdGlvbhgHIAEoCUID4EEBEhUKCHBhc3N3b3JkGAggASgJQgPgQQQSJwoFc3RhdGUYCSABKA4yEy5tZW1vcy5hcGkudjEuU3RhdGVCA+BBAhI0CgtjcmVhdGVfdGltZRgKIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBCA+BBAxI0Cgt1cGRhdGVfdGltZRgLIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBCA+BBAyIxCgRSb2xlEhQKEFJPTEVfVU5TUEVDSUZJRUQQABIJCgVBRE1JThACEggKBFVTRVIQAzo36kE0ChFtZW1vcy5hcGkudjEvVXNlchIMdXNlcnMve3VzZXJ9GgRuYW1lKgV1c2VyczIEdXNlciJzChBMaXN0VXNlcnNSZXF1ZXN0EhYKCXBhZ2Vfc2l6ZRgBIAEoBUID4EEBEhcKCnBhZ2VfdG9rZW4YAiABKAlCA+BBARITCgZmaWx0ZXIYAyABKAlCA+BBARIZCgxzaG93X2RlbGV0ZWQYBCABKAhCA+BBASJjChFMaXN0VXNlcnNSZXNwb25zZRIhCgV1c2VycxgBIAMoCzISLm1lbW9zLmFwaS52MS5Vc2VyEhcKD25leHRfcGFnZV90b2tlbhgCIAEoCRISCgp0b3RhbF9zaXplGAMgASgFIm0KDkdldFVzZXJSZXF1ZXN0EicKBG5hbWUYASABKAlCGeBBAvpBEwoRbWVtb3MuYXBpLnYxL1VzZXISMgoJcmVhZF9tYXNrGAIgASgLMhouZ29vZ2xlLnByb3RvYnVmLkZpZWxkTWFza0ID4EEBIogBChFDcmVhdGVVc2VyUmVxdWVzdBIoCgR1c2VyGAEgASgLMhIubWVtb3MuYXBpLnYxLlVzZXJCBuBBAuBBBBIUCgd1c2VyX2lkGAIgASgJQgPgQQESGgoNdmFsaWRhdGVfb25seRgDIAEoCEID4EEBEhcKCnJlcXVlc3RfaWQYBCABKAlCA+BBASKMAQoRVXBkYXRlVXNlclJlcXVlc3QSJQoEdXNlchgBIAEoCzISLm1lbW9zLmFwaS52MS5Vc2VyQgPgQQISNAoLdXBkYXRlX21hc2sYAiABKAsyGi5nb29nbGUucHJvdG9idWYuRmllbGRNYXNrQgPgQQISGgoNYWxsb3dfbWlzc2luZxgDIAEoCEID4EEBIlAKEURlbGV0ZVVzZXJSZXF1ZXN0EicKBG5hbWUYASABKAlCGeBBAvpBEwoRbWVtb3MuYXBpLnYxL1VzZXISEgoFZm9yY2UYAiABKAhCA+BBASLYAwoJVXNlclN0YXRzEhEKBG5hbWUYASABKAlCA+BBCBI7ChdtZW1vX2Rpc3BsYXlfdGltZXN0YW1wcxgCIAMoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASPgoPbWVtb190eXBlX3N0YXRzGAMgASgLMiUubWVtb3MuYXBpLnYxLlVzZXJTdGF0cy5NZW1vVHlwZVN0YXRzEjgKCXRhZ19jb3VudBgEIAMoCzIlLm1lbW9zLmFwaS52MS5Vc2VyU3RhdHMuVGFnQ291bnRFbnRyeRIUCgxwaW5uZWRfbWVtb3MYBSADKAkSGAoQdG90YWxfbWVtb19jb3VudBgGIAEoBRovCg1UYWdDb3VudEVudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoBToCOAEaXwoNTWVtb1R5cGVTdGF0cxISCgpsaW5rX2NvdW50GAEgASgFEhIKCmNvZGVfY291bnQYAiABKAUSEgoKdG9kb19jb3VudBgDIAEoBRISCgp1bmRvX2NvdW50GAQgASgFOj/qQTwKFm1lbW9zLmFwaS52MS9Vc2VyU3RhdHMSDHVzZXJzL3t1c2VyfSoJdXNlclN0YXRzMgl1c2VyU3RhdHMiPgoTR2V0VXNlclN0YXRzUmVxdWVzdBInCgRuYW1lGAEgASgJQhngQQL6QRMKEW1lbW9zLmFwaS52MS9Vc2VyIhkKF0xpc3RBbGxVc2VyU3RhdHNSZXF1ZXN0IkIKGExpc3RBbGxVc2VyU3RhdHNSZXNwb25zZRImCgVzdGF0cxgBIAMoCzIXLm1lbW9zLmFwaS52MS5Vc2VyU3RhdHMi4AMKC1VzZXJTZXR0aW5nEhEKBG5hbWUYASABKAlCA+BBCBJDCg9nZW5lcmFsX3NldHRpbmcYAiABKAsyKC5tZW1vcy5hcGkudjEuVXNlclNldHRpbmcuR2VuZXJhbFNldHRpbmdIABJFChB3ZWJob29rc19zZXR0aW5nGAUgASgLMikubWVtb3MuYXBpLnYxLlVzZXJTZXR0aW5nLldlYmhvb2tzU2V0dGluZ0gAGlcKDkdlbmVyYWxTZXR0aW5nEhMKBmxvY2FsZRgBIAEoCUID4EEBEhwKD21lbW9fdmlzaWJpbGl0eRgDIAEoCUID4EEBEhIKBXRoZW1lGAQgASgJQgPgQQEaPgoPV2ViaG9va3NTZXR0aW5nEisKCHdlYmhvb2tzGAEgAygLMhkubWVtb3MuYXBpLnYxLlVzZXJXZWJob29rIjUKA0tleRITCg9LRVlfVU5TUEVDSUZJRUQQABILCgdHRU5FUkFMEAESDAoIV0VCSE9PS1MQBDpZ6kFWChhtZW1vcy5hcGkudjEvVXNlclNldHRpbmcSH3VzZXJzL3t1c2VyfS9zZXR0aW5ncy97c2V0dGluZ30qDHVzZXJTZXR0aW5nczILdXNlclNldHRpbmdCBwoFdmFsdWUiRwoVR2V0VXNlclNldHRpbmdSZXF1ZXN0Ei4KBG5hbWUYASABKAlCIOBBAvpBGgoYbWVtb3MuYXBpLnYxL1VzZXJTZXR0aW5nIoEBChhVcGRhdGVVc2VyU2V0dGluZ1JlcXVlc3QSLwoHc2V0dGluZxgBIAEoCzIZLm1lbW9zLmFwaS52MS5Vc2VyU2V0dGluZ0ID4EECEjQKC3VwZGF0ZV9tYXNrGAIgASgLMhouZ29vZ2xlLnByb3RvYnVmLkZpZWxkTWFza0ID4EECInUKF0xpc3RVc2VyU2V0dGluZ3NSZXF1ZXN0EikKBnBhcmVudBgBIAEoCUIZ4EEC+kETChFtZW1vcy5hcGkudjEvVXNlchIWCglwYWdlX3NpemUYAiABKAVCA+BBARIXCgpwYWdlX3Rva2VuGAMgASgJQgPgQQEidAoYTGlzdFVzZXJTZXR0aW5nc1Jlc3BvbnNlEisKCHNldHRpbmdzGAEgAygLMhkubWVtb3MuYXBpLnYxLlVzZXJTZXR0aW5nEhcKD25leHRfcGFnZV90b2tlbhgCIAEoCRISCgp0b3RhbF9zaXplGAMgASgFIvICChNQZXJzb25hbEFjY2Vzc1Rva2VuEhEKBG5hbWUYASABKAlCA+BBCBIYCgtkZXNjcmlwdGlvbhgCIAEoCUID4EEBEjMKCmNyZWF0ZWRfYXQYAyABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wQgPgQQMSMwoKZXhwaXJlc19hdBgEIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBCA+BBARI1CgxsYXN0X3VzZWRfYXQYBSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wQgPgQQM6jAHqQYgBCiBtZW1vcy5hcGkudjEvUGVyc29uYWxBY2Nlc3NUb2tlbhI5dXNlcnMve3VzZXJ9L3BlcnNvbmFsQWNjZXNzVG9rZW5zL3twZXJzb25hbF9hY2Nlc3NfdG9rZW59KhRwZXJzb25hbEFjY2Vzc1Rva2VuczITcGVyc29uYWxBY2Nlc3NUb2tlbiJ9Ch9MaXN0UGVyc29uYWxBY2Nlc3NUb2tlbnNSZXF1ZXN0EikKBnBhcmVudBgBIAEoCUIZ4EEC+kETChFtZW1vcy5hcGkudjEvVXNlchIWCglwYWdlX3NpemUYAiABKAVCA+BBARIXCgpwYWdlX3Rva2VuGAMgASgJQgPgQQEikgEKIExpc3RQZXJzb25hbEFjY2Vzc1Rva2Vuc1Jlc3BvbnNlEkEKFnBlcnNvbmFsX2FjY2Vzc190b2tlbnMYASADKAsyIS5tZW1vcy5hcGkudjEuUGVyc29uYWxBY2Nlc3NUb2tlbhIXCg9uZXh0X3BhZ2VfdG9rZW4YAiABKAkSEgoKdG90YWxfc2l6ZRgDIAEoBSKFAQogQ3JlYXRlUGVyc29uYWxBY2Nlc3NUb2tlblJlcXVlc3QSKQoGcGFyZW50GAEgASgJQhngQQL6QRMKEW1lbW9zLmFwaS52MS9Vc2VyEhgKC2Rlc2NyaXB0aW9uGAIgASgJQgPgQQESHAoPZXhwaXJlc19pbl9kYXlzGAMgASgFQgPgQQEidAohQ3JlYXRlUGVyc29uYWxBY2Nlc3NUb2tlblJlc3BvbnNlEkAKFXBlcnNvbmFsX2FjY2Vzc190b2tlbhgBIAEoCzIhLm1lbW9zLmFwaS52MS5QZXJzb25hbEFjY2Vzc1Rva2VuEg0KBXRva2VuGAIgASgJIloKIERlbGV0ZVBlcnNvbmFsQWNjZXNzVG9rZW5SZXF1ZXN0EjYKBG5hbWUYASABKAlCKOBBAvpBIgogbWVtb3MuYXBpLnYxL1BlcnNvbmFsQWNjZXNzVG9rZW4iqgEKC1VzZXJXZWJob29rEgwKBG5hbWUYASABKAkSCwoDdXJsGAIgASgJEhQKDGRpc3BsYXlfbmFtZRgDIAEoCRI0CgtjcmVhdGVfdGltZRgEIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBCA+BBAxI0Cgt1cGRhdGVfdGltZRgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBCA+BBAyIuChdMaXN0VXNlcldlYmhvb2tzUmVxdWVzdBITCgZwYXJlbnQYASABKAlCA+BBAiJHChhMaXN0VXNlcldlYmhvb2tzUmVzcG9uc2USKwoId2ViaG9va3MYASADKAsyGS5tZW1vcy5hcGkudjEuVXNlcldlYmhvb2siYAoYQ3JlYXRlVXNlcldlYmhvb2tSZXF1ZXN0EhMKBnBhcmVudBgBIAEoCUID4EECEi8KB3dlYmhvb2sYAiABKAsyGS5tZW1vcy5hcGkudjEuVXNlcldlYmhvb2tCA+BBAiJ8ChhVcGRhdGVVc2VyV2ViaG9va1JlcXVlc3QSLwoHd2ViaG9vaxgBIAEoCzIZLm1lbW9zLmFwaS52MS5Vc2VyV2ViaG9va0ID4EECEi8KC3VwZGF0ZV9tYXNrGAIgASgLMhouZ29vZ2xlLnByb3RvYnVmLkZpZWxkTWFzayItChhEZWxldGVVc2VyV2ViaG9va1JlcXVlc3QSEQoEbmFtZRgBIAEoCUID4EECIvAEChBVc2VyTm90aWZpY2F0aW9uEhQKBG5hbWUYASABKAlCBuBBA+BBCBIpCgZzZW5kZXIYAiABKAlCGeBBA/pBEwoRbWVtb3MuYXBpLnYxL1VzZXISOgoGc3RhdHVzGAMgASgOMiUubWVtb3MuYXBpLnYxLlVzZXJOb3RpZmljYXRpb24uU3RhdHVzQgPgQQESNAoLY3JlYXRlX3RpbWUYBCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wQgPgQQMSNgoEdHlwZRgFIAEoDjIjLm1lbW9zLmFwaS52MS5Vc2VyTm90aWZpY2F0aW9uLlR5cGVCA+BBAxJOCgxtZW1vX2NvbW1lbnQYBiABKAsyMS5tZW1vcy5hcGkudjEuVXNlck5vdGlmaWNhdGlvbi5NZW1vQ29tbWVudFBheWxvYWRCA+BBA0gAGjgKEk1lbW9Db21tZW50UGF5bG9hZBIMCgRtZW1vGAEgASgJEhQKDHJlbGF0ZWRfbWVtbxgCIAEoCSI6CgZTdGF0dXMSFgoSU1RBVFVTX1VOU1BFQ0lGSUVEEAASCgoGVU5SRUFEEAESDAoIQVJDSElWRUQQAiIuCgRUeXBlEhQKEFRZUEVfVU5TUEVDSUZJRUQQABIQCgxNRU1PX0NPTU1FTlQQATpw6kFtCh1tZW1vcy5hcGkudjEvVXNlck5vdGlmaWNhdGlvbhIpdXNlcnMve3VzZXJ9L25vdGlmaWNhdGlvbnMve25vdGlmaWNhdGlvbn0aBG5hbWUqDW5vdGlmaWNhdGlvbnMyDG5vdGlmaWNhdGlvbkIJCgdwYXlsb2FkIo8BChxMaXN0VXNlck5vdGlmaWNhdGlvbnNSZXF1ZXN0EikKBnBhcmVudBgBIAEoCUIZ4EEC+kETChFtZW1vcy5hcGkudjEvVXNlchIWCglwYWdlX3NpemUYAiABKAVCA+BBARIXCgpwYWdlX3Rva2VuGAMgASgJQgPgQQESEwoGZmlsdGVyGAQgASgJQgPgQQEibwodTGlzdFVzZXJOb3RpZmljYXRpb25zUmVzcG9uc2USNQoNbm90aWZpY2F0aW9ucxgBIAMoCzIeLm1lbW9zLmFwaS52MS5Vc2VyTm90aWZpY2F0aW9uEhcKD25leHRfcGFnZV90b2tlbhgCIAEoCSKQAQodVXBkYXRlVXNlck5vdGlmaWNhdGlvblJlcXVlc3QSOQoMbm90aWZpY2F0aW9uGAEgASgLMh4ubWVtb3MuYXBpLnYxLlVzZXJOb3RpZmljYXRpb25CA+BBAhI0Cgt1cGRhdGVfbWFzaxgCIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5GaWVsZE1hc2tCA+BBAiJUCh1EZWxldGVVc2VyTm90aWZpY2F0aW9uUmVxdWVzdBIzCgRuYW1lGAEgASgJQiXgQQL6QR8KHW1lbW9zLmFwaS52MS9Vc2VyTm90aWZpY2F0aW9uMoMXCgtVc2VyU2VydmljZRJjCglMaXN0VXNlcnMSHi5tZW1vcy5hcGkudjEuTGlzdFVzZXJzUmVxdWVzdBofLm1lbW9zLmFwaS52MS5MaXN0VXNlcnNSZXNwb25zZSIVgtPkkwIPEg0vYXBpL3YxL3VzZXJzEmIKB0dldFVzZXISHC5tZW1vcy5hcGkudjEuR2V0VXNlclJlcXVlc3QaEi5tZW1vcy5hcGkudjEuVXNlciIl2kEEbmFtZYLT5JMCGBIWL2FwaS92MS97bmFtZT11c2Vycy8qfRJlCgpDcmVhdGVVc2VyEh8ubWVtb3MuYXBpLnYxLkNyZWF0ZVVzZXJSZXF1ZXN0GhIubWVtb3MuYXBpLnYxLlVzZXIiItpBBHVzZXKC0+STAhU6BHVzZXIiDS9hcGkvdjEvdXNlcnMSfwoKVXBkYXRlVXNlchIfLm1lbW9zLmFwaS52MS5VcGRhdGVVc2VyUmVxdWVzdBoSLm1lbW9zLmFwaS52MS5Vc2VyIjzaQRB1c2VyLHVwZGF0ZV9tYXNrgtPkkwIjOgR1c2VyMhsvYXBpL3YxL3t1c2VyLm5hbWU9dXNlcnMvKn0SbAoKRGVsZXRlVXNlchIfLm1lbW9zLmFwaS52MS5EZWxldGVVc2VyUmVxdWVzdBoWLmdvb2dsZS5wcm90b2J1Zi5FbXB0eSIl2kEEbmFtZYLT5JMCGCoWL2FwaS92MS97bmFtZT11c2Vycy8qfRJ+ChBMaXN0QWxsVXNlclN0YXRzEiUubWVtb3MuYXBpLnYxLkxpc3RBbGxVc2VyU3RhdHNSZXF1ZXN0GiYubWVtb3MuYXBpLnYxLkxpc3RBbGxVc2VyU3RhdHNSZXNwb25zZSIbgtPkkwIVEhMvYXBpL3YxL3VzZXJzOnN0YXRzEnoKDEdldFVzZXJTdGF0cxIhLm1lbW9zLmFwaS52MS5HZXRVc2VyU3RhdHNSZXF1ZXN0GhcubWVtb3MuYXBpLnYxLlVzZXJTdGF0cyIu2kEEbmFtZYLT5JMCIRIfL2FwaS92MS97bmFtZT11c2Vycy8qfTpnZXRTdGF0cxKCAQoOR2V0VXNlclNldHRpbmcSIy5tZW1vcy5hcGkudjEuR2V0VXNlclNldHRpbmdSZXF1ZXN0GhkubWVtb3MuYXBpLnYxLlVzZXJTZXR0aW5nIjDaQQRuYW1lgtPkkwIjEiEvYXBpL3YxL3tuYW1lPXVzZXJzLyovc2V0dGluZ3MvKn0SqAEKEVVwZGF0ZVVzZXJTZXR0aW5nEiYubWVtb3MuYXBpLnYxLlVwZGF0ZVVzZXJTZXR0aW5nUmVxdWVzdBoZLm1lbW9zLmFwaS52MS5Vc2VyU2V0dGluZyJQ2kETc2V0dGluZyx1cGRhdGVfbWFza4LT5JMCNDoHc2V0dGluZzIpL2FwaS92MS97c2V0dGluZy5uYW1lPXVzZXJzLyovc2V0dGluZ3MvKn0SlQEKEExpc3RVc2VyU2V0dGluZ3MSJS5tZW1vcy5hcGkudjEuTGlzdFVzZXJTZXR0aW5nc1JlcXVlc3QaJi5tZW1vcy5hcGkudjEuTGlzdFVzZXJTZXR0aW5nc1Jlc3BvbnNlIjLaQQZwYXJlbnSC0+STAiMSIS9hcGkvdjEve3BhcmVudD11c2Vycy8qfS9zZXR0aW5ncxK5AQoYTGlzdFBlcnNvbmFsQWNjZXNzVG9rZW5zEi0ubWVtb3MuYXBpLnYxLkxpc3RQZXJzb25hbEFjY2Vzc1Rva2Vuc1JlcXVlc3QaLi5tZW1vcy5hcGkudjEuTGlzdFBlcnNvbmFsQWNjZXNzVG9rZW5zUmVzcG9uc2UiPtpBBnBhcmVudILT5JMCLxItL2FwaS92MS97cGFyZW50PXVzZXJzLyp9L3BlcnNvbmFsQWNjZXNzVG9rZW5zErYBChlDcmVhdGVQZXJzb25hbEFjY2Vzc1Rva2VuEi4ubWVtb3MuYXBpLnYxLkNyZWF0ZVBlcnNvbmFsQWNjZXNzVG9rZW5SZXF1ZXN0Gi8ubWVtb3MuYXBpLnYxLkNyZWF0ZVBlcnNvbmFsQWNjZXNzVG9rZW5SZXNwb25zZSI4gtPkkwIyOgEqIi0vYXBpL3YxL3twYXJlbnQ9dXNlcnMvKn0vcGVyc29uYWxBY2Nlc3NUb2tlbnMSoQEKGURlbGV0ZVBlcnNvbmFsQWNjZXNzVG9rZW4SLi5tZW1vcy5hcGkudjEuRGVsZXRlUGVyc29uYWxBY2Nlc3NUb2tlblJlcXVlc3QaFi5nb29nbGUucHJvdG9idWYuRW1wdHkiPNpBBG5hbWWC0+STAi8qLS9hcGkvdjEve25hbWU9dXNlcnMvKi9wZXJzb25hbEFjY2Vzc1Rva2Vucy8qfRKVAQoQTGlzdFVzZXJXZWJob29rcxIlLm1lbW9zLmFwaS52MS5MaXN0VXNlcldlYmhvb2tzUmVxdWVzdBomLm1lbW9zLmFwaS52MS5MaXN0VXNlcldlYmhvb2tzUmVzcG9uc2UiMtpBBnBhcmVudILT5JMCIxIhL2FwaS92MS97cGFyZW50PXVzZXJzLyp9L3dlYmhvb2tzEpsBChFDcmVhdGVVc2VyV2ViaG9vaxImLm1lbW9zLmFwaS52MS5DcmVhdGVVc2VyV2ViaG9va1JlcXVlc3QaGS5tZW1vcy5hcGkudjEuVXNlcldlYmhvb2siQ9pBDnBhcmVudCx3ZWJob29rgtPkkwIsOgd3ZWJob29rIiEvYXBpL3YxL3twYXJlbnQ9dXNlcnMvKn0vd2ViaG9va3MSqAEKEVVwZGF0ZVVzZXJXZWJob29rEiYubWVtb3MuYXBpLnYxLlVwZGF0ZVVzZXJXZWJob29rUmVxdWVzdBoZLm1lbW9zLmFwaS52MS5Vc2VyV2ViaG9vayJQ2kETd2ViaG9vayx1cGRhdGVfbWFza4LT5JMCNDoHd2ViaG9vazIpL2FwaS92MS97d2ViaG9vay5uYW1lPXVzZXJzLyovd2ViaG9va3MvKn0ShQEKEURlbGV0ZVVzZXJXZWJob29rEiYubWVtb3MuYXBpLnYxLkRlbGV0ZVVzZXJXZWJob29rUmVxdWVzdBoWLmdvb2dsZS5wcm90b2J1Zi5FbXB0eSIw2kEEbmFtZYLT5JMCIyohL2FwaS92MS97bmFtZT11c2Vycy8qL3dlYmhvb2tzLyp9EqkBChVMaXN0VXNlck5vdGlmaWNhdGlvbnMSKi5tZW1vcy5hcGkudjEuTGlzdFVzZXJOb3RpZmljYXRpb25zUmVxdWVzdBorLm1lbW9zLmFwaS52MS5MaXN0VXNlck5vdGlmaWNhdGlvbnNSZXNwb25zZSI32kEGcGFyZW50gtPkkwIoEiYvYXBpL3YxL3twYXJlbnQ9dXNlcnMvKn0vbm90aWZpY2F0aW9ucxLLAQoWVXBkYXRlVXNlck5vdGlmaWNhdGlvbhIrLm1lbW9zLmFwaS52MS5VcGRhdGVVc2VyTm90aWZpY2F0aW9uUmVxdWVzdBoeLm1lbW9zLmFwaS52MS5Vc2VyTm90aWZpY2F0aW9uImTaQRhub3RpZmljYXRpb24sdXBkYXRlX21hc2uC0+STAkM6DG5vdGlmaWNhdGlvbjIzL2FwaS92MS97bm90aWZpY2F0aW9uLm5hbWU9dXNlcnMvKi9ub3RpZmljYXRpb25zLyp9EpQBChZEZWxldGVVc2VyTm90aWZpY2F0aW9uEisubWVtb3MuYXBpLnYxLkRlbGV0ZVVzZXJOb3RpZmljYXRpb25SZXF1ZXN0GhYuZ29vZ2xlLnByb3RvYnVmLkVtcHR5IjXaQQRuYW1lgtPkkwIoKiYvYXBpL3YxL3tuYW1lPXVzZXJzLyovbm90aWZpY2F0aW9ucy8qfUKoAQoQY29tLm1lbW9zLmFwaS52MUIQVXNlclNlcnZpY2VQcm90b1ABWjBnaXRodWIuY29tL3VzZW1lbW9zL21lbW9zL3Byb3RvL2dlbi9hcGkvdjE7YXBpdjGiAgNNQViqAgxNZW1vcy5BcGkuVjHKAgxNZW1vc1xBcGlcVjHiAhhNZW1vc1xBcGlcVjFcR1BCTWV0YWRhdGHqAg5NZW1vczo6QXBpOjpWMWIGcHJvdG8z", [file_api_v1_common, 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]); + fileDesc("ChlhcGkvdjEvdXNlcl9zZXJ2aWNlLnByb3RvEgxtZW1vcy5hcGkudjEi1gMKBFVzZXISEQoEbmFtZRgBIAEoCUID4EEIEioKBHJvbGUYAiABKA4yFy5tZW1vcy5hcGkudjEuVXNlci5Sb2xlQgPgQQISFQoIdXNlcm5hbWUYAyABKAlCA+BBAhISCgVlbWFpbBgEIAEoCUID4EEBEhkKDGRpc3BsYXlfbmFtZRgFIAEoCUID4EEBEhcKCmF2YXRhcl91cmwYBiABKAlCA+BBARIYCgtkZXNjcmlwdGlvbhgHIAEoCUID4EEBEhUKCHBhc3N3b3JkGAggASgJQgPgQQQSJwoFc3RhdGUYCSABKA4yEy5tZW1vcy5hcGkudjEuU3RhdGVCA+BBAhI0CgtjcmVhdGVfdGltZRgKIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBCA+BBAxI0Cgt1cGRhdGVfdGltZRgLIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBCA+BBAyIxCgRSb2xlEhQKEFJPTEVfVU5TUEVDSUZJRUQQABIJCgVBRE1JThACEggKBFVTRVIQAzo36kE0ChFtZW1vcy5hcGkudjEvVXNlchIMdXNlcnMve3VzZXJ9GgRuYW1lKgV1c2VyczIEdXNlciJzChBMaXN0VXNlcnNSZXF1ZXN0EhYKCXBhZ2Vfc2l6ZRgBIAEoBUID4EEBEhcKCnBhZ2VfdG9rZW4YAiABKAlCA+BBARITCgZmaWx0ZXIYAyABKAlCA+BBARIZCgxzaG93X2RlbGV0ZWQYBCABKAhCA+BBASJjChFMaXN0VXNlcnNSZXNwb25zZRIhCgV1c2VycxgBIAMoCzISLm1lbW9zLmFwaS52MS5Vc2VyEhcKD25leHRfcGFnZV90b2tlbhgCIAEoCRISCgp0b3RhbF9zaXplGAMgASgFIm0KDkdldFVzZXJSZXF1ZXN0EicKBG5hbWUYASABKAlCGeBBAvpBEwoRbWVtb3MuYXBpLnYxL1VzZXISMgoJcmVhZF9tYXNrGAIgASgLMhouZ29vZ2xlLnByb3RvYnVmLkZpZWxkTWFza0ID4EEBIogBChFDcmVhdGVVc2VyUmVxdWVzdBIoCgR1c2VyGAEgASgLMhIubWVtb3MuYXBpLnYxLlVzZXJCBuBBAuBBBBIUCgd1c2VyX2lkGAIgASgJQgPgQQESGgoNdmFsaWRhdGVfb25seRgDIAEoCEID4EEBEhcKCnJlcXVlc3RfaWQYBCABKAlCA+BBASKMAQoRVXBkYXRlVXNlclJlcXVlc3QSJQoEdXNlchgBIAEoCzISLm1lbW9zLmFwaS52MS5Vc2VyQgPgQQISNAoLdXBkYXRlX21hc2sYAiABKAsyGi5nb29nbGUucHJvdG9idWYuRmllbGRNYXNrQgPgQQISGgoNYWxsb3dfbWlzc2luZxgDIAEoCEID4EEBIlAKEURlbGV0ZVVzZXJSZXF1ZXN0EicKBG5hbWUYASABKAlCGeBBAvpBEwoRbWVtb3MuYXBpLnYxL1VzZXISEgoFZm9yY2UYAiABKAhCA+BBASLYAwoJVXNlclN0YXRzEhEKBG5hbWUYASABKAlCA+BBCBI7ChdtZW1vX2Rpc3BsYXlfdGltZXN0YW1wcxgCIAMoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASPgoPbWVtb190eXBlX3N0YXRzGAMgASgLMiUubWVtb3MuYXBpLnYxLlVzZXJTdGF0cy5NZW1vVHlwZVN0YXRzEjgKCXRhZ19jb3VudBgEIAMoCzIlLm1lbW9zLmFwaS52MS5Vc2VyU3RhdHMuVGFnQ291bnRFbnRyeRIUCgxwaW5uZWRfbWVtb3MYBSADKAkSGAoQdG90YWxfbWVtb19jb3VudBgGIAEoBRovCg1UYWdDb3VudEVudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoBToCOAEaXwoNTWVtb1R5cGVTdGF0cxISCgpsaW5rX2NvdW50GAEgASgFEhIKCmNvZGVfY291bnQYAiABKAUSEgoKdG9kb19jb3VudBgDIAEoBRISCgp1bmRvX2NvdW50GAQgASgFOj/qQTwKFm1lbW9zLmFwaS52MS9Vc2VyU3RhdHMSDHVzZXJzL3t1c2VyfSoJdXNlclN0YXRzMgl1c2VyU3RhdHMiPgoTR2V0VXNlclN0YXRzUmVxdWVzdBInCgRuYW1lGAEgASgJQhngQQL6QRMKEW1lbW9zLmFwaS52MS9Vc2VyIhkKF0xpc3RBbGxVc2VyU3RhdHNSZXF1ZXN0IkIKGExpc3RBbGxVc2VyU3RhdHNSZXNwb25zZRImCgVzdGF0cxgBIAMoCzIXLm1lbW9zLmFwaS52MS5Vc2VyU3RhdHMi5AMKC1VzZXJTZXR0aW5nEhEKBG5hbWUYASABKAlCA+BBCBJDCg9nZW5lcmFsX3NldHRpbmcYAiABKAsyKC5tZW1vcy5hcGkudjEuVXNlclNldHRpbmcuR2VuZXJhbFNldHRpbmdIABJFChB3ZWJob29rc19zZXR0aW5nGAUgASgLMikubWVtb3MuYXBpLnYxLlVzZXJTZXR0aW5nLldlYmhvb2tzU2V0dGluZ0gAGlcKDkdlbmVyYWxTZXR0aW5nEhMKBmxvY2FsZRgBIAEoCUID4EEBEhwKD21lbW9fdmlzaWJpbGl0eRgDIAEoCUID4EEBEhIKBXRoZW1lGAQgASgJQgPgQQEaPgoPV2ViaG9va3NTZXR0aW5nEisKCHdlYmhvb2tzGAEgAygLMhkubWVtb3MuYXBpLnYxLlVzZXJXZWJob29rIjUKA0tleRITCg9LRVlfVU5TUEVDSUZJRUQQABILCgdHRU5FUkFMEAESDAoIV0VCSE9PS1MQBDpd6kFaChhtZW1vcy5hcGkudjEvVXNlclNldHRpbmcSI3VzZXJzL3t1c2VybmFtZX0vc2V0dGluZ3Mve3NldHRpbmd9Kgx1c2VyU2V0dGluZ3MyC3VzZXJTZXR0aW5nQgcKBXZhbHVlIkcKFUdldFVzZXJTZXR0aW5nUmVxdWVzdBIuCgRuYW1lGAEgASgJQiDgQQL6QRoKGG1lbW9zLmFwaS52MS9Vc2VyU2V0dGluZyKBAQoYVXBkYXRlVXNlclNldHRpbmdSZXF1ZXN0Ei8KB3NldHRpbmcYASABKAsyGS5tZW1vcy5hcGkudjEuVXNlclNldHRpbmdCA+BBAhI0Cgt1cGRhdGVfbWFzaxgCIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5GaWVsZE1hc2tCA+BBAiJ1ChdMaXN0VXNlclNldHRpbmdzUmVxdWVzdBIpCgZwYXJlbnQYASABKAlCGeBBAvpBEwoRbWVtb3MuYXBpLnYxL1VzZXISFgoJcGFnZV9zaXplGAIgASgFQgPgQQESFwoKcGFnZV90b2tlbhgDIAEoCUID4EEBInQKGExpc3RVc2VyU2V0dGluZ3NSZXNwb25zZRIrCghzZXR0aW5ncxgBIAMoCzIZLm1lbW9zLmFwaS52MS5Vc2VyU2V0dGluZxIXCg9uZXh0X3BhZ2VfdG9rZW4YAiABKAkSEgoKdG90YWxfc2l6ZRgDIAEoBSLyAgoTUGVyc29uYWxBY2Nlc3NUb2tlbhIRCgRuYW1lGAEgASgJQgPgQQgSGAoLZGVzY3JpcHRpb24YAiABKAlCA+BBARIzCgpjcmVhdGVkX2F0GAMgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEID4EEDEjMKCmV4cGlyZXNfYXQYBCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wQgPgQQESNQoMbGFzdF91c2VkX2F0GAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEID4EEDOowB6kGIAQogbWVtb3MuYXBpLnYxL1BlcnNvbmFsQWNjZXNzVG9rZW4SOXVzZXJzL3t1c2VyfS9wZXJzb25hbEFjY2Vzc1Rva2Vucy97cGVyc29uYWxfYWNjZXNzX3Rva2VufSoUcGVyc29uYWxBY2Nlc3NUb2tlbnMyE3BlcnNvbmFsQWNjZXNzVG9rZW4ifQofTGlzdFBlcnNvbmFsQWNjZXNzVG9rZW5zUmVxdWVzdBIpCgZwYXJlbnQYASABKAlCGeBBAvpBEwoRbWVtb3MuYXBpLnYxL1VzZXISFgoJcGFnZV9zaXplGAIgASgFQgPgQQESFwoKcGFnZV90b2tlbhgDIAEoCUID4EEBIpIBCiBMaXN0UGVyc29uYWxBY2Nlc3NUb2tlbnNSZXNwb25zZRJBChZwZXJzb25hbF9hY2Nlc3NfdG9rZW5zGAEgAygLMiEubWVtb3MuYXBpLnYxLlBlcnNvbmFsQWNjZXNzVG9rZW4SFwoPbmV4dF9wYWdlX3Rva2VuGAIgASgJEhIKCnRvdGFsX3NpemUYAyABKAUihQEKIENyZWF0ZVBlcnNvbmFsQWNjZXNzVG9rZW5SZXF1ZXN0EikKBnBhcmVudBgBIAEoCUIZ4EEC+kETChFtZW1vcy5hcGkudjEvVXNlchIYCgtkZXNjcmlwdGlvbhgCIAEoCUID4EEBEhwKD2V4cGlyZXNfaW5fZGF5cxgDIAEoBUID4EEBInQKIUNyZWF0ZVBlcnNvbmFsQWNjZXNzVG9rZW5SZXNwb25zZRJAChVwZXJzb25hbF9hY2Nlc3NfdG9rZW4YASABKAsyIS5tZW1vcy5hcGkudjEuUGVyc29uYWxBY2Nlc3NUb2tlbhINCgV0b2tlbhgCIAEoCSJaCiBEZWxldGVQZXJzb25hbEFjY2Vzc1Rva2VuUmVxdWVzdBI2CgRuYW1lGAEgASgJQijgQQL6QSIKIG1lbW9zLmFwaS52MS9QZXJzb25hbEFjY2Vzc1Rva2VuIqoBCgtVc2VyV2ViaG9vaxIMCgRuYW1lGAEgASgJEgsKA3VybBgCIAEoCRIUCgxkaXNwbGF5X25hbWUYAyABKAkSNAoLY3JlYXRlX3RpbWUYBCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wQgPgQQMSNAoLdXBkYXRlX3RpbWUYBSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wQgPgQQMiLgoXTGlzdFVzZXJXZWJob29rc1JlcXVlc3QSEwoGcGFyZW50GAEgASgJQgPgQQIiRwoYTGlzdFVzZXJXZWJob29rc1Jlc3BvbnNlEisKCHdlYmhvb2tzGAEgAygLMhkubWVtb3MuYXBpLnYxLlVzZXJXZWJob29rImAKGENyZWF0ZVVzZXJXZWJob29rUmVxdWVzdBITCgZwYXJlbnQYASABKAlCA+BBAhIvCgd3ZWJob29rGAIgASgLMhkubWVtb3MuYXBpLnYxLlVzZXJXZWJob29rQgPgQQIifAoYVXBkYXRlVXNlcldlYmhvb2tSZXF1ZXN0Ei8KB3dlYmhvb2sYASABKAsyGS5tZW1vcy5hcGkudjEuVXNlcldlYmhvb2tCA+BBAhIvCgt1cGRhdGVfbWFzaxgCIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5GaWVsZE1hc2siLQoYRGVsZXRlVXNlcldlYmhvb2tSZXF1ZXN0EhEKBG5hbWUYASABKAlCA+BBAiLwBAoQVXNlck5vdGlmaWNhdGlvbhIUCgRuYW1lGAEgASgJQgbgQQPgQQgSKQoGc2VuZGVyGAIgASgJQhngQQP6QRMKEW1lbW9zLmFwaS52MS9Vc2VyEjoKBnN0YXR1cxgDIAEoDjIlLm1lbW9zLmFwaS52MS5Vc2VyTm90aWZpY2F0aW9uLlN0YXR1c0ID4EEBEjQKC2NyZWF0ZV90aW1lGAQgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEID4EEDEjYKBHR5cGUYBSABKA4yIy5tZW1vcy5hcGkudjEuVXNlck5vdGlmaWNhdGlvbi5UeXBlQgPgQQMSTgoMbWVtb19jb21tZW50GAYgASgLMjEubWVtb3MuYXBpLnYxLlVzZXJOb3RpZmljYXRpb24uTWVtb0NvbW1lbnRQYXlsb2FkQgPgQQNIABo4ChJNZW1vQ29tbWVudFBheWxvYWQSDAoEbWVtbxgBIAEoCRIUCgxyZWxhdGVkX21lbW8YAiABKAkiOgoGU3RhdHVzEhYKElNUQVRVU19VTlNQRUNJRklFRBAAEgoKBlVOUkVBRBABEgwKCEFSQ0hJVkVEEAIiLgoEVHlwZRIUChBUWVBFX1VOU1BFQ0lGSUVEEAASEAoMTUVNT19DT01NRU5UEAE6cOpBbQodbWVtb3MuYXBpLnYxL1VzZXJOb3RpZmljYXRpb24SKXVzZXJzL3t1c2VyfS9ub3RpZmljYXRpb25zL3tub3RpZmljYXRpb259GgRuYW1lKg1ub3RpZmljYXRpb25zMgxub3RpZmljYXRpb25CCQoHcGF5bG9hZCKPAQocTGlzdFVzZXJOb3RpZmljYXRpb25zUmVxdWVzdBIpCgZwYXJlbnQYASABKAlCGeBBAvpBEwoRbWVtb3MuYXBpLnYxL1VzZXISFgoJcGFnZV9zaXplGAIgASgFQgPgQQESFwoKcGFnZV90b2tlbhgDIAEoCUID4EEBEhMKBmZpbHRlchgEIAEoCUID4EEBIm8KHUxpc3RVc2VyTm90aWZpY2F0aW9uc1Jlc3BvbnNlEjUKDW5vdGlmaWNhdGlvbnMYASADKAsyHi5tZW1vcy5hcGkudjEuVXNlck5vdGlmaWNhdGlvbhIXCg9uZXh0X3BhZ2VfdG9rZW4YAiABKAkikAEKHVVwZGF0ZVVzZXJOb3RpZmljYXRpb25SZXF1ZXN0EjkKDG5vdGlmaWNhdGlvbhgBIAEoCzIeLm1lbW9zLmFwaS52MS5Vc2VyTm90aWZpY2F0aW9uQgPgQQISNAoLdXBkYXRlX21hc2sYAiABKAsyGi5nb29nbGUucHJvdG9idWYuRmllbGRNYXNrQgPgQQIiVAodRGVsZXRlVXNlck5vdGlmaWNhdGlvblJlcXVlc3QSMwoEbmFtZRgBIAEoCUIl4EEC+kEfCh1tZW1vcy5hcGkudjEvVXNlck5vdGlmaWNhdGlvbjKDFwoLVXNlclNlcnZpY2USYwoJTGlzdFVzZXJzEh4ubWVtb3MuYXBpLnYxLkxpc3RVc2Vyc1JlcXVlc3QaHy5tZW1vcy5hcGkudjEuTGlzdFVzZXJzUmVzcG9uc2UiFYLT5JMCDxINL2FwaS92MS91c2VycxJiCgdHZXRVc2VyEhwubWVtb3MuYXBpLnYxLkdldFVzZXJSZXF1ZXN0GhIubWVtb3MuYXBpLnYxLlVzZXIiJdpBBG5hbWWC0+STAhgSFi9hcGkvdjEve25hbWU9dXNlcnMvKn0SZQoKQ3JlYXRlVXNlchIfLm1lbW9zLmFwaS52MS5DcmVhdGVVc2VyUmVxdWVzdBoSLm1lbW9zLmFwaS52MS5Vc2VyIiLaQQR1c2VygtPkkwIVOgR1c2VyIg0vYXBpL3YxL3VzZXJzEn8KClVwZGF0ZVVzZXISHy5tZW1vcy5hcGkudjEuVXBkYXRlVXNlclJlcXVlc3QaEi5tZW1vcy5hcGkudjEuVXNlciI82kEQdXNlcix1cGRhdGVfbWFza4LT5JMCIzoEdXNlcjIbL2FwaS92MS97dXNlci5uYW1lPXVzZXJzLyp9EmwKCkRlbGV0ZVVzZXISHy5tZW1vcy5hcGkudjEuRGVsZXRlVXNlclJlcXVlc3QaFi5nb29nbGUucHJvdG9idWYuRW1wdHkiJdpBBG5hbWWC0+STAhgqFi9hcGkvdjEve25hbWU9dXNlcnMvKn0SfgoQTGlzdEFsbFVzZXJTdGF0cxIlLm1lbW9zLmFwaS52MS5MaXN0QWxsVXNlclN0YXRzUmVxdWVzdBomLm1lbW9zLmFwaS52MS5MaXN0QWxsVXNlclN0YXRzUmVzcG9uc2UiG4LT5JMCFRITL2FwaS92MS91c2VyczpzdGF0cxJ6CgxHZXRVc2VyU3RhdHMSIS5tZW1vcy5hcGkudjEuR2V0VXNlclN0YXRzUmVxdWVzdBoXLm1lbW9zLmFwaS52MS5Vc2VyU3RhdHMiLtpBBG5hbWWC0+STAiESHy9hcGkvdjEve25hbWU9dXNlcnMvKn06Z2V0U3RhdHMSggEKDkdldFVzZXJTZXR0aW5nEiMubWVtb3MuYXBpLnYxLkdldFVzZXJTZXR0aW5nUmVxdWVzdBoZLm1lbW9zLmFwaS52MS5Vc2VyU2V0dGluZyIw2kEEbmFtZYLT5JMCIxIhL2FwaS92MS97bmFtZT11c2Vycy8qL3NldHRpbmdzLyp9EqgBChFVcGRhdGVVc2VyU2V0dGluZxImLm1lbW9zLmFwaS52MS5VcGRhdGVVc2VyU2V0dGluZ1JlcXVlc3QaGS5tZW1vcy5hcGkudjEuVXNlclNldHRpbmciUNpBE3NldHRpbmcsdXBkYXRlX21hc2uC0+STAjQ6B3NldHRpbmcyKS9hcGkvdjEve3NldHRpbmcubmFtZT11c2Vycy8qL3NldHRpbmdzLyp9EpUBChBMaXN0VXNlclNldHRpbmdzEiUubWVtb3MuYXBpLnYxLkxpc3RVc2VyU2V0dGluZ3NSZXF1ZXN0GiYubWVtb3MuYXBpLnYxLkxpc3RVc2VyU2V0dGluZ3NSZXNwb25zZSIy2kEGcGFyZW50gtPkkwIjEiEvYXBpL3YxL3twYXJlbnQ9dXNlcnMvKn0vc2V0dGluZ3MSuQEKGExpc3RQZXJzb25hbEFjY2Vzc1Rva2VucxItLm1lbW9zLmFwaS52MS5MaXN0UGVyc29uYWxBY2Nlc3NUb2tlbnNSZXF1ZXN0Gi4ubWVtb3MuYXBpLnYxLkxpc3RQZXJzb25hbEFjY2Vzc1Rva2Vuc1Jlc3BvbnNlIj7aQQZwYXJlbnSC0+STAi8SLS9hcGkvdjEve3BhcmVudD11c2Vycy8qfS9wZXJzb25hbEFjY2Vzc1Rva2VucxK2AQoZQ3JlYXRlUGVyc29uYWxBY2Nlc3NUb2tlbhIuLm1lbW9zLmFwaS52MS5DcmVhdGVQZXJzb25hbEFjY2Vzc1Rva2VuUmVxdWVzdBovLm1lbW9zLmFwaS52MS5DcmVhdGVQZXJzb25hbEFjY2Vzc1Rva2VuUmVzcG9uc2UiOILT5JMCMjoBKiItL2FwaS92MS97cGFyZW50PXVzZXJzLyp9L3BlcnNvbmFsQWNjZXNzVG9rZW5zEqEBChlEZWxldGVQZXJzb25hbEFjY2Vzc1Rva2VuEi4ubWVtb3MuYXBpLnYxLkRlbGV0ZVBlcnNvbmFsQWNjZXNzVG9rZW5SZXF1ZXN0GhYuZ29vZ2xlLnByb3RvYnVmLkVtcHR5IjzaQQRuYW1lgtPkkwIvKi0vYXBpL3YxL3tuYW1lPXVzZXJzLyovcGVyc29uYWxBY2Nlc3NUb2tlbnMvKn0SlQEKEExpc3RVc2VyV2ViaG9va3MSJS5tZW1vcy5hcGkudjEuTGlzdFVzZXJXZWJob29rc1JlcXVlc3QaJi5tZW1vcy5hcGkudjEuTGlzdFVzZXJXZWJob29rc1Jlc3BvbnNlIjLaQQZwYXJlbnSC0+STAiMSIS9hcGkvdjEve3BhcmVudD11c2Vycy8qfS93ZWJob29rcxKbAQoRQ3JlYXRlVXNlcldlYmhvb2sSJi5tZW1vcy5hcGkudjEuQ3JlYXRlVXNlcldlYmhvb2tSZXF1ZXN0GhkubWVtb3MuYXBpLnYxLlVzZXJXZWJob29rIkPaQQ5wYXJlbnQsd2ViaG9va4LT5JMCLDoHd2ViaG9vayIhL2FwaS92MS97cGFyZW50PXVzZXJzLyp9L3dlYmhvb2tzEqgBChFVcGRhdGVVc2VyV2ViaG9vaxImLm1lbW9zLmFwaS52MS5VcGRhdGVVc2VyV2ViaG9va1JlcXVlc3QaGS5tZW1vcy5hcGkudjEuVXNlcldlYmhvb2siUNpBE3dlYmhvb2ssdXBkYXRlX21hc2uC0+STAjQ6B3dlYmhvb2syKS9hcGkvdjEve3dlYmhvb2submFtZT11c2Vycy8qL3dlYmhvb2tzLyp9EoUBChFEZWxldGVVc2VyV2ViaG9vaxImLm1lbW9zLmFwaS52MS5EZWxldGVVc2VyV2ViaG9va1JlcXVlc3QaFi5nb29nbGUucHJvdG9idWYuRW1wdHkiMNpBBG5hbWWC0+STAiMqIS9hcGkvdjEve25hbWU9dXNlcnMvKi93ZWJob29rcy8qfRKpAQoVTGlzdFVzZXJOb3RpZmljYXRpb25zEioubWVtb3MuYXBpLnYxLkxpc3RVc2VyTm90aWZpY2F0aW9uc1JlcXVlc3QaKy5tZW1vcy5hcGkudjEuTGlzdFVzZXJOb3RpZmljYXRpb25zUmVzcG9uc2UiN9pBBnBhcmVudILT5JMCKBImL2FwaS92MS97cGFyZW50PXVzZXJzLyp9L25vdGlmaWNhdGlvbnMSywEKFlVwZGF0ZVVzZXJOb3RpZmljYXRpb24SKy5tZW1vcy5hcGkudjEuVXBkYXRlVXNlck5vdGlmaWNhdGlvblJlcXVlc3QaHi5tZW1vcy5hcGkudjEuVXNlck5vdGlmaWNhdGlvbiJk2kEYbm90aWZpY2F0aW9uLHVwZGF0ZV9tYXNrgtPkkwJDOgxub3RpZmljYXRpb24yMy9hcGkvdjEve25vdGlmaWNhdGlvbi5uYW1lPXVzZXJzLyovbm90aWZpY2F0aW9ucy8qfRKUAQoWRGVsZXRlVXNlck5vdGlmaWNhdGlvbhIrLm1lbW9zLmFwaS52MS5EZWxldGVVc2VyTm90aWZpY2F0aW9uUmVxdWVzdBoWLmdvb2dsZS5wcm90b2J1Zi5FbXB0eSI12kEEbmFtZYLT5JMCKComL2FwaS92MS97bmFtZT11c2Vycy8qL25vdGlmaWNhdGlvbnMvKn1CqAEKEGNvbS5tZW1vcy5hcGkudjFCEFVzZXJTZXJ2aWNlUHJvdG9QAVowZ2l0aHViLmNvbS91c2VtZW1vcy9tZW1vcy9wcm90by9nZW4vYXBpL3YxO2FwaXYxogIDTUFYqgIMTWVtb3MuQXBpLlYxygIMTWVtb3NcQXBpXFYx4gIYTWVtb3NcQXBpXFYxXEdQQk1ldGFkYXRh6gIOTWVtb3M6OkFwaTo6VjFiBnByb3RvMw", [file_api_v1_common, 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]); /** * @generated from message memos.api.v1.User @@ -229,10 +229,7 @@ export const ListUsersResponseSchema: GenMessage = /*@__PURE_ export type GetUserRequest = Message<"memos.api.v1.GetUserRequest"> & { /** * Required. The resource name of the user. - * Supports both numeric IDs and username strings: - * - users/{id} (e.g., users/101) - * - users/{username} (e.g., users/steven) - * Format: users/{id_or_username} + * Format: users/{username} * * @generated from field: string name = 1; */ @@ -510,8 +507,8 @@ export const ListAllUserStatsResponseSchema: GenMessage & { /** * The name of the user setting. - * Format: users/{user}/settings/{setting}, {setting} is the key for the setting. - * For example, "users/123/settings/GENERAL" for general settings. + * Format: users/{username}/settings/{setting}, {setting} is the key for the setting. + * For example, "users/steven/settings/GENERAL" for general settings. * * @generated from field: string name = 1; */ @@ -1363,10 +1360,8 @@ export const UserService: GenService<{ output: typeof ListUsersResponseSchema; }, /** - * GetUser gets a user by ID or username. - * Supports both numeric IDs and username strings: - * - users/{id} (e.g., users/101) - * - users/{username} (e.g., users/steven) + * GetUser gets a user by username. + * Format: users/{username} (e.g., users/steven) * * @generated from rpc memos.api.v1.UserService.GetUser */